@nok-integration/storefront-react 0.16.4 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/styles.css CHANGED
@@ -49,7 +49,7 @@
49
49
  --color-text-default: #0c161c;
50
50
  --color-text-subtle: #3d4549;
51
51
  --color-text-disabled: #9ea2a4;
52
- --color-text-strong: #f9fafb;
52
+ --color-text-strong: #f9fafa;
53
53
  --color-surface-default: #f9fafa;
54
54
  --color-surface-soft: #f0f2f2;
55
55
  /* semantic/base/surface/full-color/subtle — one step darker than `soft`. Same value as
@@ -94,6 +94,15 @@
94
94
 
95
95
  /* ── Interaction ──────────────────────────────────────────────────────── */
96
96
  --tap-target-min: 44px;
97
+ /* DS `sizing/inline/inline-xxl` — the height every `mobile/button` is drawn at, in the
98
+ PDP CTA bar (5241:71430) and the cart alike. Deliberately NOT `--tap-target-min`:
99
+ that is the accessibility floor for any control and it is 44, so a button sized from
100
+ it came out 4px short of the design everywhere. Two numbers, two jobs. */
101
+ --control-height: 48px;
102
+ /* The nav row of Figma `mobile/header`, without the status bar the device draws above
103
+ it. Shared, because the menu sheet hangs from the bar's lower edge and has to move
104
+ with it rather than repeat the number. */
105
+ --header-height: 64px;
97
106
  --focus-ring: 0 0 0 2px #ffffff, 0 0 0 4px var(--color-surface-max);
98
107
  --safe-area-bottom: env(safe-area-inset-bottom, 0px);
99
108
  }
@@ -138,7 +147,7 @@
138
147
  [data-shop-theme='light'] {
139
148
  --color-text-default: #0c161c;
140
149
  --color-text-subtle: #3d4549;
141
- --color-text-strong: #f9fafb;
150
+ --color-text-strong: #f9fafa;
142
151
  --color-text-disabled: #9ea2a4;
143
152
  --color-surface-default: #f9fafa;
144
153
  --color-surface-soft: #f0f2f2;
@@ -204,12 +213,14 @@
204
213
  background: var(--color-surface-max);
205
214
  color: var(--color-text-strong);
206
215
  }
