srcdev-nuxt-components 9.6.2 → 9.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/.claude/rules/consumer/consumer-overrides.md +17 -0
  2. package/.claude/rules/consumer/css-comment-slash.md +13 -0
  3. package/.claude/rules/consumer/css-native-nesting.md +13 -0
  4. package/.claude/rules/consumer/css-no-deep.md +13 -0
  5. package/.claude/rules/consumer/css-rem-base.md +11 -0
  6. package/.claude/rules/consumer/css-surface-text-pairing.md +13 -0
  7. package/.claude/rules/consumer/css-webkit-support.md +15 -0
  8. package/.claude/rules/consumer/explicit-vue-imports.md +11 -0
  9. package/.claude/rules/consumer/nuxt-img-dimensions.md +12 -0
  10. package/.claude/rules/consumer/srcdev-skills-location.md +9 -0
  11. package/.claude/rules/consumer/story-args.md +12 -0
  12. package/.claude/rules/consumer/story-register-components.md +12 -0
  13. package/.claude/rules/consumer/vue-auto-imports.md +14 -0
  14. package/.claude/rules/consumer/vue-no-self-closing.md +11 -0
  15. package/.claude/rules/consumer/vue-prop-hyphenation.md +11 -0
  16. package/.claude/rules/consumer/vue-props-pattern.md +13 -0
  17. package/.claude/rules/consumer/vue-slots-no-computed.md +14 -0
  18. package/.claude/rules/consumer/vue-video-source.md +12 -0
  19. package/.claude/skills/component-compliance-checklist.md +23 -1
  20. package/.claude/skills/component-naming.md +1 -1
  21. package/.claude/skills/components/pricing-card.md +68 -84
  22. package/.claude/skills/index.md +9 -3
  23. package/.claude/skills/new-app-scaffold.md +4 -2
  24. package/.claude/skills/setup-postinstall.md +5 -4
  25. package/.claude/skills/storybook-add-story.md +36 -0
  26. package/.claude/skills/theming-component-token-pattern.md +46 -0
  27. package/.claude/skills/theming-partial-override.md +1 -1
  28. package/.vscode/srcdev-component-pricing-card.code-snippets +46 -84
  29. package/README.md +5 -5
  30. package/app/components/01.atoms/animations/scroll-reveal-frame/stories/ScrollRevealFrame.stories.ts +2 -0
  31. package/app/components/01.atoms/animations/scroll-reveal-image/stories/ScrollRevealImage.stories.ts +2 -0
  32. package/app/components/01.atoms/animations/section-parallax/stories/SectionParallax.stories.ts +4 -0
  33. package/app/components/01.atoms/animations/view-timeline/stories/WipeAwayVertical.stories.ts +2 -0
  34. package/app/components/02.molecules/cookie-consent-banner/stories/CookieConsentBanner.stories.ts +2 -0
  35. package/app/components/02.molecules/input-copy/stories/InputCopy.stories.ts +0 -1
  36. package/app/components/02.molecules/pricing-card/CONSUMER-STYLING.md +96 -149
  37. package/app/components/02.molecules/pricing-card/PricingCard.vue +84 -44
  38. package/app/components/02.molecules/pricing-card/stories/PricingCard.stories.ts +241 -104
  39. package/app/components/02.molecules/pricing-card/tests/PricingCard.spec.ts +149 -123
  40. package/app/components/02.molecules/toast/stories/DisplayToast.stories.ts +4 -0
  41. package/app/components/02.molecules/toast/stories/DisplayToastProvider.stories.ts +4 -0
  42. package/app/components/05.forms/toggle-switch/ToggleSwitchWithLabel.vue +1 -1
  43. package/app/composables/useAriaDescribedById.ts +1 -1
  44. package/package.json +2 -1
@@ -5,196 +5,143 @@
5
5
  All `--pricing-card-*` tokens are the stable override surface. Set them at any scope (global, page,
6
6
  or instance) without touching the component itself.
7
7
 
