@nok-integration/storefront-react 0.6.0 → 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.
package/dist/styles.css CHANGED
@@ -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,1662 +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;
1255
1818
  }
1256
-
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;
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;
1263
1836
  }
1264
1837
 
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;
1838
+ ._screen_1t90m_20 {
1839
+ display: flex;
1840
+ flex-direction: column;
1841
+ flex: 1 1 auto;
1271
1842
  }
1272
1843
 
1273
- ._toggle_9s1n4_31 {
1274
- align-self: flex-start;
1275
- padding: 0;
1276
- 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;
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;
1284
1850
  }
1285
- ._box_1cf1a_1 {
1851
+
1852
+ ._items_1t90m_34 {
1286
1853
  display: flex;
1287
1854
  flex-direction: column;
1288
- gap: var(--space-12);
1289
- padding: var(--space-16);
1290
- border-radius: var(--radius-8);
1291
- background: var(--color-surface-soft);
1855
+ gap: var(--space-20);
1292
1856
  }
1293
1857
 
1294
- ._row_1cf1a_10 {
1858
+ ._sectionHeader_1t90m_40 {
1295
1859
  display: flex;
1296
1860
  align-items: center;
1297
1861
  gap: var(--space-12);
1298
1862
  }
1299
1863
 
1300
- ._icon_1cf1a_16 {
1301
- display: block;
1302
- flex-shrink: 0;
1303
- color: var(--color-text-default);
1304
- }
1305
-
1306
- ._label_1cf1a_22 {
1864
+ ._sectionTitle_1t90m_46 {
1307
1865
  margin: 0;
1308
- font-size: var(--type-trimmed-sm-size);
1866
+ font-size: var(--type-heading-md-size);
1867
+ line-height: 1.38;
1309
1868
  font-weight: var(--font-weight-bold);
1310
- color: var(--color-text-default);
1311
1869
  }
1312
1870
 
1313
- ._divider_1cf1a_29 {
1314
- height: 1px;
1315
- background: var(--color-border-default);
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;
1316
1884
  }
1317
- ._row_l4edh_1 {
1318
- display: flex;
1319
- flex-wrap: wrap;
1320
- gap: var(--space-4) var(--space-6);
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;
1321
1893
  margin: 0;
1322
1894
  padding: 0;
1323
- list-style: none;
1895
+ display: flex;
1896
+ flex-direction: column;
1897
+ gap: var(--space-20);
1324
1898
  }
1325
1899
 
1326
- /* mobile/badge/top-pick: soft surface, dark text, uppercase, radius-min. */
1327
- ._badge_l4edh_11 {
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;
1932
+ }
1933
+
1934
+ ._image_h8m00_26 {
1935
+ object-fit: cover;
1936
+ }
1937
+
1938
+ ._placeholder_h8m00_30 {
1939
+ width: 100%;
1940
+ aspect-ratio: 375 / 485;
1941
+ background: var(--color-surface-soft);
1942
+ }
1943
+
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);
1328
1949
  display: inline-flex;
1329
1950
  align-items: center;
1330
1951
  justify-content: center;
1331
- height: 20px;
1332
- padding: var(--space-4) var(--space-6);
1333
- border-radius: var(--radius-2);
1952
+ width: 32px;
1953
+ height: 32px;
1954
+ padding: 0;
1955
+ border: none;
1956
+ border-radius: var(--radius-8);
1334
1957
  background: var(--color-surface-soft);
1335
1958
  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;
1959
+ cursor: pointer;
1342
1960
  }
1343
- /* Figma mobile/pdp/promo: success-soft surface, success text, radius-8, p16, h48. */
1344
- ._banner_1xvw4_2 {
1961
+
1962
+ ._dots_h8m00_54 {
1963
+ display: flex;
1964
+ justify-content: center;
1965
+ gap: var(--space-6);
1966
+ }
1967
+
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;
1978
+ }
1979
+
1980
+ ._dot_h8m00_54::after {
1981
+ content: '';
1982
+ position: absolute;
1983
+ inset: -18px;
1984
+ }
1985
+
1986
+ ._dotActive_h8m00_78 {
1987
+ background: var(--color-surface-max);
1988
+ }
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;
1998
+ display: flex;
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;
2003
+ }
2004
+
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);
2015
+ }
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;
2039
+ ._close_8wz6r_51 {
2040
+ display: inline-flex;
2041
+ align-items: center;
2042
+ justify-content: center;
2043
+ width: 32px;
2044
+ height: 32px;
1366
2045
  flex-shrink: 0;
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;
1367
2076
  }