207
- ._button_vvcmd_1 {
216
+ ._button_tdhm8_1 {
208
217
  display: inline-flex;
209
218
  align-items: center;
210
219
  justify-content: center;
211
220
  gap: var(--space-8);
212
- min-height: var(--tap-target-min);
221
+ /* The DS draws `mobile/button` at 48. `--tap-target-min` is 44 and is the floor for
222
+ any control, not this control's size — sizing from it left every button 4px short. */
223
+ min-height: var(--control-height);
213
224
  min-width: var(--tap-target-min);
214
225
  padding: 0 var(--space-20);
215
226
  /* DS button corners are radius-8 (not a pill), per the Figma mobile/button. */
@@ -226,51 +237,54 @@
226
237
  -webkit-tap-highlight-color: transparent;
227
238
  }
228
239
 
229
- ._fullWidth_vvcmd_23 {
240
+ ._fullWidth_tdhm8_25 {
230
241
  width: 100%;
231
242
  }
232
243
 
233
- ._primary_vvcmd_27 {
244
+ ._primary_tdhm8_29 {
234
245
  background: var(--color-surface-max);
235
246
  color: var(--color-text-strong);
236
247
  }
237
248
 
238
- ._secondary_vvcmd_32 {
249
+ ._secondary_tdhm8_34 {
239
250
  background: transparent;
240
251
  color: var(--color-text-default);
241
252
  border-color: var(--color-border-default);
242
253
  }
243
254
 
244
- ._ghost_vvcmd_38 {
255
+ ._ghost_tdhm8_40 {
245
256
  background: transparent;
246
257
  color: var(--color-text-default);
247
258
  border-color: transparent;
248
259
  }
249
260
 
250
- ._button_vvcmd_1:disabled {
261
+ ._button_tdhm8_1:disabled {
251
262
  cursor: not-allowed;
252
263
  opacity: 0.5;
253
264
  }
254
265
 
255
- ._button_vvcmd_1:focus-visible {
266
+ ._button_tdhm8_1:focus-visible {
256
267
  outline: 2px solid var(--color-surface-max);
257
268
  outline-offset: 2px;
258
269
  }
259
270
 
260
- ._spinner_vvcmd_54 {
261
- width: 16px;
262
- height: 16px;
271
+ /*
272
+ * Figma `mobile/button` state=loading (438:7004): a 20px ring in a 24px box, beside a
273
+ * label at full strength. The DS had no spinner variant when this was first built — the
274
+ * cta-bar's own note said "annotate in build" — so it was drawn at 16px and the label
275
+ * was dimmed to carry the busy signal. It has one now, and the spinner carries it alone.
276
+ */
277
+ ._spinner_tdhm8_62 {
278
+ width: 20px;
279
+ height: 20px;
280
+ margin: 2px;
263
281
  border-radius: var(--radius-full);
264
282
  border: 2px solid currentColor;
265
283
  border-top-color: transparent;
266
- animation: _spin_vvcmd_54 700ms linear infinite;
284
+ animation: _spin_tdhm8_62 700ms linear infinite;
267
285
  }
268
286
 
269
- ._labelLoading_vvcmd_63 {
270
- opacity: 0.85;
271
- }
272
-
273
- @keyframes _spin_vvcmd_54 {
287
+ @keyframes _spin_tdhm8_62 {
274
288
  to {
275
289
  transform: rotate(360deg);
276
290
  }
@@ -531,11 +545,24 @@
531
545
  color: var(--color-text-subtle);
532
546
  max-width: 34ch;
533
547
  }
534
- ._viewport_1lt9i_1 {
548
+ /*
549
+ * The viewport sits at the TOP now, not the bottom.
550
+ *
551
+ * Figma `Add to cart flow` (5295:75091) puts the confirmation directly under the header,
552
+ * pointing at the cart icon, where the old frame (4432:35943) floated it above the sticky
553
+ * CTA bar. That is the whole idea of the redesign: the message and the thing it is about
554
+ * are next to each other, so the fan's eye ends up on the cart badge rather than back on
555
+ * the button they just pressed.
556
+ *
557
+ * `--toast-top` is measured (see `Toast.tsx`). The 12px default is the design's own gap
558
+ * below the header — and it is also exactly right in the embed, where DAZN's header is
559
+ * native chrome OUTSIDE the webview, so our viewport already begins under it.
560
+ */
561
+ ._viewport_5aafz_14 {
535
562
  position: fixed;
563
+ top: var(--toast-top, var(--space-12));
536
564
  left: 0;
537
565
  right: 0;
538
- bottom: calc(var(--space-16) + var(--safe-area-bottom));
539
566
  display: flex;
540
567
  flex-direction: column;
541
568
  align-items: center;
@@ -545,40 +572,115 @@
545
572
  z-index: 1000;
546
573
  }
547
574
 
548
- ._toast_1lt9i_15 {
575
+ /*
576
+ * One wrapper per toast, so the caret can be placed against the card's own right edge
577
+ * rather than the viewport's. They differ as soon as `max-width` binds.
578
+ *
579
+ * `box-sizing` is stated here, and it is not belt and braces.
580
+ *
581
+ * The package's reset is scoped to `ShopSurface` (`.surface *`), and the toast viewport
582
+ * is mounted OUTSIDE any surface — it is a sibling of the provider's children, which is
583
+ * what lets it float over every screen. So the card gets `content-box` in any host
584
+ * without a reset of its own, and `width: 100%` plus 32px of padding made it 32px wider
585
+ * than the space it was given: it hung off the right edge, taking "View cart" with it.
586
+ * Our own app hides this, because its `globals.css` resets everything.
587
+ */
588
+ ._anchor_5aafz_41 {
589
+ box-sizing: border-box;
590
+ display: flex;
591
+ flex-direction: column;
592
+ align-items: stretch;
593
+ width: 100%;
594
+ max-width: 420px;
595
+ }
596
+
597
+ /*
598
+ * The pointer at the cart icon — Figma `.base/indicator` (24x10), the exported path
599
+ * rather than a CSS border triangle, which cannot round the apex the way the asset does.
600
+ *
601
+ * `--toast-caret-right` is the distance from the card's right edge to the caret's CENTRE,
602
+ * so the margin backs off by half the caret's width. The 60px default is the design's own
603
+ * geometry: 16px screen inset + 32px overflow button + 16px gap + 12px half-icon, less
604
+ * the 16px the card is itself inset by.
605
+ */
606
+ ._caret_5aafz_59 {
607
+ display: block;
608
+ width: 24px;
609
+ height: 10px;
610
+ margin-left: auto;
611
+ margin-right: max(0px, calc(var(--toast-caret-right, 60px) - 12px));
612
+ }
613
+
614
+ /*
615
+ * `block`, and this is the whole reason the caret looked like a nub.
616
+ *
617
+ * An `<svg>` is an inline replaced element, so it sits on the text baseline and the line
618
+ * box reserves room for descenders beneath it. In a 10px-tall parent that pushed the
619
+ * 24x10 triangle about 4px down: its lower half slid behind the card, which paints after
620
+ * it, and what was left on screen was the narrow 6px tip. Every measurement said 24x10 —
621
+ * the span, the svg box, the path's own bounding box — because nothing was resized. It
622
+ * was in the right place and drawn in the wrong one, which is why this had to be caught
623
+ * by comparing pixels against the frame rather than by reading numbers out of the DOM.
624
+ */
625
+ ._caret_5aafz_59 svg {
626
+ display: block;
627
+ }
628
+
629
+ ._toast_5aafz_82 {
630
+ box-sizing: border-box;
549
631
  display: flex;
550
632
  align-items: center;
551
633
  justify-content: space-between;
552
634
  gap: var(--space-12);
553
635
  width: 100%;
554
- max-width: 420px;
555
636
  padding: var(--space-12) var(--space-16);
556
637
  border-radius: var(--radius-12);
638
+ /*
639
+ * Type stated here, not inherited.
640
+ *
641
+ * The viewport is mounted OUTSIDE `ShopSurface` — that is what lets it float over every
642
+ * screen — so it never picked up the surface's `font-family` or line-height and rendered
643
+ * in whatever the host's body happened to be. Invisible in our own app, where the body
644
+ * is already DAZN Oscine, and wrong in every other host. `leading-[normal]` is also what
645
+ * the frame sets, and it is what makes the card 64px rather than whatever a host's
646
+ * paragraph leading works out to.
647
+ */
648
+ font-family: var(--font-family-base);
557
649
  font-size: var(--type-trimmed-sm-size);
558
650
  font-weight: var(--font-weight-bold);
651
+ line-height: normal;
559
652
  box-shadow: 0 6px 24px rgba(8, 14, 18, 0.24);
560
653
  /* The viewport is click-through so it never blocks the page beneath; the toast
561
654
  itself must not be, or an actionable toast's button would be unclickable. */
562
655
  pointer-events: auto;
563
656
  }
564
657
 
565
- ._text_1lt9i_32 {
658
+ ._text_5aafz_111 {
566
659
  display: flex;
567
660
  flex-direction: column;
568
- gap: var(--space-2);
661
+ /* spacer/spacer-xxs between the two lines. Was 2px, which set them closer than the
662
+ frame and made the pair read as one wrapped sentence. */
663
+ gap: var(--space-6);
569
664
  min-width: 0;
570
665
  }
571
666
 
572
- ._message_1lt9i_39 {
667
+ ._message_5aafz_120 {
573
668
  min-width: 0;
574
669
  }
575
670
 
576
- /* Meets the 44px tap target via padding while staying visually inline with the text. */
577
- ._action_1lt9i_44 {
671
+ /*
672
+ * The tap target is an overlay, not padding.
673
+ *
674
+ * It used to be `min-height: 44px` plus negative margins, and both changed the drawing:
675
+ * 44px against a 40px thumbnail made the card 68px tall where the frame says 64, and the
676
+ * -8px margin against a 12px padding put "View cart" 20px from the right edge where the
677
+ * frame says 16. A pseudo-element gives the same 44px of reachable area and occupies no
678
+ * layout — the pattern `ShopHeader`'s `.iconBtn::after` already uses.
679
+ */
680
+ ._action_5aafz_133 {
681
+ position: relative;
578
682
  flex-shrink: 0;
579
- min-height: var(--tap-target-min);
580
- padding: 0 var(--space-12);
581
- margin: calc(var(--space-12) * -1) calc(var(--space-8) * -1);
683
+ padding: 0;
582
684
  border: none;
583
685
  background: none;
584
686
  color: inherit;
@@ -587,50 +689,65 @@
587
689
  cursor: pointer;
588
690
  }
589
691
 
590
- ._action_1lt9i_44:focus-visible {
692
+ ._action_5aafz_133::after {
693
+ content: '';
694
+ position: absolute;
695
+ /* 18px of text, grown to the 44px minimum: 13px above and below. */
696
+ inset: -13px -12px;
697
+ }
698
+
699
+ ._action_5aafz_133:focus-visible {
591
700
  outline: 2px solid currentColor;
592
701
  outline-offset: 2px;
593
702
  border-radius: var(--radius-4);
594
703
  }
595
704
 
596
- ._neutral_1lt9i_63 {
705
+ ._neutral_5aafz_158 {
597
706
  background: var(--color-surface-max);
598
707
  color: var(--color-text-strong);
599
708
  }
600
709
 
601
- ._success_1lt9i_68 {
710
+ ._success_5aafz_163 {
602
711
  background: var(--color-state-text-success);
603
712
  color: var(--color-text-strong);
604
713
  }
605
714
 
606
- ._critical_1lt9i_73 {
715
+ ._critical_5aafz_168 {
607
716
  background: var(--color-state-surface-critical-default);
608
717
  color: var(--color-text-strong);
609
718
  }
610
719
 
611
- /* Figma `toast-added-to-cart` (4432:35943). Fixed palette rather than theme tokens: the
612
- card is dark in every state, and it renders over any screen, light or dark. The hexes
613
- are the design's own #c7cccf is not in our token set. */
614
- ._rich_1lt9i_81 {
720
+ /*
721
+ * Figma `toast-added-to-cart` (5295:75095). Fixed palette rather than theme tokens: the
722
+ * card is dark in every state and renders over any screen, light or dark. The frame
723
+ * builds it from `semantic/base/surface/full-color/max` — the "full-color" set is the one
724
+ * that does NOT invert with the mode, which is why the token names cannot be mapped onto
725
+ * our `--color-surface-max`, whose whole job is to invert.
726
+ *
727
+ * The three hexes moved with the redesign: #0c161c -> #080e12, #ffffff -> #f9fafa,
728
+ * #c7cccf -> #b3b9bb.
729
+ */
730
+ ._rich_5aafz_183 {
615
731
  justify-content: flex-start;
616
732
  gap: var(--space-12);
617
733
  padding: var(--space-12) var(--space-16) var(--space-12) var(--space-12);
618
- background: #0c161c;
619
- color: #ffffff;
734
+ background: #080e12;
735
+ color: #f9fafa;
620
736
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.25);
621
737
  }
622
738
 
623
- ._rich_1lt9i_81 ._message_1lt9i_39 {
739
+ ._rich_5aafz_183 ._message_5aafz_120 {
624
740
  font-size: var(--type-trimmed-sm-size);
625
741
  font-weight: var(--font-weight-bold);
626
742
  line-height: normal;
627
743
  }
628
744
 
629
- ._rich_1lt9i_81 ._description_1lt9i_96 {
745
+ ._rich_5aafz_183 ._description_5aafz_198 {
630
746
  font-size: var(--type-trimmed-xs-size);
631
747
  font-weight: var(--font-weight-regular);
632
748
  line-height: normal;
633
- color: #c7cccf;
749
+ letter-spacing: 0.12px;
750
+ color: #b3b9bb;
634
751
  /* Truncate rather than wrap — a long title would otherwise push "View cart" off. */
635
752
  overflow: hidden;
636
753
  text-overflow: ellipsis;
@@ -638,12 +755,12 @@
638
755
  }
639
756
 
640
757
  /* Trailing edge, without a justify-content that would also split thumb from text. */
641
- ._rich_1lt9i_81 ._action_1lt9i_44 {
758
+ ._rich_5aafz_183 ._action_5aafz_133 {
642
759
  margin-left: auto;
643
760
  text-decoration: none;
644
761
  }
645
762
 
646
- ._thumb_1lt9i_113 {
763
+ ._thumb_5aafz_216 {
647
764
  position: relative;
648
765
  flex-shrink: 0;
649
766
  width: 40px;
@@ -654,7 +771,7 @@
654
771
  background: rgba(255, 255, 255, 0.08);
655
772
  }
656
773
 
657
- ._thumbImage_1lt9i_124 {
774
+ ._thumbImage_5aafz_227 {
658
775
  object-fit: cover;
659
776
  }
660
777
  /* Figma mobile/badge/sale (sm): success-soft bg, success text, icon + -NN%, radius-4. */
@@ -730,7 +847,7 @@
730
847
  * 32 and the circles land on a 64px rhythm.
731
848
  */
732
849
 
733
- ._timeline_1j37h_7 {
850
+ ._timeline_1cum7_7 {
734
851
  list-style: none;
735
852
  margin: 0;
736
853
  padding: 0;
@@ -738,19 +855,19 @@
738
855
  flex-direction: column;
739
856
  }
740
857
 
741
- ._row_1j37h_15 {
858
+ ._row_1cum7_15 {
742
859
  display: flex;
743
860
  flex-direction: column;
744
861
  }
745
862
 
746
- ._line_1j37h_20 {
863
+ ._line_1cum7_20 {
747
864
  display: flex;
748
865
  align-items: center;
749
866
  gap: var(--space-8);
750
867
  height: 32px;
751
868
  }
752
869
 
753
- ._marker_1j37h_27 {
870
+ ._marker_1cum7_27 {
754
871
  position: relative;
755
872
  flex-shrink: 0;
756
873
  width: 32px;
@@ -758,7 +875,7 @@
758
875
  }
759
876
 
760
877
  /* Drawn at 28 inside a 32 column: the disc reads 24px and the plate behind it 26px. */
761
- ._tick_1j37h_35 {
878
+ ._tick_1cum7_35 {
762
879
  position: absolute;
763
880
  top: 50%;
764
881
  left: 50%;
@@ -767,7 +884,7 @@
767
884
  }
768
885
 
769
886
  /* A step still to come: a 24px ring with an 18px well, not a filled dot. */
770
- ._pending_1j37h_44 {
887
+ ._pending_1cum7_44 {
771
888
  position: absolute;
772
889
  top: 50%;
773
890
  left: 50%;
@@ -778,7 +895,7 @@
778
895
  background: var(--color-surface-subtle);
779
896
  }
780
897
 
781
- ._pending_1j37h_44::after {
898
+ ._pending_1cum7_44::after {
782
899
  content: '';
783
900
  position: absolute;
784
901
  top: 50%;
@@ -791,7 +908,7 @@
791
908
  }
792
909
 
793
910
  /* Its own 32px block between two rows, centred on the marker column. */
794
- ._connector_1j37h_68 {
911
+ ._connector_1cum7_68 {
795
912
  display: flex;
796
913
  align-items: center;
797
914
  justify-content: center;
@@ -799,7 +916,7 @@
799
916
  height: 32px;
800
917
  }
801
918
 
802
- ._connector_1j37h_68::before {
919
+ ._connector_1cum7_68::before {
803
920
  content: '';
804
921
  width: 1px;
805
922
  height: 24px;
@@ -807,7 +924,7 @@
807
924
  background: var(--color-surface-subtle);
808
925
  }
809
926
 
810
- ._text_1j37h_84 {
927
+ ._text_1cum7_84 {
811
928
  display: flex;
812
929
  flex-direction: column;
813
930
  gap: 10px;
@@ -824,7 +941,7 @@
824
941
  * "Packing your order". The padding gives the glyphs somewhere to go and the equal negative
825
942
  * margin takes it back out of the layout, leaving the box 11px and the text uncut.
826
943
  */
827
- ._label_1j37h_101 {
944
+ ._label_1cum7_101 {
828
945
  font-size: var(--type-trimmed-md-size);
829
946
  line-height: 11px;
830
947
  white-space: nowrap;
@@ -836,22 +953,32 @@
836
953
  margin-bottom: -6px;
837
954
  }
838
955
 
839
- ._past_1j37h_113 {
956
+ ._past_1cum7_113 {
840
957
  font-weight: var(--font-weight-regular);
841
958
  color: var(--color-text-subtle);
842
959
  }
843
960
 
844
- ._current_1j37h_118 {
961
+ ._current_1cum7_118 {
845
962
  font-weight: var(--font-weight-bold);
846
963
  color: var(--color-text-default);
847
964
  }
848
965
 
849
- ._future_1j37h_123 {
966
+ /*
967
+ * Subtle, not disabled, and the same tone as a step that has already happened.
968
+ *
969
+ * `mobile/order/delivery-widget` is ONE component with status variants, instanced by both
970
+ * the tracking screen and the confirmation, and on the confirmation frame (5295:74053)
971
+ * "On the way" and "Estimated delivery" are both `semantic/base/text/subtle`. What
972
+ * separates a step that has happened from one that has not is the marker beside it and
973
+ * whether it carries a date — not the weight of its label. Ours was `text/disabled`,
974
+ * which greys a step that is genuinely coming as though it were unavailable.
975
+ */
976
+ ._future_1cum7_133 {
850
977
  font-weight: var(--font-weight-regular);
851
- color: var(--color-text-disabled);
978
+ color: var(--color-text-subtle);
852
979
  }
853
980
 
854
- ._date_1j37h_128 {
981
+ ._date_1cum7_138 {
855
982
  font-size: var(--type-trimmed-sm-size);
856
983
  line-height: 10px;
857
984
  font-weight: var(--font-weight-regular);
@@ -859,13 +986,22 @@
859
986
  white-space: nowrap;
860
987
  }
861
988
  /* Figma mobile/checkout/summary-row: 45px rows, hairline between, both sides bold. */
862
- ._summary_1h18y_2 {
989
+ ._summary_1hsxt_2 {
863
990
  margin: 0;
864
991
  display: flex;
865
992
  flex-direction: column;
866
993
  }
867
994
 
868
- ._row_1h18y_8 {
995
+ /*
996
+ * 45 including the hairline, not 45 plus it.
997
+ *
998
+ * `mobile/checkout/summary-row` is 45px tall with its rule drawn inside that height, so a
999
+ * four-row card is exactly 180. Ours measured 46 and 47: the border sat outside the
1000
+ * min-height, and the label inherited the surface's leading where the figure beside it
1001
+ * states its own. Two pixels a row, four rows, on three screens.
1002
+ */
1003
+ ._row_1hsxt_16 {
1004
+ box-sizing: border-box;
869
1005
  display: flex;
870
1006
  align-items: center;
871
1007
  justify-content: space-between;
@@ -877,32 +1013,50 @@
877
1013
  color: var(--color-text-default);
878
1014
  }
879
1015
 
880
- ._row_1h18y_8 + ._row_1h18y_8 {
881
- border-top: 1px solid var(--color-border-default);
1016
+ /*
1017
+ * The hairline is an inset shadow, not a border, for the same reason the order cards' ring
1018
+ * is: the design's row is 45px WITH its rule inside that height, and a CSS border adds to
1019
+ * it. Four rows of 46 is a summary card 4px taller than the one drawn.
1020
+ */
1021
+ ._row_1hsxt_16 + ._row_1hsxt_16 {
1022
+ box-shadow: inset 0 1px 0 var(--color-border-default);
882
1023
  }
883
1024
 
884
- ._row_1h18y_8 dt,
885
- ._row_1h18y_8 dd {
1025
+ ._row_1hsxt_16 dt,
1026
+ ._row_1hsxt_16 dd {
886
1027
  margin: 0;
1028
+ /* Stated on both, so the row's height comes from the design rather than from whatever
1029
+ leading the host's body happens to carry. */
1030
+ line-height: var(--type-caption-sm-line);
887
1031
  }
888
1032
 
889
- ._row_1h18y_8 dd {
1033
+ ._row_1hsxt_16 dd {
890
1034
  line-height: var(--type-caption-sm-line);
891
1035
  white-space: nowrap;
892
1036
  font-variant-numeric: tabular-nums;
893
1037
  }
894
1038
 
895
1039
  /* "Free" is a merchandising signal, not a number — success tone, per the design. */
896
- ._free_1h18y_36 {
1040
+ ._free_1hsxt_53 {
897
1041
  color: var(--color-state-text-success);
898
1042
  font-weight: var(--font-weight-bold);
899
1043
  }
900
1044
 
901
1045
  /* "Calculated at checkout". Set quieter than a real figure so it never reads as one. */
902
- ._pending_1h18y_42 {
1046
+ ._pending_1hsxt_59 {
903
1047
  font-size: var(--type-trimmed-xs-size);
904
1048
  color: var(--color-text-subtle);
905
1049
  }
1050
+
1051
+ /*
1052
+ * The discount row, both sides in the success tone (Figma `5295:74073`). It is the only
1053
+ * row in the card that is not `text/default`, which is the point: a saving is the one
1054
+ * figure on a receipt the fan is meant to notice.
1055
+ */
1056
+ ._row_1hsxt_16._discount_1hsxt_69 dt,
1057
+ ._row_1hsxt_16._discount_1hsxt_69 dd {
1058
+ color: var(--color-state-text-success);
1059
+ }
906
1060
  ._page_1eqkm_1 {
907
1061
  display: flex;
908
1062
  flex-direction: column;
@@ -1198,6 +1352,269 @@
1198
1352
  ._summaryCard_1eqkm_275 > :last-child {
1199
1353
  padding: 0 var(--space-16);
1200
1354
  }
1355
+ /*
1356
+ * Figma `5295:74053`. The page's own block starts at y=118 — under the 64px bar and the
1357
+ * 54px status bar the device draws above it — with 20px above the first heading, 24px
1358
+ * below the last control, and 32px between the five blocks.
1359
+ */
1360
+ ._page_8yxu9_6 {
1361
+ display: flex;
1362
+ flex-direction: column;
1363
+ gap: var(--space-32);
1364
+ padding: var(--space-20) var(--space-16) var(--space-24);
1365
+ }
1366
+
1367
+ /* ── Confirmation + timeline ───────────────────────────────────────────────── */
1368
+
1369
+ /* `mobile/order/delivery-widget`: the heading pair, then the timeline, 24px apart. */
1370
+ ._confirmed_8yxu9_16 {
1371
+ display: flex;
1372
+ flex-direction: column;
1373
+ gap: var(--space-24);
1374
+ }
1375
+
1376
+ ._title_8yxu9_22 {
1377
+ display: flex;
1378
+ flex-direction: column;
1379
+ gap: var(--space-12);
1380
+ }
1381
+
1382
+ /*
1383
+ * Green, and the only green heading in the storefront: `semantic/state/text/success`
1384
+ * rather than `text/default`, which is the design saying this line is a result and not a
1385
+ * label. It is the same token the discount row and the "Free" shipping figure take, so
1386
+ * the screen has one success tone rather than three.
1387
+ *
1388
+ * `line-height: 14px` on 20px text, and that is the frame's own number rather than a
1389
+ * squeeze: every text layer here is `text-box-trim: trim-both` with a cap-alphabetic
1390
+ * edge, so Figma's boxes stop at the cap height and its 14 is 14 of visible letter. A CSS
1391
+ * line box carries the font's leading above and below, which renders the same heading as
1392
+ * 26 and pushes all four blocks below it down by the difference.
1393
+ *
1394
+ * `text-box-trim` would let us write the design's own number and is Chrome 133+ only; this
1395
+ * package still supports the WebView in React 16-era DAZN builds. `OrderTimeline` and
1396
+ * `ProductTile` carry the same approximation for the same reason.
1397
+ *
1398
+ * Nothing clips it, so the cap-height box needs no descender protection — unlike the
1399
+ * subtitle below, which the design truncates.
1400
+ */
1401
+ ._headline_8yxu9_47 {
1402
+ margin: 0;
1403
+ font-size: var(--type-heading-md-size);
1404
+ line-height: 14px;
1405
+ font-weight: var(--font-weight-bold);
1406
+ color: var(--color-state-text-success);
1407
+ }
1408
+
1409
+ /*
1410
+ * 11px on 16px text, the same cap-height trim as the heading, and one line: the design's
1411
+ * own text layer is `overflow-hidden` with `text-ellipsis`, which is how it holds an
1412
+ * address of unknown length to a single row.
1413
+ *
1414
+ * The padding is what the trim costs. Clipping cuts in both directions, and an 11px box
1415
+ * beheads every descender — including the `g` in the `@gmail.com` the frame itself shows.
1416
+ * The padding gives the glyphs somewhere to go and the equal negative margin takes it back
1417
+ * out of the layout, leaving the box 11px and the letters whole.
1418
+ */
1419
+ ._subtitle_8yxu9_65 {
1420
+ margin: -6px 0;
1421
+ padding: 6px 0;
1422
+ font-size: var(--type-paragraph-size);
1423
+ line-height: 11px;
1424
+ font-weight: var(--font-weight-regular);
1425
+ color: var(--color-text-subtle);
1426
+ overflow: hidden;
1427
+ text-overflow: ellipsis;
1428
+ white-space: nowrap;
1429
+ }
1430
+
1431
+ /* ── Lines ─────────────────────────────────────────────────────────────────── */
1432
+
1433
+ /*
1434
+ * `mobile/checkout/cart-row`, and deliberately NOT inside a card.
1435
+ *
1436
+ * The tracking screen puts its items in a bordered "Items" card with a count; this frame
1437
+ * lays them straight on the page between the timeline and the address. The same data, and
1438
+ * two designs — so the row is written here rather than either screen being bent to fit the
1439
+ * other, and `CartLineItem` is left alone because its copy of this component carries a
1440
+ * quantity stepper and a remove control that a receipt must not have.
1441
+ */
1442
+ ._lines_8yxu9_88 {
1443
+ list-style: none;
1444
+ margin: 0;
1445
+ padding: 0;
1446
+ display: flex;
1447
+ flex-direction: column;
1448
+ gap: var(--space-16);
1449
+ }
1450
+
1451
+ ._line_8yxu9_88 {
1452
+ display: flex;
1453
+ align-items: center;
1454
+ gap: var(--space-12);
1455
+ }
1456
+
1457
+ ._thumb_8yxu9_103 {
1458
+ position: relative;
1459
+ flex-shrink: 0;
1460
+ width: 48px;
1461
+ height: 48px;
1462
+ border-radius: var(--radius-8);
1463
+ overflow: hidden;
1464
+ background: var(--color-surface-soft);
1465
+ }
1466
+
1467
+ ._image_8yxu9_113 {
1468
+ object-fit: cover;
1469
+ }
1470
+
1471
+ ._placeholder_8yxu9_117 {
1472
+ width: 100%;
1473
+ height: 100%;
1474
+ }
1475
+
1476
+ ._lineBody_8yxu9_122 {
1477
+ display: flex;
1478
+ align-items: flex-start;
1479
+ justify-content: space-between;
1480
+ gap: var(--space-16);
1481
+ flex: 1 1 0;
1482
+ min-width: 0;
1483
+ }
1484
+
1485
+ ._lineText_8yxu9_131 {
1486
+ display: flex;
1487
+ flex-direction: column;
1488
+ gap: var(--space-12);
1489
+ flex: 1 1 0;
1490
+ min-width: 0;
1491
+ }
1492
+
1493
+ /* Cap-trimmed to the frame's 11, with the same descender protection as the subtitle —
1494
+ this one is ellipsised too, and a `p` or a `g` in a product name is not unusual. */
1495
+ ._lineTitle_8yxu9_141 {
1496
+ margin: -6px 0;
1497
+ padding: 6px 0;
1498
+ font-size: var(--type-trimmed-sm-size);
1499
+ line-height: 11px;
1500
+ font-weight: var(--font-weight-bold);
1501
+ white-space: nowrap;
1502
+ overflow: hidden;
1503
+ text-overflow: ellipsis;
1504
+ }
1505
+
1506
+ /*
1507
+ * The variant, then the sale badge beside it — 12px apart, vertically centred on it.
1508
+ *
1509
+ * Cap-trimmed like the title above, which is what leaves the badge as the tallest thing in
1510
+ * the row and holds it to the 20px the frame gives it. Untrimmed the text alone was 22, so
1511
+ * the row stood 55 against the frame's 48 and the thumbnail stopped being what sets the
1512
+ * row's height.
1513
+ */
1514
+ ._lineMeta_8yxu9_160 {
1515
+ display: flex;
1516
+ align-items: center;
1517
+ gap: var(--space-12);
1518
+ min-width: 0;
1519
+ font-size: var(--type-trimmed-sm-size);
1520
+ line-height: 11px;
1521
+ color: var(--color-text-subtle);
1522
+ }
1523
+
1524
+ /*
1525
+ * Stacked, not in a row: the frame puts what was paid above what it would have cost, at
1526
+ * two different sizes. That is why this is written here rather than with `PriceBlock`,
1527
+ * which lays the pair out inline at one size for the product card and the PDP.
1528
+ */
1529
+ ._linePrice_8yxu9_175 {
1530
+ display: flex;
1531
+ flex-direction: column;
1532
+ align-items: flex-end;
1533
+ gap: var(--space-6);
1534
+ flex-shrink: 0;
1535
+ white-space: nowrap;
1536
+ font-variant-numeric: tabular-nums;
1537
+ }
1538
+
1539
+ /* Cap-trimmed like everything else in this row: 11 on 16px, 10 on 14px. Untrimmed the
1540
+ pair stood 52 tall and became what set the row's height, against a 48px thumbnail. */
1541
+ ._paid_8yxu9_187 {
1542
+ font-size: var(--type-trimmed-md-size);
1543
+ line-height: 11px;
1544
+ font-weight: var(--font-weight-bold);
1545
+ }
1546
+
1547
+ ._was_8yxu9_193 {
1548
+ font-size: var(--type-trimmed-sm-size);
1549
+ line-height: 10px;
1550
+ font-weight: var(--font-weight-regular);
1551
+ color: var(--color-text-disabled);
1552
+ text-decoration: line-through;
1553
+ }
1554
+
1555
+ /* ── Cards ─────────────────────────────────────────────────────────────────── */
1556
+
1557
+ /*
1558
+ * The address and summary cards are one shape: a 60px titled header, a hairline, and a
1559
+ * body. They differ only in ground, which is what `.summaryCard` below changes.
1560
+ *
1561
+ * The ring is an inset shadow rather than a border, and that is a measurement rather than
1562
+ * a preference. Figma draws a frame's stroke INSIDE its box, so the address card is 113
1563
+ * tall with its outline in that 113 — 60 of header and 53 of body. A CSS border sits
1564
+ * outside the content and makes the same card 115. An inset shadow paints the same
1565
+ * hairline and occupies no layout, so the card lands on the design's own number.
1566
+ */
1567
+ ._card_8yxu9_213 {
1568
+ display: flex;
1569
+ flex-direction: column;
1570
+ box-shadow: inset 0 0 0 1px var(--color-border-default);
1571
+ border-radius: var(--radius-18);
1572
+ overflow: hidden;
1573
+ }
1574
+
1575
+ ._cardTitle_8yxu9_221 {
1576
+ display: flex;
1577
+ align-items: center;
1578
+ margin: 0;
1579
+ min-height: 60px;
1580
+ padding: 0 var(--space-16);
1581
+ border-bottom: 1px solid var(--color-border-default);
1582
+ font-size: var(--type-heading-md-size);
1583
+ line-height: 1.38;
1584
+ font-weight: var(--font-weight-bold);
1585
+ }
1586
+
1587
+ ._cardBody_8yxu9_233 {
1588
+ margin: 0;
1589
+ padding: var(--space-16);
1590
+ font-size: var(--type-trimmed-sm-size);
1591
+ line-height: 1.5;
1592
+ }
1593
+
1594
+ /* `surface/full-color/soft` — the summary is the one card the design fills. */
1595
+ ._summaryCard_8yxu9_205 {
1596
+ background: var(--color-surface-soft);
1597
+ }
1598
+
1599
+ /* The shared summary rows are drawn edge-to-edge in the cart; here they take the card's
1600
+ own gutter, as they do on the tracking screen. */
1601
+ ._summaryCard_8yxu9_205 > :last-child {
1602
+ padding: 0 var(--space-16);
1603
+ }
1604
+
1605
+ /* ── Done ──────────────────────────────────────────────────────────────────── */
1606
+
1607
+ /*
1608
+ * `mobile/button`, outlined: `border/full-color/max` on the page's own ground.
1609
+ *
1610
+ * `--color-surface-max` and not `--color-border-default`, which is the #dddedf hairline
1611
+ * the cards are drawn with. This border is the same value as the primary button's fill,
1612
+ * which is what "max" means, and taking it from that token keeps the two in step if the
1613
+ * palette ever moves.
1614
+ */
1615
+ ._done_8yxu9_261 {
1616
+ border-color: var(--color-surface-max);
1617
+ }
1201
1618
  /*
1202
1619
  * Figma `mobile/order/status-badge` — 24px tall, 2px radius, 12px bold uppercase.
1203
1620
  * Squarer and tighter than the catalogue `Badge`, which is a pill; they are separate
@@ -2002,7 +2419,7 @@
2002
2419
  height: 1px;
2003
2420
  background: var(--color-border-default);
2004
2421
  }
2005
- ._bar_1aly7_1 {
2422
+ ._bar_xqpxa_1 {
2006
2423
  position: sticky;
2007
2424
  bottom: 0;
2008
2425
  left: 0;
@@ -2016,30 +2433,46 @@
2016
2433
  z-index: 50;
2017
2434
  }
2018
2435
 
2019
- /* Figma mobile/pdp/cta-bar — a 6% lift, not the heavy shelf we had. */
2020
- ._floating_1aly7_16 {
2436
+ /*
2437
+ * Figma `mobile/pdp/cta-bar` (5241:71430): `drop-shadow(0 -4px 12px rgba(206,206,206,.48))`.
2438
+ *
2439
+ * This was 6% for a week. The August pass read the shadow off `pdp` (3601:2479) and
2440
+ * reduced it eightfold on the grounds that ours was "eight times the drawn opacity"; the
2441
+ * frame of record is `full PDP` now (ADR-0022) and it draws the lift at 48% over 12px.
2442
+ * Figma's own export settles it: the 28px above the bar shade from #f9fafa down to about
2443
+ * #ececec, which is a lift you can see. At 6% there was nothing there at all, and the bar
2444
+ * sat on the page with no separation from a background of exactly its own colour.
2445
+ *
2446
+ * `filter: drop-shadow`, not `box-shadow`, because the two do not mean the same thing by
2447
+ * "12px". A filter blurs with a Gaussian of that radius and carries a long tail; a
2448
+ * box-shadow ramps over the length itself. Given the same 12, box-shadow reached full
2449
+ * strength 8px later and the lift stopped short of the frame. This is also what the file
2450
+ * says, so there is nothing to translate.
2451
+ */
2452
+ ._floating_xqpxa_31 {
2021
2453
  border-radius: var(--radius-12) var(--radius-12) 0 0;
2022
- box-shadow: 0 -4px 6px rgba(176, 176, 176, 0.06);
2454
+ filter: drop-shadow(0 -4px 12px rgba(206, 206, 206, 0.48));
2023
2455
  }
2024
2456
 
2025
2457
  /* Figma cart. */
2026
- ._divider_1aly7_22 {
2458
+ ._divider_xqpxa_37 {
2027
2459
  border-top: 1px solid var(--color-border-default);
2028
2460
  }
2029
2461
 
2030
- ._info_1aly7_26 {
2462
+ ._info_xqpxa_41 {
2031
2463
  display: flex;
2032
2464
  align-items: center;
2033
2465
  justify-content: space-between;
2034
2466
  font-weight: var(--font-weight-bold);
2035
2467
  }
2036
2468
 
2037
- ._actions_1aly7_33 {
2469
+ /* `spacer-xs` between actions, per the `button-group` the bar wraps. */
2470
+ ._actions_xqpxa_49 {
2038
2471
  display: flex;
2039
- gap: var(--space-12);
2472
+ gap: var(--space-8);
2040
2473
  }
2041
2474
 
2042
- ._actions_1aly7_33 > * {
2475
+ ._actions_xqpxa_49 > * {
2043
2476
  flex: 1 1 0;
2044
2477
  }
2045
2478
  /*
@@ -2771,7 +3204,7 @@
2771
3204
  * the SMALL viewport (browser chrome expanded), so the hero never grows taller than the
2772
3205
  * visible area and push the first rail off-screen on load.
2773
3206
  */
2774
- ._hero_2mcnp_10 {
3207
+ ._hero_pi6qp_10 {
2775
3208
  --hero-height: clamp(480px, 100svh, 812px);
2776
3209
 
2777
3210
  position: relative;
@@ -2798,12 +3231,12 @@
2798
3231
  margin-bottom: calc(var(--hero-height) * -237 / 812);
2799
3232
  }
2800
3233
 
2801
- ._image_2mcnp_37 {
3234
+ ._image_pi6qp_37 {
2802
3235
  position: absolute;
2803
3236
  inset: 0;
2804
3237
  }
2805
3238
 
2806
- ._image_2mcnp_37 img {
3239
+ ._image_pi6qp_37 img {
2807
3240
  object-fit: cover;
2808
3241
  }
2809
3242
 
@@ -2812,13 +3245,13 @@
2812
3245
  * top third, so darkening the whole frame would only mute the photograph without helping
2813
3246
  * the words. Fixed colour — it darkens a photograph, not a themed surface.
2814
3247
  */
2815
- ._scrim_2mcnp_51 {
3248
+ ._scrim_pi6qp_51 {
2816
3249
  position: absolute;
2817
3250
  inset: 0;
2818
3251
  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%);
2819
3252
  }
2820
3253
 
2821
- ._text_2mcnp_57 {
3254
+ ._text_pi6qp_57 {
2822
3255
  position: absolute;
2823
3256
  top: 106px;
2824
3257
  left: 0;
@@ -2833,7 +3266,16 @@
2833
3266
 
2834
3267
  /* Fixed light values on both counts: this text is over a photograph, so it does not
2835
3268
  follow the surface palette. */
2836
- ._title_2mcnp_72 {
3269
+ /*
3270
+ * The frame gives the two lines fixed boxes, 327 and 224, and centres the text inside
3271
+ * them. Their centring parent sizes children to their content, so without a width stated
3272
+ * each line came out as wide as its own words: "Hoodies" measured 143 against 327. It
3273
+ * looks identical while the text fits and diverges the moment it does not, which is the
3274
+ * same shape as the shop entry's headline.
3275
+ */
3276
+ ._title_pi6qp_79 {
3277
+ width: 327px;
3278
+ max-width: 100%;
2837
3279
  margin: 0;
2838
3280
  font-size: 40px;
2839
3281
  line-height: 1;
@@ -2842,7 +3284,9 @@
2842
3284
  text-wrap: balance;
2843
3285
  }
2844
3286
 
2845
- ._subtitle_2mcnp_81 {
3287
+ ._subtitle_pi6qp_90 {
3288
+ width: 224px;
3289
+ max-width: 100%;
2846
3290
  margin: 0;
2847
3291
  font-size: var(--type-trimmed-md-size);
2848
3292
  line-height: 1;
@@ -2855,7 +3299,7 @@
2855
3299
  * be one: it collapses to the height of its own text on the page background rather than
2856
3300
  * holding a screen-tall empty box.
2857
3301
  */
2858
- ._noImage_2mcnp_94 {
3302
+ ._noImage_pi6qp_105 {
2859
3303
  height: auto;
2860
3304
  min-height: 0;
2861
3305
  max-height: none;
@@ -2863,13 +3307,13 @@
2863
3307
  margin-bottom: 0;
2864
3308
  }
2865
3309
 
2866
- ._noImage_2mcnp_94 ._text_2mcnp_57 {
3310
+ ._noImage_pi6qp_105 ._text_pi6qp_57 {
2867
3311
  position: static;
2868
3312
  padding-top: var(--space-24);
2869
3313
  }
2870
3314
 
2871
- ._noImage_2mcnp_94 ._title_2mcnp_72,
2872
- ._noImage_2mcnp_94 ._subtitle_2mcnp_81 {
3315
+ ._noImage_pi6qp_105 ._title_pi6qp_79,
3316
+ ._noImage_pi6qp_105 ._subtitle_pi6qp_90 {
2873
3317
  color: var(--color-text-default);
2874
3318
  }
2875
3319
  /*
@@ -3192,10 +3636,11 @@
3192
3636
  row-gap: var(--space-24);
3193
3637
  }
3194
3638
  /*
3195
- * Figma "homepage v2" (node 2795:98786): a full-screen, snap-scrolling editorial
3196
- * showcase. Each panel is a full-bleed photo with a bottom overlay (title + subtitle +
3197
- * outline CTA). Always dark (#080e12), independent of theme. Measurements are taken
3198
- * from Figma: overlay pad 24, gap 16, title 32, subtitle 16/1.55, button h48 radius 8.
3639
+ * Figma `homepage` (4533:31350) on the page of record: a full-screen, snap-scrolling
3640
+ * editorial showcase. Each panel is a full-bleed photo with a bottom overlay (title +
3641
+ * subtitle + outline CTA). Always dark (#080e12), independent of theme. Measurements are
3642
+ * taken from `slide/default` (4564:47655): overlay pad 24, gap 16, title 32/1.2, subtitle
3643
+ * 16/1.55, button h48 radius 8 with a 1px #f9fafa rule.
3199
3644
  */
3200
3645
 
3201
3646
  /*
@@ -3220,7 +3665,7 @@
3220
3665
  * `--showcase-top` is measured at mount and on resize (see HomeShowcase.tsx) rather than
3221
3666
  * assumed: whatever the host puts above us, we take the rest.
3222
3667
  */
3223
- ._showcase_ems17_30 {
3668
+ ._showcase_1sarz_31 {
3224
3669
  height: calc(100svh - var(--showcase-top, 0px));
3225
3670
  overflow-y: auto;
3226
3671
  scroll-snap-type: y mandatory;
@@ -3235,11 +3680,11 @@
3235
3680
  background: #080e12;
3236
3681
  scrollbar-width: none;
3237
3682
  }
3238
- ._showcase_ems17_30::-webkit-scrollbar {
3683
+ ._showcase_1sarz_31::-webkit-scrollbar {
3239
3684
  display: none;
3240
3685
  }
3241
3686
 
3242
- ._panel_ems17_49 {
3687
+ ._panel_1sarz_50 {
3243
3688
  position: relative;
3244
3689
  /* Matches the scroller exactly, or snapping has nothing to align to. */
3245
3690
  height: calc(100svh - var(--showcase-top, 0px));
@@ -3249,34 +3694,134 @@
3249
3694
  background: #080e12;
3250
3695
  }
3251
3696
 
3252
- ._image_ems17_59 {
3697
+ ._image_1sarz_60 {
3253
3698
  object-fit: cover;
3254
3699
  object-position: center;
3255
3700
  }
3256
3701
 
3257
- /* Bottom scrim so overlay text stays legible over any photo. */
3258
- ._panel_ems17_49::after {
3702
+ /*
3703
+ * The category panels carry NO scrim, and that is measured rather than assumed.
3704
+ *
3705
+ * Each `slide/default` box is 375x812, which is `hoodies.jpg` and its siblings at their
3706
+ * exact natural size at 375 wide — so the frame's own export IS the raw file plus whatever
3707
+ * the design puts over it, and the overlay can be solved with no geometry to get wrong.
3708
+ * Against the raw assets it comes out at zero the whole way down, on two slides:
3709
+ *
3710
+ * Hoodies 12% 0.003 32% 0.003 57% 0.010 81% 0.007 96% 0.008
3711
+ * All items 12% 0.005 42% 0.009 52% 0.005 62% 0.002
3712
+ *
3713
+ * The 0.85 gradient this replaced was ours, added for legibility and never in the design.
3714
+ * The five designed photographs are dark where the copy sits, which is how the frames stay
3715
+ * readable without one.
3716
+ *
3717
+ * **That makes the foot of a slide image a content rule.** These panels are CMS-authored
3718
+ * (ADR-0016), and white copy over a bright photograph fails contrast with nothing to catch
3719
+ * it — WCAG AA is a launch gate. Whoever picks a slide has to pick one with a dark foot.
3720
+ */
3721
+
3722
+ /* ── Hero ── */
3723
+ /*
3724
+ * 31px down, and running past the foot of the panel.
3725
+ *
3726
+ * `edit_9 1` sits at y=85, height 674, in an 812 frame whose first 54px are the status bar
3727
+ * the device draws and whose last 90px are the browser's own bottom bar. Against the page
3728
+ * itself that is 31px below the top, leaving a band of bare #080e12 that the bar is drawn
3729
+ * over, and carrying on 37px behind the chrome at the foot.
3730
+ *
3731
+ * The band is deliberate: it is what the frame draws, and DAZN confirmed it on 20 Aug over
3732
+ * the alternative of filling the panel.
3733
+ *
3734
+ * **The box takes the photograph's own aspect rather than the panel's**, which is what makes
3735
+ * the hero identical rather than merely similar. `hero.jpg` is 750x1348, so at 375 wide it
3736
+ * is 674 tall and lands on exactly the frame's box. Running the box to the foot of the
3737
+ * panel instead handed `cover` a shape the picture does not have, and on anything taller
3738
+ * than the artboard it cropped the sides to fill: at 393x852 the panel wants 821 where the
3739
+ * photograph is 706, so the model came out zoomed and 32px was lost from each edge.
3740
+ *
3741
+ * Nothing shows below it. The panel's ground is the same `#080e12` the gradient is running
3742
+ * to, and the gradient is at 0.95 by the row the picture ends on, so the seam is invisible
3743
+ * at any height.
3744
+ */
3745
+ ._heroImageBox_1sarz_108 {
3746
+ position: absolute;
3747
+ top: 31px;
3748
+ left: 0;
3749
+ right: 0;
3750
+ aspect-ratio: 750 / 1348;
3751
+ overflow: hidden;
3752
+ }
3753
+
3754
+ /*
3755
+ * TWO gradients, and the top one is why the photograph bleeds in rather than starting on a
3756
+ * hard edge.
3757
+ *
3758
+ * Solved against the raw file the same way as the foot, and it is the tighter fit of the
3759
+ * two. Straight from full `#080e12` at 39px to nothing at 119px, residuals inside 0.03:
3760
+ *
3761
+ * 47px 0.901 (fit .900) 79px 0.517 (fit .500) 111px 0.126 (fit .100)
3762
+ * 63px 0.703 (fit .700) 95px 0.327 (fit .300) 119px 0.000 (fit .000)
3763
+ *
3764
+ * The picture begins at 31 and the scrim is still solid at 39, so its first rows are painted
3765
+ * out completely: there is no top edge to see, which is the whole point of it. Without this
3766
+ * the shot started abruptly under the bar, and it got worse the taller the screen.
3767
+ *
3768
+ * Stated in pixels, not percentages, because what it exists for is a fixed height: it is the
3769
+ * dark the header's glyphs are read against while the bar itself is clear. That also makes
3770
+ * the pair consistent with what `Scroll` shows — scrolled, this gradient has travelled up
3771
+ * out of the frame with the panel it belongs to, and the bar switches on its own flat 0.6
3772
+ * instead. Two mechanisms, one job, never both at once.
3773
+ *
3774
+ * The two layers do not overlap and so cannot double up: this one is spent by 119px and the
3775
+ * foot's begins at 18%, which is 120px at the artboard height and further down on anything
3776
+ * taller.
3777
+ */
3778
+
3779
+ /*
3780
+ * The scrim at the foot is its own, and much stronger than a category panel's.
3781
+ *
3782
+ * Solved rather than eyeballed. The photograph and its placement are identical to the frame
3783
+ * — cross-correlated over scale and offset the best alignment is 1.00 and 0 — so every
3784
+ * remaining difference is the darkening, and that can be recovered per pixel: undo our own
3785
+ * gradient to get the ungraded value, then solve what the frame applied. Across 24 rows the
3786
+ * answer is a straight line, and the residuals are within 0.01 the whole way down:
3787
+ *
3788
+ * 18% 0.000 45% 0.378 (fit 0.375) 75% 0.790 (fit 0.792)
3789
+ * 30% 0.176 60% 0.586 (fit 0.583) 90% 1.007 (fit 1.000)
3790
+ *
3791
+ * The end stop is the tell that this is real and not a curve bent to fit: the gradient
3792
+ * reaches full `#080e12` at 90%, and 90% of 668 is 601 — which is where the opaque swipe
3793
+ * cue starts. The photograph is drawn to go completely black exactly as the bar takes over.
3794
+ */
3795
+ ._hero_1sarz_108::after {
3259
3796
  content: '';
3260
3797
  position: absolute;
3261
3798
  inset: 0;
3262
- background: linear-gradient(to bottom, rgba(8, 14, 18, 0) 45%, rgba(8, 14, 18, 0.85) 100%);
3799
+ background:
3800
+ linear-gradient(to bottom, rgba(8, 14, 18, 1) 39px, rgba(8, 14, 18, 0) 119px),
3801
+ linear-gradient(to bottom, rgba(8, 14, 18, 0) 18%, rgba(8, 14, 18, 1) 90%);
3263
3802
  pointer-events: none;
3264
3803
  }
3265
3804
 
3266
- /* ── Hero ── */
3267
3805
  /*
3268
- * Unlike the category panels (`edit_6 1`, full-bleed 375x812), the hero photo in Figma
3269
- * (`edit_9 1`) is 375x674 and stops where the opaque "swipe down" bar starts. Giving it
3270
- * its own box keeps the shot at its native aspect instead of cover-cropping the sides.
3806
+ * Anchored to its top edge, not its centre.
3807
+ *
3808
+ * The frame hangs the photo from y=31 at its natural height and lets the foot run off the
3809
+ * screen; `cover` centred instead splits the overflow between top and bottom, which moved
3810
+ * the whole shot up by half of it and put the model's eyeline in the wrong place. Same box,
3811
+ * same crop amount, wrong half.
3271
3812
  */
3272
- ._heroImageBox_ems17_79 {
3273
- position: absolute;
3274
- inset: 0 0 82px;
3275
- overflow: hidden;
3813
+ ._hero_1sarz_108 ._image_1sarz_60 {
3814
+ object-position: top;
3276
3815
  }
3277
- ._hero_ems17_79 ._heroContent_ems17_84 {
3816
+ ._hero_1sarz_108 ._heroContent_1sarz_179 {
3278
3817
  position: absolute;
3279
- bottom: 15px;
3818
+ /*
3819
+ * Flush. The frame's content block ends at y=722, and 722 is where the page ends — the
3820
+ * 90px below it is the browser bar, which is chrome in the mock and outside the viewport
3821
+ * on a device. The 15px this replaced was a strip of bare panel under an opaque bar, and
3822
+ * it read as nothing only because both are #080e12.
3823
+ */
3824
+ bottom: 0;
3280
3825
  left: 0;
3281
3826
  right: 0;
3282
3827
  z-index: 1;
@@ -3285,7 +3830,7 @@
3285
3830
  gap: 24px;
3286
3831
  align-items: stretch;
3287
3832
  }
3288
- ._heroText_ems17_95 {
3833
+ ._heroText_1sarz_196 {
3289
3834
  display: flex;
3290
3835
  flex-direction: column;
3291
3836
  gap: 8px;
@@ -3293,47 +3838,85 @@
3293
3838
  padding: 0 24px;
3294
3839
  text-align: center;
3295
3840
  }
3296
- ._logo_ems17_103 {
3841
+ /*
3842
+ * `.heroText .logo`, and the extra selector is the whole point.
3843
+ *
3844
+ * `ShopSurface` resets content images with `.surface img { max-width: 100%; height: auto }`
3845
+ * — one class and one type, which outranks a bare module class, so the 48px height here
3846
+ * never applied. It looked right only because `dazn-logo.svg` happens to be square: the
3847
+ * mark was being sized by the file's own intrinsic aspect rather than by the design, and
3848
+ * anything else in that slot, or the same file before it loads, comes out a different
3849
+ * height and takes the whole hero text block with it. Rendered against a missing asset it
3850
+ * is 48x41, and the block sits 7px short of where the frame puts it.
3851
+ */
3852
+ ._heroText_1sarz_196 ._logo_1sarz_205 {
3297
3853
  width: 48px;
3298
3854
  height: 48px;
3299
3855
  }
3300
- ._heroTitle_ems17_107 {
3856
+ /*
3857
+ * Full width, not shrink-to-fit. `.heroText` centres its children, which in a column flex
3858
+ * container sizes them to their content — so the heading came out as wide as the words
3859
+ * happened to be rather than the 327 the frame gives it. Centred either way while it fits;
3860
+ * the frame clips at 327 and this would have overflowed instead.
3861
+ */
3862
+ ._heroTitle_1sarz_225 {
3863
+ width: 100%;
3301
3864
  margin: 0;
3865
+ /*
3866
+ * The one string in the shop not set on the brand face, and that is the frame's doing.
3867
+ *
3868
+ * `Meta` (4564:47781) is drawn in SF Pro Display Bold, where the line beneath it and every
3869
+ * other string on the screen resolve `font/primary-font` to DAZN Oscine. It measures 308
3870
+ * wide there against 276 in Oscine, which is the last thing separating this screen from
3871
+ * the frame. We follow the frame; whether that layer is a decision or an unlinked text
3872
+ * style is a question back to DAZN, and Oscine is a one-line change if they say so.
3873
+ *
3874
+ * SF Pro is not a webfont anyone can serve, so this is the system stack: it is the real
3875
+ * face on the iOS webview the shop ships in, and the platform's own display face
3876
+ * everywhere else. Weight is stated because `system-ui` resolves to a different family
3877
+ * per platform and none of them synthesise the same bold.
3878
+ */
3879
+ font-family: 'SF Pro Display', -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
3302
3880
  font-size: 56px;
3303
3881
  line-height: 1.3;
3304
3882
  font-weight: 700;
3305
3883
  letter-spacing: -1.12px;
3306
3884
  color: #f9fafa;
3307
3885
  }
3308
- ._heroSub_ems17_115 {
3886
+ ._heroSub_1sarz_249 {
3309
3887
  margin: 0;
3310
3888
  font-size: 16px;
3311
3889
  line-height: 1.55;
3312
3890
  color: #f9fafa;
3313
3891
  max-width: 327px;
3314
3892
  }
3315
- ._swipe_ems17_122 {
3893
+ /*
3894
+ * `Component 24`: 8px of padding, a 14px label, 6px, and a 24px chevron — 60px, not 82.
3895
+ * Stated as padding rather than a height so the bar is the sum of its parts; at 82 with
3896
+ * the contents centred the cue sat 11px lower than the frame puts it and the gap above it
3897
+ * closed by the same amount.
3898
+ */
3899
+ ._swipe_1sarz_262 {
3316
3900
  display: flex;
3317
3901
  flex-direction: column;
3318
3902
  align-items: center;
3319
3903
  gap: 6px;
3320
- height: 82px;
3321
- justify-content: center;
3904
+ padding: 8px 0;
3322
3905
  background: #080e12;
3323
3906
  }
3324
- ._swipeLabel_ems17_131 {
3907
+ ._swipeLabel_1sarz_270 {
3325
3908
  margin: 0;
3326
3909
  font-size: 14px;
3327
3910
  line-height: 1;
3328
3911
  color: rgba(255, 255, 255, 0.54);
3329
3912
  }
3330
- ._chevron_ems17_137 {
3913
+ ._chevron_1sarz_276 {
3331
3914
  width: 24px;
3332
3915
  height: 24px;
3333
3916
  color: rgba(255, 255, 255, 0.54);
3334
- animation: _bob_ems17_1 1.8s ease-in-out infinite;
3917
+ animation: _bob_1sarz_1 1.8s ease-in-out infinite;
3335
3918
  }
3336
- @keyframes _bob_ems17_1 {
3919
+ @keyframes _bob_1sarz_1 {
3337
3920
  0%, 100% { transform: translateY(0); }
3338
3921
  50% { transform: translateY(4px); }
3339
3922
  }
@@ -3344,7 +3927,7 @@
3344
3927
  * only at the bottom. That is what gives the stretched CTA below something panel-sized to
3345
3928
  * stretch against, which is how the design's "whole card = tap target" is honoured.
3346
3929
  */
3347
- ._overlay_ems17_154 {
3930
+ ._overlay_1sarz_293 {
3348
3931
  position: absolute;
3349
3932
  inset: 0;
3350
3933
  z-index: 1;
@@ -3355,25 +3938,25 @@
3355
3938
  align-items: flex-start;
3356
3939
  padding: 0 24px 24px;
3357
3940
  }
3358
- ._copy_ems17_165 {
3941
+ ._copy_1sarz_304 {
3359
3942
  display: flex;
3360
3943
  flex-direction: column;
3361
3944
  gap: 8px;
3362
3945
  }
3363
- ._title_ems17_170 {
3946
+ ._title_1sarz_309 {
3364
3947
  margin: 0;
3365
3948
  font-size: 32px;
3366
3949
  line-height: 1.2;
3367
3950
  font-weight: 700;
3368
3951
  color: #f9fafa;
3369
3952
  }
3370
- ._subtitle_ems17_177 {
3953
+ ._subtitle_1sarz_316 {
3371
3954
  margin: 0;
3372
3955
  font-size: 16px;
3373
3956
  line-height: 1.55;
3374
3957
  color: #f9fafa;
3375
3958
  }
3376
- ._cta_ems17_183 {
3959
+ ._cta_1sarz_322 {
3377
3960
  display: inline-flex;
3378
3961
  align-items: center;
3379
3962
  justify-content: center;
@@ -3391,57 +3974,156 @@
3391
3974
  * The hit area, stretched over the whole card. The button stays exactly where the design
3392
3975
  * puts it and keeps its own visual state; the tappable region is everything above it.
3393
3976
  */
3394
- ._cta_ems17_183::after {
3977
+ ._cta_1sarz_322::after {
3395
3978
  content: '';
3396
3979
  position: absolute;
3397
3980
  inset: 0;
3398
3981
  }
3399
3982
 
3400
- ._cta_ems17_183:active {
3983
+ ._cta_1sarz_322:active {
3401
3984
  background: rgba(249, 250, 250, 0.12);
3402
3985
  }
3403
3986
 
3404
3987
  /* Keyboard users get the outline on the button itself, not on the invisible card-sized
3405
3988
  region — a focus ring the size of the viewport communicates nothing. */
3406
- ._cta_ems17_183:focus-visible {
3989
+ ._cta_1sarz_322:focus-visible {
3407
3990
  outline: none;
3408
3991
  box-shadow: var(--focus-ring);
3409
3992
  }
3410
3993
  /*
3411
- * Figma `mobile/header`, context=shop. The component is 96px: a 40px status bar the
3412
- * device draws, and the 56px `top.navigation` below it that is the part we can render.
3413
- * 16px inset both sides, 24px icons.
3994
+ * Figma `mobile/header`, context=shop (5130:62877 on the page of record). The component is
3995
+ * 118px: a 54px status bar the device draws, and the 64px nav row below it that is the
3996
+ * part we can render. 16px inset both sides, 24px icons.
3997
+ *
3998
+ * **The row is 64, not 56.** The old number came from this same component on the page
3999
+ * ADR-0022 retired, where the mock device drew a 40px status bar and a 56px row. On the
4000
+ * page of record it is 54 + 64, and two frames agree to the pixel: the glyph centre sits
4001
+ * at y=86 with the bar ending at 118, on the dark homepage (4564:47652) and on the light
4002
+ * PDP header (5295:75116) alike. At 56 every screen's content started 8px high.
3414
4003
  */
3415
- ._header_7rinq_6 {
4004
+ /*
4005
+ * Pinned, because the design pins it: inside `mobile/header` the `Slot/Header` that
4006
+ * carries the bar is `position: sticky; top: 0`, and the header instance is the LAST
4007
+ * child of the page frame — drawn over everything else rather than scrolled past.
4008
+ *
4009
+ * It is also what makes the rest of the screen work. The cart badge is the feedback for
4010
+ * adding something, and the added-to-cart card points a caret at it; both are useless
4011
+ * from halfway down a 1751px product page if the bar they live in has scrolled away.
4012
+ *
4013
+ * `z-index: 40` places it above page content and below everything designed to cover it:
4014
+ * the sticky CTA bar (50), the size-guide sheet (60), the bottom sheets (900) and the
4015
+ * toast (1000).
4016
+ */
4017
+ ._header_1hcuj_25 {
4018
+ position: sticky;
4019
+ top: 0;
4020
+ z-index: 40;
3416
4021
  display: flex;
3417
4022
  align-items: center;
3418
4023
  justify-content: space-between;
3419
- height: 56px;
4024
+ height: var(--header-height);
3420
4025
  padding: 0 var(--space-16);
3421
4026
  background: var(--color-surface-default);
4027
+ /*
4028
+ * semantic/base/border/full-color/default, drawn under the bar in the current frame
4029
+ * (5295:75116). It is what separates the header from a white PDP, which otherwise runs
4030
+ * into it with nothing between them.
4031
+ *
4032
+ * An inset shadow, not a border, because Figma draws the rule INSIDE the component's
4033
+ * 118px and a CSS border adds to it. As a border the bar stood 65 and every screen under
4034
+ * it began a pixel low — measurable against `5295:74053`, where shifting our whole render
4035
+ * up by one takes the difference from the frame from 5.7 of 255 to 3.6. It also put the
4036
+ * menu sheet 15px below the bar where `--header-height` plus `--space-16` says 16.
4037
+ */
4038
+ box-shadow: inset 0 -1px 0 var(--color-border-default);
3422
4039
  }
3423
4040
 
3424
4041
  /*
3425
4042
  * Overlaid rather than stacked, for screens whose artwork runs to the top of the display.
3426
4043
  *
4044
+ * `absolute`, so this deliberately opts OUT of the sticky default above: the category
4045
+ * hero scrolls away under its own title and a bar pinned over a photograph that is no
4046
+ * longer there would be a bar floating on nothing.
4047
+ *
3427
4048
  * `mobile/hero` carries its own `mobile/header` instance at y=0, hidden, and positions the
3428
4049
  * category title at y=106 — measured from the top of the SCREEN, not from under a bar. In
3429
- * flow the header pushes the picture down and every measurement inside it lands 56px low,
4050
+ * flow the header pushes the picture down and every measurement inside it lands a bar
4051
+ * height low,
3430
4052
  * which is how the category page shipped.
3431
4053
  *
3432
4054
  * Declared after `.header` so it wins the background: both are one class, so the later
3433
4055
  * rule takes it.
3434
4056
  */
3435
- ._floating_7rinq_26 {
4057
+ ._floating_1hcuj_65 {
3436
4058
  position: absolute;
3437
4059
  top: 0;
3438
4060
  left: 0;
3439
4061
  right: 0;
3440
4062
  z-index: 5;
4063
+ /*
4064
+ * Clear at rest. The scrim belongs to `.scrolled` below.
4065
+ */
3441
4066
  background: none;
4067
+ transition: background-color 160ms ease;
4068
+ /* No rule over artwork: it exists to separate the bar from the page below it, and there
4069
+ is no page below it here. */
4070
+ box-shadow: none;
4071
+ }
4072
+
4073
+ /*
4074
+ * Once anything has scrolled under it, the overlaid bar takes a 60% scrim.
4075
+ *
4076
+ * Both halves are drawn. `full screen` (4564:47652) is the shop entry at rest and the bar
4077
+ * shows the ground straight through; `Scroll` (5130:62654) is the same screen moved 154px
4078
+ * and the photograph comes through darkened. The second is solvable rather than a matter of
4079
+ * taste, because `Scroll[y]` must equal `full screen[y+154]` wherever nothing overlays:
4080
+ * below the bar they match exactly, above it they do not, and the ratio is the same to
4081
+ * three places across the whole bar and every sampled pixel:
4082
+ *
4083
+ * y=80, x=150 clean (193,172,157) scrimmed (82,77,74) t = .600 / .601 / .597
4084
+ * y=100, x=150 clean (181,157,137) scrimmed (77,71,65) t = .601 / .601 / .605
4085
+ * y=40, x=80 clean (61, 67, 72) scrimmed (29,35,39) t = .604
4086
+ *
4087
+ * Fixed rather than tokenised, like the hero's own palette: it is drawn over a photograph
4088
+ * and does not follow the surface.
4089
+ */
4090
+ ._floating_1hcuj_65._scrolled_1hcuj_72 {
4091
+ background: rgba(8, 14, 18, 0.6);
4092
+ }
4093
+
4094
+ @media (prefers-reduced-motion: reduce) {
4095
+ ._floating_1hcuj_65 {
4096
+ transition: none;
4097
+ }
4098
+ }
4099
+
4100
+ /*
4101
+ * `DAZN Shop`, at the width the frame gives it.
4102
+ *
4103
+ * 222 with `justify-content: space-between` is what places it: 375 less the 16px insets,
4104
+ * the 24px back control and the 72px icon cluster leaves 25px of free space, split into two
4105
+ * 12.5px gaps, which puts the text box at x=52.5. Measured off the frame's export the "D"
4106
+ * lands at 55, which is the glyph's own side bearing.
4107
+ *
4108
+ * `semantic/base/text/static-light` is #f9fafa, and taking it from the theme instead is
4109
+ * the same colour everywhere the frame actually draws it — the bar carries a title only on
4110
+ * the dark screens, and the light PDP header (5295:75116) has an empty title slot. Written
4111
+ * as a static white it would be invisible the first time a host passed one on a light page.
4112
+ */
4113
+ ._title_1hcuj_121 {
4114
+ flex: 0 0 auto;
4115
+ width: 222px;
4116
+ min-width: 0;
4117
+ font-size: var(--type-caption-sm-size);
4118
+ line-height: var(--type-caption-sm-line);
4119
+ font-weight: var(--font-weight-bold);
4120
+ color: var(--color-text-default);
4121
+ overflow: hidden;
4122
+ text-overflow: ellipsis;
4123
+ white-space: nowrap;
3442
4124
  }
3443
4125
 
3444
- ._right_7rinq_35 {
4126
+ ._right_1hcuj_134 {
3445
4127
  display: flex;
3446
4128
  align-items: center;
3447
4129
  gap: var(--space-16);
@@ -3454,12 +4136,12 @@
3454
4136
  * header without a back button would silently re-lay-out rather than simply losing one
3455
4137
  * icon. 24px is the icon box it stands in for.
3456
4138
  */
3457
- ._spacer_7rinq_48 {
4139
+ ._spacer_1hcuj_147 {
3458
4140
  width: 24px;
3459
4141
  height: 24px;
3460
4142
  }
3461
4143
 
3462
- ._iconBtn_7rinq_53 {
4144
+ ._iconBtn_1hcuj_152 {
3463
4145
  position: relative;
3464
4146
  display: inline-flex;
3465
4147
  align-items: center;
@@ -3475,7 +4157,7 @@
3475
4157
  }
3476
4158
 
3477
4159
  /* Expand the hit area to >=44px without moving the 24px icon off the 16px inset. */
3478
- ._iconBtn_7rinq_53::after {
4160
+ ._iconBtn_1hcuj_152::after {
3479
4161
  content: '';
3480
4162
  position: absolute;
3481
4163
  inset: -10px;
@@ -3486,12 +4168,12 @@
3486
4168
  * its glyph on the design's centre line — 16px from the right edge, not 12. Its own icon
3487
4169
  * stays 24px.
3488
4170
  */
3489
- ._menuBtn_7rinq_80 {
4171
+ ._menuBtn_1hcuj_179 {
3490
4172
  width: 32px;
3491
4173
  height: 32px;
3492
4174
  }
3493
4175
 
3494
- ._menuBtn_7rinq_80::after {
4176
+ ._menuBtn_1hcuj_179::after {
3495
4177
  inset: -6px;
3496
4178
  }
3497
4179
 
@@ -3499,18 +4181,22 @@
3499
4181
  * Cart count, anchored where the design anchors it: 14px in and 6px above the icon's own
3500
4182
  * box, so the circle overhangs the trolley's top-right corner.
3501
4183
  *
3502
- * Fixed colours on both counts. The pill is `#dddedf` with `#080e12` on it in the frame
3503
- * light on the dark category header and it reads over a photograph there, so it does not
3504
- * follow the surface palette any more than the hero title does. Sampled from the frame
3505
- * rather than taken from the token dump, which resolved the same DAZN token two different
3506
- * ways depending on which node was asked.
4184
+ * **The pill is red now.** It was `#dddedf` with `#080e12` on it, sampled off the frame
4185
+ * at the time; `.cart.icon` has since been rebuilt on
4186
+ * `semantic/state/surface/critical/default` with white on it (5295:75116, 20 Aug 2026),
4187
+ * and the DS component is one component so it is red on the light PDP header and on the
4188
+ * dark category one alike.
4189
+ *
4190
+ * Still fixed rather than tokenised: it reads over a photograph on the category hero, so
4191
+ * it does not follow the surface palette any more than the hero title does. `--color-…`
4192
+ * would be the same hex today and would invert the day somebody adds a critical remap.
3507
4193
  *
3508
4194
  * `min-width` rather than a fixed circle so a two- or three-character value ("99+") grows
3509
4195
  * leftwards instead of being clipped.
3510
4196
  *
3511
4197
  * Sits above the ::after hit area so the number is never painted underneath it.
3512
4198
  */
3513
- ._badge_7rinq_104 {
4199
+ ._badge_1hcuj_207 {
3514
4200
  position: absolute;
3515
4201
  top: -6px;
3516
4202
  left: 14px;
@@ -3523,8 +4209,8 @@
3523
4209
  align-items: center;
3524
4210
  justify-content: center;
3525
4211
  border-radius: var(--radius-full);
3526
- background: #dddedf;
3527
- color: #080e12;
4212
+ background: #d12424;
4213
+ color: #ffffff;
3528
4214
  font-size: var(--type-trimmed-xs-size);
3529
4215
  line-height: 1;
3530
4216
  letter-spacing: 0.12px;
@@ -3532,43 +4218,79 @@
3532
4218
  font-variant-numeric: tabular-nums;
3533
4219
  pointer-events: none;
3534
4220
  }
4221
+
3535
4222
  /*
3536
- * Figma `Settings` (636:51006): the card is anchored under the 96px header, inset 16px on
4223
+ * `context=checkout` (5295:74075). Both side slots are the 32px box the frame draws the
4224
+ * left one empty, so the title centres on the screen rather than on what is left over.
4225
+ */
4226
+ ._closeSlot_1hcuj_234 {
4227
+ flex: 0 0 auto;
4228
+ width: 32px;
4229
+ height: 32px;
4230
+ }
4231
+
4232
+ ._closeSlot_1hcuj_234._iconBtn_1hcuj_152::after {
4233
+ inset: -6px;
4234
+ }
4235
+
4236
+ /*
4237
+ * The checkout title takes the middle instead of the shop entry's fixed 222px: the frame
4238
+ * centres "Order #2304" between two equal slots, so it is the free space, not a measured
4239
+ * width. Same size, weight and colour as the shop entry's — one text style, two positions.
4240
+ */
4241
+ ._centred_1hcuj_249 {
4242
+ flex: 1 1 auto;
4243
+ width: auto;
4244
+ text-align: center;
4245
+ }
4246
+ /*
4247
+ * Figma `menu-open` (4613:58644): the card is anchored under the header, inset 16px on
3537
4248
  * each side, and sized by its content — not a bottom sheet, despite the frame's name.
3538
4249
  */
3539
- ._overlay_lm5qk_5 {
4250
+ ._overlay_1i5ok_5 {
3540
4251
  position: fixed;
3541
4252
  inset: 0;
3542
4253
  z-index: 900;
3543
4254
  display: flex;
3544
4255
  align-items: flex-start;
3545
4256
  justify-content: center;
3546
- /* Clears the header the design draws this beneath. `env()` on top so a notch does not
3547
- put the card under the status bar in the embed. */
3548
- padding: calc(96px + env(safe-area-inset-top, 0px)) var(--space-16) var(--space-16);
4257
+ /* Clears the header the design draws this beneath: the frame puts the card's top edge
4258
+ at 134 with the bar ending at 118, which is the 16px inset below it. `env()` on top so
4259
+ a notch does not put the card under the status bar in the embed. The 96 this replaced
4260
+ was a 40px status bar plus a 56px row, and it counted the status bar twice — once as a
4261
+ literal and again through `env()`. */
4262
+ padding: calc(var(--header-height) + var(--space-16) + env(safe-area-inset-top, 0px))
4263
+ var(--space-16) var(--space-16);
3549
4264
  }
3550
4265
 
3551
- ._scrim_lm5qk_17 {
4266
+ ._scrim_1i5ok_21 {
3552
4267
  position: absolute;
3553
4268
  inset: 0;
3554
- background: rgba(8, 14, 18, 0.5);
4269
+ /* `semantic/base/surface/backdrop`. Measured off the frame rather than read off a
4270
+ token name: the photo behind the card comes through at exactly 0.32 of the way to
4271
+ #0c161c on every sampled pixel. At 0.5 the shop behind the menu went noticeably
4272
+ darker than the design draws it. */
4273
+ background: rgba(12, 22, 28, 0.32);
3555
4274
  }
3556
4275
 
3557
- ._sheet_lm5qk_23 {
4276
+ ._sheet_1i5ok_31 {
3558
4277
  position: relative;
3559
4278
  width: 100%;
3560
4279
  max-width: 343px;
3561
4280
  max-height: calc(100dvh - 128px);
3562
4281
  overflow-y: auto;
3563
- background: var(--color-surface-default);
4282
+ /* `semantic/base/surface/full-color/soft` — #f0f2f2, not the page's default #f9fafa.
4283
+ The card is deliberately a shade off white so its rows, which carry no visible rule
4284
+ between them, still read as a stack. */
4285
+ background: var(--color-surface-soft);
3564
4286
  border: 1px solid var(--color-border-transparent-soft);
3565
4287
  border-radius: var(--radius-12);
3566
4288
  display: flex;
3567
4289
  flex-direction: column;
3568
- animation: _menu-in_lm5qk_1 160ms ease;
4290
+ animation: _menu-in_1i5ok_1 160ms ease;
3569
4291
  }
3570
4292
 
3571
- @keyframes _menu-in_lm5qk_1 {
4293
+ @keyframes _menu-in_1i5ok_1 {
3572
4294
  from {
3573
4295
  opacity: 0;
3574
4296
  transform: translateY(-8px);
@@ -3580,20 +4302,26 @@
3580
4302
  }
3581
4303
 
3582
4304
  @media (prefers-reduced-motion: reduce) {
3583
- ._sheet_lm5qk_23 {
4305
+ ._sheet_1i5ok_31 {
3584
4306
  animation: none;
3585
4307
  }
3586
4308
  }
3587
4309
 
3588
4310
  /* `.sheet-header`: 48px, its own bottom rule, and the close control alone on the right.
3589
- The left and centre slots the component defines are empty in this instance. */
3590
- ._header_lm5qk_56 {
4311
+ The left and centre slots the component defines are empty in this instance.
4312
+
4313
+ The rule is `semantic/base/border/transparent-static-light` and it is nearly invisible
4314
+ over the card: rgba(249,250,250,.2) on #f0f2f2 lands on #f2f4f4, which is what the frame
4315
+ renders. Written literally rather than through `--color-border-transparent-soft`, whose
4316
+ light value is rgba(0,0,0,.1) — a line four times the contrast the design draws, and the
4317
+ wrong way round. Static because the card is light in every theme. */
4318
+ ._header_1i5ok_73 {
3591
4319
  display: flex;
3592
4320
  justify-content: flex-end;
3593
- border-bottom: 1px solid var(--color-border-transparent-soft);
4321
+ border-bottom: 1px solid rgba(249, 250, 250, 0.2);
3594
4322
  }
3595
4323
 
3596
- ._close_lm5qk_62 {
4324
+ ._close_1i5ok_79 {
3597
4325
  display: inline-flex;
3598
4326
  align-items: center;
3599
4327
  justify-content: center;
@@ -3606,13 +4334,13 @@
3606
4334
  cursor: pointer;
3607
4335
  }
3608
4336
 
3609
- ._close_lm5qk_62:focus-visible {
4337
+ ._close_1i5ok_79:focus-visible {
3610
4338
  outline: none;
3611
4339
  box-shadow: var(--focus-ring);
3612
4340
  border-radius: var(--radius-4);
3613
4341
  }
3614
4342
 
3615
- ._list_lm5qk_81 {
4343
+ ._list_1i5ok_98 {
3616
4344
  list-style: none;
3617
4345
  margin: 0;
3618
4346
  padding: var(--space-6) 0;
@@ -3620,14 +4348,20 @@
3620
4348
  flex-direction: column;
3621
4349
  }
3622
4350
 
3623
- ._row_lm5qk_89 {
4351
+ /*
4352
+ * No rule between rows, and that is what the frame draws rather than an omission. The
4353
+ * design gives each row a bottom border in `semantic/base/border/soft` — #f0f2f2, the card's
4354
+ * own colour — so it never appears; sampled down the frame clear of the text there is no
4355
+ * divider anywhere in the list. The separation comes from the card being a shade off white,
4356
+ * which is why `.sheet` is `--color-surface-soft`.
4357
+ */
4358
+ ._row_1i5ok_113 {
3624
4359
  display: flex;
3625
4360
  align-items: center;
3626
4361
  gap: var(--space-8);
3627
4362
  width: 100%;
3628
4363
  padding: var(--space-12) var(--space-16);
3629
4364
  border: 0;
3630
- border-bottom: 1px solid var(--color-border-transparent-soft);
3631
4365
  background: none;
3632
4366
  color: inherit;
3633
4367
  font-family: inherit;
@@ -3641,16 +4375,16 @@
3641
4375
  * yet on either side, which is our problem and not something to dress up as their
3642
4376
  * permission. `aria-disabled` carries the truth to a screen reader.
3643
4377
  */
3644
- ._row_lm5qk_89[disabled] {
4378
+ ._row_1i5ok_113[disabled] {
3645
4379
  cursor: default;
3646
4380
  }
3647
4381
 
3648
- ._row_lm5qk_89:focus-visible {
4382
+ ._row_1i5ok_113:focus-visible {
3649
4383
  outline: none;
3650
4384
  box-shadow: var(--focus-ring);
3651
4385
  }
3652
4386
 
3653
- ._icon_lm5qk_119 {
4387
+ ._icon_1i5ok_142 {
3654
4388
  display: inline-flex;
3655
4389
  flex: 0 0 24px;
3656
4390
  width: 24px;
@@ -3658,7 +4392,7 @@
3658
4392
  color: var(--color-text-default);
3659
4393
  }
3660
4394
 
3661
- ._text_lm5qk_127 {
4395
+ ._text_1i5ok_150 {
3662
4396
  display: flex;
3663
4397
  flex: 1 1 auto;
3664
4398
  flex-direction: column;
@@ -3666,20 +4400,20 @@
3666
4400
  min-width: 0;
3667
4401
  }
3668
4402
 
3669
- ._title_lm5qk_135 {
4403
+ ._title_1i5ok_158 {
3670
4404
  font-size: var(--type-paragraph-size);
3671
4405
  font-weight: var(--font-weight-bold);
3672
4406
  line-height: 1;
3673
4407
  color: var(--color-text-default);
3674
4408
  }
3675
4409
 
3676
- ._subtitle_lm5qk_142 {
4410
+ ._subtitle_1i5ok_165 {
3677
4411
  font-size: var(--type-trimmed-xs-size);
3678
4412
  line-height: 1.52;
3679
4413
  color: var(--color-text-subtle);
3680
4414
  }
3681
4415
 
3682
- ._chevron_lm5qk_148 {
4416
+ ._chevron_1i5ok_171 {
3683
4417
  display: inline-flex;
3684
4418
  flex: 0 0 16px;
3685
4419
  width: 16px;