8
- Note: the price amount defaults to a `$` prefix. Pass `currency-symbol="£"` (or any other symbol) to
9
- override it — there is no locale-aware formatting, just a literal prefix character.
8
+ > Changed 2026-10-14: colour defaults now come from the `--theme-*` slots (card background
9
+ > `--theme-surface-subtle`, text `--theme-text`, badge `--theme-surface`/`--theme-on-surface`,
10
+ > highlight border `--theme-border`, checkmark `--theme-accent`) instead of fixed `--slate-*`,
11
+ > `--teal-*` and hex values. The badge (`0.75rem` → `1.2rem`) and billing period (`0.9rem` →
12
+ > `1.4rem`) font sizes were too small at this library's 10px rem base and were raised. The five
13
+ > `--pricing-card-cta-*` colour/shape tokens were removed: their rule used `:deep()` in an unscoped
14
+ > style block, which browsers drop, so they never applied. Style the CTA with `InputButton`'s own
15
+ > tokens instead (see **CTA button** below).
16
+
17
+ ### Card
10
18
 
11
19
  | Token | Default | Controls |
12
20
  |---|---|---|
21
+ | `--pricing-card-background` | `var(--theme-surface-subtle)` | Card background colour |
13
22
  | `--pricing-card-border` | `1px solid var(--slate-03)` | Card border |
14
23
  | `--pricing-card-border-radius` | `0.8rem` | Corner rounding |
15
24
  | `--pricing-card-padding` | `2rem` | Internal padding |
16
- | `--pricing-card-background` | `var(--slate-00)` | Card background colour |
17
25
  | `--pricing-card-shadow` | `0 2px 8px oklch(...)` | Box shadow |
18
- | `--pricing-card-gap` | `1.2rem` | Gap between elements (name, price, features, CTA) |
19
- | `--pricing-card-highlight-border` | `2px solid var(--teal-06)` | Border when `isHighlighted` |
20
- | `--pricing-card-highlight-shadow` | `0 8px 24px oklch(...)` | Shadow when highlighted |
21
- | `--pricing-card-highlight-scale` | `1.05` | Scale transform when highlighted |
22
- | `--pricing-card-badge-bg` | `var(--teal-06)` | "Most Popular" badge background |
23
- | `--pricing-card-badge-text` | `var(--teal-00)` | Badge text colour |
24
- | `--pricing-card-ribbon-bg` | `var(--red-06)` | Diagonal corner ribbon background (rendered when `ribbon-text` is set) |
25
- | `--pricing-card-ribbon-text` | `var(--red-00, white)` | Ribbon text colour |
26
- | `--pricing-card-ribbon-size` | `20rem` | Width/height of the ribbon's clipping box — controls how far it extends from the top-right corner |
27
- | `--pricing-card-name-font-size` | `1.8rem` | Plan name heading size |
28
- | `--pricing-card-name-color` | `#1a1a1a` | Plan name text colour |
29
- | `--pricing-card-amount-font-size` | `3.2rem` | Price amount size |
30
- | `--pricing-card-amount-color` | `#1a1a1a` | Price text colour |
31
- | `--pricing-card-period-font-size` | `0.9rem` | Billing period ("one-time", "per month") size |
32
- | `--pricing-card-period-color` | `#666` | Billing period text colour |
33
- | `--pricing-card-description-color` | `#555` | Plan description text colour |
34
- | `--pricing-card-feature-color` | `#333` | Feature list text colour |
35
- | `--pricing-card-cta-align` | `flex-start` | CTA wrapper `align-self` — set to `stretch`, `center`, or `flex-end` to change CTA alignment/width within the card |
36
- | `--pricing-card-cta-bg` | `var(--input-button-primary-surface)` | CTA button background |
37
- | `--pricing-card-cta-bg-hover` | `var(--input-button-primary-surface-hover)` | CTA button background on hover |
38
- | `--pricing-card-cta-text` | `var(--input-button-primary-text)` | CTA button text colour |
39
- | `--pricing-card-cta-padding` | `1rem 1.6rem` | CTA button padding |
40
- | `--pricing-card-cta-border-radius` | `0.4rem` | CTA button corner rounding |
26
+ | `--pricing-card-gap` | `1.2rem` | Gap between name, price, description, features and CTA |
27
+ | `--pricing-card-transition-duration` | `300ms` | Transform/shadow/border transition (skipped under `prefers-reduced-motion: reduce`) |
41
28
 