1368
2077
 
1369
- ._timer_1xvw4_27 {
1370
- display: inline-flex;
1371
- align-items: center;
1372
- gap: var(--space-4);
1373
- flex-shrink: 0;
1374
- font-size: var(--type-trimmed-sm-size);
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;
1600
2336
  }
1601
2337
 
1602
- ._dotActive_yfu87_59 {
1603
- background: var(--color-text-default);
2338
+ ._selected_1mnz1_36 {
2339
+ border-color: var(--color-surface-max);
2340
+ background: var(--color-surface-max);
2341
+ color: var(--color-text-strong);
2342
+ }
2343
+
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);
1639
- color: var(--color-text-subtle);
2381
+ /* paragraph-spacing/lg between paragraphs, nothing after the last. */
2382
+ ._paragraph_t4qba_26 + ._paragraph_t4qba_26 {
2383
+ margin-top: var(--space-12);
1640
2384
  }
1641
- ._section_3mufa_1 {
1642
- display: flex;
1643
- flex-direction: column;
1644
- gap: var(--space-8);
1645
- padding: var(--space-16);
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;
1646
2398
  }
1647
2399
 
1648
- ._title_3mufa_8 {
1649
- font-size: var(--type-heading-md-size);
1650
- line-height: var(--type-heading-md-line);
2400
+ ._toggle_t4qba_51 {
2401
+ align-self: flex-start;
2402
+ padding: 0;
2403
+ border: none;
2404
+ background: none;
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. */
1651
2410
  font-weight: var(--font-weight-bold);
1652
- color: var(--color-text-default);
2411
+ cursor: pointer;
1653
2412
  }
1654
-
1655
- ._list_3mufa_15 {
1656
- list-style: none;
2413
+ ._row_l4edh_1 {
2414
+ display: flex;
2415
+ flex-wrap: wrap;
2416
+ gap: var(--space-4) var(--space-6);
1657
2417
  margin: 0;
1658
2418
  padding: 0;
2419
+ list-style: none;
1659
2420
  }
1660
2421
 
1661
- ._row_3mufa_21 {
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;
2438
+ }
2439
+ /* Figma mobile/pdp/promo: success-soft surface, success text, radius-8, p16, h48. */
2440
+ ._banner_1xvw4_2 {
1662
2441
  display: flex;
1663
2442
  align-items: center;
1664
2443
  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);
2444
+ gap: var(--space-12);
2445
+ min-height: 48px;
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);
2450
+ }
2451
+
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);
1671
2457
  font-weight: var(--font-weight-bold);
1672
2458
  }
1673
2459
 
