@tuturuuu/ui 0.29.2 → 0.30.1
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/CHANGELOG.md +18 -0
- package/package.json +9 -4
- package/src/components/ui/command.tsx +9 -1
- package/src/components/ui/custom/__tests__/tuturuuu-wordmark.test.ts +22 -0
- package/src/components/ui/custom/tuturuuu-wordmark.tsx +72 -0
- package/src/components/ui/marketing/accents.ts +139 -0
- package/src/components/ui/marketing/atmosphere.tsx +155 -0
- package/src/components/ui/marketing/cta.tsx +150 -0
- package/src/components/ui/marketing/faq-list.tsx +55 -0
- package/src/components/ui/marketing/fonts.ts +33 -0
- package/src/components/ui/marketing/index.ts +37 -0
- package/src/components/ui/marketing/marketing-footer.tsx +91 -0
- package/src/components/ui/marketing/marketing-nav.tsx +167 -0
- package/src/components/ui/marketing/section-shell.tsx +148 -0
- package/src/components/ui/marketing/stat-band.tsx +46 -0
- package/src/components/ui/marketing/surface-card.tsx +144 -0
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { ArrowUpRight } from '@tuturuuu/icons';
|
|
2
|
+
import { cn } from '@tuturuuu/utils/format';
|
|
3
|
+
import type { ComponentType, ReactNode } from 'react';
|
|
4
|
+
import { getMarketingAccent, type MarketingAccent } from './accents';
|
|
5
|
+
|
|
6
|
+
interface SurfaceCardProps {
|
|
7
|
+
accent: MarketingAccent;
|
|
8
|
+
icon?: ComponentType<{ className?: string }>;
|
|
9
|
+
title: ReactNode;
|
|
10
|
+
description: ReactNode;
|
|
11
|
+
/** Small mono label above the title (a role, a category). */
|
|
12
|
+
eyebrow?: ReactNode;
|
|
13
|
+
/** Renders the card as a link and reveals a corner arrow. */
|
|
14
|
+
href?: string;
|
|
15
|
+
/** Opens the link in a new tab — used for apps on their own domain. */
|
|
16
|
+
external?: boolean;
|
|
17
|
+
/** `stack` puts the icon above the copy; `inline` places it alongside. */
|
|
18
|
+
layout?: 'stack' | 'inline';
|
|
19
|
+
size?: 'md' | 'lg';
|
|
20
|
+
/** Extra content rendered under the description (a chip row, a mini preview). */
|
|
21
|
+
footer?: ReactNode;
|
|
22
|
+
className?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The marketing card surface: border, lit top edge, corner bloom, hover lift.
|
|
27
|
+
*
|
|
28
|
+
* Every feature grid, use-case tile and app link on a satellite landing page
|
|
29
|
+
* routes through this, so a change to the treatment lands everywhere at once
|
|
30
|
+
* instead of being re-typed per section.
|
|
31
|
+
*/
|
|
32
|
+
export function SurfaceCard({
|
|
33
|
+
accent,
|
|
34
|
+
icon: Icon,
|
|
35
|
+
title,
|
|
36
|
+
description,
|
|
37
|
+
eyebrow,
|
|
38
|
+
href,
|
|
39
|
+
external,
|
|
40
|
+
layout = 'stack',
|
|
41
|
+
size = 'md',
|
|
42
|
+
footer,
|
|
43
|
+
className,
|
|
44
|
+
}: SurfaceCardProps) {
|
|
45
|
+
const styles = getMarketingAccent(accent);
|
|
46
|
+
const Root = href ? 'a' : 'div';
|
|
47
|
+
const isLarge = size === 'lg';
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<Root
|
|
51
|
+
{...(href ? { href } : {})}
|
|
52
|
+
{...(href && external
|
|
53
|
+
? { target: '_blank', rel: 'noopener noreferrer' }
|
|
54
|
+
: {})}
|
|
55
|
+
className={cn(
|
|
56
|
+
'group relative flex h-full overflow-hidden rounded-2xl border border-foreground/[0.08] bg-foreground/[0.015] transition-all duration-500',
|
|
57
|
+
'hover:-translate-y-1 hover:border-foreground/15 hover:bg-foreground/[0.03]',
|
|
58
|
+
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
|
|
59
|
+
layout === 'inline' ? 'items-start gap-3 p-4' : 'flex-col',
|
|
60
|
+
layout === 'stack' && (isLarge ? 'p-6 sm:p-7' : 'p-5'),
|
|
61
|
+
className
|
|
62
|
+
)}
|
|
63
|
+
>
|
|
64
|
+
<span
|
|
65
|
+
aria-hidden
|
|
66
|
+
className={cn(
|
|
67
|
+
'pointer-events-none absolute inset-x-6 top-0 h-px bg-gradient-to-r from-transparent to-transparent transition-opacity duration-500 group-hover:opacity-100',
|
|
68
|
+
layout === 'inline' ? 'opacity-0' : 'opacity-40',
|
|
69
|
+
styles.rule
|
|
70
|
+
)}
|
|
71
|
+
/>
|
|
72
|
+
<span
|
|
73
|
+
aria-hidden
|
|
74
|
+
className={cn(
|
|
75
|
+
'pointer-events-none absolute -top-16 -right-10 h-40 w-40 rounded-full opacity-0 blur-3xl transition-opacity duration-700 group-hover:opacity-100',
|
|
76
|
+
styles.bloom
|
|
77
|
+
)}
|
|
78
|
+
/>
|
|
79
|
+
|
|
80
|
+
{Icon ? (
|
|
81
|
+
layout === 'inline' ? (
|
|
82
|
+
<span
|
|
83
|
+
className={cn(
|
|
84
|
+
'relative flex h-8 w-8 shrink-0 items-center justify-center rounded-lg border border-foreground/10 bg-foreground/[0.03] transition-transform duration-500 group-hover:scale-105',
|
|
85
|
+
styles.text
|
|
86
|
+
)}
|
|
87
|
+
>
|
|
88
|
+
<Icon className="h-3.5 w-3.5" />
|
|
89
|
+
</span>
|
|
90
|
+
) : (
|
|
91
|
+
<Icon
|
|
92
|
+
className={cn(
|
|
93
|
+
'relative transition-transform duration-500 group-hover:scale-110',
|
|
94
|
+
isLarge ? 'h-5 w-5' : 'h-4 w-4',
|
|
95
|
+
styles.text
|
|
96
|
+
)}
|
|
97
|
+
/>
|
|
98
|
+
)
|
|
99
|
+
) : null}
|
|
100
|
+
|
|
101
|
+
<span className={cn('relative min-w-0', layout === 'inline' && 'flex-1')}>
|
|
102
|
+
{eyebrow ? (
|
|
103
|
+
<span
|
|
104
|
+
className={cn(
|
|
105
|
+
'block font-mono-ui text-[0.6rem] uppercase tracking-[0.18em]',
|
|
106
|
+
layout === 'stack' && Icon && 'mt-4',
|
|
107
|
+
styles.text
|
|
108
|
+
)}
|
|
109
|
+
>
|
|
110
|
+
{eyebrow}
|
|
111
|
+
</span>
|
|
112
|
+
) : null}
|
|
113
|
+
|
|
114
|
+
<span
|
|
115
|
+
className={cn(
|
|
116
|
+
'flex items-center gap-1.5 font-display font-semibold tracking-[-0.01em]',
|
|
117
|
+
layout === 'stack' &&
|
|
118
|
+
!eyebrow &&
|
|
119
|
+
Icon &&
|
|
120
|
+
(isLarge ? 'mt-5' : 'mt-4'),
|
|
121
|
+
eyebrow && 'mt-1.5',
|
|
122
|
+
isLarge ? 'text-xl sm:text-2xl' : 'text-[0.95rem]'
|
|
123
|
+
)}
|
|
124
|
+
>
|
|
125
|
+
{title}
|
|
126
|
+
{href ? (
|
|
127
|
+
<ArrowUpRight className="h-3 w-3 shrink-0 text-foreground/25 transition-all duration-300 group-hover:translate-x-0.5 group-hover:-translate-y-0.5 group-hover:text-foreground/50" />
|
|
128
|
+
) : null}
|
|
129
|
+
</span>
|
|
130
|
+
|
|
131
|
+
<span
|
|
132
|
+
className={cn(
|
|
133
|
+
'mt-2 block text-foreground/50 leading-relaxed',
|
|
134
|
+
isLarge ? 'max-w-md text-sm' : 'text-xs'
|
|
135
|
+
)}
|
|
136
|
+
>
|
|
137
|
+
{description}
|
|
138
|
+
</span>
|
|
139
|
+
|
|
140
|
+
{footer ? <span className="relative mt-4 block">{footer}</span> : null}
|
|
141
|
+
</span>
|
|
142
|
+
</Root>
|
|
143
|
+
);
|
|
144
|
+
}
|