42
- ---
29
+ ### Highlight and badge (`is-highlighted`)
43
30
 
44
- ## Global theming
31
+ | Token | Default | Controls |
32
+ |---|---|---|
33
+ | `--pricing-card-highlight-border` | `2px solid var(--theme-border)` | Border when highlighted |
34
+ | `--pricing-card-highlight-shadow` | `0 8px 24px oklch(...)` | Shadow when highlighted |
35
+ | `--pricing-card-highlight-scale` | `1.05` | Scale transform when highlighted; set `1` in a single-column layout so it doesn't overhang |
36
+ | `--pricing-card-badge-bg` | `var(--theme-surface)` | Badge background |
37
+ | `--pricing-card-badge-text` | `var(--theme-on-surface)` | Badge text colour |
38
+ | `--pricing-card-badge-font-size` | `1.2rem` | Badge text size |
45
39
 
46
- Create `assets/styles/setup/07.components/pricing-card.css` in the consuming app and set tokens on
47
- `:root`. These values apply to every `PricingCard` instance across the site.
40
+ ### Ribbon (`ribbon-text`)
48
41
 
49
- ```css
50
- /* assets/styles/setup/07.components/pricing-card.css */
51
- :root {
52
- --pricing-card-border-radius: 1rem;
53
- --pricing-card-padding: 2.4rem;
54
- --pricing-card-shadow: 0 4px 12px rgb(0 0 0 / 10%);
55
- --pricing-card-gap: 1.6rem;
56
- --pricing-card-badge-bg: var(--brand-primary);
57
- --pricing-card-highlight-border: 2px solid var(--brand-primary);
58
- --pricing-card-highlight-shadow: 0 12px 32px rgb(0 0 0 / 15%);
59
- --pricing-card-cta-border-radius: 0.6rem;
60
- }
61
- ```
42
+ | Token | Default | Controls |
43
+ |---|---|---|
44
+ | `--pricing-card-ribbon-bg` | `var(--red-06)` | Ribbon background |
45
+ | `--pricing-card-ribbon-text` | `var(--red-00, white)` | Ribbon text colour |
46
+ | `--pricing-card-ribbon-size` | `20rem` | Width/height of the ribbon's clipping box: how far it reaches from the top-right corner |
47
+ | `--pricing-card-ribbon-line-clamp` | `2` | Max ribbon lines, with an ellipsis on the last. `1` is single-line ellipsis |
48
+ | `--pricing-card-ribbon-clearance` | `6rem` | Right padding on the plan name while a ribbon is shown, so the name doesn't run under it |
62
49
 
63
- ---
50
+ ### Text
64
51
 
65
- ## Local overrides
52
+ | Token | Default | Controls |
53
+ |---|---|---|
54
+ | `--pricing-card-name-font-size` | `1.8rem` | Plan name size |
55
+ | `--pricing-card-name-color` | `var(--theme-text)` | Plan name colour |
56
+ | `--pricing-card-name-line-clamp` | `none` | Max plan name lines. `1` is single-line ellipsis, `none` shows everything |
57
+ | `--pricing-card-amount-font-size` | `3.2rem` | Price size |
58
+ | `--pricing-card-amount-color` | `var(--theme-text)` | Price colour |
59
+ | `--pricing-card-period-font-size` | `1.4rem` | Billing period size |
60
+ | `--pricing-card-period-color` | `var(--theme-text)` | Billing period colour |
61
+ | `--pricing-card-description-color` | `var(--theme-text)` | Description colour |
62
+ | `--pricing-card-description-line-clamp` | `none` | Max description lines |
63
+ | `--pricing-card-feature-color` | `var(--theme-text)` | Feature text colour |
64
+ | `--pricing-card-feature-icon-color` | `var(--theme-accent)` | Feature checkmark colour |
65
+ | `--pricing-card-feature-line-clamp` | `none` | Max lines per feature (built-in feature list only; `#features` slot content is your own markup) |
66
+
67
+ ### CTA
66
68
 
