pricing-renderer 0.1.0 → 0.2.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.
Files changed (175) hide show
  1. package/CHANGELOG.md +133 -0
  2. package/README.md +142 -25
  3. package/THIRD_PARTY_NOTICES.md +84 -6
  4. package/custom-elements.json +233 -3
  5. package/dist/core/apply-selection.d.ts +8 -0
  6. package/dist/core/apply-selection.d.ts.map +1 -0
  7. package/dist/core/apply-selection.js +76 -0
  8. package/dist/core/apply-selection.js.map +1 -0
  9. package/dist/core/breakdown.d.ts +13 -0
  10. package/dist/core/breakdown.d.ts.map +1 -0
  11. package/dist/core/breakdown.js +56 -0
  12. package/dist/core/breakdown.js.map +1 -0
  13. package/dist/core/compare-sides.d.ts +16 -0
  14. package/dist/core/compare-sides.d.ts.map +1 -0
  15. package/dist/core/compare-sides.js +73 -0
  16. package/dist/core/compare-sides.js.map +1 -0
  17. package/dist/core/compare-values.d.ts +13 -0
  18. package/dist/core/compare-values.d.ts.map +1 -0
  19. package/dist/core/compare-values.js +100 -0
  20. package/dist/core/compare-values.js.map +1 -0
  21. package/dist/core/compare.d.ts +7 -0
  22. package/dist/core/compare.d.ts.map +1 -0
  23. package/dist/core/compare.js +301 -0
  24. package/dist/core/compare.js.map +1 -0
  25. package/dist/core/expression.js +2 -2
  26. package/dist/core/expression.js.map +1 -1
  27. package/dist/core/i18n.d.ts.map +1 -1
  28. package/dist/core/i18n.js +136 -2
  29. package/dist/core/i18n.js.map +1 -1
  30. package/dist/core/index.d.ts +9 -2
  31. package/dist/core/index.d.ts.map +1 -1
  32. package/dist/core/index.js +6 -1
  33. package/dist/core/index.js.map +1 -1
  34. package/dist/core/normalize.d.ts.map +1 -1
  35. package/dist/core/normalize.js +70 -9
  36. package/dist/core/normalize.js.map +1 -1
  37. package/dist/core/plan-relation.d.ts +8 -0
  38. package/dist/core/plan-relation.d.ts.map +1 -0
  39. package/dist/core/plan-relation.js +15 -0
  40. package/dist/core/plan-relation.js.map +1 -0
  41. package/dist/core/price-stabilizer.d.ts +3 -0
  42. package/dist/core/price-stabilizer.d.ts.map +1 -0
  43. package/dist/core/price-stabilizer.js +73 -0
  44. package/dist/core/price-stabilizer.js.map +1 -0
  45. package/dist/core/resolve.d.ts +7 -1
  46. package/dist/core/resolve.d.ts.map +1 -1
  47. package/dist/core/resolve.js +66 -21
  48. package/dist/core/resolve.js.map +1 -1
  49. package/dist/core/search-params.d.ts +15 -0
  50. package/dist/core/search-params.d.ts.map +1 -0
  51. package/dist/core/search-params.js +175 -0
  52. package/dist/core/search-params.js.map +1 -0
  53. package/dist/core/selection-change.d.ts +10 -0
  54. package/dist/core/selection-change.d.ts.map +1 -0
  55. package/dist/core/selection-change.js +124 -0
  56. package/dist/core/selection-change.js.map +1 -0
  57. package/dist/core/types.d.ts +209 -0
  58. package/dist/core/types.d.ts.map +1 -1
  59. package/dist/core/units.d.ts +13 -0
  60. package/dist/core/units.d.ts.map +1 -0
  61. package/dist/core/units.js +84 -0
  62. package/dist/core/units.js.map +1 -0
  63. package/dist/core/utils.d.ts.map +1 -1
  64. package/dist/core/utils.js +91 -32
  65. package/dist/core/utils.js.map +1 -1
  66. package/dist/core/view-model.d.ts +3 -1
  67. package/dist/core/view-model.d.ts.map +1 -1
  68. package/dist/core/view-model.js +113 -7
  69. package/dist/core/view-model.js.map +1 -1
  70. package/dist/element/change-review-define.d.ts +4 -0
  71. package/dist/element/change-review-define.d.ts.map +1 -0
  72. package/dist/element/change-review-define.js +17 -0
  73. package/dist/element/change-review-define.js.map +1 -0
  74. package/dist/element/change-review-element.d.ts +53 -0
  75. package/dist/element/change-review-element.d.ts.map +1 -0
  76. package/dist/element/change-review-element.js +104 -0
  77. package/dist/element/change-review-element.js.map +1 -0
  78. package/dist/element/change-review-events.d.ts +10 -0
  79. package/dist/element/change-review-events.d.ts.map +1 -0
  80. package/dist/element/change-review-events.js +2 -0
  81. package/dist/element/change-review-events.js.map +1 -0
  82. package/dist/element/change-review-renderers.d.ts +23 -0
  83. package/dist/element/change-review-renderers.d.ts.map +1 -0
  84. package/dist/element/change-review-renderers.js +391 -0
  85. package/dist/element/change-review-renderers.js.map +1 -0
  86. package/dist/element/cta.d.ts +45 -0
  87. package/dist/element/cta.d.ts.map +1 -0
  88. package/dist/element/cta.js +90 -0
  89. package/dist/element/cta.js.map +1 -0
  90. package/dist/element/current-selection.d.ts +13 -0
  91. package/dist/element/current-selection.d.ts.map +1 -0
  92. package/dist/element/current-selection.js +57 -0
  93. package/dist/element/current-selection.js.map +1 -0
  94. package/dist/element/define-change-review.d.ts +2 -0
  95. package/dist/element/define-change-review.d.ts.map +1 -0
  96. package/dist/element/define-change-review.js +7 -0
  97. package/dist/element/define-change-review.js.map +1 -0
  98. package/dist/element/events.d.ts +32 -6
  99. package/dist/element/events.d.ts.map +1 -1
  100. package/dist/element/hide-sections.d.ts +6 -0
  101. package/dist/element/hide-sections.d.ts.map +1 -0
  102. package/dist/element/hide-sections.js +26 -0
  103. package/dist/element/hide-sections.js.map +1 -0
  104. package/dist/element/index.d.ts +8 -1
  105. package/dist/element/index.d.ts.map +1 -1
  106. package/dist/element/index.js +3 -0
  107. package/dist/element/index.js.map +1 -1
  108. package/dist/element/pricing-renderer-element.d.ts +95 -7
  109. package/dist/element/pricing-renderer-element.d.ts.map +1 -1
  110. package/dist/element/pricing-renderer-element.js +563 -217
  111. package/dist/element/pricing-renderer-element.js.map +1 -1
  112. package/dist/element/renderers.d.ts +49 -4
  113. package/dist/element/renderers.d.ts.map +1 -1
  114. package/dist/element/renderers.js +795 -263
  115. package/dist/element/renderers.js.map +1 -1
  116. package/dist/element/same-input.d.ts +3 -0
  117. package/dist/element/same-input.d.ts.map +1 -0
  118. package/dist/element/same-input.js +56 -0
  119. package/dist/element/same-input.js.map +1 -0
  120. package/dist/react/headless.d.ts +9 -0
  121. package/dist/react/headless.d.ts.map +1 -0
  122. package/dist/react/headless.js +8 -0
  123. package/dist/react/headless.js.map +1 -0
  124. package/dist/react/index.d.ts +54 -4
  125. package/dist/react/index.d.ts.map +1 -1
  126. package/dist/react/index.js +156 -6
  127. package/dist/react/index.js.map +1 -1
  128. package/dist/react/provider.d.ts +17 -0
  129. package/dist/react/provider.d.ts.map +1 -0
  130. package/dist/react/provider.js +38 -0
  131. package/dist/react/provider.js.map +1 -0
  132. package/dist/react/selection-store.d.ts +51 -0
  133. package/dist/react/selection-store.d.ts.map +1 -0
  134. package/dist/react/selection-store.js +239 -0
  135. package/dist/react/selection-store.js.map +1 -0
  136. package/dist/react/slots-types.d.ts +38 -0
  137. package/dist/react/slots-types.d.ts.map +1 -0
  138. package/dist/react/slots-types.js +2 -0
  139. package/dist/react/slots-types.js.map +1 -0
  140. package/dist/react/slots.d.ts +40 -0
  141. package/dist/react/slots.d.ts.map +1 -0
  142. package/dist/react/slots.js +165 -0
  143. package/dist/react/slots.js.map +1 -0
  144. package/dist/react/use-pricing-selection.d.ts +10 -0
  145. package/dist/react/use-pricing-selection.d.ts.map +1 -0
  146. package/dist/react/use-pricing-selection.js +44 -0
  147. package/dist/react/use-pricing-selection.js.map +1 -0
  148. package/dist/react/use-pricing-source.d.ts +34 -0
  149. package/dist/react/use-pricing-source.d.ts.map +1 -0
  150. package/dist/react/use-pricing-source.js +186 -0
  151. package/dist/react/use-pricing-source.js.map +1 -0
  152. package/dist/react/use-renderer-state.d.ts +22 -0
  153. package/dist/react/use-renderer-state.d.ts.map +1 -0
  154. package/dist/react/use-renderer-state.js +112 -0
  155. package/dist/react/use-renderer-state.js.map +1 -0
  156. package/dist/space/index.d.ts +26 -0
  157. package/dist/space/index.d.ts.map +1 -0
  158. package/dist/space/index.js +71 -0
  159. package/dist/space/index.js.map +1 -0
  160. package/dist/styles/base.css +1030 -76
  161. package/dist/styles/theme-minimal.css +103 -0
  162. package/dist/styles/theme.css +22 -3
  163. package/dist/styles/tokens-shadcn.css +40 -0
  164. package/dist/yaml/index.d.ts +2 -0
  165. package/dist/yaml/index.d.ts.map +1 -1
  166. package/dist/yaml/index.js +1 -0
  167. package/dist/yaml/index.js.map +1 -1
  168. package/dist/yaml/load.d.ts.map +1 -1
  169. package/dist/yaml/load.js +106 -17
  170. package/dist/yaml/load.js.map +1 -1
  171. package/dist/yaml/source.d.ts +18 -0
  172. package/dist/yaml/source.d.ts.map +1 -0
  173. package/dist/yaml/source.js +46 -0
  174. package/dist/yaml/source.js.map +1 -0
  175. package/package.json +25 -13
