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.
- package/.claude/rules/consumer/consumer-overrides.md +17 -0
- package/.claude/rules/consumer/css-comment-slash.md +13 -0
- package/.claude/rules/consumer/css-native-nesting.md +13 -0
- package/.claude/rules/consumer/css-no-deep.md +13 -0
- package/.claude/rules/consumer/css-rem-base.md +11 -0
- package/.claude/rules/consumer/css-surface-text-pairing.md +13 -0
- package/.claude/rules/consumer/css-webkit-support.md +15 -0
- package/.claude/rules/consumer/explicit-vue-imports.md +11 -0
- package/.claude/rules/consumer/nuxt-img-dimensions.md +12 -0
- package/.claude/rules/consumer/srcdev-skills-location.md +9 -0
- package/.claude/rules/consumer/story-args.md +12 -0
- package/.claude/rules/consumer/story-register-components.md +12 -0
- package/.claude/rules/consumer/vue-auto-imports.md +14 -0
- package/.claude/rules/consumer/vue-no-self-closing.md +11 -0
- package/.claude/rules/consumer/vue-prop-hyphenation.md +11 -0
- package/.claude/rules/consumer/vue-props-pattern.md +13 -0
- package/.claude/rules/consumer/vue-slots-no-computed.md +14 -0
- package/.claude/rules/consumer/vue-video-source.md +12 -0
- package/.claude/skills/component-compliance-checklist.md +23 -1
- package/.claude/skills/component-naming.md +1 -1
- package/.claude/skills/components/pricing-card.md +68 -84
- package/.claude/skills/index.md +9 -3
- package/.claude/skills/new-app-scaffold.md +4 -2
- package/.claude/skills/setup-postinstall.md +5 -4
- package/.claude/skills/storybook-add-story.md +36 -0
- package/.claude/skills/theming-component-token-pattern.md +46 -0
- package/.claude/skills/theming-partial-override.md +1 -1
- package/.vscode/srcdev-component-pricing-card.code-snippets +46 -84
- package/README.md +5 -5
- package/app/components/01.atoms/animations/scroll-reveal-frame/stories/ScrollRevealFrame.stories.ts +2 -0
- package/app/components/01.atoms/animations/scroll-reveal-image/stories/ScrollRevealImage.stories.ts +2 -0
- package/app/components/01.atoms/animations/section-parallax/stories/SectionParallax.stories.ts +4 -0
- package/app/components/01.atoms/animations/view-timeline/stories/WipeAwayVertical.stories.ts +2 -0
- package/app/components/02.molecules/cookie-consent-banner/stories/CookieConsentBanner.stories.ts +2 -0
- package/app/components/02.molecules/input-copy/stories/InputCopy.stories.ts +0 -1
- package/app/components/02.molecules/pricing-card/CONSUMER-STYLING.md +96 -149
- package/app/components/02.molecules/pricing-card/PricingCard.vue +84 -44
- package/app/components/02.molecules/pricing-card/stories/PricingCard.stories.ts +241 -104
- package/app/components/02.molecules/pricing-card/tests/PricingCard.spec.ts +149 -123
- package/app/components/02.molecules/toast/stories/DisplayToast.stories.ts +4 -0
- package/app/components/02.molecules/toast/stories/DisplayToastProvider.stories.ts +4 -0
- package/app/components/05.forms/toggle-switch/ToggleSwitchWithLabel.vue +1 -1
- package/app/composables/useAriaDescribedById.ts +1 -1
- 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
|
-
|
|
9
|
-
|
|
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
|
|
19
|
-
| `--pricing-card-
|
|
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
|
-
|
|
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
|
-
|
|
47
|
-
`:root`. These values apply to every `PricingCard` instance across the site.
|
|
40
|
+
### Ribbon (`ribbon-text`)
|
|
48
41
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
|
|
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
|
-
|
|
76
|
+
---
|
|
76
77
|
|
|
77
|
-
|
|
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
|
-
|
|
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
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
##
|
|
112
|
-
|
|
113
|
-
When displaying multiple cards side-by-side (common in pricing pages), wrap in a grid container:
|
|
92
|
+
## CTA button
|
|
114
93
|
|
|
115
|
-
|
|
116
|
-
|
|
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-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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
|
-
##
|
|
124
|
+
## Local overrides
|
|
178
125
|
|
|
179
|
-
|
|
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
|
-
|
|
182
|
-
.pricing-page {
|
|
183
|
-
.pricing-card__feature::before {
|
|
184
|
-
color: var(--brand-primary);
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
```
|
|
132
|
+
---
|
|
188
133
|
|
|
189
|
-
|
|
134
|
+
## Class passthrough
|
|
190
135
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
<
|
|
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 }}{{
|
|
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(--
|
|
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
|
-
|
|
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(--
|
|
108
|
-
box-shadow: var(--pricing-card-highlight-shadow, 0 8px 24px oklch(from var(--
|
|
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
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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(--
|
|
124
|
-
color: var(--pricing-card-badge-text, var(--
|
|
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:
|
|
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:
|
|
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,
|
|
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,
|
|
215
|
+
color: var(--pricing-card-amount-color, var(--theme-text));
|
|
180
216
|
}
|
|
181
217
|
|
|
182
218
|
.pricing-card__period {
|
|
183
|
-
|
|
184
|
-
|
|
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,
|
|
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
|
-
|
|
206
|
-
|
|
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
|
-
|
|
213
|
-
color: var(--
|
|
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
|
}
|