67
- Set the tokens above on an element you own (a page or section class, or a class added with
68
- `:style-class-passthrough`): they inherit down into the component. Keep the block **unlayered** (no
69
- `@layer` wrapper) so it beats the library's `@layer components`. If your own file uses
70
- `<style scoped>`, tokens set on your element still work, but selectors that reach inside the component need
71
- `:deep()`. Patterns and examples: `.claude/skills/component-local-style-override.md`.
69
+ | Token | Default | Controls |
70
+ |---|---|---|
71
+ | `--pricing-card-cta-align` | `flex-start` | CTA wrapper `align-self`: `stretch`, `center` or `flex-end` |
72
72
 
73
- ### One instance
73
+ Private tokens (not public API): `--_ribbon-size`, the resolved `--pricing-card-ribbon-size` read
74
+ by both the clip box and the ribbon.
74
75
 
75
- Override tokens directly on a single card instance:
76
+ ---
76
77
 
77
- ```vue
78
- <PricingCard
79
- planName="Enterprise"
80
- :price="999"
81
- style="
82
- --pricing-card-border-radius: 1.2rem;
83
- --pricing-card-highlight-shadow: 0 16px 48px rgb(0 0 0 / 20%);
84
- "
85
- @select="handleSelect"
86
- />
87
- ```
78
+ ## State hooks
88
79
 
89
- ### Page or section
80
+ | Hook | Meaning |
81
+ |---|---|
82
+ | `.is-highlighted` | `is-highlighted` is set: badge shown, highlight border/shadow/scale applied |
83
+ | `.has-ribbon` | `ribbon-text` is set: the plan name gets `--pricing-card-ribbon-clearance` |
90
84
 
91
- When pricing cards appear in a specific page context, scope overrides under the page wrapper:
92
-
93
- ```css
94
- /* In the consuming page's unscoped <style> block */
95
- .pricing-page {
96
- .pricing-card {
97
- --pricing-card-border-radius: 1.2rem;
98
- --pricing-card-padding: 2.8rem;
99
- --pricing-card-gap: 1.8rem;
100
-
101
- &.is-highlighted {
102
- --pricing-card-highlight-scale: 1.08;
103
- --pricing-card-highlight-shadow: 0 20px 48px rgb(0 0 0 / 18%);
104
- }
105
- }
106
- }
107
- ```
85
+ Inner elements: `.pricing-card__badge`, `.pricing-card__ribbon-clip`, `.pricing-card__ribbon`,
86
+ `.pricing-card__name`, `.pricing-card__price`, `.pricing-card__amount`, `.pricing-card__period`,
87
+ `.pricing-card__description`, `.pricing-card__features`, `.pricing-card__feature`,
88
+ `.pricing-card__cta`.
108
89
 
109
90
  ---
110
91
 
111
- ## Three-card comparison grid
112
-
113
- When displaying multiple cards side-by-side (common in pricing pages), wrap in a grid container:
92
+ ## CTA button
114
93
 
115
- ```vue
116
- <div class="pricing-grid">
117
- <PricingCard v-for="plan in plans" :key="plan.id" v-bind="plan" @select="handleSelect" />
118
- </div>
119
- ```
94
+ The default CTA is a primary `InputButton`. Its tokens inherit, so set them on the card (or any
95
+ ancestor) to restyle it:
120
96
 
