@dloizides/marketing-astro-kit 1.3.3 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +54 -53
- package/src/DemoFrame.astro +268 -264
- package/src/PricingComparison.astro +433 -428
- package/src/PrimaryCta.astro +146 -138
- package/src/TrustSection.astro +524 -516
- package/src/a11y.css +22 -0
package/src/PrimaryCta.astro
CHANGED
|
@@ -1,138 +1,146 @@
|
|
|
1
|
-
---
|
|
2
|
-
// Reusable primary call-to-action for product marketing sites — renders a
|
|
3
|
-
// `MarketingCta` (link OR spam-safe contact) as a themeable button, with an
|
|
4
|
-
// optional secondary CTA beside it. One component for every "primary action"
|
|
5
|
-
// on a site (hero, closing band …) so the link-vs-lead-capture + analytics
|
|
6
|
-
// wiring is never hand-coded per page.
|
|
7
|
-
//
|
|
8
|
-
// <PrimaryCta cta={primary} secondary={secondary} />
|
|
9
|
-
//
|
|
10
|
-
// Pair it with `buildPrimaryCta` (schema.ts): the site holds ONE `SIGNUP_URL`
|
|
11
|
-
// constant and the CTA auto-switches between a real signup link (app is live)
|
|
12
|
-
// and lead-capture contact (app not public yet) — no template change.
|
|
13
|
-
//
|
|
14
|
-
// Zero runtime JS of its own: `kind: 'contact'` CTAs render the same
|
|
15
|
-
// `[data-contact]`/`data-local`/`data-domain` pattern the host page's existing
|
|
16
|
-
// contact-reveal handler assembles on click. Themed via host CSS custom props.
|
|
17
|
-
import type { MarketingCta } from './schema';
|
|
18
|
-
import
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
secondary
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
--_brand
|
|
64
|
-
--
|
|
65
|
-
--
|
|
66
|
-
--
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
99
|
-
.tmk-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
.tmk-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
1
|
+
---
|
|
2
|
+
// Reusable primary call-to-action for product marketing sites — renders a
|
|
3
|
+
// `MarketingCta` (link OR spam-safe contact) as a themeable button, with an
|
|
4
|
+
// optional secondary CTA beside it. One component for every "primary action"
|
|
5
|
+
// on a site (hero, closing band …) so the link-vs-lead-capture + analytics
|
|
6
|
+
// wiring is never hand-coded per page.
|
|
7
|
+
//
|
|
8
|
+
// <PrimaryCta cta={primary} secondary={secondary} />
|
|
9
|
+
//
|
|
10
|
+
// Pair it with `buildPrimaryCta` (schema.ts): the site holds ONE `SIGNUP_URL`
|
|
11
|
+
// constant and the CTA auto-switches between a real signup link (app is live)
|
|
12
|
+
// and lead-capture contact (app not public yet) — no template change.
|
|
13
|
+
//
|
|
14
|
+
// Zero runtime JS of its own: `kind: 'contact'` CTAs render the same
|
|
15
|
+
// `[data-contact]`/`data-local`/`data-domain` pattern the host page's existing
|
|
16
|
+
// contact-reveal handler assembles on click. Themed via host CSS custom props.
|
|
17
|
+
import type { MarketingCta } from './schema';
|
|
18
|
+
import './a11y.css';
|
|
19
|
+
import { ctaAttrs } from './schema';
|
|
20
|
+
|
|
21
|
+
interface Props {
|
|
22
|
+
/** Primary CTA (link or contact). */
|
|
23
|
+
cta: MarketingCta;
|
|
24
|
+
/** Optional secondary CTA rendered beside the primary. */
|
|
25
|
+
secondary?: MarketingCta;
|
|
26
|
+
/** Button size. */
|
|
27
|
+
size?: 'md' | 'lg';
|
|
28
|
+
/** Horizontal alignment of the button group. */
|
|
29
|
+
align?: 'start' | 'center';
|
|
30
|
+
/** Set 'dark' when the CTA sits on a dark band so the ghost secondary stays
|
|
31
|
+
* legible (light text + translucent border). Defaults to 'light'. */
|
|
32
|
+
tone?: 'light' | 'dark';
|
|
33
|
+
id?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const {
|
|
37
|
+
cta,
|
|
38
|
+
secondary,
|
|
39
|
+
size = 'lg',
|
|
40
|
+
align = 'start',
|
|
41
|
+
tone = 'light',
|
|
42
|
+
id,
|
|
43
|
+
} = Astro.props as Props;
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
<div
|
|
47
|
+
class={`tmk-cta-group tmk-cta-group--${align} tmk-cta-group--${tone}`}
|
|
48
|
+
id={id}
|
|
49
|
+
data-reveal
|
|
50
|
+
>
|
|
51
|
+
<a class={`tmk-btn tmk-btn--primary tmk-btn--${size}`} {...ctaAttrs(cta)}>
|
|
52
|
+
{cta.label} <span class="tmk-btn__arrow" aria-hidden="true">→</span>
|
|
53
|
+
</a>
|
|
54
|
+
{secondary && (
|
|
55
|
+
<a class={`tmk-btn tmk-btn--ghost tmk-btn--${size}`} {...ctaAttrs(secondary)}>
|
|
56
|
+
{secondary.label}
|
|
57
|
+
</a>
|
|
58
|
+
)}
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
<style>
|
|
62
|
+
.tmk-cta-group {
|
|
63
|
+
--_brand: var(--brand, #d8332a);
|
|
64
|
+
--_brand-deep: var(--brand-deep, #a8231c);
|
|
65
|
+
--_ink: var(--ink, #0a0a1f);
|
|
66
|
+
--_paper: var(--paper, #fff);
|
|
67
|
+
--_hairline: var(--hairline, rgba(10, 10, 31, 0.1));
|
|
68
|
+
display: flex;
|
|
69
|
+
flex-wrap: wrap;
|
|
70
|
+
gap: 12px;
|
|
71
|
+
}
|
|
72
|
+
.tmk-cta-group--center {
|
|
73
|
+
justify-content: center;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.tmk-btn {
|
|
77
|
+
box-sizing: border-box;
|
|
78
|
+
min-height: var(--tmk-min-target);
|
|
79
|
+
min-width: var(--tmk-min-target);
|
|
80
|
+
display: inline-flex;
|
|
81
|
+
align-items: center;
|
|
82
|
+
justify-content: center;
|
|
83
|
+
gap: 8px;
|
|
84
|
+
padding: 14px 22px;
|
|
85
|
+
border-radius: 999px;
|
|
86
|
+
font-family: 'Inter', system-ui, sans-serif;
|
|
87
|
+
font-weight: 600;
|
|
88
|
+
font-size: 15px;
|
|
89
|
+
letter-spacing: 0.01em;
|
|
90
|
+
border: 1px solid transparent;
|
|
91
|
+
cursor: pointer;
|
|
92
|
+
text-decoration: none;
|
|
93
|
+
transition: transform 0.12s ease, background 0.12s ease, box-shadow 0.12s ease,
|
|
94
|
+
color 0.12s ease, border-color 0.12s ease;
|
|
95
|
+
}
|
|
96
|
+
.tmk-btn:hover {
|
|
97
|
+
transform: translateY(-1px);
|
|
98
|
+
}
|
|
99
|
+
.tmk-btn:focus-visible {
|
|
100
|
+
outline: 2px solid var(--_brand);
|
|
101
|
+
outline-offset: 3px;
|
|
102
|
+
}
|
|
103
|
+
.tmk-btn--lg {
|
|
104
|
+
padding: 16px 28px;
|
|
105
|
+
font-size: 16px;
|
|
106
|
+
}
|
|
107
|
+
.tmk-btn__arrow {
|
|
108
|
+
transition: transform 0.12s ease;
|
|
109
|
+
}
|
|
110
|
+
.tmk-btn:hover .tmk-btn__arrow {
|
|
111
|
+
transform: translateX(3px);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/* Primary — solid brand. */
|
|
115
|
+
.tmk-btn--primary {
|
|
116
|
+
background: var(--_brand-deep);
|
|
117
|
+
color: var(--_paper);
|
|
118
|
+
box-shadow: 0 8px 24px -10px color-mix(in srgb, var(--_brand) 70%, transparent);
|
|
119
|
+
}
|
|
120
|
+
.tmk-btn--primary:hover {
|
|
121
|
+
background: var(--_brand);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/* Ghost secondary — on light. */
|
|
125
|
+
.tmk-btn--ghost {
|
|
126
|
+
background: transparent;
|
|
127
|
+
color: var(--_ink);
|
|
128
|
+
border-color: color-mix(in srgb, var(--_ink) 18%, transparent);
|
|
129
|
+
}
|
|
130
|
+
.tmk-btn--ghost:hover {
|
|
131
|
+
background: var(--_ink);
|
|
132
|
+
color: var(--_paper);
|
|
133
|
+
border-color: var(--_ink);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/* Ghost secondary — on a dark band. */
|
|
137
|
+
.tmk-cta-group--dark .tmk-btn--ghost {
|
|
138
|
+
color: var(--_paper);
|
|
139
|
+
border-color: rgba(255, 255, 255, 0.32);
|
|
140
|
+
}
|
|
141
|
+
.tmk-cta-group--dark .tmk-btn--ghost:hover {
|
|
142
|
+
background: var(--_paper);
|
|
143
|
+
color: var(--_ink);
|
|
144
|
+
border-color: var(--_paper);
|
|
145
|
+
}
|
|
146
|
+
</style>
|