1674
- ._row_3mufa_21 svg {
1675
- color: var(--color-text-subtle);
2460
+ ._icon_1xvw4_22 {
2461
+ display: block;
2462
+ flex-shrink: 0;
1676
2463
  }
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
2464
 
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 {
2465
+ ._timer_1xvw4_27 {
2466
+ display: inline-flex;
2467
+ align-items: center;
2468
+ gap: var(--space-4);
2469
+ flex-shrink: 0;
2470
+ font-size: var(--type-trimmed-sm-size);
2471
+ font-weight: var(--font-weight-bold);
2472
+ font-variant-numeric: tabular-nums;
2473
+ }
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. */
2484
+ }
2485
+
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
- ._showcase_1osag_7 {
1733
- height: calc(100dvh - 56px); /* below the 56px webview header */
1734
- overflow-y: auto;
1735
- scroll-snap-type: y mandatory;
1736
- -webkit-overflow-scrolling: touch;
1737
- background: #080e12;
1738
- scrollbar-width: none;
2534
+
2535
+ ._low_1hv16_62 {
2536
+ color: var(--color-state-text-warning);
1739
2537
  }
1740
- ._showcase_1osag_7::-webkit-scrollbar {
1741
- display: none;
2538
+
2539
+ ._oos_1hv16_66 {
2540
+ color: var(--color-state-surface-critical-default);
1742
2541
  }
2542
+ /*
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.
2545
+ *
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.
2550
+ */
2551
+ ._hero_2mcnp_10 {
2552
+ --hero-height: clamp(480px, 100svh, 812px);
1743
2553
 
1744
- ._panel_1osag_19 {
1745
2554
  position: relative;
1746
- height: calc(100dvh - 56px);
1747
- scroll-snap-align: start;
1748
- scroll-snap-stop: always;
2555
+ height: var(--hero-height);
1749
2556
  overflow: hidden;
1750
- background: #080e12;
1751
- }
1752
-
1753
- ._image_1osag_28 {
1754
- object-fit: cover;
1755
- object-position: center;
1756
- }
2557
+ background: var(--color-surface-default);
1757
2558
 
1758
- /* Bottom scrim so overlay text stays legible over any photo. */
1759
- ._panel_1osag_19::after {
1760
- content: '';
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);
2576
+ }
2577
+
2578
+ ._image_2mcnp_37 {
1761
2579
  position: absolute;
1762
2580
  inset: 0;
1763
- background: linear-gradient(to bottom, rgba(8, 14, 18, 0) 45%, rgba(8, 14, 18, 0.85) 100%);
1764
- pointer-events: none;
1765
2581
  }
1766
2582
 
1767
- /* ── Hero ── */
2583
+ ._image_2mcnp_37 img {
2584
+ object-fit: cover;
2585
+ }
2586
+
1768
2587
  /*
1769
- * Unlike the category panels (`edit_6 1`, full-bleed 375x812), the hero photo in Figma
1770
- * (`edit_9 1`) is 375x674 and stops where the opaque "swipe down" bar starts. Giving it
1771
- * 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.
1772
2591
  */
1773
- ._heroImageBox_1osag_48 {
2592
+ ._scrim_2mcnp_51 {
1774
2593
  position: absolute;
1775
- inset: 0 0 82px;
1776
- 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%);
1777
2596
  }
1778
- ._hero_1osag_48 ._heroContent_1osag_53 {
2597
+
2598
+ ._text_2mcnp_57 {
1779
2599
  position: absolute;
1780
- bottom: 15px;
2600
+ top: 106px;
1781
2601
  left: 0;
1782
2602
  right: 0;
1783
- z-index: 1;
1784
2603
  display: flex;
1785
2604
  flex-direction: column;
1786
- gap: 24px;
1787
- align-items: stretch;
1788
- }
1789
- ._heroText_1osag_64 {
1790
- display: flex;
1791
- flex-direction: column;
1792
- gap: 8px;
1793
2605
  align-items: center;
1794
- padding: 0 24px;
2606
+ gap: var(--space-8);
2607
+ padding: var(--space-12) var(--space-24) var(--space-24);
1795
2608
  text-align: center;
1796
2609
  }
1797
- ._logo_1osag_72 {
1798
- width: 48px;
1799
- height: 48px;
1800
- }
1801
- ._heroTitle_1osag_76 {
2610
+
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 {
1802
2614
  margin: 0;
1803
- font-size: 56px;
1804
- line-height: 1.3;
1805
- font-weight: 700;
1806
- letter-spacing: -1.12px;
2615
+ font-size: 40px;
2616
+ line-height: 1;
2617
+ font-weight: var(--font-weight-bold);
1807
2618
  color: #f9fafa;
2619
+ text-wrap: balance;
1808
2620
  }
1809
- ._heroSub_1osag_84 {
2621
+
2622
+ ._subtitle_2mcnp_81 {
1810
2623
  margin: 0;
1811
- font-size: 16px;
1812
- line-height: 1.55;
2624
+ font-size: var(--type-trimmed-md-size);
2625
+ line-height: 1;
2626
+ font-weight: var(--font-weight-regular);
1813
2627
  color: #f9fafa;
1814
- max-width: 327px;
1815
2628
  }
1816
- ._swipe_1osag_91 {
1817
- display: flex;
1818
- flex-direction: column;
1819
- align-items: center;
1820
- gap: 6px;
1821
- height: 82px;
1822
- justify-content: center;
1823
- background: #080e12;
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);
2646
+ }
2647
+
2648
+ ._noImage_2mcnp_94 ._title_2mcnp_72,
2649
+ ._noImage_2mcnp_94 ._subtitle_2mcnp_81 {
2650
+ color: var(--color-text-default);
1824
2651
  }
1825
- ._swipeLabel_1osag_100 {
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 {
1826
2658
  margin: 0;
1827
- font-size: 14px;
2659
+ padding: var(--space-24);
2660
+ min-height: 106px;
2661
+ display: flex;
2662
+ align-items: center;
2663
+ font-size: var(--type-heading-lg-size);
1828
2664
  line-height: 1;
1829
- color: rgba(255, 255, 255, 0.54);
2665
+ font-weight: var(--font-weight-bold);
2666
+ color: var(--color-text-default);
1830
2667
  }
1831
- ._chevron_1osag_106 {
1832
- width: 24px;
1833
- height: 24px;
1834
- color: rgba(255, 255, 255, 0.54);
1835
- animation: _bob_1osag_1 1.8s ease-in-out infinite;
2668
+
2669
+ ._center_1saw4_18 {
2670
+ justify-content: center;
2671
+ text-align: center;
1836
2672
  }
1837
- @keyframes _bob_1osag_1 {
1838
- 0%, 100% { transform: translateY(0); }
1839
- 50% { transform: translateY(4px); }
2673
+
2674
+ ._left_1saw4_23 {
2675
+ justify-content: flex-start;
2676
+ text-align: left;
2677
+ }
2678
+ ._wrap_9mquf_1 {
2679
+ padding: var(--space-24) var(--space-16);
1840
2680
  }
1841
2681
 
1842
- /* ── Category panels ── */
1843
- ._overlay_1osag_118 {
1844
- position: absolute;
1845
- bottom: 0;
1846
- left: 0;
1847
- right: 0;
1848
- z-index: 1;
1849
- display: flex;
1850
- flex-direction: column;
1851
- gap: 16px;
1852
- align-items: flex-start;
1853
- padding: 0 24px 24px;
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);
1854
2690
  }
1855
- ._copy_1osag_130 {
2691
+
2692
+ ._track_9mquf_15 {
1856
2693
  display: flex;
1857
- flex-direction: column;
1858
- gap: 8px;
1859
- }
1860
- ._title_1osag_135 {
2694
+ list-style: none;
1861
2695
  margin: 0;
1862
- font-size: 32px;
1863
- line-height: 1.2;
1864
- font-weight: 700;
1865
- color: #f9fafa;
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;
1866
2702
  }
1867
- ._subtitle_1osag_142 {
1868
- margin: 0;
1869
- font-size: 16px;
1870
- line-height: 1.55;
1871
- color: #f9fafa;
2703
+
2704
+ ._track_9mquf_15::-webkit-scrollbar {
2705
+ display: none;
1872
2706
  }
1873
- ._cta_1osag_148 {
1874
- display: inline-flex;
1875
- align-items: center;
1876
- justify-content: center;
1877
- min-height: 48px;
1878
- padding: 8px 12px;
1879
- border: 1px solid #f9fafa;
1880
- border-radius: 8px;
1881
- background: transparent;
1882
- color: #f9fafa;
1883
- font-size: 16px;
1884
- font-weight: 700;
1885
- text-decoration: none;
2707
+
2708
+ ._slide_9mquf_31 {
2709
+ position: relative;
2710
+ flex: 0 0 100%;
2711
+ height: 100%;
2712
+ scroll-snap-align: start;
1886
2713
  }
1887
- ._cta_1osag_148:active {
1888
- background: rgba(249, 250, 250, 0.12);
2714
+
2715
+ ._image_9mquf_38 {
2716
+ object-fit: cover;
1889
2717
  }
1890
2718
 
1891
- /* ── Mosaic gallery panel ── */
1892
- ._mosaicPanel_1osag_167 {
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;
1893
2726
  display: flex;
1894
2727
  align-items: center;
1895
2728
  justify-content: center;
2729
+ gap: var(--space-8);
2730
+ pointer-events: none;
1896
2731
  }
1897
- ._mosaicPanel_1osag_167::after {
1898
- display: none;
2732
+
2733
+ ._dot_9mquf_43 {
2734
+ width: 8px;
2735
+ height: 8px;
2736
+ border-radius: var(--radius-full);
2737
+ background: #9ea2a4;
2738
+ }
2739
+
2740
+ ._dotActive_9mquf_63 {
2741
+ background: #f9fafa;
1899
2742
  }
1900
- ._mosaic_1osag_167 {
2743
+ ._section_iy175_1 {
1901
2744
  display: flex;
1902
2745
  flex-direction: column;
1903
- gap: 12px;
1904
- width: 343px;
2746
+ gap: var(--space-12);
2747
+ padding: var(--space-24) 0 var(--space-16);
1905
2748
  }
1906
- ._mRow_1osag_181 {
2749
+
2750
+ ._track_iy175_8 {
1907
2751
  display: flex;
1908
- gap: 12px;
1909
- align-items: center;
1910
- }
1911
- ._mBetween_1osag_186 {
1912
- justify-content: space-between;
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;
1913
2760
  }
1914
- ._mCol_1osag_189 {
2761
+
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;
1915
2777
  display: flex;
1916
2778
  flex-direction: column;
1917
- gap: 10px;
2779
+ gap: var(--space-12);
2780
+ padding: 25px var(--space-24);
2781
+ border-radius: var(--radius-8);
2782
+ background: #f9fafa;
1918
2783
  }
1919
- ._tile_1osag_194 {
1920
- position: relative;
1921
- border-radius: 6px;
1922
- overflow: hidden;
1923
- flex-shrink: 0;
2784
+
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;
1924
2791
  }
1925
- ._solid_1osag_200 {
1926
- background: #171e23;
2792
+
2793
+ ._author_iy175_51 {
2794
+ margin: 0;
2795
+ font-size: var(--type-trimmed-sm-size);
2796
+ color: #3d4549;
1927
2797
  }
1928
- ._overlay_s4eai_1 {
1929
- position: fixed;
1930
- inset: 0;
1931
- z-index: 900;
2798
+
2799
+ ._dots_iy175_57 {
1932
2800
  display: flex;
1933
- align-items: flex-end;
1934
2801
  justify-content: center;
2802
+ gap: var(--space-8);
1935
2803
  }
1936
2804
 
1937
- ._scrim_s4eai_10 {
1938
- position: absolute;
1939
- inset: 0;
1940
- background: rgba(8, 14, 18, 0.5);
2805
+ ._dot_iy175_57 {
2806
+ width: 8px;
2807
+ height: 8px;
2808
+ border-radius: var(--radius-full);
2809
+ background: #9ea2a4;
1941
2810
  }
1942
2811
 
1943
- ._sheet_s4eai_16 {
1944
- position: relative;
1945
- width: 100%;
1946
- max-width: 480px;
1947
- max-height: 90dvh;
1948
- background: var(--color-surface-default);
1949
- border-radius: var(--radius-18) var(--radius-18) 0 0;
1950
- display: flex;
1951
- flex-direction: column;
1952
- animation: _slide-up_s4eai_1 200ms ease;
1953
- padding-bottom: var(--safe-area-bottom);
2812
+ ._dotActive_iy175_70 {
2813
+ background: #f9fafa;
1954
2814
  }
1955
2815
 
1956
- @keyframes _slide-up_s4eai_1 {
1957
- from {
1958
- transform: translateY(100%);
1959
- }
1960
- to {
1961
- transform: translateY(0);
1962
- }
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);
2822
+ color: var(--color-text-subtle);
1963
2823
  }
1964
-
1965
- /* Drag handle strip. `touch-action: none` stops the browser claiming the vertical
1966
- gesture as a page scroll before the pointer handlers see it. Confined to this strip
1967
- so drags starting on the scrollable body still scroll the body. */
1968
- ._grabArea_s4eai_41 {
1969
- padding: var(--space-8) 0 var(--space-4);
1970
- touch-action: none;
1971
- cursor: grab;
2824
+ ._section_145t0_1 {
2825
+ display: flex;
2826
+ flex-direction: column;
2827
+ gap: var(--space-12);
2828
+ padding: var(--space-24) var(--space-16);
1972
2829
  }
1973
2830
 
1974
- ._grabArea_s4eai_41:active {
1975
- cursor: grabbing;
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);
1976
2839
  }
1977
2840
 
1978
- ._grabber_s4eai_51 {
1979
- width: 40px;
1980
- height: 4px;
1981
- margin: 0 auto;
1982
- border-radius: 2px;
1983
- background: var(--color-border-default);
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);
1984
2850
  }
1985
2851
 
1986
- ._header_s4eai_59 {
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 {
1987
2858
  display: flex;
1988
- align-items: center;
1989
- justify-content: space-between;
1990
- padding: var(--space-16);
1991
- border-bottom: 1px solid var(--color-border-default);
2859
+ flex-direction: column;
2860
+ gap: var(--space-16);
2861
+ padding: var(--space-16) var(--space-16) var(--space-24);
1992
2862
  }
1993
2863
 
1994
- ._title_s4eai_67 {
2864
+ ._title_opjfc_8 {
1995
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);
1996
2869
  }
1997
2870
 
1998
- ._close_s4eai_71 {
1999
- min-width: var(--tap-target-min);
2000
- min-height: var(--tap-target-min);
2001
- display: inline-flex;
2871
+ ._list_opjfc_15 {
2872
+ list-style: none;
2873
+ margin: 0;
2874
+ padding: 0;
2875
+ }
2876
+
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;
2002
2884
  align-items: center;
2003
- justify-content: center;
2004
- border: none;
2005
- background: transparent;
2006
- font-size: var(--type-trimmed-md-size);
2007
- cursor: pointer;
2008
- color: var(--color-text-subtle);
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);
2890
+ font-weight: var(--font-weight-bold);
2009
2891
  }
2010
2892
 
2011
- ._body_s4eai_84 {
2012
- overflow-y: auto;
2013
- padding: var(--space-16);
2014
- flex: 1 1 auto;
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);
2015
2896
  }
2016
2897
 
2017
- ._footer_s4eai_90 {
2018
- border-top: 1px solid var(--color-border-default);
2019
- padding: var(--space-16);
2898
+ ._row_opjfc_26 svg {
2899
+ flex-shrink: 0;
2900
+ color: var(--color-text-default);
2020
2901
  }
2021
- ._wrap_v9onk_1 {
2022
- display: inline-flex;
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;
2023
2909
  flex-direction: column;
2024
- align-items: flex-start;
2025
- gap: var(--space-4);
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}`. */
2026
2921
  }
2027
2922
 
2028
- ._stepper_v9onk_8 {
2029
- display: inline-flex;
2030
- align-items: center;
2031
- gap: var(--space-4);
2032
- border: 1px solid var(--color-border-default);
2033
- border-radius: var(--radius-full);
2034
- padding: var(--space-2);
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;
2035
2930
  }
2036
2931
 
2037
- /* Why the increment stopped. Subtle, but present — never a silently dead control. */
2038
- ._cap_v9onk_18 {
2039
- font-size: var(--type-trimmed-xs-size, 12px);
2040
- font-weight: var(--font-weight-regular, 400);
2041
- color: var(--color-text-subtle);
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;
2042
2945
  }
2043
2946
 
2044
- ._button_v9onk_24 {
2045
- min-width: var(--tap-target-min);
2046
- min-height: var(--tap-target-min);
2047
- /* Centres the icon in the 44px target — without this the svg sits on the text
2048
- baseline and the row reads as misaligned. */
2049
- display: inline-flex;
2050
- align-items: center;
2051
- justify-content: center;
2052
- border: none;
2053
- background: transparent;
2054
- border-radius: var(--radius-full);
2055
- 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;
2056
2951
  font-weight: var(--font-weight-bold);
2057
2952
  color: var(--color-text-default);
2058
- cursor: pointer;
2059
- }
2060
-
2061
- ._button_v9onk_24:disabled {
2062
- color: var(--color-text-disabled);
2063
- cursor: not-allowed;
2064
2953
  }
2065
2954
 
2066
- ._value_v9onk_46 {
2067
- min-width: 2ch;
2068
- text-align: center;
2069
- font-size: var(--type-trimmed-md-size);
2070
- font-weight: var(--font-weight-bold);
2071
- }
2072
- ._row_stxj2_1 {
2955
+ ._grid_6gy3c_39 {
2956
+ list-style: none;
2957
+ margin: 0;
2958
+ padding: 0 var(--space-16);
2073
2959
  display: grid;
2074
- grid-template-columns: 72px 1fr auto;
2075
- gap: var(--space-12);
2076
- align-items: start;
2077
- padding: var(--space-12) 0;
2078
- 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);
2079
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
+ */
2080
2970
 
2081
- ._pending_stxj2_10 {
2082
- 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;
2083
3010
  }
2084
3011
 
2085
- ._media_stxj2_14 {
3012
+ ._panel_ems17_49 {
2086
3013
  position: relative;
2087
- width: 72px;
2088
- height: 72px;
2089
- 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;
2090
3018
  overflow: hidden;
2091
- background: var(--color-surface-soft);
3019
+ background: #080e12;
2092
3020
  }
2093
3021
 
2094
- ._image_stxj2_23 {
3022
+ ._image_ems17_59 {
2095
3023
  object-fit: cover;
3024
+ object-position: center;
2096
3025
  }
2097
3026
 
2098
- ._placeholder_stxj2_27 {
2099
- width: 100%;
2100
- 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;
2101
3034
  }
2102
3035
 
2103
- ._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;
2104
3053
  display: flex;
2105
3054
  flex-direction: column;
2106
- gap: var(--space-8);
2107
- min-width: 0;
3055
+ gap: 24px;
3056
+ align-items: stretch;
2108
3057
  }
2109
-
2110
- ._text_stxj2_39 {
3058
+ ._heroText_ems17_95 {
2111
3059
  display: flex;
2112
3060
  flex-direction: column;
2113
- gap: var(--space-4);
2114
- min-width: 0;
3061
+ gap: 8px;
3062
+ align-items: center;
3063
+ padding: 0 24px;
3064
+ text-align: center;
2115
3065
  }
2116
-
2117
- ._title_stxj2_46 {
2118
- font-size: var(--type-trimmed-sm-size);
2119
- font-weight: var(--font-weight-bold);
2120
- /* Two lines, then ellipsis. Product titles are catalogue copy and can run long; an
2121
- unbounded title pushes the stepper and the line total out of alignment down the
2122
- list. */
2123
- display: -webkit-box;
2124
- -webkit-line-clamp: 2;
2125
- -webkit-box-orient: vertical;
2126
- overflow: hidden;
3066
+ ._logo_ems17_103 {
3067
+ width: 48px;
3068
+ height: 48px;
2127
3069
  }
2128
-
2129
- /* "Black · M" — which variant this line is. Without it two sizes of one product are
2130
- the same line twice. */
2131
- ._variant_stxj2_60 {
2132
- font-size: var(--type-trimmed-xs-size);
2133
- 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;
2134
3077
  }
2135
-
2136
- ._unit_stxj2_65 {
2137
- font-size: var(--type-trimmed-xs-size);
2138
- 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;
2139
3084
  }
2140
-
2141
- ._lineTotal_stxj2_70 {
2142
- font-size: var(--type-trimmed-md-size);
2143
- font-weight: var(--font-weight-bold);
2144
- 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;
2145
3093
  }
2146
- ._summary_dmv1d_1 {
3094
+ ._swipeLabel_ems17_131 {
2147
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;
2148
3121
  display: flex;
2149
3122
  flex-direction: column;
2150
- gap: var(--space-8);
3123
+ justify-content: flex-end;
3124
+ gap: 16px;
3125
+ align-items: flex-start;
3126
+ padding: 0 24px 24px;
2151
3127
  }
2152
-
2153
- ._row_dmv1d_8 {
3128
+ ._copy_ems17_165 {
2154
3129
  display: flex;
2155
- align-items: baseline;
2156
- justify-content: space-between;
2157
- font-size: var(--type-trimmed-sm-size);
2158
- color: var(--color-text-subtle);
3130
+ flex-direction: column;
3131
+ gap: 8px;
2159
3132
  }
2160
-
2161
- ._row_dmv1d_8 dt,
2162
- ._row_dmv1d_8 dd {
3133
+ ._title_ems17_170 {
2163
3134
  margin: 0;
3135
+ font-size: 32px;
3136
+ line-height: 1.2;
3137
+ font-weight: 700;
3138
+ color: #f9fafa;
2164
3139
  }
2165
-
2166
- ._total_dmv1d_21 {
2167
- padding-top: var(--space-8);
2168
- border-top: 1px solid var(--color-border-default);
2169
- font-size: var(--type-trimmed-md-size);
2170
- font-weight: var(--font-weight-bold);
2171
- 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;
2172
3168
  }
2173
3169
 
2174
- /* "Free" is a merchandising signal, not a number — success tone, per the design. */
2175
- ._free_dmv1d_30 {
2176
- color: var(--color-state-text-success);
2177
- font-weight: var(--font-weight-bold);
3170
+ ._cta_ems17_183:active {
3171
+ background: rgba(249, 250, 250, 0.12);
2178
3172
  }
2179
3173
 
2180
- /* "Calculated at checkout". Set quieter than a real figure so it never reads as one. */
2181
- ._pending_dmv1d_36 {
2182
- font-size: var(--type-trimmed-xs-size);
2183
- 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);
2184
3179
  }
2185
3180
  /*
2186
- * Shown while the cart is locked for checkout (contracts 2.11.0 `Cart.locked`).
2187
- *
2188
- * Warning tones rather than critical: nothing has gone wrong, the fan simply cannot edit
2189
- * right now. Critical red here would read as a failure and push people to abandon a
2190
- * 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.
2191
3184
  */
2192
- ._lockNotice_1f0ae_8 {
2193
- margin: 0;
2194
- padding: var(--space-12) var(--space-16);
2195
- background: var(--color-state-surface-warning-soft);
2196
- color: var(--color-text-default);
2197
- border-bottom: 1px solid var(--color-border-subtle);
2198
- font-size: 14px;
2199
- line-height: 1.4;
2200
- }
2201
- /* Figma mobile/header top.navigation (context=shop): 56px bar, 16px inset, 24px icons. */
2202
- ._header_nbe8a_2 {
3185
+ ._header_7rinq_6 {
2203
3186
  display: flex;
2204
3187
  align-items: center;
2205
3188
  justify-content: space-between;
@@ -2208,7 +3191,27 @@
2208
3191
  background: var(--color-surface-default);
2209
3192
  }
2210
3193
 
2211
- ._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 {
2212
3215
  display: flex;
2213
3216
  align-items: center;
2214
3217
  gap: var(--space-16);
@@ -2221,12 +3224,12 @@
2221
3224
  * header without a back button would silently re-lay-out rather than simply losing one
2222
3225
  * icon. 24px is the icon box it stands in for.
2223
3226
  */
2224
- ._spacer_nbe8a_24 {
3227
+ ._spacer_7rinq_48 {
2225
3228
  width: 24px;
2226
3229
  height: 24px;
2227
3230
  }
2228
3231
 
2229
- ._iconBtn_nbe8a_29 {
3232
+ ._iconBtn_7rinq_53 {
2230
3233
  position: relative;
2231
3234
  display: inline-flex;
2232
3235
  align-items: center;
@@ -2242,35 +3245,59 @@
2242
3245
  }
2243
3246
 
2244
3247
  /* Expand the hit area to >=44px without moving the 24px icon off the 16px inset. */
2245
- ._iconBtn_nbe8a_29::after {
3248
+ ._iconBtn_7rinq_53::after {
2246
3249
  content: '';
2247
3250
  position: absolute;
2248
3251
  inset: -10px;
2249
3252
  }
2250
3253
 
2251
3254
  /*
2252
- * Cart count. Anchored to the icon's top-right corner and allowed to grow leftwards for
2253
- * 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.
2254
3280
  *
2255
3281
  * Sits above the ::after hit area so the number is never painted underneath it.
2256
3282
  */
2257
- ._badge_nbe8a_57 {
3283
+ ._badge_7rinq_104 {
2258
3284
  position: absolute;
2259
- top: -4px;
2260
- left: 50%;
3285
+ top: -6px;
3286
+ left: 14px;
2261
3287
  z-index: 1;
2262
3288
  min-width: 16px;
2263
3289
  height: 16px;
2264
- padding: 0 4px;
3290
+ padding: 0 var(--space-4);
2265
3291
  box-sizing: border-box;
2266
3292
  display: inline-flex;
2267
3293
  align-items: center;
2268
3294
  justify-content: center;
2269
3295
  border-radius: var(--radius-full);
2270
- background: var(--color-state-surface-critical-default);
2271
- color: #ffffff;
2272
- font-size: 10px;
3296
+ background: #dddedf;
3297
+ color: #080e12;
3298
+ font-size: var(--type-trimmed-xs-size);
2273
3299
  line-height: 1;
3300
+ letter-spacing: 0.12px;
2274
3301
  font-weight: var(--font-weight-bold);
2275
3302
  font-variant-numeric: tabular-nums;
2276
3303
  pointer-events: none;