121
97
  ```css
122
- .pricing-grid {
123
- display: grid;
124
- grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
125
- gap: 2rem;
126
-
127
- .pricing-card {
128
- /* Optional per-card overrides */
129
- }
130
-
131
- /* Alternate: fixed 3-column layout */
132
- &.three-col {
133
- grid-template-columns: repeat(3, 1fr);
134
- }
98
+ .pricing-page .pricing-card {
99
+ --input-button-primary-surface: var(--brand-primary);
100
+ --input-button-primary-surface-hover: var(--brand-primary-dark);
101
+ --input-button-primary-text: white;
135
102
  }
136
103
  ```
137
104
 
138
- ---
139
-
140
- ## Highlight badge text
141
-
142
- `isHighlighted` shows a top-centred badge reading "Most Popular". Pass `badge-text` to change it,
143
- e.g. for localisation:
144
-
145
- ```vue
146
- <PricingCard planName="Standard" :price="99" is-highlighted :badge-text="t('pricing.mostPopular')" />
147
- ```
105
+ Shape (`--button-border-radius`, `--button-padding-inline`, `--button-padding-block`) comes from the
106
+ global button tokens and can be set the same way. For a different variant or extra controls, replace
107
+ the button with the `#cta` slot.
148
108
 
149
109
  ---
150
110
 
151
- ## Discount ribbon
152
-
153
- Pass `ribbon-text` to render a diagonal corner ribbon (top-right, 45°) for marketing callouts —
154
- leave it unset for no ribbon. It renders independently of `isHighlighted`, so both can be combined.
155
-
156
- ```vue
157
- <PricingCard
158
- planName="Annual"
159
- :price="49"
160
- billing-period="per year"
161
- ribbon-text="EARLYBIRD50 · 50% off"
162
- @select="handleSelect"
163
- />
164
- ```
111
+ ## Global theming
165
112
 
166
113
  ```css
167
- .pricing-page {
168
- .pricing-card {
169
- --pricing-card-ribbon-bg: var(--brand-accent);
170
- --pricing-card-ribbon-text: white;
171
- }
114
+ :where(html) {
115
+ --pricing-card-border-radius: 1.2rem;
116
+ --pricing-card-padding: 2.4rem;
117
+ --pricing-card-badge-bg: var(--brand-primary);
118
+ --pricing-card-highlight-border: 2px solid var(--brand-primary);
172
119
  }
173
120
  ```
174
121
 
175
122
  ---
176
123
 
177
- ## Feature checkmark colour
124
+ ## Local overrides
178
125
 
179
- The feature list checkmarks use `--teal-06` by default. Override globally or per-instance:
126
+ Set the tokens above on an element you own (a page or section class, or a class added with
127
+ `:style-class-passthrough`): they inherit down into the component. Keep the block **unlayered** (no
128
+ `@layer` wrapper) so it beats the library's `@layer components`. If your own file uses
129
+ `<style scoped>`, tokens set on your element still work, but selectors that reach inside the component need
130
+ `:deep()`. Patterns and examples: `.claude/skills/component-local-style-override.md`.
180
131
 
181
- ```css
182
- .pricing-page {
183
- .pricing-card__feature::before {
184
- color: var(--brand-primary);
185
- }
186
- }
187
- ```
132
+ ---
188
133
 
189
- Or inline:
134
+ ## Class passthrough
190
135
 
191
- ```vue
192
- <PricingCard
193
- v-bind="plan"
194
- style="--_feature-checkmark-color: var(--brand-accent);"
195
- @select="handleSelect"
196
- />
197
- ```
136
+ `style-class-passthrough` adds classes to the card root (the `article`/`section`/`div`), so a class
137
+ passed there is a direct hook for both tokens and selectors.
138
+
139
+ ---
198
140
 
199
- (Note: `--_feature-checkmark-color` is an internal token; consider requesting it as a public token if override is needed frequently.)
141
+ ## Notes
200
142
 
143
+ - The price is `currency-symbol` + the number, with fractional prices shown to two decimals
144
+ (`9.5` → `9.50`). There is no locale-aware formatting.
145
+ - The highlight scale is a `transform`, so it doesn't change layout size; in a stacked
146
+ single-column layout the scaled card can overhang its column. Set
147
+ `--pricing-card-highlight-scale: 1` there.
@@ -1,15 +1,20 @@
1
1
  <template>
