@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.
@@ -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 { ctaAttrs } from './schema';
19
-
20
- interface Props {
21
- /** Primary CTA (link or contact). */
22
- cta: MarketingCta;
23
- /** Optional secondary CTA rendered beside the primary. */
24
- secondary?: MarketingCta;
25
- /** Button size. */
26
- size?: 'md' | 'lg';
27
- /** Horizontal alignment of the button group. */
28
- align?: 'start' | 'center';
29
- /** Set 'dark' when the CTA sits on a dark band so the ghost secondary stays
30
- * legible (light text + translucent border). Defaults to 'light'. */
31
- tone?: 'light' | 'dark';
32
- id?: string;
33
- }
34
-
35
- const {
36
- cta,
37
- secondary,
38
- size = 'lg',
39
- align = 'start',
40
- tone = 'light',
41
- id,
42
- } = Astro.props as Props;
43
- ---
44
-
45
- <div
46
- class={`tmk-cta-group tmk-cta-group--${align} tmk-cta-group--${tone}`}
47
- id={id}
48
- data-reveal
49
- >
50
- <a class={`tmk-btn tmk-btn--primary tmk-btn--${size}`} {...ctaAttrs(cta)}>
51
- {cta.label} <span class="tmk-btn__arrow" aria-hidden="true">→</span>
52
- </a>
53
- {secondary && (
54
- <a class={`tmk-btn tmk-btn--ghost tmk-btn--${size}`} {...ctaAttrs(secondary)}>
55
- {secondary.label}
56
- </a>
57
- )}
58
- </div>
59
-
60
- <style>
61
- .tmk-cta-group {
62
- --_brand: var(--brand, #d8332a);
63
- --_brand-deep: var(--brand-deep, #a8231c);
64
- --_ink: var(--ink, #0a0a1f);
65
- --_paper: var(--paper, #fff);
66
- --_hairline: var(--hairline, rgba(10, 10, 31, 0.1));
67
- display: flex;
68
- flex-wrap: wrap;
69
- gap: 12px;
70
- }
71
- .tmk-cta-group--center {
72
- justify-content: center;
73
- }
74
-
75
- .tmk-btn {
76
- display: inline-flex;
77
- align-items: center;
78
- justify-content: center;
79
- gap: 8px;
80
- padding: 14px 22px;
81
- border-radius: 999px;
82
- font-family: 'Inter', system-ui, sans-serif;
83
- font-weight: 600;
84
- font-size: 15px;
85
- letter-spacing: 0.01em;
86
- border: 1px solid transparent;
87
- cursor: pointer;
88
- text-decoration: none;
89
- transition: transform 0.12s ease, background 0.12s ease, box-shadow 0.12s ease,
90
- color 0.12s ease, border-color 0.12s ease;
91
- }
92
- .tmk-btn:hover {
93
- transform: translateY(-1px);
94
- }
95
- .tmk-btn--lg {
96
- padding: 16px 28px;
97
- font-size: 16px;
98
- }
99
- .tmk-btn__arrow {
100
- transition: transform 0.12s ease;
101
- }
102
- .tmk-btn:hover .tmk-btn__arrow {
103
- transform: translateX(3px);
104
- }
105
-
106
- /* Primary — solid brand. */
107
- .tmk-btn--primary {
108
- background: var(--_brand-deep);
109
- color: var(--_paper);
110
- box-shadow: 0 8px 24px -10px color-mix(in srgb, var(--_brand) 70%, transparent);
111
- }
112
- .tmk-btn--primary:hover {
113
- background: var(--_brand);
114
- }
115
-
116
- /* Ghost secondary — on light. */
117
- .tmk-btn--ghost {
118
- background: transparent;
119
- color: var(--_ink);
120
- border-color: color-mix(in srgb, var(--_ink) 18%, transparent);
121
- }
122
- .tmk-btn--ghost:hover {
123
- background: var(--_ink);
124
- color: var(--_paper);
125
- border-color: var(--_ink);
126
- }
127
-
128
- /* Ghost secondary — on a dark band. */
129
- .tmk-cta-group--dark .tmk-btn--ghost {
130
- color: var(--_paper);
131
- border-color: rgba(255, 255, 255, 0.32);
132
- }
133
- .tmk-cta-group--dark .tmk-btn--ghost:hover {
134
- background: var(--_paper);
135
- color: var(--_ink);
136
- border-color: var(--_paper);
137
- }
138
- </style>
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>