@@ -5,10 +5,24 @@
5
5
  min-inline-size: 0;
6
6
  color: var(--pr-color-text);
7
7
  font: inherit;
8
- line-height: 1.5;
8
+ /* unset, the token is invalid at computed-value time and the
9
+ inherited property inherits, like font: inherit */
10
+ font-family: var(--pr-font-family);
11
+ line-height: var(--pr-line-height, 1.5);
9
12
  text-rendering: optimizeLegibility;
10
13
  }
11
14
 
15
+ /* opt-in compact spacing; comfortable adds no rule */
16
+ pricing-renderer[density='compact'] {
17
+ --pr-space-1: 0.2rem;
18
+ --pr-space-2: 0.4rem;
19
+ --pr-space-3: 0.55rem;
20
+ --pr-space-4: 0.75rem;
21
+ --pr-space-5: 1.1rem;
22
+ --pr-space-section: clamp(2rem, 5cqi, 4rem);
23
+ --pr-shell-padding: clamp(0.75rem, 3cqi, 2rem);
24
+ }
25
+
12
26
  pricing-renderer[data-pr-root],
13
27
  pricing-renderer[data-pr-root] *,
14
28
  pricing-renderer[data-pr-root] *::before,
@@ -26,6 +40,39 @@
26
40
  outline-offset: 3px;
27
41
  }
28
42
 
43
+ /* rows and cards take focus after "show more" */
44
+ pricing-renderer[data-pr-root]
45
+ :where([data-pr-part='comparison-row'], [data-pr-part='add-on-card']):focus-visible {
46
+ outline: 2px solid var(--pr-color-focus);
47
+ outline-offset: 3px;
48
+ }
49
+
50
+ /* A focused table row sits inside the scroll container and under the opaque sticky first cell,
51
+ so the ring is drawn inside the row and again as an inset shadow on its cells. */
52
+ pricing-renderer[data-pr-root] tr[data-pr-part='comparison-row']:focus-visible {
53
+ outline-offset: -2px;
54
+ }
55
+
56
+ pricing-renderer[data-pr-root] tr[data-pr-part='comparison-row']:focus-visible > :where(th, td) {
57
+ box-shadow:
58
+ inset 0 2px 0 var(--pr-color-focus),
59
+ inset 0 -2px 0 var(--pr-color-focus);
60
+ }
61
+
62
+ pricing-renderer[data-pr-root] tr[data-pr-part='comparison-row']:focus-visible > :first-child {
63
+ box-shadow:
64
+ inset 0 2px 0 var(--pr-color-focus),
65
+ inset 0 -2px 0 var(--pr-color-focus),
66
+ inset 2px 0 0 var(--pr-color-focus);
67
+ }
68
+
69
+ pricing-renderer[data-pr-root] tr[data-pr-part='comparison-row']:focus-visible > :last-child {
70
+ box-shadow:
71
+ inset 0 2px 0 var(--pr-color-focus),
72
+ inset 0 -2px 0 var(--pr-color-focus),
73
+ inset -2px 0 0 var(--pr-color-focus);
74
+ }
75
+
29
76
  pricing-renderer[data-pr-root] :where(a) {
30
77
  color: var(--pr-color-link);
31
78
  text-underline-offset: 0.18em;
@@ -48,44 +95,56 @@
48
95
  isolation: isolate;
49
96
  inline-size: 100%;
50
97
  min-inline-size: 0;
51
- overflow-wrap: anywhere;
98
+ /* Break a word only when it does not fit on a line of its own: short words are never split,
99
+ and the min-content width of a text keeps its longest word. */
100
+ overflow-wrap: break-word;
52
101
  }
53
102
 
54
103
  pricing-renderer[data-pr-root] .pr-content {
55
104
  display: grid;
105
+ grid-template-columns: minmax(0, 1fr);
56
106
  gap: var(--pr-space-section);
57
107
  padding: var(--pr-shell-padding);
58
108
  }
59
109
 
110
+ /* The hero and its items never get wider than the content box: a title with a word longer than
111
+ the line still wraps (break-word) instead of widening the hero past the renderer. */
60
112
  pricing-renderer[data-pr-root] .pr-hero {
61
113
  display: grid;
114
+ grid-template-columns: minmax(0, 1fr);
62
115
  justify-items: center;
63
- max-inline-size: 760px;
116
+ min-inline-size: 0;
117
+ max-inline-size: min(760px, 100%);
64
118
  margin-inline: auto;
65
119
  text-align: center;
66
120
  }
67
121
 
122
+ pricing-renderer[data-pr-root] .pr-hero > * {
123
+ max-inline-size: 100%;
124
+ }
125
+
68
126
  pricing-renderer[data-pr-root] .pr-hero__eyebrow,
69
127
  pricing-renderer[data-pr-root] .pr-section__eyebrow {
70
128
  margin: 0 0 var(--pr-space-2);
71
129
  color: var(--pr-color-accent);
72
130
  font-size: var(--pr-font-size-sm);
73
- font-weight: 750;
131
+ font-weight: var(--pr-font-weight-bold, 750);
74
132
  letter-spacing: 0.09em;
75
133
  text-transform: uppercase;
76
134
  }
77
135
 
78
136
  pricing-renderer[data-pr-root] .pr-hero__title {
79
137
  margin: 0;
138
+ font-family: var(--pr-font-family-display, var(--pr-font-family));
80
139
  font-size: clamp(2rem, 8cqi, 4rem);
81
- font-weight: 780;
140
+ font-weight: var(--pr-font-weight-bold, 780);
82
141
  letter-spacing: -0.045em;
83
142
  line-height: 1.02;
84
143
  text-wrap: balance;
85
144
  }
86
145
 