2
- <component :is="tag" class="pricing-card" :class="[elementClasses, { 'is-highlighted': isHighlighted }]">
2
+ <component
3
+ :is="tag"
4
+ class="pricing-card"
5
+ :class="[elementClasses, { 'is-highlighted': isHighlighted, 'has-ribbon': Boolean(ribbonText) }]"
6
+ :aria-labelledby="headingId"
7
+ >
3
8
  <div v-if="isHighlighted" class="pricing-card__badge">{{ badgeText }}</div>
4
9
 
5
10
  <div v-if="ribbonText" class="pricing-card__ribbon-clip">
6
11
  <span class="pricing-card__ribbon">{{ ribbonText }}</span>
7
12
  </div>
8
13
 
9
- <h3 class="pricing-card__name">{{ planName }}</h3>
14
+ <component :is="headingTag" :id="headingId" class="pricing-card__name">{{ planName }}</component>
10
15
 
11
16
  <div class="pricing-card__price">
12
- <span class="pricing-card__amount">{{ currencySymbol }}{{ price }}</span>
17
+ <span class="pricing-card__amount">{{ currencySymbol }}{{ displayPrice }}</span>
13
18
  <span v-if="billingPeriod" class="pricing-card__period">{{ billingPeriod }}</span>
14
19
  </div>
15
20
 
@@ -34,6 +39,7 @@
34
39
  <script setup lang="ts">
