@lukoweb/apitogo-module-landing 0.1.59 → 0.1.61
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 +2 -2
- package/src/LandingFooterSlot.tsx +1 -6
- package/src/LandingShell.tsx +6 -14
- package/src/landing.css +1 -256
- package/src/sections/ApiShowcaseSection.tsx +22 -16
- package/src/sections/CodeExampleCard.tsx +11 -11
- package/src/sections/FeaturesSection.tsx +12 -7
- package/src/sections/FooterSection.tsx +9 -11
- package/src/sections/HeroSection.tsx +13 -16
- package/src/sections/LandingButtons.tsx +8 -9
- package/src/sections/PricingCtaSection.tsx +2 -2
- package/src/sections/TrustSection.tsx +5 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lukoweb/apitogo-module-landing",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.61",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./src/index.tsx"
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"devDependencies": {
|
|
15
15
|
"@types/react": "19.2.14",
|
|
16
16
|
"@types/react-dom": "19.2.3",
|
|
17
|
-
"@lukoweb/apitogo": "0.1.
|
|
17
|
+
"@lukoweb/apitogo": "0.1.61",
|
|
18
18
|
"react": "19.2.4",
|
|
19
19
|
"react-dom": "19.2.4",
|
|
20
20
|
"typescript": "5.9.3"
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { ResolvedLandingContent } from "@lukoweb/apitogo";
|
|
2
2
|
import type { ComponentType } from "react";
|
|
3
3
|
import { FooterSection } from "./sections/FooterSection.js";
|
|
4
|
-
import "./landing.css";
|
|
5
4
|
|
|
6
5
|
export type LandingFooterSlotConfig = ResolvedLandingContent["footer"];
|
|
7
6
|
|
|
@@ -16,11 +15,7 @@ export const createLandingFooterSlot = (
|
|
|
16
15
|
return undefined;
|
|
17
16
|
}
|
|
18
17
|
|
|
19
|
-
const LandingFooterSlot = () =>
|
|
20
|
-
<div className="apitogo-landing">
|
|
21
|
-
<FooterSection footer={footer} />
|
|
22
|
-
</div>
|
|
23
|
-
);
|
|
18
|
+
const LandingFooterSlot = () => <FooterSection footer={footer} />;
|
|
24
19
|
|
|
25
20
|
return LandingFooterSlot;
|
|
26
21
|
};
|
package/src/LandingShell.tsx
CHANGED
|
@@ -30,7 +30,8 @@ export type LandingShellProps = {
|
|
|
30
30
|
description?: string;
|
|
31
31
|
/**
|
|
32
32
|
* Show the "Built with APIToGo" line. Defaults to true.
|
|
33
|
-
* Automatically hidden when a `FooterSection` is composed as a child
|
|
33
|
+
* Automatically hidden when a `FooterSection` is composed as a child or
|
|
34
|
+
* a footer slot is configured (e.g. `modules.landing.content.footer`).
|
|
34
35
|
*/
|
|
35
36
|
showPoweredBy?: boolean;
|
|
36
37
|
};
|
|
@@ -45,21 +46,12 @@ export const LandingShell = ({
|
|
|
45
46
|
const siteTitle =
|
|
46
47
|
options.metadata?.applicationName ?? options.site?.title ?? "APIToGo";
|
|
47
48
|
const pageTitle = title ?? siteTitle;
|
|
48
|
-
const hasFooter =
|
|
49
|
+
const hasFooter =
|
|
50
|
+
containsFooterSection(children) || Boolean(options.slots?.footer);
|
|
49
51
|
|
|
50
52
|
return (
|
|
51
|
-
<div className="
|
|
53
|
+
<div className="relative overflow-hidden bg-background text-foreground">
|
|
52
54
|
<Head>
|
|
53
|
-
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
54
|
-
<link
|
|
55
|
-
rel="preconnect"
|
|
56
|
-
href="https://fonts.gstatic.com"
|
|
57
|
-
crossOrigin=""
|
|
58
|
-
/>
|
|
59
|
-
<link
|
|
60
|
-
href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=IBM+Plex+Sans:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap"
|
|
61
|
-
rel="stylesheet"
|
|
62
|
-
/>
|
|
63
55
|
<title>{pageTitle}</title>
|
|
64
56
|
{description ? <meta name="description" content={description} /> : null}
|
|
65
57
|
</Head>
|
|
@@ -69,7 +61,7 @@ export const LandingShell = ({
|
|
|
69
61
|
{showPoweredBy && !hasFooter ? (
|
|
70
62
|
<div
|
|
71
63
|
className={cn(
|
|
72
|
-
"
|
|
64
|
+
"border-t border-border py-6 text-center text-xs text-muted-foreground/70",
|
|
73
65
|
!options.site?.footer && "mb-8",
|
|
74
66
|
)}
|
|
75
67
|
>
|
package/src/landing.css
CHANGED
|
@@ -1,52 +1,3 @@
|
|
|
1
|
-
.apitogo-landing {
|
|
2
|
-
--landing-display: "Space Grotesk", var(--font-sans, system-ui), sans-serif;
|
|
3
|
-
--landing-body: "IBM Plex Sans", var(--font-sans, system-ui), sans-serif;
|
|
4
|
-
--landing-mono: "JetBrains Mono", var(--font-mono, ui-monospace), monospace;
|
|
5
|
-
--landing-bg: #0b0d12;
|
|
6
|
-
--landing-bg-2: #0e1119;
|
|
7
|
-
--landing-surface: #14171f;
|
|
8
|
-
--landing-surface-2: #191d27;
|
|
9
|
-
--landing-border: #252b37;
|
|
10
|
-
--landing-border-2: #333a48;
|
|
11
|
-
--landing-text: #edeff5;
|
|
12
|
-
--landing-text-2: #a6aebf;
|
|
13
|
-
--landing-text-3: #6b7385;
|
|
14
|
-
--landing-accent: #f23645;
|
|
15
|
-
--landing-accent-hover: #ff515d;
|
|
16
|
-
--landing-accent-soft: rgba(242, 54, 69, 0.13);
|
|
17
|
-
--landing-on-accent: #fff;
|
|
18
|
-
--landing-green: #16c784;
|
|
19
|
-
--landing-green-soft: rgba(22, 199, 132, 0.13);
|
|
20
|
-
--landing-peri: #aeb9e8;
|
|
21
|
-
--landing-code-bg: #0c0f16;
|
|
22
|
-
--landing-shadow:
|
|
23
|
-
0 1px 2px rgba(0, 0, 0, 0.4), 0 8px 24px -8px rgba(0, 0, 0, 0.55);
|
|
24
|
-
background: var(--landing-bg);
|
|
25
|
-
color: var(--landing-text);
|
|
26
|
-
font-family: var(--landing-body);
|
|
27
|
-
-webkit-font-smoothing: antialiased;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
:not(.dark) .apitogo-landing {
|
|
31
|
-
--landing-bg: #f5f6f9;
|
|
32
|
-
--landing-bg-2: #ffffff;
|
|
33
|
-
--landing-surface: #ffffff;
|
|
34
|
-
--landing-surface-2: #fafbfc;
|
|
35
|
-
--landing-border: #e6e8ef;
|
|
36
|
-
--landing-border-2: #d6dae3;
|
|
37
|
-
--landing-text: #0e1016;
|
|
38
|
-
--landing-text-2: #565e70;
|
|
39
|
-
--landing-text-3: #8a92a3;
|
|
40
|
-
--landing-accent: #e11d2a;
|
|
41
|
-
--landing-accent-hover: #c2151f;
|
|
42
|
-
--landing-accent-soft: rgba(225, 29, 42, 0.08);
|
|
43
|
-
--landing-green: #0e9e63;
|
|
44
|
-
--landing-green-soft: rgba(14, 158, 99, 0.1);
|
|
45
|
-
--landing-peri: #4e5ac0;
|
|
46
|
-
--landing-shadow:
|
|
47
|
-
0 1px 2px rgba(16, 24, 40, 0.05), 0 8px 24px -10px rgba(16, 24, 40, 0.14);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
1
|
@keyframes landing-fu {
|
|
51
2
|
from {
|
|
52
3
|
opacity: 0;
|
|
@@ -58,212 +9,6 @@
|
|
|
58
9
|
}
|
|
59
10
|
}
|
|
60
11
|
|
|
61
|
-
.
|
|
62
|
-
font-family: var(--landing-display);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
.apitogo-landing .landing-mono {
|
|
66
|
-
font-family: var(--landing-mono);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
.apitogo-landing .landing-fu {
|
|
12
|
+
.landing-fu {
|
|
70
13
|
animation: landing-fu 0.5s cubic-bezier(0.2, 0.7, 0.3, 1) both;
|
|
71
14
|
}
|
|
72
|
-
|
|
73
|
-
.apitogo-landing .landing-section-label {
|
|
74
|
-
color: var(--landing-accent);
|
|
75
|
-
font-family: var(--landing-mono);
|
|
76
|
-
font-size: 0.8125rem;
|
|
77
|
-
font-weight: 600;
|
|
78
|
-
margin-bottom: 0.75rem;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
.apitogo-landing .landing-muted {
|
|
82
|
-
color: var(--landing-text-2);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
.apitogo-landing .landing-muted-3 {
|
|
86
|
-
color: var(--landing-text-3);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
.apitogo-landing .landing-btn-primary {
|
|
90
|
-
align-items: center;
|
|
91
|
-
background: var(--landing-accent);
|
|
92
|
-
border: none;
|
|
93
|
-
border-radius: 11px;
|
|
94
|
-
box-shadow: 0 8px 24px -8px var(--landing-accent-soft);
|
|
95
|
-
color: var(--landing-on-accent);
|
|
96
|
-
cursor: pointer;
|
|
97
|
-
display: inline-flex;
|
|
98
|
-
font-family: inherit;
|
|
99
|
-
font-size: 0.96875rem;
|
|
100
|
-
font-weight: 600;
|
|
101
|
-
gap: 0.5rem;
|
|
102
|
-
padding: 0.8125rem 1.375rem;
|
|
103
|
-
text-decoration: none;
|
|
104
|
-
transition: background 0.15s ease;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
.apitogo-landing .landing-btn-primary:hover {
|
|
108
|
-
background: var(--landing-accent-hover);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
.apitogo-landing .landing-btn-secondary {
|
|
112
|
-
align-items: center;
|
|
113
|
-
background: var(--landing-surface);
|
|
114
|
-
border: 1px solid var(--landing-border);
|
|
115
|
-
border-radius: 11px;
|
|
116
|
-
color: var(--landing-text);
|
|
117
|
-
cursor: pointer;
|
|
118
|
-
display: inline-flex;
|
|
119
|
-
font-family: inherit;
|
|
120
|
-
font-size: 0.96875rem;
|
|
121
|
-
font-weight: 600;
|
|
122
|
-
gap: 0.5rem;
|
|
123
|
-
padding: 0.8125rem 1.375rem;
|
|
124
|
-
text-decoration: none;
|
|
125
|
-
transition:
|
|
126
|
-
border-color 0.15s ease,
|
|
127
|
-
background 0.15s ease;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
.apitogo-landing .landing-btn-secondary:hover {
|
|
131
|
-
border-color: var(--landing-border-2);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
.apitogo-landing .landing-btn-sm {
|
|
135
|
-
border-radius: 9px;
|
|
136
|
-
font-size: 0.875rem;
|
|
137
|
-
padding: 0.625rem 1rem;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
.apitogo-landing .landing-hero-gradient {
|
|
141
|
-
background: radial-gradient(
|
|
142
|
-
1000px 480px at 78% -8%,
|
|
143
|
-
var(--landing-accent-soft),
|
|
144
|
-
transparent 70%
|
|
145
|
-
);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
.apitogo-landing .landing-badge {
|
|
149
|
-
align-items: center;
|
|
150
|
-
background: var(--landing-surface);
|
|
151
|
-
border: 1px solid var(--landing-border);
|
|
152
|
-
border-radius: 999px;
|
|
153
|
-
color: var(--landing-text-2);
|
|
154
|
-
display: inline-flex;
|
|
155
|
-
font-size: 0.78125rem;
|
|
156
|
-
gap: 0.5rem;
|
|
157
|
-
margin-bottom: 1.375rem;
|
|
158
|
-
padding: 0.375rem 0.75rem 0.375rem 0.5rem;
|
|
159
|
-
width: fit-content;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
.apitogo-landing .landing-status-pill {
|
|
163
|
-
align-items: center;
|
|
164
|
-
background: var(--landing-green-soft);
|
|
165
|
-
border-radius: 999px;
|
|
166
|
-
color: var(--landing-green);
|
|
167
|
-
display: inline-flex;
|
|
168
|
-
font-family: var(--landing-mono);
|
|
169
|
-
font-size: 0.6875rem;
|
|
170
|
-
font-weight: 600;
|
|
171
|
-
gap: 0.3125rem;
|
|
172
|
-
padding: 0.125rem 0.5rem;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
.apitogo-landing .landing-status-dot {
|
|
176
|
-
background: var(--landing-green);
|
|
177
|
-
border-radius: 50%;
|
|
178
|
-
height: 6px;
|
|
179
|
-
width: 6px;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
.apitogo-landing .landing-feature-card {
|
|
183
|
-
background: var(--landing-surface);
|
|
184
|
-
border: 1px solid var(--landing-border);
|
|
185
|
-
border-radius: 14px;
|
|
186
|
-
padding: 1.5rem;
|
|
187
|
-
transition: border-color 0.15s ease;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
.apitogo-landing .landing-feature-card:hover {
|
|
191
|
-
border-color: var(--landing-border-2);
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
.apitogo-landing .landing-feature-icon {
|
|
195
|
-
background: var(--landing-accent-soft);
|
|
196
|
-
border-radius: 11px;
|
|
197
|
-
color: var(--landing-accent);
|
|
198
|
-
display: grid;
|
|
199
|
-
height: 2.625rem;
|
|
200
|
-
margin-bottom: 1rem;
|
|
201
|
-
place-items: center;
|
|
202
|
-
width: 2.625rem;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
.apitogo-landing .landing-api-card {
|
|
206
|
-
background: var(--landing-surface);
|
|
207
|
-
border: 1px solid var(--landing-border);
|
|
208
|
-
border-radius: 13px;
|
|
209
|
-
color: var(--landing-text);
|
|
210
|
-
cursor: pointer;
|
|
211
|
-
padding: 1.125rem;
|
|
212
|
-
text-decoration: none;
|
|
213
|
-
transition:
|
|
214
|
-
border-color 0.15s ease,
|
|
215
|
-
transform 0.15s ease;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
.apitogo-landing .landing-api-card:hover {
|
|
219
|
-
border-color: var(--landing-border-2);
|
|
220
|
-
transform: translateY(-2px);
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
.apitogo-landing .landing-api-icon {
|
|
224
|
-
background: var(--landing-surface-2);
|
|
225
|
-
border: 1px solid var(--landing-border);
|
|
226
|
-
border-radius: 9px;
|
|
227
|
-
color: var(--landing-peri);
|
|
228
|
-
display: grid;
|
|
229
|
-
height: 2.25rem;
|
|
230
|
-
place-items: center;
|
|
231
|
-
width: 2.25rem;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
.apitogo-landing .landing-latency-badge {
|
|
235
|
-
background: var(--landing-green-soft);
|
|
236
|
-
border-radius: 6px;
|
|
237
|
-
color: var(--landing-green);
|
|
238
|
-
font-family: var(--landing-mono);
|
|
239
|
-
font-size: 0.6875rem;
|
|
240
|
-
font-weight: 600;
|
|
241
|
-
padding: 0.1875rem 0.4375rem;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
.apitogo-landing .landing-trust-logo {
|
|
245
|
-
color: var(--landing-text-2);
|
|
246
|
-
font-family: var(--landing-display);
|
|
247
|
-
font-size: 1.1875rem;
|
|
248
|
-
font-weight: 600;
|
|
249
|
-
letter-spacing: -0.01em;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
.apitogo-landing .landing-showcase-section {
|
|
253
|
-
background: var(--landing-bg-2);
|
|
254
|
-
border-bottom: 1px solid var(--landing-border);
|
|
255
|
-
border-top: 1px solid var(--landing-border);
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
.apitogo-landing .landing-hero-section {
|
|
259
|
-
border-bottom: 1px solid var(--landing-border);
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
.apitogo-landing .landing-powered-by {
|
|
263
|
-
border-top: 1px solid var(--landing-border);
|
|
264
|
-
color: var(--landing-text-3);
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
.apitogo-landing .landing-footer-social-link {
|
|
268
|
-
color: var(--landing-text-3);
|
|
269
|
-
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ResolvedLandingContent } from "@lukoweb/apitogo";
|
|
2
2
|
import { Link } from "@lukoweb/apitogo/components";
|
|
3
|
+
import { Button } from "@lukoweb/apitogo/ui/Button.js";
|
|
3
4
|
import { ArrowRightIcon } from "lucide-react";
|
|
4
5
|
import { LandingIcon } from "./LandingIcon.js";
|
|
5
6
|
|
|
@@ -19,25 +20,28 @@ export const ApiShowcaseSection = ({
|
|
|
19
20
|
const browseLabel = apiShowcase.browseAllLabel ?? "Browse all APIs";
|
|
20
21
|
|
|
21
22
|
return (
|
|
22
|
-
<section className="
|
|
23
|
+
<section className="border-y border-border bg-muted/30">
|
|
23
24
|
<div className="mx-auto max-w-[1200px] px-6 py-[3.75rem]">
|
|
24
25
|
<div className="mb-8 flex flex-wrap items-end justify-between gap-5">
|
|
25
26
|
<div>
|
|
26
|
-
<p className="
|
|
27
|
-
|
|
27
|
+
<p className="mb-3 font-mono text-[0.8125rem] font-semibold text-primary">
|
|
28
|
+
{"// the catalog"}
|
|
29
|
+
</p>
|
|
30
|
+
<h2 className="text-[2.125rem] leading-tight font-bold tracking-[-0.025em]">
|
|
28
31
|
{apiShowcase.title ?? "Production APIs. One key."}
|
|
29
32
|
</h2>
|
|
30
33
|
{apiShowcase.subtitle ? (
|
|
31
|
-
<p className="
|
|
34
|
+
<p className="mt-2 text-muted-foreground">
|
|
35
|
+
{apiShowcase.subtitle}
|
|
36
|
+
</p>
|
|
32
37
|
) : null}
|
|
33
38
|
</div>
|
|
34
|
-
<
|
|
35
|
-
to={browseHref}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
</Link>
|
|
39
|
+
<Button asChild variant="outline" size="sm">
|
|
40
|
+
<Link to={browseHref}>
|
|
41
|
+
{browseLabel}
|
|
42
|
+
<ArrowRightIcon size={15} aria-hidden />
|
|
43
|
+
</Link>
|
|
44
|
+
</Button>
|
|
41
45
|
</div>
|
|
42
46
|
|
|
43
47
|
<div className="grid gap-3.5 sm:grid-cols-2 lg:grid-cols-4">
|
|
@@ -45,20 +49,22 @@ export const ApiShowcaseSection = ({
|
|
|
45
49
|
<Link
|
|
46
50
|
key={`${item.name}-${item.path}`}
|
|
47
51
|
to={browseHref}
|
|
48
|
-
className="
|
|
52
|
+
className="block rounded-[13px] border border-border bg-card p-[1.125rem] text-foreground no-underline transition-[border-color,transform] hover:-translate-y-0.5 hover:border-muted-foreground/30"
|
|
49
53
|
>
|
|
50
54
|
<div className="mb-3.5 flex items-center justify-between">
|
|
51
|
-
<div className="
|
|
55
|
+
<div className="grid size-9 place-items-center rounded-[9px] border border-border bg-muted/40 text-primary">
|
|
52
56
|
<LandingIcon name={item.icon ?? "boxes"} size={18} />
|
|
53
57
|
</div>
|
|
54
58
|
{item.latency ? (
|
|
55
|
-
<span className="
|
|
59
|
+
<span className="rounded-md bg-primary/10 px-[0.4375rem] py-[0.1875rem] font-mono text-[0.6875rem] font-semibold text-primary">
|
|
60
|
+
{item.latency}
|
|
61
|
+
</span>
|
|
56
62
|
) : null}
|
|
57
63
|
</div>
|
|
58
|
-
<h3 className="
|
|
64
|
+
<h3 className="mb-1 text-[0.9375rem] font-semibold">
|
|
59
65
|
{item.name}
|
|
60
66
|
</h3>
|
|
61
|
-
<p className="
|
|
67
|
+
<p className="font-mono text-[0.71875rem] text-muted-foreground/70">
|
|
62
68
|
{item.path}
|
|
63
69
|
</p>
|
|
64
70
|
</Link>
|
|
@@ -12,24 +12,24 @@ export const CodeExampleCard = ({ example }: CodeExampleCardProps) => {
|
|
|
12
12
|
const language = example.language ?? "bash";
|
|
13
13
|
|
|
14
14
|
return (
|
|
15
|
-
<div
|
|
16
|
-
className="
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
<span className="size-[11px] rounded-full bg-
|
|
22
|
-
<span className="size-[11px] rounded-full bg-
|
|
23
|
-
<span className="
|
|
15
|
+
<div className="overflow-hidden rounded-2xl border border-border bg-card shadow-sm">
|
|
16
|
+
<div className="flex items-center gap-2 border-b border-border bg-muted/40 px-4 py-3">
|
|
17
|
+
<span
|
|
18
|
+
className="size-[11px] rounded-full bg-destructive/80"
|
|
19
|
+
aria-hidden
|
|
20
|
+
/>
|
|
21
|
+
<span className="size-[11px] rounded-full bg-warning" aria-hidden />
|
|
22
|
+
<span className="size-[11px] rounded-full bg-primary" aria-hidden />
|
|
23
|
+
<span className="ms-2 font-mono text-xs text-muted-foreground">
|
|
24
24
|
{filename}
|
|
25
25
|
</span>
|
|
26
26
|
<TerminalIcon
|
|
27
27
|
size={14}
|
|
28
|
-
className="ms-auto text-
|
|
28
|
+
className="ms-auto text-muted-foreground/70"
|
|
29
29
|
aria-hidden
|
|
30
30
|
/>
|
|
31
31
|
</div>
|
|
32
|
-
<pre className="
|
|
32
|
+
<pre className="overflow-x-auto p-[1.125rem] font-mono text-[0.78125rem] leading-[1.85] text-foreground">
|
|
33
33
|
<code data-language={language}>{example.code}</code>
|
|
34
34
|
</pre>
|
|
35
35
|
</div>
|
|
@@ -23,25 +23,30 @@ export const FeaturesSection = ({
|
|
|
23
23
|
return (
|
|
24
24
|
<section className="mx-auto max-w-[1200px] px-6 py-10 pb-16">
|
|
25
25
|
<div className="mb-11 max-w-[640px]">
|
|
26
|
-
<p className="
|
|
27
|
-
|
|
26
|
+
<p className="mb-3 font-mono text-[0.8125rem] font-semibold text-primary">
|
|
27
|
+
{label}
|
|
28
|
+
</p>
|
|
29
|
+
<h2 className="mb-3.5 text-[2.375rem] leading-tight font-bold tracking-[-0.025em] text-balance">
|
|
28
30
|
{title}
|
|
29
31
|
</h2>
|
|
30
|
-
<p className="
|
|
32
|
+
<p className="text-[1.0625rem] leading-relaxed text-muted-foreground">
|
|
31
33
|
{description}
|
|
32
34
|
</p>
|
|
33
35
|
</div>
|
|
34
36
|
|
|
35
37
|
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
|
36
38
|
{features.map((feature) => (
|
|
37
|
-
<article
|
|
38
|
-
|
|
39
|
+
<article
|
|
40
|
+
key={feature.title}
|
|
41
|
+
className="rounded-[14px] border border-border bg-card p-6 transition-colors hover:border-muted-foreground/30"
|
|
42
|
+
>
|
|
43
|
+
<div className="mb-4 grid size-[2.625rem] place-items-center rounded-[11px] bg-primary/10 text-primary">
|
|
39
44
|
<LandingIcon name={feature.icon} size={21} />
|
|
40
45
|
</div>
|
|
41
|
-
<h3 className="
|
|
46
|
+
<h3 className="mb-2 text-[1.09375rem] font-semibold">
|
|
42
47
|
{feature.title}
|
|
43
48
|
</h3>
|
|
44
|
-
<p className="
|
|
49
|
+
<p className="text-[0.90625rem] leading-relaxed text-muted-foreground">
|
|
45
50
|
{feature.description}
|
|
46
51
|
</p>
|
|
47
52
|
</article>
|
|
@@ -55,7 +55,7 @@ const FooterSocialLink = ({
|
|
|
55
55
|
target="_blank"
|
|
56
56
|
rel="noopener noreferrer"
|
|
57
57
|
aria-label={accessibleLabel}
|
|
58
|
-
className="
|
|
58
|
+
className="inline-flex items-center justify-center text-muted-foreground/70 transition-colors hover:text-foreground"
|
|
59
59
|
>
|
|
60
60
|
{Icon ? (
|
|
61
61
|
<Icon className="h-[1.125rem] w-[1.125rem]" />
|
|
@@ -85,7 +85,7 @@ export const FooterSection = ({ footer }: FooterSectionProps) => {
|
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
return (
|
|
88
|
-
<footer className="
|
|
88
|
+
<footer className="border-t bg-background">
|
|
89
89
|
<div className="mx-auto max-w-[1200px] px-6 py-12">
|
|
90
90
|
<div className="grid gap-10 sm:grid-cols-2 lg:grid-cols-[1.4fr_repeat(3,minmax(0,1fr))]">
|
|
91
91
|
<div className="sm:col-span-2 lg:col-span-1">
|
|
@@ -97,14 +97,14 @@ export const FooterSection = ({ footer }: FooterSectionProps) => {
|
|
|
97
97
|
className="h-7 w-auto object-contain"
|
|
98
98
|
/>
|
|
99
99
|
) : (
|
|
100
|
-
<span className="
|
|
101
|
-
<span className="text-
|
|
100
|
+
<span className="text-[1.125rem] font-bold tracking-[-0.02em]">
|
|
101
|
+
<span className="text-primary">API</span>
|
|
102
102
|
<span>ToGo</span>
|
|
103
103
|
</span>
|
|
104
104
|
)}
|
|
105
105
|
</div>
|
|
106
106
|
{tagline ? (
|
|
107
|
-
<p className="
|
|
107
|
+
<p className="max-w-[280px] text-[0.875rem] leading-relaxed text-muted-foreground">
|
|
108
108
|
{tagline}
|
|
109
109
|
</p>
|
|
110
110
|
) : null}
|
|
@@ -112,15 +112,13 @@ export const FooterSection = ({ footer }: FooterSectionProps) => {
|
|
|
112
112
|
|
|
113
113
|
{columns.map((column) => (
|
|
114
114
|
<div key={column.title}>
|
|
115
|
-
<h3 className="mb-4 text-
|
|
116
|
-
{column.title}
|
|
117
|
-
</h3>
|
|
115
|
+
<h3 className="mb-4 text-sm font-semibold">{column.title}</h3>
|
|
118
116
|
<ul className="space-y-2.5">
|
|
119
117
|
{column.links.map((link) => (
|
|
120
118
|
<li key={`${column.title}-${link.label}`}>
|
|
121
119
|
<a
|
|
122
120
|
href={link.href}
|
|
123
|
-
className="
|
|
121
|
+
className="text-sm text-muted-foreground transition-colors hover:text-foreground"
|
|
124
122
|
>
|
|
125
123
|
{link.label}
|
|
126
124
|
</a>
|
|
@@ -131,8 +129,8 @@ export const FooterSection = ({ footer }: FooterSectionProps) => {
|
|
|
131
129
|
))}
|
|
132
130
|
</div>
|
|
133
131
|
|
|
134
|
-
<div className="
|
|
135
|
-
<p className="
|
|
132
|
+
<div className="mt-10 flex flex-col items-center justify-between gap-4 border-t border-border pt-6 sm:flex-row">
|
|
133
|
+
<p className="text-sm text-muted-foreground/70">{copyright}</p>
|
|
136
134
|
{social.length > 0 ? (
|
|
137
135
|
<div className="flex items-center gap-4">
|
|
138
136
|
{social.map((item) => (
|
|
@@ -19,10 +19,10 @@ export const HeroSection = ({
|
|
|
19
19
|
const highlights = hero.highlights ?? [];
|
|
20
20
|
|
|
21
21
|
return (
|
|
22
|
-
<section className="
|
|
22
|
+
<section className="relative overflow-hidden border-b border-border">
|
|
23
23
|
<div
|
|
24
24
|
aria-hidden
|
|
25
|
-
className="
|
|
25
|
+
className="pointer-events-none absolute inset-0 bg-[radial-gradient(1000px_480px_at_78%_-8%,color-mix(in_oklch,var(--primary)_13%,transparent),transparent_70%)]"
|
|
26
26
|
/>
|
|
27
27
|
|
|
28
28
|
<div
|
|
@@ -39,10 +39,13 @@ export const HeroSection = ({
|
|
|
39
39
|
)}
|
|
40
40
|
>
|
|
41
41
|
{(hero.badge || hero.subtitle) && (
|
|
42
|
-
<div className="
|
|
42
|
+
<div className="mb-[1.375rem] inline-flex w-fit items-center gap-2 rounded-full border border-border bg-card px-3 py-1.5 text-[0.78125rem] text-muted-foreground">
|
|
43
43
|
{hero.badge ? (
|
|
44
|
-
<span className="
|
|
45
|
-
<span
|
|
44
|
+
<span className="inline-flex items-center gap-1.5 rounded-full bg-primary/10 px-2 py-0.5 font-mono text-[0.6875rem] font-semibold text-primary">
|
|
45
|
+
<span
|
|
46
|
+
className="size-1.5 rounded-full bg-primary"
|
|
47
|
+
aria-hidden
|
|
48
|
+
/>
|
|
46
49
|
{hero.badge}
|
|
47
50
|
</span>
|
|
48
51
|
) : null}
|
|
@@ -50,20 +53,18 @@ export const HeroSection = ({
|
|
|
50
53
|
</div>
|
|
51
54
|
)}
|
|
52
55
|
|
|
53
|
-
<h1 className="
|
|
56
|
+
<h1 className="mb-5 text-[2.5rem] leading-[1.02] font-bold tracking-[-0.03em] text-balance sm:text-5xl lg:text-[3.5rem]">
|
|
54
57
|
{hero.title}
|
|
55
58
|
{hero.titleAccent ? (
|
|
56
59
|
<>
|
|
57
60
|
<br />
|
|
58
|
-
<span className="text-
|
|
59
|
-
{hero.titleAccent}
|
|
60
|
-
</span>
|
|
61
|
+
<span className="text-primary">{hero.titleAccent}</span>
|
|
61
62
|
</>
|
|
62
63
|
) : null}
|
|
63
64
|
</h1>
|
|
64
65
|
|
|
65
66
|
{hero.description ? (
|
|
66
|
-
<p className="
|
|
67
|
+
<p className="mb-[1.875rem] max-w-[480px] text-lg leading-relaxed text-pretty text-muted-foreground">
|
|
67
68
|
{hero.description}
|
|
68
69
|
</p>
|
|
69
70
|
) : null}
|
|
@@ -91,17 +92,13 @@ export const HeroSection = ({
|
|
|
91
92
|
)}
|
|
92
93
|
|
|
93
94
|
{highlights.length > 0 ? (
|
|
94
|
-
<ul className="
|
|
95
|
+
<ul className="mt-[1.875rem] flex flex-wrap items-center gap-x-[1.375rem] gap-y-2 text-[0.8125rem] text-muted-foreground/70">
|
|
95
96
|
{highlights.map((item) => (
|
|
96
97
|
<li
|
|
97
98
|
key={item}
|
|
98
99
|
className="inline-flex items-center gap-[0.4375rem]"
|
|
99
100
|
>
|
|
100
|
-
<CheckIcon
|
|
101
|
-
size={15}
|
|
102
|
-
className="text-[var(--landing-green)]"
|
|
103
|
-
aria-hidden
|
|
104
|
-
/>
|
|
101
|
+
<CheckIcon size={15} className="text-primary" aria-hidden />
|
|
105
102
|
{item}
|
|
106
103
|
</li>
|
|
107
104
|
))}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { cn } from "@lukoweb/apitogo";
|
|
2
2
|
import { Link } from "@lukoweb/apitogo/components";
|
|
3
|
+
import { Button } from "@lukoweb/apitogo/ui/Button.js";
|
|
3
4
|
import type { LucideIcon } from "lucide-react";
|
|
4
5
|
import type { ReactNode } from "react";
|
|
5
6
|
|
|
@@ -33,15 +34,13 @@ export const LandingButton = ({
|
|
|
33
34
|
);
|
|
34
35
|
|
|
35
36
|
return (
|
|
36
|
-
<
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
className,
|
|
42
|
-
)}
|
|
37
|
+
<Button
|
|
38
|
+
asChild
|
|
39
|
+
variant={variant === "primary" ? "default" : "outline"}
|
|
40
|
+
size={size === "sm" ? "sm" : "lg"}
|
|
41
|
+
className={cn(className)}
|
|
43
42
|
>
|
|
44
|
-
{content}
|
|
45
|
-
</
|
|
43
|
+
<Link to={href}>{content}</Link>
|
|
44
|
+
</Button>
|
|
46
45
|
);
|
|
47
46
|
};
|
|
@@ -14,10 +14,10 @@ export const PricingCtaSection = ({ pricingCta }: PricingCtaSectionProps) => {
|
|
|
14
14
|
|
|
15
15
|
return (
|
|
16
16
|
<section className="mx-auto max-w-[1200px] px-6 py-[4.5rem] text-center">
|
|
17
|
-
<h2 className="
|
|
17
|
+
<h2 className="mb-4 text-[2.5rem] leading-tight font-bold tracking-[-0.03em] text-balance">
|
|
18
18
|
{title}
|
|
19
19
|
</h2>
|
|
20
|
-
<p className="
|
|
20
|
+
<p className="mx-auto mb-[1.875rem] max-w-[520px] text-[1.09375rem] leading-relaxed text-muted-foreground">
|
|
21
21
|
{description}
|
|
22
22
|
</p>
|
|
23
23
|
<div className="flex flex-wrap items-center justify-center gap-3">
|
|
@@ -13,13 +13,16 @@ export const TrustSection = ({ trust }: TrustSectionProps) => {
|
|
|
13
13
|
return (
|
|
14
14
|
<section className="mx-auto max-w-[1200px] px-6 py-10">
|
|
15
15
|
{trust.title ? (
|
|
16
|
-
<p className="
|
|
16
|
+
<p className="mb-[1.375rem] text-center text-[0.78125rem] font-semibold tracking-[0.08em] text-muted-foreground/70 uppercase">
|
|
17
17
|
{trust.title}
|
|
18
18
|
</p>
|
|
19
19
|
) : null}
|
|
20
20
|
<div className="flex flex-wrap items-center justify-center gap-x-[2.875rem] gap-y-3.5 opacity-70">
|
|
21
21
|
{logos.map((logo) => (
|
|
22
|
-
<span
|
|
22
|
+
<span
|
|
23
|
+
key={logo}
|
|
24
|
+
className="text-[1.1875rem] font-semibold tracking-[-0.01em] text-muted-foreground"
|
|
25
|
+
>
|
|
23
26
|
{logo}
|
|
24
27
|
</span>
|
|
25
28
|
))}
|