87
146
  pricing-renderer[data-pr-root] .pr-hero__subtitle {
88
- max-inline-size: 62ch;
147
+ max-inline-size: min(62ch, 100%);
89
148
  margin: var(--pr-space-4) 0 0;
90
149
  color: var(--pr-color-muted);
91
150
  font-size: var(--pr-font-size-lg);
@@ -106,6 +165,11 @@
106
165
  pricing-renderer[data-pr-root] .pr-badge {
107
166
  display: inline-flex;
108
167
  align-items: center;
168
+ min-inline-size: 0;
169
+ max-inline-size: 100%;
170
+ /* The text is an anonymous flex item whose min-content width stays that of the longest word
171
+ with break-word; anywhere lets it shrink, and still breaks only a word that does not fit. */
172
+ overflow-wrap: anywhere;
109
173
  min-block-size: 28px;
110
174
  padding: 0.2rem 0.65rem;
111
175
  border: 1px solid var(--pr-color-border);
@@ -113,7 +177,7 @@
113
177
  background: var(--pr-color-surface-raised);
114
178
  color: var(--pr-color-muted);
115
179
  font-size: var(--pr-font-size-xs);
116
- font-weight: 700;
180
+ font-weight: var(--pr-font-weight-bold, 700);
117
181
  }
118
182
 
119
183
  pricing-renderer[data-pr-root] .pr-badge[data-tone='accent'] {
@@ -134,6 +198,17 @@
134
198
  color: var(--pr-color-warning);
135
199
  }
136
200
 
201
+ /* The current plan and the current add-ons get a success-toned border and badge. The badge also
202
+ names the state in text, so colour is not the only cue. */
203
+ pricing-renderer[data-pr-root] .pr-plan-card[data-current='true'],
204
+ pricing-renderer[data-pr-root] .pr-addon-card[data-current='true'] {
205
+ border-color: color-mix(in srgb, var(--pr-color-success) 55%, var(--pr-color-border));
206
+ }
207
+
208
+ pricing-renderer[data-pr-root] .pr-addon-card__heading [data-pr-part='current-add-on-badge'] {
209
+ margin-block-end: var(--pr-space-2);
210
+ }
211
+
137
212
  pricing-renderer[data-pr-root] .pr-section,
138
213
  pricing-renderer[data-pr-root] .pr-configurator,
139
214
  pricing-renderer[data-pr-root] .pr-metadata {
@@ -150,7 +225,9 @@
150
225
 
151
226
  pricing-renderer[data-pr-root] :where(.pr-section__heading, .pr-configurator, .pr-metadata) h3 {
152
227
  margin: 0;
228
+ font-family: var(--pr-font-family-display, var(--pr-font-family));
153
229
  font-size: var(--pr-font-size-2xl);
230
+ font-weight: var(--pr-font-weight-bold, 700);
154
231
  letter-spacing: -0.025em;
155
232
  }
156
233
 
@@ -166,7 +243,7 @@
166
243
  margin: 0 auto var(--pr-space-2);
167
244
  color: var(--pr-color-muted);
168
245
  font-size: var(--pr-font-size-sm);
169
- font-weight: 700;
246
+ font-weight: var(--pr-font-weight-bold, 700);
170
247
  text-align: center;
171
248
  }
172
249
 
@@ -207,10 +284,17 @@
207
284
  pricing-renderer[data-pr-root] .pr-segmented__option small {
208
285
  color: var(--pr-color-success);
209
286
  font-size: var(--pr-font-size-xs);
210
- font-weight: 750;
287
+ font-weight: var(--pr-font-weight-bold, 750);
288
+ }
289
+
290
+ /* the plan grid takes the token; the add-on and variable grids
291
+ keep their own columns */
292
+ pricing-renderer[data-pr-root] .pr-plan-grid {
293
+ display: grid;
294
+ grid-template-columns: var(--pr-plan-columns, minmax(0, 1fr));
295
+ gap: var(--pr-space-4);
211
296
  }
212
297
 
213
- pricing-renderer[data-pr-root] .pr-plan-grid,
214
298
  pricing-renderer[data-pr-root] .pr-addon-grid,
215
299
  pricing-renderer[data-pr-root] .pr-variable-grid {
216
300
  display: grid;
@@ -283,6 +367,8 @@
283
367
  display: flex;
284
368
  flex-wrap: wrap;
285
369
  gap: var(--pr-space-2);
370
+ min-inline-size: 0;
371
+ max-inline-size: 100%;
286
372
  }
287
373
 
288
374
  pricing-renderer[data-pr-root] .pr-selection-control {
@@ -299,7 +385,7 @@
299
385
  background: var(--pr-color-surface-raised);
300
386
  color: var(--pr-color-muted);
301
387
  font-size: var(--pr-font-size-sm);
302
- font-weight: 750;
388
+ font-weight: var(--pr-font-weight-bold, 750);
303
389
  line-height: 1;
304
390
  cursor: pointer;
305
391
  transition:
@@ -336,7 +422,7 @@
336
422
  pricing-renderer[data-pr-root] .pr-selection-control__icon::after {
337
423
  color: var(--pr-color-on-accent);
338
424
  font-size: 0.75rem;
339
- font-weight: 900;
425
+ font-weight: var(--pr-font-weight-heavy, 900);
340
426
  line-height: 1;
341
427
  content: '✓';
342
428
  opacity: 0;
@@ -383,12 +469,35 @@
383
469
  letter-spacing: -0.02em;
384
470
  }
385
471
 
472
+ /* the plan name only */
473
+ pricing-renderer[data-pr-root] .pr-plan-card__top h4 {
474
+ font-family: var(--pr-font-family-display, var(--pr-font-family));
475
+ font-weight: var(--pr-font-weight-bold, 700);
476
+ }
477
+
478
+ /* the add-on name: the user-agent bold, now explicit */
479
+ pricing-renderer[data-pr-root] .pr-addon-card h4 {
480
+ font-weight: var(--pr-font-weight-bold, 700);
481
+ }
482
+
386
483
  pricing-renderer[data-pr-root] .pr-plan-card__top p,
387
484
  pricing-renderer[data-pr-root] .pr-addon-card p {
388
485
  margin: var(--pr-space-2) 0 0;
389
486
  color: var(--pr-color-muted);
390
487
  }
391
488
 
489
+ /* The action slot of the plan already held: same height and alignment as a button, without
490
+ pretending to be one. */
491
+ pricing-renderer[data-pr-root] .pr-plan-card__current {
492
+ display: flex;
493
+ align-items: center;
494
+ justify-content: center;
495
+ min-block-size: 2.75rem;
496
+ margin: 0;
497
+ color: var(--pr-color-muted);
498
+ font-size: var(--pr-font-size-sm);
499
+ }
500
+
392
501
  pricing-renderer[data-pr-root] .pr-price {
393
502
  display: flex;
394
503
  flex-wrap: wrap;
@@ -400,12 +509,17 @@
400
509
 
401
510
  pricing-renderer[data-pr-root] .pr-price__amount,
402
511
  pricing-renderer[data-pr-root] .pr-price__quote {
403
- font-size: clamp(2rem, 8cqi, 3rem);
404
- font-weight: 790;
405
- letter-spacing: -0.05em;
512
+ font-size: var(--pr-price-size, clamp(2rem, 8cqi, 3rem));
513
+ font-weight: var(--pr-price-weight, var(--pr-font-weight-bold, 790));
514
+ letter-spacing: var(--pr-price-tracking, -0.05em);
406
515
  line-height: 1;
407
516
  }
408
517
 
518
+ /* the amount only, not the quote */
519
+ pricing-renderer[data-pr-root] .pr-price__amount {
520
+ font-family: var(--pr-font-family-display, var(--pr-font-family));
521
+ }
522
+
409
523
  pricing-renderer[data-pr-root] .pr-price__quote {
410
524
  font-size: var(--pr-font-size-2xl);
411
525
  letter-spacing: -0.02em;
@@ -418,6 +532,7 @@
418
532
 
419
533
  pricing-renderer[data-pr-root] .pr-highlight-list {
420
534
  display: grid;
535
+ grid-template-columns: minmax(0, 1fr);
421
536
  gap: var(--pr-space-3);
422
537
  padding: 0;
423
538
  margin: 0 0 var(--pr-space-5);
@@ -429,22 +544,32 @@
429
544
  margin: 0 0 var(--pr-space-3);
430
545
  color: var(--pr-color-text);
431
546
  font-size: var(--pr-font-size-sm);
432
- font-weight: 750;
547
+ font-weight: var(--pr-font-weight-bold, 750);
433
548
  }
434
549
 
550
+ /* Name and value share a line when both fit; otherwise the value moves to the next line. The
551
+ name never gets narrower than its longest word (or the whole row), so short words are not
552
+ split, and a string longer than the row still wraps. */
435
553
  pricing-renderer[data-pr-root] .pr-highlight-list li {
436
- display: grid;
437
- grid-template-columns: auto minmax(0, 1fr) auto;
554
+ display: flex;
555
+ flex-wrap: wrap;
438
556
  align-items: start;
439
- gap: var(--pr-space-2);
557
+ gap: var(--pr-space-1) var(--pr-space-2);
558
+ }
559
+
560
+ pricing-renderer[data-pr-root] .pr-highlight-list li > :not([class]) {
561
+ flex: 1 1 0;
562
+ max-inline-size: 100%;
440
563
  }
441
564
 
442
565
  pricing-renderer[data-pr-root] .pr-highlight-list__icon {
443
566
  color: var(--pr-color-success);
444
- font-weight: 800;
567
+ font-weight: var(--pr-font-weight-heavy, 800);
445
568
  }
446
569
 
447
570
  pricing-renderer[data-pr-root] .pr-highlight-list__value {
571
+ min-inline-size: 0;
572
+ margin-inline-start: auto;
448
573
  color: var(--pr-color-muted);
449
574
  font-size: var(--pr-font-size-sm);
450
575
  }
@@ -457,7 +582,7 @@
457
582
  padding: 0.65rem 1rem;
458
583
  border: 1px solid transparent;
459
584
  border-radius: var(--pr-radius-md);
460
- font-weight: 750;
585
+ font-weight: var(--pr-font-weight-bold, 750);
461
586
  line-height: 1.2;
462
587
  text-align: center;
463
588
  text-decoration: none;
@@ -487,6 +612,50 @@
487
612
  padding-inline: 0.8rem;
488
613
  }
489
614
 
615
+ /* The current plan's CTA and a summary action without a
616
+ change are disabled controls, so they read as inactive in the light and dark themes. */
617
+ pricing-renderer[data-pr-root] .pr-button:disabled {
618
+ border-color: var(--pr-color-border);
619
+ background: var(--pr-color-surface-subtle);
620
+ color: var(--pr-color-muted);
621
+ cursor: default;
622
+ }
623
+
624
+ /* A pending host action keeps every CTA in place (aria-disabled,
625
+ not disabled, so focus stays) and marks the busy control with a spinner. */
626
+ pricing-renderer[data-pr-root] .pr-button[aria-disabled='true'] {
627
+ cursor: progress;
628
+ }
629
+
630
+ pricing-renderer[data-pr-root] .pr-button[data-pending='true'] {
631
+ gap: var(--pr-space-2);
632
+ }
633
+
634
+ pricing-renderer[data-pr-root] [data-pr-part='cta-pending']::before {
635
+ content: '';
636
+ display: inline-block;
637
+ box-sizing: border-box;
638
+ inline-size: 1em;
639
+ block-size: 1em;
640
+ margin-inline-end: var(--pr-space-2);
641
+ vertical-align: -0.125em;
642
+ border: 2px solid currentcolor;
643
+ border-inline-end-color: transparent;
644
+ border-radius: 50%;
645
+ animation: pr-spin 0.8s linear infinite;
646
+ }
647
+
648
+ @keyframes pr-spin {
649
+ to {
650
+ transform: rotate(360deg);
651
+ }
652
+ }
653
+
654
+ /* The summary action takes its own row under the total. */
655
+ pricing-renderer[data-pr-root] .pr-summary > [data-pr-part='summary-action'] {
656
+ inline-size: 100%;
657
+ }
658
+
490
659
  pricing-renderer[data-pr-root] .pr-configurator,
491
660
  pricing-renderer[data-pr-root] .pr-metadata {
492
661
  padding: var(--pr-space-5);
@@ -494,6 +663,7 @@
494
663
 
495
664
  pricing-renderer[data-pr-root] .pr-field {
496
665
  display: grid;
666
+ grid-template-columns: minmax(0, 1fr);
497
667
  align-content: start;
498
668
  gap: var(--pr-space-2);
499
669
  }
@@ -501,7 +671,7 @@
501
671
  pricing-renderer[data-pr-root] .pr-field > label,
502
672
  pricing-renderer[data-pr-root] .pr-stepper > span {
503
673
  font-size: var(--pr-font-size-sm);
504
- font-weight: 750;
674
+ font-weight: var(--pr-font-weight-bold, 750);
505
675
  }
506
676
 
507
677
  pricing-renderer[data-pr-root] .pr-field p {
@@ -516,7 +686,7 @@
516
686
 
517
687
  pricing-renderer[data-pr-root] .pr-field .pr-field__error {
518
688
  color: var(--pr-color-warning);
519
- font-weight: 650;
689
+ font-weight: var(--pr-font-weight-medium, 650);
520
690
  }
521
691
 
522
692
  pricing-renderer[data-pr-root]
@@ -589,7 +759,8 @@
589
759
  pricing-renderer[data-pr-root] .pr-comparison-toolbar {
590
760
  position: sticky;
591
761
  z-index: 3;
592
- inset-block-start: 0;
762
+ /* measured against the page */
763
+ inset-block-start: var(--pr-sticky-top, 0px);
593
764
  display: flex;
594
765
  flex-wrap: wrap;
595
766
  align-items: center;
@@ -631,12 +802,13 @@
631
802
  padding: 0.5rem 0.7rem;
632
803
  border: 0;
633
804
  background: transparent;
634
- font-weight: 750;
805
+ font-weight: var(--pr-font-weight-bold, 750);
635
806
  cursor: pointer;
636
807
  }
637
808
 
638
809
  pricing-renderer[data-pr-root] .pr-compact-comparison {
639
810
  display: grid;
811
+ grid-template-columns: minmax(0, 1fr);
640
812
  gap: var(--pr-space-3);
641
813
  }
642
814
 
@@ -658,6 +830,7 @@
658
830
 
659
831
  pricing-renderer[data-pr-root] .pr-feature-card {
660
832
  display: grid;
833
+ grid-template-columns: minmax(0, 1fr);
661
834
  gap: var(--pr-space-4);
662
835
  padding: var(--pr-space-4);
663
836
  margin-block-end: var(--pr-space-3);
@@ -675,14 +848,17 @@
675
848
 
676
849
  pricing-renderer[data-pr-root] .pr-feature-card dl {
677
850
  display: grid;
851
+ grid-template-columns: minmax(0, 1fr);
678
852
  gap: var(--pr-space-2);
679
853
  margin: 0;
680
854
  }
681
855
 
856
+ /* Plan name and value share a line when both fit, else the value moves to the next line: the
857
+ name keeps its longest word (at most the whole row) and is never split inside a short word. */
682
858
  pricing-renderer[data-pr-root] .pr-feature-card dl div {
683
- display: grid;
684
- grid-template-columns: minmax(0, 1fr) auto;
685
- gap: var(--pr-space-3);
859
+ display: flex;
860
+ flex-wrap: wrap;
861
+ gap: var(--pr-space-1) var(--pr-space-3);
686
862
  padding-block: var(--pr-space-2);
687
863
  border-block-start: 1px solid var(--pr-color-border);
688
864
  }
@@ -691,12 +867,34 @@
691
867
  margin: 0;
692
868
  }
693
869
 
870
+ pricing-renderer[data-pr-root] .pr-feature-card dd {
871
+ min-inline-size: 0;
872
+ margin-inline-start: auto;
873
+ }
874
+
694
875
  pricing-renderer[data-pr-root] .pr-feature-card dt {
876
+ flex: 1 1 0;
877
+ max-inline-size: 100%;
695
878
  color: var(--pr-color-muted);
696
879
  }
697
880
 
881
+ /* The height limit makes the box scroll vertically, so the
882
+ sticky header cells pin to it. 80vh is the fallback for browsers without dvh. */
883
+ pricing-renderer[data-pr-root] .pr-flow {
884
+ display: flex;
885
+ flex-direction: column;
886
+ gap: var(--pr-space-section);
887
+ min-inline-size: 0;
888
+ }
889
+
890
+ /* An empty flow (no summary, comparison or add-ons) takes no space and no gap. */
891
+ pricing-renderer[data-pr-root] .pr-flow:empty {
892
+ display: none;
893
+ }
894
+
698
895
  pricing-renderer[data-pr-root] .pr-table-scroll {
699
896
  max-inline-size: 100%;
897
+ max-block-size: 80vh;
700
898
  overflow: auto;
701
899
  border: 1px solid var(--pr-color-border);
702
900
  border-radius: var(--pr-radius-lg);
@@ -704,9 +902,17 @@
704
902
  overscroll-behavior-inline: contain;
705
903
  }
706
904
 
905
+ /* a var() declaration is valid when parsed, so it sits behind a
906
+ dvh guard, after the 80vh rule it replaces where dvh is supported. */
907
+ @supports (max-block-size: 1dvh) {
908
+ pricing-renderer[data-pr-root] .pr-table-scroll {
909
+ max-block-size: var(--pr-table-max-height, 80dvh);
910
+ }
911
+ }
912
+
707
913
  pricing-renderer[data-pr-root] .pr-comparison-table {
708
914
  width: 100%;
709
- min-width: 720px;
915
+ min-width: var(--pr-table-min-width, 720px);
710
916
  border-collapse: separate;
711
917
  border-spacing: 0;
712
918
  }
@@ -744,7 +950,7 @@
744
950
  display: block;
745
951
  margin-block-start: var(--pr-space-1);
746
952
  color: var(--pr-color-muted);
747
- font-weight: 400;
953
+ font-weight: var(--pr-font-weight-regular, 400);
748
954
  }
749
955
 
750
956
  pricing-renderer[data-pr-root] .pr-row-metadata {
@@ -752,7 +958,7 @@
752
958
  margin-block-start: var(--pr-space-1);
753
959
  color: var(--pr-color-muted);
754
960
  font-size: var(--pr-font-size-xs);
755
- font-weight: 500;
961
+ font-weight: var(--pr-font-weight-medium, 500);
756
962
  }
757
963
 
758
964
  pricing-renderer[data-pr-root] .pr-category-row th {
@@ -769,19 +975,33 @@
769
975
 
770
976
  pricing-renderer[data-pr-root] .pr-value--included {
771
977
  color: var(--pr-color-success);
772
- font-weight: 800;
978
+ font-weight: var(--pr-font-weight-heavy, 800);
773
979
  }
774
980
 
775
981
  pricing-renderer[data-pr-root] .pr-value--excluded {
776
982
  color: var(--pr-color-muted);
777
983
  }
778
984
 
985
+ pricing-renderer[data-pr-root] .pr-value--addon {
986
+ display: inline-flex;
987
+ align-items: center;
988
+ padding: 0.1rem 0.5rem;
989
+ border: 1px solid color-mix(in srgb, var(--pr-color-accent) 45%, var(--pr-color-border));
990
+ border-radius: 999px;
991
+ background: color-mix(in srgb, var(--pr-color-accent) 11%, var(--pr-color-surface-raised));
992
+ color: var(--pr-color-link);
993
+ font-size: var(--pr-font-size-xs);
994
+ font-weight: var(--pr-font-weight-bold, 700);
995
+ white-space: nowrap;
996
+ }
997
+
779
998
  pricing-renderer[data-pr-root] .pr-value--code {
780
999
  white-space: pre-wrap;
781
1000
  }
782
1001
 
783
1002
  pricing-renderer[data-pr-root] .pr-addon-layout {
784
1003
  display: grid;
1004
+ grid-template-columns: minmax(0, 1fr);
785
1005
  gap: var(--pr-space-5);
786
1006
  }
787
1007
 
@@ -830,6 +1050,7 @@
830
1050
 
831
1051
  pricing-renderer[data-pr-root] .pr-addon-card__details {
832
1052
  display: grid;
1053
+ grid-template-columns: minmax(0, 1fr);
833
1054
  gap: var(--pr-space-1);
834
1055
  font-size: var(--pr-font-size-sm);
835
1056
  }
@@ -841,27 +1062,37 @@
841
1062
 
842
1063
  pricing-renderer[data-pr-root] .pr-addon-effects summary {
843
1064
  font-size: var(--pr-font-size-sm);
844
- font-weight: 750;
1065
+ font-weight: var(--pr-font-weight-bold, 750);
845
1066
  cursor: pointer;
846
1067
  }
847
1068
 
848
1069
  pricing-renderer[data-pr-root] .pr-addon-effects dl {
849
1070
  display: grid;
1071
+ grid-template-columns: minmax(0, 1fr);
850
1072
  gap: var(--pr-space-2);
851
1073
  margin: var(--pr-space-3) 0 0;
852
1074
  }
853
1075
 
854
1076
  pricing-renderer[data-pr-root] .pr-addon-effects dl div {
855
1077
  display: flex;
1078
+ flex-wrap: wrap;
856
1079
  justify-content: space-between;
857
1080
  gap: var(--pr-space-3);
858
1081
  }
859
1082
 
860
1083
  pricing-renderer[data-pr-root] .pr-addon-effects :where(dt, dd) {
1084
+ min-inline-size: 0;
861
1085
  margin: 0;
862
1086
  font-size: var(--pr-font-size-sm);
863
1087
  }
864
1088
 
1089
+ /* Flex items holding names or values may shrink below a long unbroken string, which wraps. */
1090
+ pricing-renderer[data-pr-root] .pr-price > *,
1091
+ pricing-renderer[data-pr-root] .pr-breakdown__line > *,
1092
+ pricing-renderer[data-pr-root] .pr-breakdown__totals :where(dt, dd) {
1093
+ min-inline-size: 0;
1094
+ }
1095
+
865
1096
  pricing-renderer[data-pr-root] .pr-stepper {
866
1097
  display: flex;
867
1098
  flex-wrap: wrap;
@@ -897,30 +1128,162 @@
897
1128
  z-index: 4;
898
1129
  inset-block-end: max(0px, env(safe-area-inset-bottom));
899
1130
  display: grid;
900
- grid-template-columns: minmax(0, 1fr) auto;
901
- align-items: center;
1131
+ grid-template-columns: minmax(0, 1fr);
902
1132
  gap: var(--pr-space-2) var(--pr-space-4);
903
1133
  padding: var(--pr-space-4);
1134
+ /* room for the breakdown toggle, which is
1135
+ positioned and so never changes the block size */
1136
+ padding-inline-end: calc(var(--pr-space-4) + 24px + var(--pr-space-2));
904
1137
  background: color-mix(in srgb, var(--pr-color-surface) 95%, transparent);
905
1138
  backdrop-filter: blur(16px);
906
1139
  }
907
1140
 
1141
+ pricing-renderer[data-pr-root] .pr-summary__live {
1142
+ display: grid;
1143
+ grid-template-columns: minmax(0, 1fr) auto;
1144
+ align-items: center;
1145
+ gap: var(--pr-space-2) var(--pr-space-4);
1146
+ }
1147
+
908
1148
  pricing-renderer[data-pr-root] .pr-summary :where(p, strong, span) {
909
1149
  margin: 0;
910
1150
  }
911
1151
 
912
- pricing-renderer[data-pr-root] .pr-summary strong {
1152
+ pricing-renderer[data-pr-root] .pr-summary__live strong {
913
1153
  font-size: var(--pr-font-size-xl);
914
1154
  }
915
1155
 
916
- pricing-renderer[data-pr-root] .pr-summary span {
1156
+ pricing-renderer[data-pr-root] .pr-summary__live > span {
917
1157
  grid-column: 1 / -1;
918
1158
  color: var(--pr-color-warning);
919
1159
  font-size: var(--pr-font-size-sm);
920
1160
  }
921
1161
 
1162
+ /* 24px visual box; the ::after layer extends the
1163
+ pointer target to 44px without layout effect. An absolute layer is placed against the 22px
1164
+ padding box (24px minus the 1px border on each side), so inset -11px gives 22 + 2 * 11 = 44px
1165
+ centered on the toggle.
1166
+ the icon uses --pr-color-muted, 5.9:1 on the
1167
+ light and 7.5:1 on the dark surface.
1168
+ the only transition is shortened by the
1169
+ prefers-reduced-motion rule below. */
1170
+ pricing-renderer[data-pr-root] .pr-breakdown-toggle {
1171
+ position: absolute;
1172
+ inset-block-start: calc(var(--pr-space-4) + 4px);
1173
+ inset-inline-end: var(--pr-space-4);
1174
+ display: grid;
1175
+ place-items: center;
1176
+ inline-size: 24px;
1177
+ block-size: 24px;
1178
+ padding: 0;
1179
+ border: 1px solid currentcolor;
1180
+ border-radius: 50%;
1181
+ background: transparent;
1182
+ color: var(--pr-color-muted);
1183
+ cursor: pointer;
1184
+ transition: background-color var(--pr-motion-fast);
1185
+ }
1186
+
1187
+ pricing-renderer[data-pr-root] .pr-breakdown-toggle::before {
1188
+ content: 'i';
1189
+ font-size: var(--pr-font-size-sm);
1190
+ font-style: italic;
1191
+ font-weight: var(--pr-font-weight-bold, 700);
1192
+ line-height: 1;
1193
+ }
1194
+
1195
+ pricing-renderer[data-pr-root] .pr-breakdown-toggle::after {
1196
+ position: absolute;
1197
+ inset: -11px;
1198
+ content: '';
1199
+ }
1200
+
1201
+ pricing-renderer[data-pr-root] .pr-breakdown-toggle[aria-expanded='true'] {
1202
+ background: var(--pr-color-surface-subtle);
1203
+ color: var(--pr-color-text);
1204
+ }
1205
+
1206
+ @media (hover: hover) {
1207
+ pricing-renderer[data-pr-root] .pr-breakdown-toggle:hover {
1208
+ background: var(--pr-color-surface-subtle);
1209
+ }
1210
+ }
1211
+
1212
+ pricing-renderer[data-pr-root] .pr-breakdown {
1213
+ display: grid;
1214
+ grid-template-columns: minmax(0, 1fr);
1215
+ gap: var(--pr-space-3);
1216
+ padding-block-start: var(--pr-space-3);
1217
+ border-block-start: 1px solid var(--pr-color-border);
1218
+ font-size: var(--pr-font-size-sm);
1219
+ }
1220
+
1221
+ pricing-renderer[data-pr-root] .pr-breakdown__title {
1222
+ font-weight: var(--pr-font-weight-bold, 750);
1223
+ }
1224
+
1225
+ pricing-renderer[data-pr-root] .pr-breakdown__lines {
1226
+ display: grid;
1227
+ grid-template-columns: minmax(0, 1fr);
1228
+ gap: var(--pr-space-2);
1229
+ padding: 0;
1230
+ margin: 0;
1231
+ list-style: none;
1232
+ }
1233
+
1234
+ pricing-renderer[data-pr-root] .pr-breakdown__line {
1235
+ display: flex;
1236
+ flex-wrap: wrap;
1237
+ justify-content: space-between;
1238
+ gap: var(--pr-space-1) var(--pr-space-3);
1239
+ }
1240
+
1241
+ pricing-renderer[data-pr-root] .pr-breakdown__name {
1242
+ font-weight: var(--pr-font-weight-medium, 650);
1243
+ }
1244
+
1245
+ pricing-renderer[data-pr-root] .pr-breakdown__detail {
1246
+ color: var(--pr-color-muted);
1247
+ font-variant-numeric: tabular-nums;
1248
+ }
1249
+
1250
+ pricing-renderer[data-pr-root] .pr-breakdown__totals {
1251
+ display: grid;
1252
+ grid-template-columns: minmax(0, 1fr);
1253
+ gap: var(--pr-space-1);
1254
+ padding-block-start: var(--pr-space-3);
1255
+ margin: 0;
1256
+ border-block-start: 1px solid var(--pr-color-border);
1257
+ }
1258
+
1259
+ pricing-renderer[data-pr-root] .pr-breakdown__totals > div {
1260
+ display: flex;
1261
+ flex-wrap: wrap;
1262
+ justify-content: space-between;
1263
+ gap: var(--pr-space-3);
1264
+ }
1265
+
1266
+ pricing-renderer[data-pr-root] .pr-breakdown__totals dt {
1267
+ color: var(--pr-color-muted);
1268
+ }
1269
+
1270
+ pricing-renderer[data-pr-root] .pr-breakdown__totals dd {
1271
+ margin: 0;
1272
+ font-weight: var(--pr-font-weight-medium, 650);
1273
+ }
1274
+
1275
+ pricing-renderer[data-pr-root] .pr-breakdown__total :where(dt, dd) {
1276
+ color: var(--pr-color-text);
1277
+ font-weight: var(--pr-font-weight-bold, 750);
1278
+ }
1279
+
1280
+ pricing-renderer[data-pr-root] .pr-breakdown__quote {
1281
+ color: var(--pr-color-warning);
1282
+ }
1283
+
922
1284
  pricing-renderer[data-pr-root] .pr-metadata dl {
923
1285
  display: grid;
1286
+ grid-template-columns: minmax(0, 1fr);
924
1287
  gap: var(--pr-space-2);
925
1288
  margin: var(--pr-space-4) 0 0;
926
1289
  }
@@ -949,8 +1312,14 @@
949
1312
  background: var(--pr-color-warning-surface);
950
1313
  }
951
1314
 
1315
+ pricing-renderer[data-pr-root] .pr-error .pr-diagnostics {
1316
+ padding: 0;
1317
+ border: 0;
1318
+ background: none;
1319
+ }
1320
+
952
1321
  pricing-renderer[data-pr-root] .pr-diagnostics summary {
953
- font-weight: 750;
1322
+ font-weight: var(--pr-font-weight-bold, 750);
954
1323
  cursor: pointer;
955
1324
  }
956
1325
 
@@ -961,7 +1330,7 @@
961
1330
 
962
1331
  pricing-renderer[data-pr-root] .pr-dialog-backdrop {
963
1332
  position: fixed;
964
- z-index: 1000;
1333
+ z-index: var(--pr-z-dialog, 1000);
965
1334
  inset: 0;
966
1335
  display: grid;
967
1336
  place-items: end center;
@@ -981,6 +1350,7 @@
981
1350
 
982
1351
  pricing-renderer[data-pr-root] .pr-dialog h3 {
983
1352
  margin-block-start: 0;
1353
+ font-weight: var(--pr-font-weight-bold, 700);
984
1354
  }
985
1355
 
986
1356
  pricing-renderer[data-pr-root] .pr-dialog__actions {
@@ -1019,8 +1389,7 @@
1019
1389
 
1020
1390
  @container pricing-renderer (min-width: 600px) {
1021
1391
  pricing-renderer[data-pr-root] .pr-plan-grid {
1022
- grid-template-columns: repeat(2, minmax(0, 320px));
1023
- justify-content: center;
1392
+ grid-template-columns: var(--pr-plan-columns, repeat(2, minmax(0, 1fr)));
1024
1393
  }
1025
1394
 
1026
1395
  pricing-renderer[data-pr-root] .pr-addon-grid,
@@ -1028,11 +1397,19 @@
1028
1397
  grid-template-columns: repeat(2, minmax(0, 1fr));
1029
1398
  }
1030
1399
 
1400
+ /* One or two plans (no third child) keep the centered cards of at most 320px they had before:
1401
+ this selector is more specific than the plan count rules, which use :where(). */
1402
+ pricing-renderer[data-pr-root] .pr-plan-grid:not(:has(> :nth-child(3))) {
1403
+ grid-template-columns: var(--pr-plan-columns, repeat(2, minmax(0, 320px)));
1404
+ justify-content: center;
1405
+ }
1406
+
1031
1407
  pricing-renderer[data-pr-root] .pr-feature-card {
1032
1408
  grid-template-columns: minmax(0, 0.8fr) minmax(0, 1.2fr);
1033
1409
  }
1034
1410
 
1035
- pricing-renderer[data-pr-root] .pr-dialog-backdrop {
1411
+ /* :where(*) adds no specificity; it keeps the base rule the only one with this exact selector */
1412
+ pricing-renderer[data-pr-root] .pr-dialog-backdrop:where(*) {
1036
1413
  place-items: center;
1037
1414
  padding: var(--pr-space-5);
1038
1415
  }
@@ -1044,60 +1421,198 @@
1044
1421
  }
1045
1422
  }
1046
1423
 
1047
- @container pricing-renderer (min-width: 840px) {
1048
- pricing-renderer[data-pr-root] .pr-plan-grid {
1049
- grid-template-columns: repeat(auto-fit, minmax(220px, 320px));
1424
+ /* Below 840px the space above the plans gets the same caps it already has from 840px (UX-3), so
1425
+ the first plan price fits in the first screen of a phone. Smaller host tokens still win
1426
+ through min(); the space after the plans is unchanged. */
1427
+ @container pricing-renderer (max-width: 839.98px) {
1428
+ pricing-renderer[data-pr-root] .pr-content {
1429
+ gap: min(var(--pr-space-section), var(--pr-space-5));
1430
+ padding-block-start: min(var(--pr-shell-padding), var(--pr-space-4));
1050
1431
  }
1051
1432
 
1052
- pricing-renderer[data-pr-root] .pr-addon-layout {
1053
- grid-template-columns: minmax(0, 1fr) 288px;
1054
- align-items: start;
1433
+ pricing-renderer[data-pr-root] .pr-content > [data-pr-part='plans'] ~ * {
1434
+ margin-block-start: calc(
1435
+ var(--pr-space-section) - min(var(--pr-space-section), var(--pr-space-5))
1436
+ );
1055
1437
  }
1056
1438
 
1057
- pricing-renderer[data-pr-root] .pr-summary {
1058
- inset-block-start: var(--pr-space-4);
1059
- inset-block-end: auto;
1060
- grid-template-columns: 1fr;
1439
+ pricing-renderer[data-pr-root] [data-pr-part='plans'] > .pr-section__heading {
1440
+ margin-block-end: var(--pr-space-3);
1061
1441
  }
1062
1442
  }
1063
1443
 
1064
- @container pricing-renderer (min-width: 1200px) {
1444
+ @container pricing-renderer (min-width: 840px) {
1445
+ /* Plan columns by plan count (table in tests/fixtures/layout/plan-grid-spec.md). The
1446
+ generic rule sets the band maximum; the :where() rules after it keep the same specificity
1447
+ and set the counts that need fewer columns, so the later rule wins. A grid whose N-th child
1448
+ is its last child has exactly N plans. The generic rule of a wider band comes later in the
1449
+ file and overrides the count rules of the narrower ones. */
1065
1450
  pricing-renderer[data-pr-root] .pr-plan-grid {
1066
- grid-template-columns: repeat(auto-fit, minmax(220px, 320px));
1451
+ grid-template-columns: var(--pr-plan-columns, repeat(3, minmax(0, 1fr)));
1067
1452
  }
1068
1453
 
1069
- pricing-renderer[data-pr-root] .pr-addon-grid {
1070
- grid-template-columns: repeat(3, minmax(0, 1fr));
1454
+ pricing-renderer[data-pr-root] .pr-plan-grid:where(:has(> :nth-child(4):last-child)),
1455
+ pricing-renderer[data-pr-root] .pr-plan-grid:where(:has(> :nth-child(10):last-child)),
1456
+ pricing-renderer[data-pr-root] .pr-plan-grid:where(:has(> :nth-child(16):last-child)) {
1457
+ grid-template-columns: var(--pr-plan-columns, repeat(2, minmax(0, 1fr)));
1071
1458
  }
1072
1459
 
1073
- pricing-renderer[data-pr-root] .pr-addon-layout {
1074
- grid-template-columns: minmax(0, 1fr) minmax(320px, 360px);
1460
+ pricing-renderer[data-pr-root] .pr-plan-grid:not(:has(> :nth-child(3))) {
1461
+ grid-template-columns: var(--pr-plan-columns, repeat(auto-fit, minmax(220px, 320px)));
1075
1462
  }
1076
1463
 
1077
- pricing-renderer[data-pr-root] .pr-dialog {
1078
- width: min(960px, 100%);
1464
+ /* Tighter plan cards and less space above the plans bring the first CTA into the first
1465
+ screen (savings per lever in tests/fixtures/layout/plan-grid-spec.md); the space after
1466
+ the plans stays var(--pr-space-section). */
1467
+ pricing-renderer[data-pr-root] .pr-plan-card {
1468
+ padding: var(--pr-space-4);
1079
1469
  }
1080
- }
1081
1470
 
1082
- @media (prefers-reduced-motion: reduce) {
1083
- pricing-renderer[data-pr-root] *,
1084
- pricing-renderer[data-pr-root] *::before,
1085
- pricing-renderer[data-pr-root] *::after {
1086
- scroll-behavior: auto !important;
1087
- animation-duration: 0.01ms !important;
1088
- animation-iteration-count: 1 !important;
1089
- transition-duration: 0.01ms !important;
1471
+ pricing-renderer[data-pr-root] .pr-plan-card__top h4 {
1472
+ margin-block-start: var(--pr-space-2);
1090
1473
  }
1091
- }
1092
1474
 
1093
- @media (forced-colors: active) {
1094
- pricing-renderer[data-pr-root] .pr-plan-card.is-selected,
1095
- pricing-renderer[data-pr-root] .pr-addon-card.is-selected {
1096
- outline: 3px solid Highlight;
1475
+ pricing-renderer[data-pr-root] .pr-plan-card .pr-price {
1476
+ min-block-size: 4rem;
1477
+ padding-block: var(--pr-space-2);
1097
1478
  }
1098
1479
 
1099
- pricing-renderer[data-pr-root] .pr-selection-control:has(input:checked) {
1100
- border-color: Highlight;
1480
+ pricing-renderer[data-pr-root] .pr-highlight-list {
1481
+ gap: var(--pr-space-2);
1482
+ margin-block-end: var(--pr-space-3);
1483
+ }
1484
+
1485
+ pricing-renderer[data-pr-root] .pr-hero__title {
1486
+ font-size: clamp(2rem, 8cqi, 3rem);
1487
+ }
1488
+
1489
+ pricing-renderer[data-pr-root] [data-pr-part='plans'] > .pr-section__heading {
1490
+ margin-block-end: var(--pr-space-3);
1491
+ }
1492
+
1493
+ /* The top padding and the row gap (every row above the plans: diagnostics, hero, metadata,
1494
+ billing, variables) are capped, so smaller --pr-shell-padding and --pr-space-section values
1495
+ still apply. The margin after the plans adds back what the cap removes. */
1496
+ pricing-renderer[data-pr-root] .pr-content {
1497
+ gap: min(var(--pr-space-section), var(--pr-space-5));
1498
+ padding-block-start: min(var(--pr-shell-padding), var(--pr-space-4));
1499
+ }
1500
+
1501
+ pricing-renderer[data-pr-root] .pr-content > [data-pr-part='plans'] ~ * {
1502
+ margin-block-start: calc(
1503
+ var(--pr-space-section) - min(var(--pr-space-section), var(--pr-space-5))
1504
+ );
1505
+ }
1506
+
1507
+ pricing-renderer[data-pr-root] .pr-addon-layout {
1508
+ grid-template-columns: minmax(0, 1fr) 288px;
1509
+ align-items: start;
1510
+ }
1511
+
1512
+ /* The summary is not in here any more, so its column would be 288px of nothing. */
1513
+ pricing-renderer[data-pr-root] .pr-addon-layout[data-pr-summary='outside'] {
1514
+ grid-template-columns: minmax(0, 1fr);
1515
+ }
1516
+
1517
+ pricing-renderer[data-pr-root] .pr-summary {
1518
+ inset-block-start: calc(var(--pr-sticky-top, 0px) + var(--pr-space-4));
1519
+ inset-block-end: auto;
1520
+ }
1521
+
1522
+ pricing-renderer[data-pr-root] .pr-summary__live {
1523
+ grid-template-columns: 1fr;
1524
+ }
1525
+
1526
+ /* The summary docked after the plans is one compact
1527
+ line (label and total side by side). It is not sticky, so it never covers the toolbar. */
1528
+ pricing-renderer[data-pr-root] .pr-flow > .pr-summary {
1529
+ position: relative;
1530
+ inset-block-start: auto;
1531
+ padding-block: var(--pr-space-3);
1532
+ }
1533
+
1534
+ pricing-renderer[data-pr-root] .pr-flow > .pr-summary:empty {
1535
+ display: none;
1536
+ }
1537
+
1538
+ pricing-renderer[data-pr-root] .pr-flow > .pr-summary .pr-summary__live {
1539
+ grid-template-columns: minmax(0, 1fr) auto;
1540
+ }
1541
+
1542
+ pricing-renderer[data-pr-root] .pr-flow > .pr-summary .pr-breakdown-toggle {
1543
+ inset-block-start: var(--pr-space-3);
1544
+ }
1545
+
1546
+ pricing-renderer[data-pr-root] .pr-breakdown-toggle {
1547
+ inset-block-start: var(--pr-space-4);
1548
+ }
1549
+ }
1550
+
1551
+ @container pricing-renderer (min-width: 1040px) {
1552
+ pricing-renderer[data-pr-root] .pr-plan-grid {
1553
+ grid-template-columns: var(--pr-plan-columns, repeat(4, minmax(0, 1fr)));
1554
+ }
1555
+
1556
+ pricing-renderer[data-pr-root] .pr-plan-grid:where(:has(> :nth-child(3):last-child)),
1557
+ pricing-renderer[data-pr-root] .pr-plan-grid:where(:has(> :nth-child(5):last-child)),
1558
+ pricing-renderer[data-pr-root] .pr-plan-grid:where(:has(> :nth-child(6):last-child)),
1559
+ pricing-renderer[data-pr-root] .pr-plan-grid:where(:has(> :nth-child(9):last-child)) {
1560
+ grid-template-columns: var(--pr-plan-columns, repeat(3, minmax(0, 1fr)));
1561
+ }
1562
+ }
1563
+
1564
+ @container pricing-renderer (min-width: 1200px) {
1565
+ pricing-renderer[data-pr-root] .pr-addon-grid {
1566
+ grid-template-columns: repeat(3, minmax(0, 1fr));
1567
+ }
1568
+
1569
+ pricing-renderer[data-pr-root] .pr-addon-layout {
1570
+ grid-template-columns: minmax(0, 1fr) minmax(320px, 360px);
1571
+ }
1572
+
1573
+ pricing-renderer[data-pr-root] .pr-addon-layout[data-pr-summary='outside'] {
1574
+ grid-template-columns: minmax(0, 1fr);
1575
+ }
1576
+
1577
+ pricing-renderer[data-pr-root] .pr-dialog {
1578
+ width: min(960px, 100%);
1579
+ }
1580
+ }
1581
+
1582
+ /* At most 4 columns from 1280px too: 5 columns would make the cards about 243px wide at 1440,
1583
+ taller than today, and push the first CTA below the first screen. */
1584
+ @container pricing-renderer (min-width: 1280px) {
1585
+ pricing-renderer[data-pr-root] .pr-plan-grid {
1586
+ grid-template-columns: var(--pr-plan-columns, repeat(4, minmax(0, 1fr)));
1587
+ }
1588
+
1589
+ pricing-renderer[data-pr-root] .pr-plan-grid:where(:has(> :nth-child(3):last-child)),
1590
+ pricing-renderer[data-pr-root] .pr-plan-grid:where(:has(> :nth-child(5):last-child)),
1591
+ pricing-renderer[data-pr-root] .pr-plan-grid:where(:has(> :nth-child(6):last-child)),
1592
+ pricing-renderer[data-pr-root] .pr-plan-grid:where(:has(> :nth-child(9):last-child)) {
1593
+ grid-template-columns: var(--pr-plan-columns, repeat(3, minmax(0, 1fr)));
1594
+ }
1595
+ }
1596
+
1597
+ @media (prefers-reduced-motion: reduce) {
1598
+ pricing-renderer[data-pr-root] *,
1599
+ pricing-renderer[data-pr-root] *::before,
1600
+ pricing-renderer[data-pr-root] *::after {
1601
+ scroll-behavior: auto !important;
1602
+ animation-duration: 0.01ms !important;
1603
+ animation-iteration-count: 1 !important;
1604
+ transition-duration: 0.01ms !important;
1605
+ }
1606
+ }
1607
+
1608
+ @media (forced-colors: active) {
1609
+ pricing-renderer[data-pr-root] .pr-plan-card.is-selected,
1610
+ pricing-renderer[data-pr-root] .pr-addon-card.is-selected {
1611
+ outline: 3px solid Highlight;
1612
+ }
1613
+
1614
+ pricing-renderer[data-pr-root] .pr-selection-control:has(input:checked) {
1615
+ border-color: Highlight;
1101
1616
  background: Highlight;
1102
1617
  color: HighlightText;
1103
1618
  forced-color-adjust: none;
@@ -1106,5 +1621,444 @@
1106
1621
  pricing-renderer[data-pr-root] .pr-button--primary {
1107
1622
  border-color: ButtonText;
1108
1623
  }
1624
+
1625
+ /* the current badge and the pending spinner keep system colours; the text names the state */
1626
+ pricing-renderer[data-pr-root] .pr-plan-card[data-current='true'],
1627
+ pricing-renderer[data-pr-root] .pr-addon-card[data-current='true'] {
1628
+ border-color: CanvasText;
1629
+ }
1630
+
1631
+ pricing-renderer[data-pr-root] .pr-button:disabled {
1632
+ border-color: GrayText;
1633
+ color: GrayText;
1634
+ }
1635
+ }
1636
+
1637
+ /* Dense add-on grid: cards in a row share a height, and the disclosure and the stepper sit at the
1638
+ bottom of each card. */
1639
+ pricing-renderer[data-pr-root] .pr-addon-grid--dense {
1640
+ align-items: stretch;
1641
+ }
1642
+
1643
+ pricing-renderer[data-pr-root] .pr-addon-card--dense .pr-addon-card__details {
1644
+ flex: 1 1 auto;
1645
+ }
1646
+
1647
+ /* Filter mode: the toolbar and the grid share the left column of the layout. */
1648
+ pricing-renderer[data-pr-root] .pr-addon-layout > .pr-addon-main {
1649
+ display: grid;
1650
+ grid-template-columns: minmax(0, 1fr);
1651
+ gap: var(--pr-space-4);
1652
+ min-inline-size: 0;
1653
+ }
1654
+
1655
+ pricing-renderer[data-pr-root] .pr-addon-main > .pr-button {
1656
+ justify-self: start;
1657
+ }
1658
+
1659
+ pricing-renderer[data-pr-root] .pr-addon-toolbar {
1660
+ display: flex;
1661
+ flex-wrap: wrap;
1662
+ align-items: center;
1663
+ gap: var(--pr-space-3);
1664
+ padding: var(--pr-space-3);
1665
+ margin-block-end: 0;
1666
+ border: 1px solid var(--pr-color-border);
1667
+ border-radius: var(--pr-radius-md);
1668
+ background: color-mix(in srgb, var(--pr-color-surface) 94%, transparent);
1669
+ position: static;
1670
+ }
1671
+
1672
+ pricing-renderer[data-pr-root] .pr-addon-effects .pr-addon-notes {
1673
+ display: grid;
1674
+ grid-template-columns: minmax(0, 1fr);
1675
+ gap: var(--pr-space-2);
1676
+ margin: var(--pr-space-3) 0 0;
1677
+ }
1678
+
1679
+ pricing-renderer[data-pr-root] .pr-addon-effects .pr-addon-notes p {
1680
+ margin: 0;
1681
+ font-size: var(--pr-font-size-sm);
1682
+ }
1683
+
1684
+ /* <pricing-change-review>, after the approved review mock. The review is its own element. Its tokens
1685
+ and theme switches (theme attribute, data-theme ancestor, theme="auto") live in theme.css,
1686
+ which must be loaded, as for the renderer. The host overrides a token with an unlayered rule
1687
+ on pricing-change-review. */
1688
+ pricing-change-review {
1689
+ display: block;
1690
+ container: change-review / inline-size;
1691
+ min-inline-size: 0;
1692
+ color: var(--pr-color-text);
1693
+ font: inherit;
1694
+ /* as on the renderer root */
1695
+ font-family: var(--pr-font-family);
1696
+ line-height: var(--pr-line-height, 1.5);
1697
+ text-rendering: optimizeLegibility;
1698
+ }
1699
+
1700
+ pricing-change-review,
1701
+ pricing-change-review *,
1702
+ pricing-change-review *::before,
1703
+ pricing-change-review *::after {
1704
+ box-sizing: border-box;
1705
+ }
1706
+
1707
+ pricing-change-review :where(button) {
1708
+ color: inherit;
1709
+ font: inherit;
1710
+ }
1711
+
1712
+ pricing-change-review :where(button, h2):focus-visible {
1713
+ outline: 2px solid var(--pr-color-focus);
1714
+ outline-offset: 3px;
1715
+ }
1716
+
1717
+ pricing-change-review [data-pr-part='change-review'] {
1718
+ display: grid;
1719
+ grid-template-columns: minmax(0, 1fr);
1720
+ gap: var(--pr-space-4);
1721
+ min-inline-size: 0;
1722
+ overflow-wrap: break-word;
1723
+ }
1724
+
1725
+ pricing-change-review :where(h2, h3, p, ul, dl, dd) {
1726
+ margin: 0;
1727
+ }
1728
+
1729
+ pricing-change-review ul {
1730
+ padding: 0;
1731
+ list-style: none;
1732
+ }
1733
+
1734
+ pricing-change-review .pr-cr-header {
1735
+ display: flex;
1736
+ flex-wrap: wrap;
1737
+ align-items: center;
1738
+ gap: var(--pr-space-2) var(--pr-space-3);
1739
+ }
1740
+
1741
+ pricing-change-review .pr-cr-header h2 {
1742
+ font-size: var(--pr-font-size-2xl);
1743
+ font-weight: var(--pr-font-weight-bold, 700);
1744
+ letter-spacing: -0.025em;
1745
+ }
1746
+
1747
+ pricing-change-review [data-pr-part='change-review'] > section {
1748
+ display: grid;
1749
+ grid-template-columns: minmax(0, 1fr);
1750
+ gap: var(--pr-space-3);
1751
+ min-inline-size: 0;
1752
+ padding: var(--pr-space-4);
1753
+ border: 1px solid var(--pr-color-border);
1754
+ border-radius: var(--pr-radius-lg);
1755
+ background: var(--pr-color-surface);
1756
+ box-shadow: var(--pr-shadow-sm);
1757
+ }
1758
+
1759
+ pricing-change-review [data-pr-part='change-review'] > section > h3 {
1760
+ font-size: var(--pr-font-size-lg);
1761
+ font-weight: var(--pr-font-weight-bold, 700);
1762
+ letter-spacing: -0.02em;
1763
+ }
1764
+
1765
+ pricing-change-review [data-pr-part='change-row'] {
1766
+ display: flex;
1767
+ flex-wrap: wrap;
1768
+ align-items: baseline;
1769
+ justify-content: space-between;
1770
+ gap: var(--pr-space-1) var(--pr-space-3);
1771
+ padding-block: var(--pr-space-2);
1772
+ }
1773
+
1774
+ pricing-change-review [data-pr-part='change-row'] + [data-pr-part='change-row'] {
1775
+ border-block-start: 1px solid var(--pr-color-border);
1776
+ }
1777
+
1778
+ pricing-change-review .pr-cr-row__name {
1779
+ flex: 1 1 10rem;
1780
+ min-inline-size: 0;
1781
+ font-weight: var(--pr-font-weight-medium, 650);
1782
+ }
1783
+
1784
+ pricing-change-review .pr-cr-row__detail {
1785
+ min-inline-size: 0;
1786
+ color: var(--pr-color-muted);
1787
+ font-size: var(--pr-font-size-sm);
1788
+ font-variant-numeric: tabular-nums;
1789
+ }
1790
+
1791
+ /* A limit that grows is a gain, one that shrinks is what the visitor gives up. There is no
1792
+ red in this theme and inventing one for a downgrade would overstate it: warning is the tone
1793
+ already used a few lines below for a limit that falls under current usage. */
1794
+ pricing-change-review .pr-cr-row__delta {
1795
+ font-variant-numeric: tabular-nums;
1796
+ }
1797
+
1798
+ pricing-change-review .pr-cr-row__delta[data-direction='increase'] {
1799
+ color: var(--pr-color-success);
1800
+ }
1801
+
1802
+ pricing-change-review .pr-cr-row__delta[data-direction='decrease'] {
1803
+ color: var(--pr-color-warning);
1804
+ }
1805
+
1806
+ pricing-change-review .pr-cr-row__reason {
1807
+ flex-basis: 100%;
1808
+ color: var(--pr-color-muted);
1809
+ font-size: var(--pr-font-size-sm);
1810
+ }
1811
+
1812
+ pricing-change-review [data-pr-part='change-badge'] {
1813
+ display: inline-flex;
1814
+ align-items: center;
1815
+ min-inline-size: 0;
1816
+ max-inline-size: 100%;
1817
+ min-block-size: 28px;
1818
+ padding: 0.2rem 0.65rem;
1819
+ border: 1px solid var(--pr-color-border);
1820
+ border-radius: 999px;
1821
+ background: var(--pr-color-surface-raised);
1822
+ color: var(--pr-color-muted);
1823
+ font-size: var(--pr-font-size-xs);
1824
+ font-weight: var(--pr-font-weight-bold, 700);
1825
+ overflow-wrap: anywhere;
1826
+ }
1827
+
1828
+ pricing-change-review [data-pr-part='change-badge'][data-tone='accent'] {
1829
+ border-color: color-mix(in srgb, var(--pr-color-accent) 45%, var(--pr-color-border));
1830
+ background: color-mix(in srgb, var(--pr-color-accent) 11%, var(--pr-color-surface-raised));
1831
+ color: var(--pr-color-link);
1832
+ }
1833
+
1834
+ pricing-change-review [data-pr-part='change-badge'][data-tone='success'] {
1835
+ border-color: color-mix(in srgb, var(--pr-color-success) 42%, var(--pr-color-border));
1836
+ background: color-mix(in srgb, var(--pr-color-success) 10%, var(--pr-color-surface-raised));
1837
+ /* the text is darkened (lightened in the dark theme) to keep 4.5:1 on the tinted background */
1838
+ color: color-mix(in srgb, var(--pr-color-success) 78%, var(--pr-color-text));
1839
+ }
1840
+
1841
+ pricing-change-review [data-pr-part='change-badge'][data-tone='warning'] {
1842
+ border-color: color-mix(in srgb, var(--pr-color-warning) 42%, var(--pr-color-border));
1843
+ background: var(--pr-color-warning-surface);
1844
+ color: var(--pr-color-warning);
1845
+ }
1846
+
1847
+ pricing-change-review .pr-cr-banner,
1848
+ pricing-change-review .pr-cr-notice,
1849
+ pricing-change-review .pr-cr-error {
1850
+ padding: var(--pr-space-3) var(--pr-space-4);
1851
+ border: 1px solid var(--pr-color-border);
1852
+ border-radius: var(--pr-radius-md);
1853
+ background: var(--pr-color-surface-subtle);
1854
+ }
1855
+
1856
+ pricing-change-review .pr-cr-banner {
1857
+ border-color: color-mix(in srgb, var(--pr-color-accent) 45%, var(--pr-color-border));
1858
+ background: color-mix(in srgb, var(--pr-color-accent) 8%, var(--pr-color-surface));
1859
+ }
1860
+
1861
+ pricing-change-review .pr-cr-notice + .pr-cr-notice {
1862
+ margin-block-start: var(--pr-space-2);
1863
+ }
1864
+
1865
+ pricing-change-review .pr-cr-error,
1866
+ pricing-change-review .pr-cr-warning {
1867
+ border-color: var(--pr-color-warning);
1868
+ background: var(--pr-color-warning-surface);
1869
+ }
1870
+
1871
+ pricing-change-review .pr-cr-warning {
1872
+ flex-basis: 100%;
1873
+ padding: var(--pr-space-2) var(--pr-space-3);
1874
+ border: 1px solid var(--pr-color-warning);
1875
+ border-radius: var(--pr-radius-sm);
1876
+ color: var(--pr-color-warning);
1877
+ font-weight: var(--pr-font-weight-medium, 650);
1878
+ }
1879
+
1880
+ pricing-change-review .pr-cr-error {
1881
+ color: var(--pr-color-text);
1882
+ }
1883
+
1884
+ pricing-change-review .pr-cr-error strong {
1885
+ display: block;
1886
+ }
1887
+
1888
+ pricing-change-review .pr-cr-price {
1889
+ display: grid;
1890
+ grid-template-columns: minmax(0, 1fr);
1891
+ gap: var(--pr-space-3);
1892
+ }
1893
+
1894
+ pricing-change-review .pr-cr-price > div {
1895
+ min-inline-size: 0;
1896
+ }
1897
+
1898
+ pricing-change-review .pr-cr-price dt {
1899
+ color: var(--pr-color-muted);
1900
+ font-size: var(--pr-font-size-sm);
1901
+ }
1902
+
1903
+ pricing-change-review .pr-cr-price dd {
1904
+ font-size: var(--pr-font-size-xl);
1905
+ font-weight: var(--pr-font-weight-bold, 790);
1906
+ letter-spacing: -0.03em;
1907
+ font-variant-numeric: tabular-nums;
1908
+ }
1909
+
1910
+ pricing-change-review .pr-cr-price dd small {
1911
+ color: var(--pr-color-muted);
1912
+ font-size: var(--pr-font-size-sm);
1913
+ font-weight: var(--pr-font-weight-regular, 400);
1914
+ letter-spacing: 0;
1915
+ }
1916
+
1917
+ /* The difference is the number the screen exists for, so it outranks the two it is derived
1918
+ from. A decrease is the pleasant exception and gets the success tone; an increase is not an
1919
+ error (the visitor asked for it), so it takes the accent rather than a warning colour. */
1920
+ pricing-change-review [data-pr-part='price-difference'] dd {
1921
+ font-size: var(--pr-font-size-2xl);
1922
+ line-height: 1.1;
1923
+ }
1924
+
1925
+ pricing-change-review [data-pr-part='price-difference'][data-direction='decrease'] dd {
1926
+ color: var(--pr-color-success);
1927
+ }
1928
+
1929
+ pricing-change-review [data-pr-part='price-difference'][data-direction='increase'] dd {
1930
+ color: var(--pr-color-accent);
1931
+ }
1932
+
1933
+ pricing-change-review .pr-cr-note {
1934
+ color: var(--pr-color-muted);
1935
+ font-size: var(--pr-font-size-sm);
1936
+ }
1937
+
1938
+ /* Confirm and Back stay reachable however tall the diff is. This used to apply only below a
1939
+ 600px container, so on a wider one the buttons scrolled out of a long comparison and the
1940
+ reader had to hunt for them. The background is fully opaque: at 95% the rows behind it
1941
+ showed through and the bar read as broken. */
1942
+ pricing-change-review [data-pr-part='change-actions'] {
1943
+ position: sticky;
1944
+ z-index: 4;
1945
+ inset-block-end: 0;
1946
+ display: flex;
1947
+ flex-wrap: wrap;
1948
+ align-items: center;
1949
+ justify-content: flex-end;
1950
+ gap: var(--pr-space-3);
1951
+ padding-block: var(--pr-space-3);
1952
+ border-block-start: 1px solid var(--pr-color-border);
1953
+ background: var(--pr-color-surface);
1954
+ }
1955
+
1956
+ pricing-change-review .pr-cr-status {
1957
+ margin-inline-end: auto;
1958
+ color: var(--pr-color-muted);
1959
+ font-size: var(--pr-font-size-sm);
1960
+ }
1961
+
1962
+ pricing-change-review .pr-button {
1963
+ display: inline-flex;
1964
+ align-items: center;
1965
+ justify-content: center;
1966
+ min-block-size: 44px;
1967
+ padding: 0.65rem 1rem;
1968
+ border: 1px solid transparent;
1969
+ border-radius: var(--pr-radius-md);
1970
+ font-weight: var(--pr-font-weight-bold, 750);
1971
+ line-height: 1.2;
1972
+ text-align: center;
1973
+ /* Unlike a plan CTA, which is a word, confirming names what is being confirmed. In a narrow
1974
+ dialog that sentence has to wrap rather than be cut off, so min-block-size sets the floor
1975
+ and the box grows with the text. */
1976
+ white-space: normal;
1977
+ text-wrap: balance;
1978
+ cursor: pointer;
1979
+ transition: background-color var(--pr-motion-fast);
1980
+ }
1981
+
1982
+ pricing-change-review .pr-button--primary {
1983
+ border-color: var(--pr-color-accent);
1984
+ background: var(--pr-color-accent);
1985
+ color: var(--pr-color-on-accent);
1986
+ }
1987
+
1988
+ pricing-change-review .pr-button--secondary {
1989
+ border-color: var(--pr-color-border);
1990
+ background: var(--pr-color-surface);
1991
+ color: var(--pr-color-text);
1992
+ }
1993
+
1994
+ /* Confirm while pending or with nothing to confirm is aria-disabled, so focus stays on it. */
1995
+ pricing-change-review .pr-button[aria-disabled='true'] {
1996
+ border-color: var(--pr-color-border);
1997
+ background: var(--pr-color-surface-subtle);
1998
+ color: var(--pr-color-muted);
1999
+ cursor: not-allowed;
2000
+ }
2001
+
2002
+ pricing-change-review .pr-button[aria-busy='true'] {
2003
+ cursor: progress;
2004
+ }
2005
+
2006
+ /* Wide: the price in columns (two or three) and roomier sections. */
2007
+ @container change-review (min-width: 600px) {
2008
+ pricing-change-review .pr-cr-price {
2009
+ grid-template-columns: repeat(3, minmax(0, 1fr));
2010
+ }
2011
+
2012
+ pricing-change-review .pr-cr-price:where(:not(:has(> :nth-child(3)))) {
2013
+ grid-template-columns: repeat(2, minmax(0, 1fr));
2014
+ }
2015
+
2016
+ pricing-change-review [data-pr-part='change-review'] > section {
2017
+ padding: var(--pr-space-5);
2018
+ }
2019
+ }
2020
+
2021
+ /* Narrow: the actions stay in view at the bottom of the container. */
2022
+ @container change-review (max-width: 599.98px) {
2023
+ pricing-change-review [data-pr-part='change-actions'] .pr-button {
2024
+ flex: 1 1 8rem;
2025
+ }
2026
+ }
2027
+
2028
+ @media (prefers-reduced-motion: reduce) {
2029
+ pricing-change-review *,
2030
+ pricing-change-review *::before,
2031
+ pricing-change-review *::after {
2032
+ scroll-behavior: auto !important;
2033
+ animation-duration: 0.01ms !important;
2034
+ animation-iteration-count: 1 !important;
2035
+ transition-duration: 0.01ms !important;
2036
+ }
2037
+ }
2038
+
2039
+ /* The root, every section and every button keep a visible
2040
+ border of system colours. The changes are named in text, so no meaning is left to colour. */
2041
+ @media (forced-colors: active) {
2042
+ pricing-change-review [data-pr-part='change-review'] {
2043
+ padding: var(--pr-space-3);
2044
+ border: 1px solid CanvasText;
2045
+ }
2046
+
2047
+ pricing-change-review [data-pr-part='change-review'] > section {
2048
+ border: 1px solid CanvasText;
2049
+ }
2050
+
2051
+ pricing-change-review [data-pr-part='change-actions'] {
2052
+ background: Canvas;
2053
+ }
2054
+
2055
+ pricing-change-review .pr-button {
2056
+ border: 1px solid ButtonText;
2057
+ }
2058
+
2059
+ pricing-change-review .pr-button[aria-disabled='true'] {
2060
+ border-color: GrayText;
2061
+ color: GrayText;
2062
+ }
1109
2063
  }
1110
2064
  }