@ugurdemirel/landcraft 0.1.2 → 0.1.4
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/dist/index.cjs +2 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +41 -22
- package/dist/index.js +2261 -1937
- package/dist/index.js.map +1 -1
- package/dist/styles.css +3 -0
- package/package.json +1 -1
- package/src/components/Blog/BlogCard.tsx +12 -0
- package/src/components/Blog/BlogSection.tsx +95 -0
- package/src/components/Blog/index.ts +8 -0
- package/src/components/Blog/parts.tsx +38 -0
- package/src/components/Blog/types.ts +42 -0
- package/src/components/Blog/variants/Card.tsx +62 -0
- package/src/components/Blog/variants/Row.tsx +41 -0
- package/src/components/CTA/CTA.tsx +12 -0
- package/src/components/CTA/index.ts +2 -0
- package/src/components/CTA/parts.tsx +26 -0
- package/src/components/CTA/types.ts +12 -0
- package/src/components/CTA/variants/Inverse.tsx +50 -0
- package/src/components/CTA/variants/Panel.tsx +60 -0
- package/src/components/CTA/variants/Surface.tsx +34 -0
- package/src/components/FAQ/FAQ.tsx +48 -0
- package/src/components/FAQ/index.ts +2 -0
- package/src/components/FAQ/parts.tsx +74 -0
- package/src/components/FAQ/types.ts +18 -0
- package/src/components/FAQ/variants/Accordion.tsx +46 -0
- package/src/components/FAQ/variants/Cards.tsx +40 -0
- package/src/components/FAQ/variants/Split.tsx +44 -0
- package/src/components/Features/FeatureCard.tsx +44 -0
- package/src/components/Features/FeatureGrid.tsx +14 -0
- package/src/components/Features/index.ts +3 -0
- package/src/components/Features/parts.ts +15 -0
- package/src/components/Features/types.ts +25 -0
- package/src/components/Features/variants/Bento.tsx +32 -0
- package/src/components/Features/variants/Columns.tsx +21 -0
- package/src/components/Features/variants/EditorialRows.tsx +38 -0
- package/src/components/Footer/Footer.tsx +11 -0
- package/src/components/Footer/index.ts +2 -0
- package/src/components/Footer/parts.tsx +101 -0
- package/src/components/Footer/types.ts +32 -0
- package/src/components/Footer/variants/Classic.tsx +59 -0
- package/src/components/Footer/variants/Editorial.tsx +47 -0
- package/src/components/Footer/variants/Minimal.tsx +33 -0
- package/src/components/Hero/Hero.tsx +12 -0
- package/src/components/Hero/index.ts +2 -0
- package/src/components/Hero/parts.tsx +29 -0
- package/src/components/Hero/types.ts +20 -0
- package/src/components/Hero/variants/Centered.tsx +54 -0
- package/src/components/Hero/variants/Split.tsx +53 -0
- package/src/components/Hero/variants/Statement.tsx +60 -0
- package/src/components/LanguageSwitcher/LanguageSwitcher.tsx +37 -0
- package/src/components/LanguageSwitcher/index.ts +6 -0
- package/src/components/LanguageSwitcher/parts.tsx +144 -0
- package/src/components/LanguageSwitcher/types.ts +40 -0
- package/src/components/LanguageSwitcher/useLanguageSwitcher.ts +23 -0
- package/src/components/LanguageSwitcher/variants/Dropdown.tsx +88 -0
- package/src/components/LanguageSwitcher/variants/Modal.tsx +56 -0
- package/src/components/LogoCloud/LogoCloud.tsx +14 -0
- package/src/components/LogoCloud/index.ts +2 -0
- package/src/components/LogoCloud/parts.tsx +32 -0
- package/src/components/LogoCloud/types.ts +16 -0
- package/src/components/LogoCloud/variants/Marquee.tsx +30 -0
- package/src/components/LogoCloud/variants/Quiet.tsx +26 -0
- package/src/components/LogoCloud/variants/Strip.tsx +25 -0
- package/src/components/{MegaMenu.tsx → MegaMenu/MegaMenu.tsx} +108 -126
- package/src/components/MegaMenu/index.ts +9 -0
- package/src/components/MegaMenu/types.ts +56 -0
- package/src/components/Modal.tsx +1 -0
- package/src/components/Navbar/Navbar.tsx +185 -0
- package/src/components/Navbar/index.ts +2 -0
- package/src/components/Navbar/types.ts +30 -0
- package/src/components/Newsletter/Newsletter.tsx +12 -0
- package/src/components/Newsletter/index.ts +2 -0
- package/src/components/Newsletter/parts.tsx +23 -0
- package/src/components/Newsletter/types.ts +12 -0
- package/src/components/Newsletter/useNewsletterForm.ts +27 -0
- package/src/components/Newsletter/variants/Card.tsx +62 -0
- package/src/components/Newsletter/variants/Inline.tsx +54 -0
- package/src/components/Newsletter/variants/Underline.tsx +52 -0
- package/src/components/Pricing/Pricing.tsx +37 -0
- package/src/components/Pricing/index.ts +2 -0
- package/src/components/Pricing/parts.tsx +132 -0
- package/src/components/Pricing/types.ts +28 -0
- package/src/components/Pricing/variants/Bento.tsx +72 -0
- package/src/components/Pricing/variants/Cards.tsx +79 -0
- package/src/components/Pricing/variants/Compact.tsx +69 -0
- package/src/components/Stats/Stats.tsx +16 -0
- package/src/components/Stats/index.ts +2 -0
- package/src/components/Stats/parts.tsx +29 -0
- package/src/components/Stats/types.ts +23 -0
- package/src/components/Stats/variants/Cells.tsx +34 -0
- package/src/components/Stats/variants/Editorial.tsx +41 -0
- package/src/components/Stats/variants/Hairline.tsx +31 -0
- package/src/components/Stats/variants/Ticker.tsx +36 -0
- package/src/components/Testimonials/TestimonialCard.tsx +61 -0
- package/src/components/Testimonials/Testimonials.tsx +14 -0
- package/src/components/Testimonials/index.ts +3 -0
- package/src/components/Testimonials/parts.ts +7 -0
- package/src/components/Testimonials/types.ts +24 -0
- package/src/components/Testimonials/variants/Carousel.tsx +94 -0
- package/src/components/Testimonials/variants/Grid.tsx +19 -0
- package/src/components/Testimonials/variants/Marquee.tsx +28 -0
- package/src/utils/useTokenForeground.ts +1 -0
- package/src/components/Blog.tsx +0 -251
- package/src/components/CTA.tsx +0 -155
- package/src/components/FAQ.tsx +0 -163
- package/src/components/Features.tsx +0 -153
- package/src/components/Footer.tsx +0 -185
- package/src/components/Hero.tsx +0 -169
- package/src/components/LanguageSwitcher.tsx +0 -223
- package/src/components/LogoCloud.tsx +0 -100
- package/src/components/Navbar.tsx +0 -195
- package/src/components/Newsletter.tsx +0 -180
- package/src/components/Pricing.tsx +0 -336
- package/src/components/Stats.tsx +0 -181
- package/src/components/Testimonials.tsx +0 -205
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
import { forwardRef, type HTMLAttributes, type ReactNode } from "react";
|
|
2
|
-
import { cn } from "../utils/cn";
|
|
3
|
-
import { getContrastText } from "../utils/contrast";
|
|
4
|
-
|
|
5
|
-
export interface FeatureItem {
|
|
6
|
-
icon?: ReactNode;
|
|
7
|
-
title: string;
|
|
8
|
-
description?: string;
|
|
9
|
-
accent?: string;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export interface FeatureCardProps extends Omit<HTMLAttributes<HTMLDivElement>, "title"> {
|
|
13
|
-
icon?: ReactNode;
|
|
14
|
-
title: ReactNode;
|
|
15
|
-
description?: ReactNode;
|
|
16
|
-
/** Arbitrary CSS color for the icon chip; glyph color computed for contrast. */
|
|
17
|
-
accent?: string;
|
|
18
|
-
large?: boolean;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export const FeatureCard = forwardRef<HTMLDivElement, FeatureCardProps>(
|
|
22
|
-
({ className, icon, title, description, accent, large = false, ...props }, ref) => {
|
|
23
|
-
const chipStyle = accent
|
|
24
|
-
? { backgroundColor: accent, color: getContrastText(accent, "#111111", "#ffffff") }
|
|
25
|
-
: undefined;
|
|
26
|
-
|
|
27
|
-
return (
|
|
28
|
-
<div
|
|
29
|
-
ref={ref}
|
|
30
|
-
className={cn(
|
|
31
|
-
"group relative flex h-full flex-col rounded-xl border border-border bg-surface p-7 transition-colors duration-200 hover:border-foreground/15",
|
|
32
|
-
className,
|
|
33
|
-
)}
|
|
34
|
-
{...props}
|
|
35
|
-
>
|
|
36
|
-
{icon ? (
|
|
37
|
-
<div
|
|
38
|
-
className={cn(
|
|
39
|
-
"mb-5 inline-flex h-10 w-10 items-center justify-center rounded-lg text-[1.05rem]",
|
|
40
|
-
!accent && "bg-primary-soft text-primary",
|
|
41
|
-
)}
|
|
42
|
-
style={chipStyle}
|
|
43
|
-
>
|
|
44
|
-
{icon}
|
|
45
|
-
</div>
|
|
46
|
-
) : null}
|
|
47
|
-
<h3 className={cn("font-display font-semibold tracking-tight text-foreground", large ? "text-2xl" : "text-lg")}>
|
|
48
|
-
{title}
|
|
49
|
-
</h3>
|
|
50
|
-
{description ? (
|
|
51
|
-
<p className={cn("mt-2.5 text-pretty leading-relaxed text-muted-foreground", large ? "text-[15px]" : "text-sm")}>
|
|
52
|
-
{description}
|
|
53
|
-
</p>
|
|
54
|
-
) : null}
|
|
55
|
-
</div>
|
|
56
|
-
);
|
|
57
|
-
},
|
|
58
|
-
);
|
|
59
|
-
FeatureCard.displayName = "FeatureCard";
|
|
60
|
-
|
|
61
|
-
export interface FeatureGridProps extends Omit<HTMLAttributes<HTMLDivElement>, "title"> {
|
|
62
|
-
features: FeatureItem[];
|
|
63
|
-
option?: "columns" | "bento" | "editorialRows";
|
|
64
|
-
columns?: 2 | 3 | 4;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const gridCols: Record<NonNullable<FeatureGridProps["columns"]>, string> = {
|
|
68
|
-
2: "sm:grid-cols-2",
|
|
69
|
-
3: "sm:grid-cols-2 lg:grid-cols-3",
|
|
70
|
-
4: "sm:grid-cols-2 lg:grid-cols-4",
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
/** Asymmetric bento layout for up to 6 features. */
|
|
74
|
-
const bentoSpans: string[] = [
|
|
75
|
-
"sm:col-span-2 lg:row-span-2",
|
|
76
|
-
"",
|
|
77
|
-
"",
|
|
78
|
-
"lg:col-span-2",
|
|
79
|
-
"",
|
|
80
|
-
"lg:col-span-2",
|
|
81
|
-
];
|
|
82
|
-
|
|
83
|
-
export const FeatureGrid = forwardRef<HTMLDivElement, FeatureGridProps>(
|
|
84
|
-
({ className, features, option = "columns", columns = 3, ...props }, ref) => {
|
|
85
|
-
if (option === "editorialRows") {
|
|
86
|
-
return (
|
|
87
|
-
<div ref={ref} className={cn("w-full", className)} {...props}>
|
|
88
|
-
<div className="divide-y divide-border border-t border-b border-border">
|
|
89
|
-
{features.map((feature, i) => (
|
|
90
|
-
<div
|
|
91
|
-
key={i}
|
|
92
|
-
className="group flex flex-col gap-3 py-6 transition-colors duration-200 sm:flex-row sm:items-baseline sm:gap-10"
|
|
93
|
-
>
|
|
94
|
-
<span className="text-sm font-medium tabular-nums text-muted-foreground/60 sm:w-12">
|
|
95
|
-
{String(i + 1).padStart(2, "0")}
|
|
96
|
-
</span>
|
|
97
|
-
<div className="flex-1 sm:flex sm:items-baseline sm:justify-between sm:gap-10">
|
|
98
|
-
<h3 className="font-display text-xl font-semibold tracking-tight text-foreground sm:w-2/5">
|
|
99
|
-
{feature.title}
|
|
100
|
-
</h3>
|
|
101
|
-
<p className="mt-1.5 text-sm leading-7 text-muted-foreground sm:mt-0 sm:w-1/2">
|
|
102
|
-
{feature.description}
|
|
103
|
-
</p>
|
|
104
|
-
</div>
|
|
105
|
-
{feature.icon ? (
|
|
106
|
-
<span className="hidden text-foreground/30 sm:inline-flex">{feature.icon}</span>
|
|
107
|
-
) : null}
|
|
108
|
-
</div>
|
|
109
|
-
))}
|
|
110
|
-
</div>
|
|
111
|
-
</div>
|
|
112
|
-
);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
if (option === "bento") {
|
|
116
|
-
return (
|
|
117
|
-
<div
|
|
118
|
-
ref={ref}
|
|
119
|
-
className={cn(
|
|
120
|
-
"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4 lg:auto-rows-[minmax(9rem,auto)]",
|
|
121
|
-
className,
|
|
122
|
-
)}
|
|
123
|
-
{...props}
|
|
124
|
-
>
|
|
125
|
-
{features.map((feature, i) =>
|
|
126
|
-
feature ? (
|
|
127
|
-
<div key={i} className={cn(i < bentoSpans.length && bentoSpans[i])}>
|
|
128
|
-
<FeatureCard {...feature} large={i === 0} className="h-full" />
|
|
129
|
-
</div>
|
|
130
|
-
) : null,
|
|
131
|
-
)}
|
|
132
|
-
</div>
|
|
133
|
-
);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
return (
|
|
137
|
-
<div
|
|
138
|
-
ref={ref}
|
|
139
|
-
className={cn(
|
|
140
|
-
"grid grid-cols-1 gap-4",
|
|
141
|
-
gridCols[columns],
|
|
142
|
-
className,
|
|
143
|
-
)}
|
|
144
|
-
{...props}
|
|
145
|
-
>
|
|
146
|
-
{features.map((feature, i) => (
|
|
147
|
-
<FeatureCard key={i} {...feature} className="h-full" />
|
|
148
|
-
))}
|
|
149
|
-
</div>
|
|
150
|
-
);
|
|
151
|
-
},
|
|
152
|
-
);
|
|
153
|
-
FeatureGrid.displayName = "FeatureGrid";
|
|
@@ -1,185 +0,0 @@
|
|
|
1
|
-
import { type ElementType, type HTMLAttributes, type ReactNode } from "react";
|
|
2
|
-
import { cn } from "../utils/cn";
|
|
3
|
-
import { Container } from "./Container";
|
|
4
|
-
|
|
5
|
-
export interface FooterLink {
|
|
6
|
-
label: string;
|
|
7
|
-
href: string;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export interface FooterColumn {
|
|
11
|
-
title: string;
|
|
12
|
-
links: FooterLink[];
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export interface FooterProps extends HTMLAttributes<HTMLElement> {
|
|
16
|
-
brand?: ReactNode;
|
|
17
|
-
/** Custom logo node (component, <img>, inline SVG…). Replaces the wordmark. */
|
|
18
|
-
logo?: ReactNode;
|
|
19
|
-
/** Quick logo image source. */
|
|
20
|
-
logoSrc?: string;
|
|
21
|
-
logoAlt?: string;
|
|
22
|
-
/** Size / position tuning for logo images. Defaults to `h-7 w-auto`. */
|
|
23
|
-
logoClassName?: string;
|
|
24
|
-
description?: string;
|
|
25
|
-
columns: FooterColumn[];
|
|
26
|
-
socials?: ReactNode;
|
|
27
|
-
bottom?: ReactNode;
|
|
28
|
-
/** Optional language switcher rendered in the footer's bottom bar. */
|
|
29
|
-
languageSwitcher?: ReactNode;
|
|
30
|
-
option?: "classic" | "minimal" | "editorial";
|
|
31
|
-
badge?: ReactNode;
|
|
32
|
-
/** Component used to render footer links. Defaults to `<a>`. Pass your router's `<Link>` (Next.js, Remix, React Router…) for framework-aware navigation. */
|
|
33
|
-
LinkComponent?: ElementType;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export const Footer = ({
|
|
37
|
-
className,
|
|
38
|
-
brand,
|
|
39
|
-
logo,
|
|
40
|
-
logoSrc,
|
|
41
|
-
logoAlt,
|
|
42
|
-
logoClassName,
|
|
43
|
-
description,
|
|
44
|
-
columns,
|
|
45
|
-
socials,
|
|
46
|
-
bottom,
|
|
47
|
-
languageSwitcher,
|
|
48
|
-
option = "classic",
|
|
49
|
-
badge,
|
|
50
|
-
LinkComponent,
|
|
51
|
-
...props
|
|
52
|
-
}: FooterProps) => {
|
|
53
|
-
const Link = LinkComponent ?? "a";
|
|
54
|
-
|
|
55
|
-
const logoNode = (imageClassName = "h-7 w-auto") =>
|
|
56
|
-
logo ??
|
|
57
|
-
(logoSrc ? (
|
|
58
|
-
<img
|
|
59
|
-
src={logoSrc}
|
|
60
|
-
alt={logoAlt ?? (typeof brand === "string" ? brand : "Logo")}
|
|
61
|
-
className={cn(imageClassName, logoClassName)}
|
|
62
|
-
/>
|
|
63
|
-
) : null);
|
|
64
|
-
|
|
65
|
-
const brandNode = (
|
|
66
|
-
<span className="flex items-center gap-2 font-display text-lg font-bold tracking-tight">
|
|
67
|
-
<span aria-hidden className="inline-block h-2.5 w-2.5 rounded-sm bg-current" />
|
|
68
|
-
{brand}
|
|
69
|
-
</span>
|
|
70
|
-
);
|
|
71
|
-
|
|
72
|
-
const classicBrand = logoNode() ?? brandNode;
|
|
73
|
-
const minimalBrand = logoNode() ?? brandNode;
|
|
74
|
-
const editorialBrand = logoNode("h-12 w-auto") ?? (
|
|
75
|
-
<span className="font-display text-5xl font-bold tracking-[-0.03em] text-foreground sm:text-7xl">
|
|
76
|
-
{brand}
|
|
77
|
-
</span>
|
|
78
|
-
);
|
|
79
|
-
|
|
80
|
-
if (option === "minimal") {
|
|
81
|
-
return (
|
|
82
|
-
<footer className={cn("w-full border-t border-border bg-background", className)} {...props}>
|
|
83
|
-
<Container className="flex flex-col items-center justify-between gap-6 py-10 sm:flex-row">
|
|
84
|
-
{minimalBrand}
|
|
85
|
-
<ul className="flex flex-wrap items-center justify-center gap-x-8 gap-y-3">
|
|
86
|
-
{columns.flatMap((c) => c.links).slice(0, 6).map((link) => (
|
|
87
|
-
<li key={link.href}>
|
|
88
|
-
<Link href={link.href} className="text-sm text-muted-foreground transition-colors duration-150 hover:text-foreground">
|
|
89
|
-
{link.label}
|
|
90
|
-
</Link>
|
|
91
|
-
</li>
|
|
92
|
-
))}
|
|
93
|
-
</ul>
|
|
94
|
-
<div className="flex flex-col items-center gap-3">
|
|
95
|
-
{languageSwitcher ? <div>{languageSwitcher}</div> : null}
|
|
96
|
-
<div className="text-sm text-muted-foreground">{bottom}</div>
|
|
97
|
-
</div>
|
|
98
|
-
</Container>
|
|
99
|
-
</footer>
|
|
100
|
-
);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
if (option === "editorial") {
|
|
104
|
-
return (
|
|
105
|
-
<footer className={cn("w-full border-t border-border bg-background", className)} {...props}>
|
|
106
|
-
<Container className="py-16 sm:py-20">
|
|
107
|
-
<div className="mb-14 flex flex-col justify-between gap-8 sm:flex-row sm:items-end">
|
|
108
|
-
<div>
|
|
109
|
-
{editorialBrand}
|
|
110
|
-
{description ? (
|
|
111
|
-
<p className="mt-3 max-w-sm text-sm leading-6 text-muted-foreground">{description}</p>
|
|
112
|
-
) : null}
|
|
113
|
-
</div>
|
|
114
|
-
{socials ? <div className="flex items-center gap-3">{socials}</div> : null}
|
|
115
|
-
</div>
|
|
116
|
-
<div className="grid grid-cols-2 gap-8 border-t border-border pt-12 md:grid-cols-4">
|
|
117
|
-
{columns.map((column) => (
|
|
118
|
-
<div key={column.title}>
|
|
119
|
-
<h3 className="text-xs font-semibold uppercase tracking-[0.16em] text-muted-foreground">
|
|
120
|
-
{column.title}
|
|
121
|
-
</h3>
|
|
122
|
-
<ul className="mt-4 space-y-2.5">
|
|
123
|
-
{column.links.map((link) => (
|
|
124
|
-
<li key={link.href}>
|
|
125
|
-
<Link href={link.href} className="text-[15px] text-foreground/80 transition-colors duration-150 hover:text-foreground">
|
|
126
|
-
{link.label}
|
|
127
|
-
</Link>
|
|
128
|
-
</li>
|
|
129
|
-
))}
|
|
130
|
-
</ul>
|
|
131
|
-
</div>
|
|
132
|
-
))}
|
|
133
|
-
</div>
|
|
134
|
-
<div className="mt-12 flex flex-col items-center justify-between gap-4 border-t border-border pt-8 text-sm text-muted-foreground sm:flex-row">
|
|
135
|
-
<span>{bottom}</span>
|
|
136
|
-
{languageSwitcher ? <div>{languageSwitcher}</div> : null}
|
|
137
|
-
<span>© {new Date().getFullYear()} {brand}. All rights reserved.</span>
|
|
138
|
-
</div>
|
|
139
|
-
</Container>
|
|
140
|
-
</footer>
|
|
141
|
-
);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
return (
|
|
145
|
-
<footer className={cn("w-full bg-secondary text-on-secondary", className)} {...props}>
|
|
146
|
-
<Container className="py-16">
|
|
147
|
-
<div className="grid grid-cols-2 gap-10 md:grid-cols-4 lg:grid-cols-6">
|
|
148
|
-
<div className="col-span-2">
|
|
149
|
-
<span style={{ color: "rgb(var(--color-on-secondary))" }}>{classicBrand}</span>
|
|
150
|
-
{description ? (
|
|
151
|
-
<p className="mt-4 max-w-xs text-sm leading-6 text-on-secondary/60">{description}</p>
|
|
152
|
-
) : null}
|
|
153
|
-
{socials ? <div className="mt-6 flex items-center gap-3">{socials}</div> : null}
|
|
154
|
-
{badge ? <div className="mt-6">{badge}</div> : null}
|
|
155
|
-
</div>
|
|
156
|
-
{columns.map((column) => (
|
|
157
|
-
<div key={column.title}>
|
|
158
|
-
<h3 className="text-xs font-semibold uppercase tracking-[0.16em] text-on-secondary/50">
|
|
159
|
-
{column.title}
|
|
160
|
-
</h3>
|
|
161
|
-
<ul className="mt-4 space-y-2.5">
|
|
162
|
-
{column.links.map((link) => (
|
|
163
|
-
<li key={link.href}>
|
|
164
|
-
<Link
|
|
165
|
-
href={link.href}
|
|
166
|
-
className="text-sm text-on-secondary/70 transition-colors duration-150 hover:text-on-secondary"
|
|
167
|
-
>
|
|
168
|
-
{link.label}
|
|
169
|
-
</Link>
|
|
170
|
-
</li>
|
|
171
|
-
))}
|
|
172
|
-
</ul>
|
|
173
|
-
</div>
|
|
174
|
-
))}
|
|
175
|
-
</div>
|
|
176
|
-
<div className="mt-14 flex flex-col items-center justify-between gap-4 border-t border-white/10 pt-8 text-sm text-on-secondary/50 sm:flex-row">
|
|
177
|
-
<span>{bottom}</span>
|
|
178
|
-
{languageSwitcher ? <div>{languageSwitcher}</div> : null}
|
|
179
|
-
<span>© {new Date().getFullYear()} {brand}. All rights reserved.</span>
|
|
180
|
-
</div>
|
|
181
|
-
</Container>
|
|
182
|
-
</footer>
|
|
183
|
-
);
|
|
184
|
-
};
|
|
185
|
-
Footer.displayName = "Footer";
|
package/src/components/Hero.tsx
DELETED
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
import { forwardRef, type HTMLAttributes, type ReactNode } from "react";
|
|
2
|
-
import { cn } from "../utils/cn";
|
|
3
|
-
import { useTokenForeground } from "../utils/useTokenForeground";
|
|
4
|
-
|
|
5
|
-
export interface HeroMeta {
|
|
6
|
-
label: string;
|
|
7
|
-
icon?: ReactNode;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export interface HeroProps extends Omit<HTMLAttributes<HTMLElement>, "title"> {
|
|
11
|
-
variant?: "split" | "centered" | "statement";
|
|
12
|
-
eyebrow?: ReactNode;
|
|
13
|
-
title: ReactNode;
|
|
14
|
-
description?: ReactNode;
|
|
15
|
-
primaryAction?: ReactNode;
|
|
16
|
-
secondaryAction?: ReactNode;
|
|
17
|
-
media?: ReactNode;
|
|
18
|
-
/** Small trust/status row shown under the copy or CTAs. */
|
|
19
|
-
meta?: HeroMeta[];
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const titleBase =
|
|
23
|
-
"font-display font-semibold tracking-[-0.03em] text-balance text-foreground";
|
|
24
|
-
|
|
25
|
-
export const Hero = forwardRef<HTMLElement, HeroProps>(
|
|
26
|
-
(
|
|
27
|
-
{ className, variant = "split", eyebrow, title, description, primaryAction, secondaryAction, media, meta, id, ...props },
|
|
28
|
-
ref,
|
|
29
|
-
) => {
|
|
30
|
-
const isCentered = variant === "centered";
|
|
31
|
-
const isStatement = variant === "statement";
|
|
32
|
-
// Statement paints its own background from --color-secondary, so the
|
|
33
|
-
// readable text color must be derived from THAT token, not the brand.
|
|
34
|
-
const fg = useTokenForeground(isStatement ? "--color-secondary" : "--color-primary");
|
|
35
|
-
|
|
36
|
-
const container =
|
|
37
|
-
"relative mx-auto flex w-full max-w-6xl flex-col items-center px-5 sm:px-8";
|
|
38
|
-
|
|
39
|
-
const eyebrowNode =
|
|
40
|
-
typeof eyebrow === "string" ? (
|
|
41
|
-
<p className="text-xs font-semibold uppercase tracking-[0.2em] text-primary">{eyebrow}</p>
|
|
42
|
-
) : eyebrow ? (
|
|
43
|
-
<div>{eyebrow}</div>
|
|
44
|
-
) : null;
|
|
45
|
-
|
|
46
|
-
const actionsNode = primaryAction || secondaryAction ? (
|
|
47
|
-
<div className="flex flex-wrap items-center gap-4">{primaryAction}{secondaryAction}</div>
|
|
48
|
-
) : null;
|
|
49
|
-
|
|
50
|
-
/** Split — editorial grid: huge type left, media right, meta rule under. */
|
|
51
|
-
if (variant === "split") {
|
|
52
|
-
return (
|
|
53
|
-
<section
|
|
54
|
-
ref={ref}
|
|
55
|
-
id={id}
|
|
56
|
-
className={cn("relative w-full border-b border-border bg-background", className)}
|
|
57
|
-
{...props}
|
|
58
|
-
>
|
|
59
|
-
<div className={cn(container, "items-start gap-14 pt-20 pb-20 sm:pt-28 sm:pb-28 lg:grid lg:grid-cols-[1.05fr_0.95fr] lg:gap-20")}>
|
|
60
|
-
<div className="max-w-xl">
|
|
61
|
-
{eyebrowNode}
|
|
62
|
-
<h1 className={cn(titleBase, "mt-6 text-5xl leading-[1.03] sm:text-6xl lg:text-7xl")}>
|
|
63
|
-
{title}
|
|
64
|
-
</h1>
|
|
65
|
-
{description ? (
|
|
66
|
-
<p className="mt-7 max-w-md text-pretty leading-relaxed text-muted-foreground sm:text-lg">
|
|
67
|
-
{description}
|
|
68
|
-
</p>
|
|
69
|
-
) : null}
|
|
70
|
-
{actionsNode ? <div className="mt-10">{actionsNode}</div> : null}
|
|
71
|
-
{meta ? (
|
|
72
|
-
<ul className="mt-14 flex flex-wrap items-center gap-x-8 gap-y-3 border-t border-border pt-6">
|
|
73
|
-
{meta.map((m) => (
|
|
74
|
-
<li key={m.label} className="flex items-center gap-2 text-sm text-muted-foreground">
|
|
75
|
-
{m.icon ? <span className="text-foreground/35">{m.icon}</span> : null}
|
|
76
|
-
{m.label}
|
|
77
|
-
</li>
|
|
78
|
-
))}
|
|
79
|
-
</ul>
|
|
80
|
-
) : null}
|
|
81
|
-
</div>
|
|
82
|
-
{media ? <div className="w-full self-center">{media}</div> : null}
|
|
83
|
-
</div>
|
|
84
|
-
</section>
|
|
85
|
-
);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/** Centered — understated, one statement, generous white space. */
|
|
89
|
-
if (isCentered) {
|
|
90
|
-
return (
|
|
91
|
-
<section
|
|
92
|
-
ref={ref}
|
|
93
|
-
id={id}
|
|
94
|
-
className={cn("relative w-full border-b border-border bg-background", className)}
|
|
95
|
-
{...props}
|
|
96
|
-
>
|
|
97
|
-
<div className={cn(container, "pt-24 pb-20 text-center sm:pt-32 sm:pb-24")}>
|
|
98
|
-
{eyebrowNode}
|
|
99
|
-
<h1 className={cn(titleBase, "mt-7 max-w-4xl text-5xl leading-[1.04] sm:text-6xl lg:text-7xl")}>
|
|
100
|
-
{title}
|
|
101
|
-
</h1>
|
|
102
|
-
{description ? (
|
|
103
|
-
<p className="mx-auto mt-7 max-w-xl text-pretty leading-relaxed text-muted-foreground sm:text-lg">
|
|
104
|
-
{description}
|
|
105
|
-
</p>
|
|
106
|
-
) : null}
|
|
107
|
-
{actionsNode ? <div className="mt-10 justify-center">{actionsNode}</div> : null}
|
|
108
|
-
{meta ? (
|
|
109
|
-
<ul className="mt-14 flex flex-wrap items-center justify-center gap-x-9 gap-y-3">
|
|
110
|
-
{meta.map((m, i) => (
|
|
111
|
-
<li
|
|
112
|
-
key={m.label}
|
|
113
|
-
className={cn(
|
|
114
|
-
"flex items-center gap-2 text-sm text-muted-foreground",
|
|
115
|
-
i > 0 && "sm:border-l sm:border-border sm:pl-9",
|
|
116
|
-
)}
|
|
117
|
-
>
|
|
118
|
-
{m.icon ? <span className="text-foreground/35">{m.icon}</span> : null}
|
|
119
|
-
{m.label}
|
|
120
|
-
</li>
|
|
121
|
-
))}
|
|
122
|
-
</ul>
|
|
123
|
-
) : null}
|
|
124
|
-
{media ? <div className="mt-16 w-full">{media}</div> : null}
|
|
125
|
-
</div>
|
|
126
|
-
</section>
|
|
127
|
-
);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/** Statement — full-bleed ink band. No eyebrow, pure message. */
|
|
131
|
-
return (
|
|
132
|
-
<section
|
|
133
|
-
ref={ref}
|
|
134
|
-
id={id}
|
|
135
|
-
className={cn("relative w-full overflow-hidden bg-secondary", className)}
|
|
136
|
-
style={{ color: fg }}
|
|
137
|
-
{...props}
|
|
138
|
-
>
|
|
139
|
-
<div aria-hidden className="pointer-events-none absolute inset-0 grain opacity-[0.07] mix-blend-overlay" />
|
|
140
|
-
<div aria-hidden className="pointer-events-none absolute inset-x-0 top-0 h-px bg-white/10" />
|
|
141
|
-
<div className={cn(container, "pt-28 pb-24 text-center sm:pt-36 sm:pb-32")}>
|
|
142
|
-
<h1
|
|
143
|
-
className="max-w-5xl font-display text-[2.9rem] font-semibold leading-[1.02] tracking-[-0.04em] text-balance sm:text-7xl lg:text-8xl"
|
|
144
|
-
style={{ color: fg }}
|
|
145
|
-
>
|
|
146
|
-
{title}
|
|
147
|
-
</h1>
|
|
148
|
-
{description ? (
|
|
149
|
-
<p className="mx-auto mt-8 max-w-xl text-pretty leading-relaxed sm:text-lg" style={{ color: fg, opacity: 0.72 }}>
|
|
150
|
-
{description}
|
|
151
|
-
</p>
|
|
152
|
-
) : null}
|
|
153
|
-
{actionsNode ? <div className="mt-11 justify-center">{actionsNode}</div> : null}
|
|
154
|
-
{meta ? (
|
|
155
|
-
<ul className="mt-16 flex flex-wrap items-center justify-center gap-x-10 gap-y-3">
|
|
156
|
-
{meta.map((m) => (
|
|
157
|
-
<li key={m.label} className="flex items-center gap-2 text-sm font-medium" style={{ color: fg, opacity: 0.72 }}>
|
|
158
|
-
{m.icon ? <span>{m.icon}</span> : null}
|
|
159
|
-
{m.label}
|
|
160
|
-
</li>
|
|
161
|
-
))}
|
|
162
|
-
</ul>
|
|
163
|
-
) : null}
|
|
164
|
-
</div>
|
|
165
|
-
</section>
|
|
166
|
-
);
|
|
167
|
-
},
|
|
168
|
-
);
|
|
169
|
-
Hero.displayName = "Hero";
|