35
40
  interface Props {
36
41
  tag?: "div" | "section" | "article";
42
+ headingTag?: "h2" | "h3" | "h4" | "h5" | "h6";
37
43
  planName: string;
38
44
  price: number;
39
45
  currencySymbol?: string;
@@ -50,6 +56,7 @@ interface Props {
50
56
 
51
57
  const props = withDefaults(defineProps<Props>(), {
52
58
  tag: "article",
59
+ headingTag: "h3",
53
60
  currencySymbol: "$",
54
61
  billingPeriod: "one-time",
55
62
  description: undefined,
@@ -66,6 +73,13 @@ const emit = defineEmits<{
66
73
  select: [planName: string];
67
74
  }>();
68
75
 
76
+ const headingId = useId();
77
+
78
+ const displayPrice = computed(() => {
79
+ if (!Number.isFinite(props.price)) return "";
80
+ return Number.isInteger(props.price) ? String(props.price) : props.price.toFixed(2);
81
+ });
82
+
69
83
  const { elementClasses, resetElementClasses } = useStyleClassPassthrough(props.styleClassPassthrough);
70
84
 
71
85
  const handleSelect = () => {
@@ -85,48 +99,52 @@ watch(
85
99
  .pricing-card {
86
100
  --_ribbon-size: var(--pricing-card-ribbon-size, 20rem);
87
101
 
88
- --_cta-bg-hover: var(
89
- --pricing-card-cta-bg-hover,
90
- var(--input-button-primary-surface-hover, var(--theme-surface-hover))
91
- );
92
-
93
102
  display: flex;
94
103
  flex-direction: column;
95
104
  gap: var(--pricing-card-gap, 1.2rem);
96
105
  position: relative;
106
+ min-inline-size: 0;
97
107
 
98
108
  padding: var(--pricing-card-padding, 2rem);
99
- background-color: var(--pricing-card-background, var(--slate-00));
109
+ background-color: var(--pricing-card-background, var(--theme-surface-subtle));
100
110
  border: var(--pricing-card-border, 1px solid var(--slate-03));
101
111
  border-radius: var(--pricing-card-border-radius, 0.8rem);
102
112
  box-shadow: var(--pricing-card-shadow, 0 2px 8px oklch(from var(--slate-08) l c h / 0.12));
103
113
 
104
- transition: all 0.3s ease;
114
+ @media (prefers-reduced-motion: no-preference) {
115
+ transition-property: transform, box-shadow, border-color;
116
+ transition-duration: var(--pricing-card-transition-duration, 300ms);
117
+ transition-timing-function: ease;
118
+ }
105
119
 
106
120
  &.is-highlighted {
107
- border: var(--pricing-card-highlight-border, 2px solid var(--teal-06));
108
- box-shadow: var(--pricing-card-highlight-shadow, 0 8px 24px oklch(from var(--teal-06) l c h / 0.2));
121
+ border: var(--pricing-card-highlight-border, 2px solid var(--theme-border));
122
+ box-shadow: var(--pricing-card-highlight-shadow, 0 8px 24px oklch(from var(--slate-08) l c h / 0.2));
109
123
  transform: scale(var(--pricing-card-highlight-scale, 1.05));
110
124
  }
111
125
 
112
126
  .pricing-card__badge {
113
127
  position: absolute;
114
- top: -0.6rem;
115
- left: 50%;
116
- transform: translateX(-50%);
117
- /* Above .pricing-card__ribbon-clip (z-index: 1) — without this the badge has no stacking
118
- context of its own and the ribbon, which does, paints over it regardless of DOM order. */
128
+ inset-block-start: 0;
129
+ inset-inline: 0;
130
+ translate: 0 -50%;
131
+ inline-size: fit-content;
132
+ max-inline-size: calc(100% - 2rem);
133
+ margin-inline: auto;
134
+ /* Above .pricing-card__ribbon-clip (z-index: 1), which otherwise paints over it. */
119
135
  z-index: 2;
120
136
 
121
- display: inline-block;
122
137
  padding: 0.4rem 1rem;
123
- background-color: var(--pricing-card-badge-bg, var(--teal-06));
124
- color: var(--pricing-card-badge-text, var(--teal-00));
138
+ background-color: var(--pricing-card-badge-bg, var(--theme-surface));
139
+ color: var(--pricing-card-badge-text, var(--theme-on-surface));
125
140
  border-radius: 2rem;
126
- font-size: 0.75rem;
141
+ font-size: var(--pricing-card-badge-font-size, 1.2rem);
127
142
  font-weight: 600;
143
+ text-align: center;
128
144
  text-transform: uppercase;
129
145
  letter-spacing: 0.05em;
146
+ overflow-wrap: anywhere;
147
+ text-wrap: balance;
130
148
  }
131
149
 
132
150
  .pricing-card__ribbon-clip {
@@ -137,7 +155,6 @@ watch(
137
155
  height: var(--_ribbon-size);
138
156
  overflow: hidden;
139
157
  pointer-events: none;
140
- /* Sits above card content but stays clear of the top-centred "Most Popular" badge. */
141
158
  z-index: 1;
142
159
 
143
160
  .pricing-card__ribbon {
@@ -148,7 +165,12 @@ watch(
148
165
  transform: rotate(23deg);
149
166
  transform-origin: center;
150
167
 
151
- display: block;
168
+ display: -webkit-box;
169
+ -webkit-box-orient: vertical;
170
+ overflow: hidden;
171
+ -webkit-line-clamp: var(--pricing-card-ribbon-line-clamp, 2);
172
+ line-clamp: var(--pricing-card-ribbon-line-clamp, 2);
173
+ overflow-wrap: anywhere;
152
174
  padding: 0.6rem 1.4rem 0.6rem 0;
153
175
  background-color: var(--pricing-card-ribbon-bg, var(--red-06));
154
176
  color: var(--pricing-card-ribbon-text, var(--red-00, white));
@@ -160,33 +182,55 @@ watch(
160
182
  }
161
183
  }
162
184
 
185
+ &.has-ribbon .pricing-card__name {
186
+ padding-right: var(--pricing-card-ribbon-clearance, 6rem);
187
+ }
188
+
163
189
  .pricing-card__name {
190
+ display: -webkit-box;
191
+ -webkit-box-orient: vertical;
192
+ overflow: hidden;
193
+ -webkit-line-clamp: var(--pricing-card-name-line-clamp, none);
194
+ line-clamp: var(--pricing-card-name-line-clamp, none);
195
+ overflow-wrap: anywhere;
164
196
  margin: 0;
165
197
  font-size: var(--pricing-card-name-font-size, 1.8rem);
166
198
  font-weight: 600;
167
- color: var(--pricing-card-name-color, #1a1a1a);
199
+ color: var(--pricing-card-name-color, var(--theme-text));
168
200
  }
169
201
 
170
202
  .pricing-card__price {
171
203
  display: flex;
204
+ flex-wrap: wrap;
172
205
  align-items: baseline;
173
206
  gap: 0.4rem;
207
+ min-inline-size: 0;
174
208
  }
175
209
 
176
210
  .pricing-card__amount {
211
+ min-inline-size: 0;
212
+ overflow-wrap: anywhere;
177
213
  font-size: var(--pricing-card-amount-font-size, 3.2rem);
178
214
  font-weight: 700;
179
- color: var(--pricing-card-amount-color, #1a1a1a);
215
+ color: var(--pricing-card-amount-color, var(--theme-text));
180
216
  }
181
217
 
182
218
  .pricing-card__period {
183
- font-size: var(--pricing-card-period-font-size, 0.9rem);
184
- color: var(--pricing-card-period-color, #666);
219
+ min-inline-size: 0;
220
+ overflow-wrap: anywhere;
221
+ font-size: var(--pricing-card-period-font-size, 1.4rem);
222
+ color: var(--pricing-card-period-color, var(--theme-text));
185
223
  }
186
224
 
187
225
  .pricing-card__description {
226
+ display: -webkit-box;
227
+ -webkit-box-orient: vertical;
228
+ overflow: hidden;
229
+ -webkit-line-clamp: var(--pricing-card-description-line-clamp, none);
230
+ line-clamp: var(--pricing-card-description-line-clamp, none);
231
+ overflow-wrap: anywhere;
188
232
  margin: 0;
189
- color: var(--pricing-card-description-color, #555);
233
+ color: var(--pricing-card-description-color, var(--theme-text));
190
234
  line-height: 1.5;
191
235
  }
192
236
 
@@ -202,33 +246,29 @@ watch(
202
246
  }
203
247
 
204
248
  .pricing-card__feature {
205
- color: var(--pricing-card-feature-color, #333);
206
- padding-left: 1.6rem;
249
+ display: -webkit-box;
250
+ -webkit-box-orient: vertical;
251
+ overflow: hidden;
252
+ -webkit-line-clamp: var(--pricing-card-feature-line-clamp, none);
253
+ line-clamp: var(--pricing-card-feature-line-clamp, none);
254
+ overflow-wrap: anywhere;
255
+ color: var(--pricing-card-feature-color, var(--theme-text));
256
+ padding-inline-start: 1.6rem;
207
257
  position: relative;
208
258
 
209
259
  &::before {
210
260
  content: "✓";
261
+ content: "✓" / "";
211
262
  position: absolute;
212
- left: 0;
213
- color: var(--teal-06);
263
+ inset-inline-start: 0;
264
+ color: var(--pricing-card-feature-icon-color, var(--theme-accent));
214
265
  font-weight: bold;
215
266
  }
216
267
  }
217
268
 
218
269
  .pricing-card__cta {
219
270
  align-self: var(--pricing-card-cta-align, flex-start);
220
-
221
- :deep(.input-button) {
222
- padding: var(--pricing-card-cta-padding, 1rem 1.6rem);
223
- background-color: var(--pricing-card-cta-bg, var(--input-button-primary-surface, var(--theme-surface)));
224
- color: var(--pricing-card-cta-text, var(--input-button-primary-text, var(--theme-on-surface)));
225
- border-radius: var(--pricing-card-cta-border-radius, 0.4rem);
226
- font-weight: 600;
227
-
228
- &:hover:not([aria-disabled="true"]) {
229
- background-color: var(--_cta-bg-hover);
230
- }
231
- }
271
+ max-inline-size: 100%;
232
272
  }
233
273
  }
234
274
  }