This commit is contained in:
67
src/components/Button.tsx
Normal file
67
src/components/Button.tsx
Normal file
@@ -0,0 +1,67 @@
|
||||
import * as React from 'react';
|
||||
import { ArrowRight } from 'lucide-react';
|
||||
import { motion } from 'framer-motion';
|
||||
import Link from 'next/link';
|
||||
|
||||
interface ButtonProps {
|
||||
href: string;
|
||||
children: React.ReactNode;
|
||||
variant?: 'primary' | 'outline';
|
||||
className?: string;
|
||||
showArrow?: boolean;
|
||||
}
|
||||
|
||||
export const Button: React.FC<ButtonProps> = ({
|
||||
href,
|
||||
children,
|
||||
variant = 'primary',
|
||||
className = "",
|
||||
showArrow = true
|
||||
}) => {
|
||||
const baseStyles = "inline-flex items-center gap-4 rounded-full font-bold uppercase tracking-widest transition-all duration-500 ease-[cubic-bezier(0.23,1,0.32,1)] group";
|
||||
|
||||
const variants = {
|
||||
primary: "px-10 py-5 bg-slate-900 text-white hover:bg-slate-800 hover:-translate-y-1 hover:shadow-2xl hover:shadow-slate-900/20 text-sm",
|
||||
outline: "px-8 py-4 border border-slate-200 bg-white text-slate-900 hover:border-slate-400 hover:bg-slate-50 hover:-translate-y-0.5 hover:shadow-xl hover:shadow-slate-100 text-sm"
|
||||
};
|
||||
|
||||
const content = (
|
||||
<>
|
||||
{children}
|
||||
{showArrow && <ArrowRight className="w-4 h-4 group-hover:translate-x-1 transition-transform" />}
|
||||
</>
|
||||
);
|
||||
|
||||
if (href.startsWith('#')) {
|
||||
return (
|
||||
<a href={href} className={`${baseStyles} ${variants[variant]} ${className}`}>
|
||||
{content}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Link href={href} className={`${baseStyles} ${variants[variant]} ${className}`}>
|
||||
{content}
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
export const MotionButton: React.FC<ButtonProps> = ({
|
||||
href,
|
||||
children,
|
||||
variant = 'primary',
|
||||
className = "",
|
||||
showArrow = true
|
||||
}) => {
|
||||
return (
|
||||
<motion.div
|
||||
whileHover={{ scale: 1.02 }}
|
||||
whileTap={{ scale: 0.98 }}
|
||||
>
|
||||
<Button href={href} variant={variant} className={className} showArrow={showArrow}>
|
||||
{children}
|
||||
</Button>
|
||||
</motion.div>
|
||||
);
|
||||
};
|
||||
@@ -8,7 +8,6 @@ interface LineProps {
|
||||
delay?: number;
|
||||
}
|
||||
|
||||
// ... existing components ...
|
||||
export const HeroLines: React.FC<LineProps> = ({ className = "", delay = 0 }) => {
|
||||
return (
|
||||
<svg className={`absolute pointer-events-none ${className}`} viewBox="0 0 800 600" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
@@ -216,150 +215,7 @@ export const ComparisonLines: React.FC<LineProps> = ({ className = "", delay = 0
|
||||
)
|
||||
}
|
||||
|
||||
// --- New Connector Components ---
|
||||
|
||||
export const ConnectorStart: React.FC<LineProps> = ({ className = "", delay = 0 }) => {
|
||||
return (
|
||||
<svg className={`absolute pointer-events-none ${className}`} viewBox="0 0 100 400" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<motion.path
|
||||
d="M 50 0 V 400"
|
||||
stroke="currentColor"
|
||||
strokeWidth="3"
|
||||
className="text-slate-300"
|
||||
initial={{ pathLength: 0 }}
|
||||
whileInView={{ pathLength: 1 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 1.5, delay: delay }}
|
||||
/>
|
||||
<circle cx="50" cy="10" r="8" className="fill-slate-900" />
|
||||
|
||||
{/* Multiple Pulses */}
|
||||
<motion.circle r="5" fill="currentColor" className="text-slate-900">
|
||||
<animateMotion
|
||||
dur="4s"
|
||||
repeatCount="indefinite"
|
||||
path="M 50 0 V 400"
|
||||
/>
|
||||
</motion.circle>
|
||||
<motion.circle r="5" fill="currentColor" className="text-slate-900">
|
||||
<animateMotion
|
||||
dur="4s"
|
||||
begin="2s"
|
||||
repeatCount="indefinite"
|
||||
path="M 50 0 V 400"
|
||||
/>
|
||||
</motion.circle>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export const ConnectorBranch: React.FC<LineProps> = ({ className = "", delay = 0 }) => {
|
||||
return (
|
||||
<svg className={`absolute pointer-events-none ${className}`} viewBox="0 0 200 400" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
{/* Main vertical line */}
|
||||
<motion.path
|
||||
d="M 50 0 V 400"
|
||||
stroke="currentColor"
|
||||
strokeWidth="3"
|
||||
className="text-slate-300"
|
||||
initial={{ pathLength: 0 }}
|
||||
whileInView={{ pathLength: 1 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 1.5, delay: delay }}
|
||||
/>
|
||||
{/* Branch */}
|
||||
<motion.path
|
||||
d="M 50 100 C 50 150, 100 150, 150 150 H 200"
|
||||
stroke="currentColor"
|
||||
strokeWidth="3"
|
||||
className="text-slate-300"
|
||||
initial={{ pathLength: 0 }}
|
||||
whileInView={{ pathLength: 1 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 1.5, delay: delay + 0.5 }}
|
||||
/>
|
||||
|
||||
{/* Pulses */}
|
||||
<motion.circle r="5" fill="currentColor" className="text-slate-900">
|
||||
<animateMotion
|
||||
dur="4s"
|
||||
repeatCount="indefinite"
|
||||
path="M 50 0 V 400"
|
||||
/>
|
||||
</motion.circle>
|
||||
<motion.circle r="5" fill="currentColor" className="text-slate-900">
|
||||
<animateMotion
|
||||
dur="3s"
|
||||
repeatCount="indefinite"
|
||||
path="M 50 100 C 50 150, 100 150, 150 150 H 200"
|
||||
/>
|
||||
</motion.circle>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export const ConnectorSplit: React.FC<LineProps> = ({ className = "", delay = 0 }) => {
|
||||
return (
|
||||
<svg className={`absolute pointer-events-none ${className}`} viewBox="0 0 200 400" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<motion.path
|
||||
d="M 50 0 V 50 C 50 100, 100 100, 150 100 H 200"
|
||||
stroke="currentColor"
|
||||
strokeWidth="3"
|
||||
className="text-slate-300"
|
||||
initial={{ pathLength: 0 }}
|
||||
whileInView={{ pathLength: 1 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 1.5, delay: delay }}
|
||||
/>
|
||||
<motion.path
|
||||
d="M 50 50 V 400"
|
||||
stroke="currentColor"
|
||||
strokeWidth="3"
|
||||
className="text-slate-300"
|
||||
initial={{ pathLength: 0 }}
|
||||
whileInView={{ pathLength: 1 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 1.5, delay: delay }}
|
||||
/>
|
||||
<motion.circle r="5" fill="currentColor" className="text-slate-900">
|
||||
<animateMotion
|
||||
dur="4s"
|
||||
repeatCount="indefinite"
|
||||
path="M 50 0 V 400"
|
||||
/>
|
||||
</motion.circle>
|
||||
<motion.circle r="5" fill="currentColor" className="text-slate-900">
|
||||
<animateMotion
|
||||
dur="3s"
|
||||
repeatCount="indefinite"
|
||||
path="M 50 0 V 50 C 50 100, 100 100, 150 100 H 200"
|
||||
/>
|
||||
</motion.circle>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export const ConnectorEnd: React.FC<LineProps> = ({ className = "", delay = 0 }) => {
|
||||
return (
|
||||
<svg className={`absolute pointer-events-none ${className}`} viewBox="0 0 100 400" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<motion.path
|
||||
d="M 50 0 V 200"
|
||||
stroke="currentColor"
|
||||
strokeWidth="3"
|
||||
className="text-slate-300"
|
||||
initial={{ pathLength: 0 }}
|
||||
whileInView={{ pathLength: 1 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 1.5, delay: delay }}
|
||||
/>
|
||||
<circle cx="50" cy="200" r="8" className="fill-slate-900" />
|
||||
<motion.circle r="5" fill="currentColor" className="text-slate-900">
|
||||
<animateMotion
|
||||
dur="2s"
|
||||
repeatCount="indefinite"
|
||||
path="M 50 0 V 200"
|
||||
/>
|
||||
</motion.circle>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
export const ConnectorStart: React.FC<LineProps> = ({ className = "", delay = 0 }) => null;
|
||||
export const ConnectorBranch: React.FC<LineProps> = ({ className = "", delay = 0 }) => null;
|
||||
export const ConnectorSplit: React.FC<LineProps> = ({ className = "", delay = 0 }) => null;
|
||||
export const ConnectorEnd: React.FC<LineProps> = ({ className = "", delay = 0 }) => null;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import * as React from 'react';
|
||||
import { ArrowRight } from 'lucide-react';
|
||||
import { Reveal } from '../Reveal';
|
||||
import { Label, H3, LeadText } from '../Typography';
|
||||
|
||||
interface ComparisonRowProps {
|
||||
negativeLabel: string;
|
||||
@@ -23,12 +24,12 @@ export const ComparisonRow: React.FC<ComparisonRowProps> = ({
|
||||
<Reveal delay={delay}>
|
||||
<div className={`flex flex-col ${reverse ? 'md:flex-row-reverse' : 'md:flex-row'} gap-8 md:gap-12 items-center`}>
|
||||
<div className="flex-1 p-8 md:p-10 bg-slate-50/50 rounded-2xl text-slate-400 border border-transparent w-full">
|
||||
<div className="text-[10px] font-bold uppercase tracking-[0.2em] mb-4 line-through decoration-slate-200">
|
||||
<Label className="mb-4 line-through decoration-slate-200">
|
||||
{negativeLabel}
|
||||
</div>
|
||||
<div className="text-lg md:text-xl font-serif italic line-through decoration-slate-200 leading-snug">
|
||||
</Label>
|
||||
<LeadText className="line-through decoration-slate-200 leading-snug">
|
||||
{negativeText}
|
||||
</div>
|
||||
</LeadText>
|
||||
</div>
|
||||
|
||||
<div className="shrink-0">
|
||||
@@ -36,12 +37,12 @@ export const ComparisonRow: React.FC<ComparisonRowProps> = ({
|
||||
</div>
|
||||
|
||||
<div className="flex-1 p-8 md:p-10 border border-slate-100 rounded-2xl bg-white hover:border-slate-200 transition-all duration-500 hover:shadow-xl hover:shadow-slate-100/50 w-full">
|
||||
<div className="text-[10px] font-bold uppercase tracking-[0.2em] text-slate-900 mb-4">
|
||||
<Label className="text-slate-900 mb-4">
|
||||
{positiveLabel}
|
||||
</div>
|
||||
<div className="text-2xl md:text-3xl font-bold text-slate-900 leading-tight tracking-tight">
|
||||
</Label>
|
||||
<H3 className="text-2xl md:text-3xl">
|
||||
{positiveText}
|
||||
</div>
|
||||
</H3>
|
||||
</div>
|
||||
</div>
|
||||
</Reveal>
|
||||
|
||||
@@ -13,3 +13,4 @@ export * from './HeroMainIllustration';
|
||||
export * from './ExperienceIllustration';
|
||||
export * from './ResponsibilityIllustration';
|
||||
export * from './ResultIllustration';
|
||||
export * from './WebsitesDescriptive';
|
||||
|
||||
68
src/components/Layout.tsx
Normal file
68
src/components/Layout.tsx
Normal file
@@ -0,0 +1,68 @@
|
||||
import * as React from 'react';
|
||||
import { cn } from '../utils/cn';
|
||||
|
||||
export const BackgroundGrid: React.FC = () => (
|
||||
<div className="fixed inset-0 pointer-events-none -z-20 opacity-[0.01]" style={{
|
||||
backgroundImage: 'linear-gradient(#0f172a 1px, transparent 1px), linear-gradient(90deg, #0f172a 1px, transparent 1px)',
|
||||
backgroundSize: '60px 60px'
|
||||
}} />
|
||||
);
|
||||
|
||||
interface CardProps {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
variant?: 'white' | 'dark' | 'gray';
|
||||
hover?: boolean;
|
||||
padding?: 'none' | 'small' | 'normal' | 'large';
|
||||
}
|
||||
|
||||
export const Card: React.FC<CardProps> = ({
|
||||
children,
|
||||
className = "",
|
||||
variant = 'white',
|
||||
hover = true,
|
||||
padding = 'normal'
|
||||
}) => {
|
||||
const variants = {
|
||||
white: 'bg-white border-slate-100 text-slate-900 shadow-sm',
|
||||
dark: 'bg-slate-900 border-white/5 text-white shadow-xl',
|
||||
gray: 'bg-slate-50/50 border-slate-100 text-slate-900'
|
||||
};
|
||||
|
||||
const paddings = {
|
||||
none: 'p-0',
|
||||
small: 'p-6 md:p-8',
|
||||
normal: 'p-8 md:p-10',
|
||||
large: 'p-10 md:p-12'
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={cn(
|
||||
"rounded-2xl border h-full flex flex-col justify-between transition-all duration-500 ease-out",
|
||||
variants[variant],
|
||||
paddings[padding],
|
||||
hover ? 'hover:border-slate-200 hover:shadow-md' : '',
|
||||
className
|
||||
)}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const Container: React.FC<{ children: React.ReactNode; className?: string; variant?: 'narrow' | 'normal' | 'wide' }> = ({
|
||||
children,
|
||||
className = "",
|
||||
variant = 'normal'
|
||||
}) => {
|
||||
const variants = {
|
||||
narrow: 'max-w-4xl',
|
||||
normal: 'max-w-6xl',
|
||||
wide: 'max-w-7xl'
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={cn("mx-auto px-6 w-full", variants[variant], className)}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -2,6 +2,8 @@ import * as React from 'react';
|
||||
import { ArrowLeft } from 'lucide-react';
|
||||
import Link from 'next/link';
|
||||
import { Reveal } from './Reveal';
|
||||
import { H1, LeadText } from './Typography';
|
||||
import { cn } from '../utils/cn';
|
||||
|
||||
interface PageHeaderProps {
|
||||
title: React.ReactNode;
|
||||
@@ -11,18 +13,20 @@ interface PageHeaderProps {
|
||||
label: string;
|
||||
};
|
||||
backgroundSymbol?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const PageHeader: React.FC<PageHeaderProps> = ({
|
||||
title,
|
||||
description,
|
||||
backLink,
|
||||
backgroundSymbol
|
||||
backgroundSymbol,
|
||||
className = ""
|
||||
}) => {
|
||||
return (
|
||||
<section className="narrow-container relative">
|
||||
<section className={cn("narrow-container relative pt-24 pb-16 md:pt-40 md:pb-24", className)}>
|
||||
{backgroundSymbol && (
|
||||
<div className="absolute -left-24 -top-24 text-[20rem] font-bold text-slate-50 select-none -z-10 opacity-50">
|
||||
<div className="absolute -left-24 -top-12 text-[20rem] md:text-[24rem] font-bold text-slate-50 select-none -z-10 opacity-40 tracking-tighter leading-none">
|
||||
{backgroundSymbol}
|
||||
</div>
|
||||
)}
|
||||
@@ -30,24 +34,24 @@ export const PageHeader: React.FC<PageHeaderProps> = ({
|
||||
{backLink && (
|
||||
<Link
|
||||
href={backLink.href}
|
||||
className="inline-flex items-center gap-3 px-4 py-2 border border-slate-200 rounded-full text-slate-400 hover:text-slate-900 hover:border-slate-400 hover:bg-slate-50 mb-12 transition-all duration-500 ease-[cubic-bezier(0.23,1,0.32,1)] hover:-translate-y-0.5 hover:shadow-lg hover:shadow-slate-100 font-bold text-[10px] uppercase tracking-[0.3em] group"
|
||||
className="inline-flex items-center gap-2 text-slate-400 hover:text-slate-900 mb-12 transition-colors font-bold text-[10px] uppercase tracking-[0.4em] group"
|
||||
>
|
||||
<ArrowLeft className="w-4 h-4 group-hover:-translate-x-1 transition-transform" /> {backLink.label}
|
||||
<ArrowLeft className="w-3 h-3 group-hover:-translate-x-1 transition-transform" /> {backLink.label}
|
||||
</Link>
|
||||
)}
|
||||
|
||||
<div className="space-y-16">
|
||||
<div className="space-y-8 relative">
|
||||
<Reveal>
|
||||
<h1 className="text-6xl md:text-8xl font-bold text-slate-900 tracking-tighter leading-[0.95]">
|
||||
<H1 className="max-w-4xl">
|
||||
{title}
|
||||
</h1>
|
||||
</H1>
|
||||
</Reveal>
|
||||
|
||||
{description && (
|
||||
<Reveal delay={0.2}>
|
||||
<p className="text-2xl md:text-3xl text-slate-500 font-serif italic leading-snug max-w-3xl">
|
||||
<LeadText className="text-xl md:text-2xl text-slate-400 leading-relaxed max-w-2xl font-serif italic">
|
||||
{description}
|
||||
</p>
|
||||
</LeadText>
|
||||
</Reveal>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import * as React from 'react';
|
||||
import { Reveal } from './Reveal';
|
||||
import { Label } from './Typography';
|
||||
import { cn } from '../utils/cn';
|
||||
|
||||
interface SectionProps {
|
||||
number?: string;
|
||||
@@ -9,7 +11,7 @@ interface SectionProps {
|
||||
delay?: number;
|
||||
variant?: 'white' | 'gray';
|
||||
borderTop?: boolean;
|
||||
connector?: React.ReactNode;
|
||||
borderBottom?: boolean;
|
||||
containerVariant?: 'narrow' | 'normal' | 'wide';
|
||||
illustration?: React.ReactNode;
|
||||
}
|
||||
@@ -22,93 +24,59 @@ export const Section: React.FC<SectionProps> = ({
|
||||
delay = 0,
|
||||
variant = 'white',
|
||||
borderTop = false,
|
||||
connector,
|
||||
borderBottom = false,
|
||||
containerVariant = 'narrow',
|
||||
illustration,
|
||||
}) => {
|
||||
const bgClass = variant === 'gray' ? 'bg-slate-50' : 'bg-white';
|
||||
const borderClass = borderTop ? 'border-t border-slate-100' : '';
|
||||
const bgClass = variant === 'gray' ? 'bg-slate-50/50' : 'bg-white';
|
||||
const borderTopClass = borderTop ? 'border-t border-slate-100' : '';
|
||||
const borderBottomClass = borderBottom ? 'border-b border-slate-100' : '';
|
||||
const containerClass = containerVariant === 'wide' ? 'wide-container' : containerVariant === 'normal' ? 'container' : 'narrow-container';
|
||||
|
||||
// If no number and title, or if we want to force a simple layout, we could add a prop.
|
||||
// But let's make it smart: if it's wide, maybe we want the title on top anyway?
|
||||
// The user specifically asked to move it above for the configurator.
|
||||
|
||||
const isTopTitle = containerVariant === 'wide';
|
||||
|
||||
return (
|
||||
<section className={`relative py-24 md:py-32 group ${bgClass} ${borderClass} ${className}`}>
|
||||
<div className={containerClass}>
|
||||
{isTopTitle ? (
|
||||
<div className="space-y-16">
|
||||
{(number || title) && (
|
||||
<div className="flex flex-col md:flex-row md:items-end gap-6 md:gap-12">
|
||||
{number && (
|
||||
<Reveal delay={delay}>
|
||||
<span className="block text-6xl md:text-8xl font-bold text-slate-100 leading-none select-none">
|
||||
{number}
|
||||
</span>
|
||||
</Reveal>
|
||||
)}
|
||||
{title && (
|
||||
<Reveal delay={delay + 0.1}>
|
||||
<div className="flex items-center gap-3 mb-2 md:mb-4">
|
||||
<div className="h-px w-6 bg-slate-900"></div>
|
||||
<h2 className="text-xs font-bold uppercase tracking-[0.3em] text-slate-900">
|
||||
{title}
|
||||
</h2>
|
||||
</div>
|
||||
</Reveal>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div>{children}</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 md:grid-cols-12 gap-12 md:gap-16">
|
||||
{/* Sidebar: Number & Title */}
|
||||
<div className="md:col-span-3 relative">
|
||||
{/* Connector Line */}
|
||||
{connector && (
|
||||
<div className="absolute left-[2.5rem] top-0 bottom-0 w-24 hidden md:block -z-10 pointer-events-none">
|
||||
{connector}
|
||||
</div>
|
||||
<section className={cn(
|
||||
"relative py-24 md:py-40 group overflow-hidden",
|
||||
bgClass,
|
||||
borderTopClass,
|
||||
borderBottomClass,
|
||||
className
|
||||
)}>
|
||||
<div className={cn("relative z-10", containerClass)}>
|
||||
<div className="grid grid-cols-1 md:grid-cols-12 gap-12 md:gap-24">
|
||||
{/* Sidebar: Number & Title */}
|
||||
<div className="md:col-span-3">
|
||||
<div className="md:sticky md:top-40 space-y-8">
|
||||
{number && (
|
||||
<Reveal delay={delay}>
|
||||
<span className="block text-7xl md:text-8xl font-bold text-slate-100 leading-none select-none tracking-tighter">
|
||||
{number}
|
||||
</span>
|
||||
</Reveal>
|
||||
)}
|
||||
{title && (
|
||||
<Reveal delay={delay + 0.1}>
|
||||
<div className="flex items-center gap-4">
|
||||
<Label className="text-slate-900 text-[10px] tracking-[0.4em]">
|
||||
{title}
|
||||
</Label>
|
||||
</div>
|
||||
</Reveal>
|
||||
)}
|
||||
{illustration && (
|
||||
<Reveal delay={delay + 0.2}>
|
||||
<div className="pt-8 opacity-100 group-hover:scale-105 transition-transform duration-1000 ease-out origin-left grayscale hover:grayscale-0">
|
||||
{illustration}
|
||||
</div>
|
||||
</Reveal>
|
||||
)}
|
||||
|
||||
<div className="md:sticky md:top-32 space-y-6">
|
||||
{number && (
|
||||
<Reveal delay={delay}>
|
||||
<span className="block text-6xl md:text-8xl font-bold text-slate-100 leading-none select-none relative bg-white/0 backdrop-blur-[2px]">
|
||||
{number}
|
||||
</span>
|
||||
</Reveal>
|
||||
)}
|
||||
{title && (
|
||||
<Reveal delay={delay + 0.1}>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="h-px w-6 bg-slate-900"></div>
|
||||
<h2 className="text-xs font-bold uppercase tracking-[0.3em] text-slate-900">
|
||||
{title}
|
||||
</h2>
|
||||
</div>
|
||||
</Reveal>
|
||||
)}
|
||||
{illustration && (
|
||||
<Reveal delay={delay + 0.2}>
|
||||
<div className="pt-12 opacity-100 group-hover:scale-110 transition-transform duration-700 origin-left">
|
||||
{illustration}
|
||||
</div>
|
||||
</Reveal>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Main Content */}
|
||||
<div className="md:col-span-9">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Main Content */}
|
||||
<div className="md:col-span-9">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
|
||||
54
src/components/Typography.tsx
Normal file
54
src/components/Typography.tsx
Normal file
@@ -0,0 +1,54 @@
|
||||
import * as React from 'react';
|
||||
|
||||
interface TypographyProps {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const H1: React.FC<TypographyProps> = ({ children, className = "" }) => (
|
||||
<h1 className={`text-5xl md:text-7xl font-bold text-slate-900 tracking-tighter leading-[1.1] ${className}`}>
|
||||
{children}
|
||||
</h1>
|
||||
);
|
||||
|
||||
export const H2: React.FC<TypographyProps> = ({ children, className = "" }) => (
|
||||
<h2 className={`text-3xl md:text-5xl font-bold text-slate-900 tracking-tighter leading-tight ${className}`}>
|
||||
{children}
|
||||
</h2>
|
||||
);
|
||||
|
||||
export const H3: React.FC<TypographyProps> = ({ children, className = "" }) => (
|
||||
<h3 className={`text-2xl md:text-4xl font-bold text-slate-900 tracking-tight leading-tight ${className}`}>
|
||||
{children}
|
||||
</h3>
|
||||
);
|
||||
|
||||
export const H4: React.FC<TypographyProps> = ({ children, className = "" }) => (
|
||||
<h4 className={`text-xl md:text-2xl font-bold text-slate-900 tracking-tight ${className}`}>
|
||||
{children}
|
||||
</h4>
|
||||
);
|
||||
|
||||
export const LeadText: React.FC<TypographyProps> = ({ children, className = "" }) => (
|
||||
<p className={`text-lg md:text-xl font-serif italic text-slate-500 leading-relaxed ${className}`}>
|
||||
{children}
|
||||
</p>
|
||||
);
|
||||
|
||||
export const BodyText: React.FC<TypographyProps> = ({ children, className = "" }) => (
|
||||
<p className={`text-base text-slate-500 leading-relaxed ${className}`}>
|
||||
{children}
|
||||
</p>
|
||||
);
|
||||
|
||||
export const Label: React.FC<TypographyProps> = ({ children, className = "" }) => (
|
||||
<span className={`text-[10px] font-bold uppercase tracking-[0.3em] text-slate-400 block ${className}`}>
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
|
||||
export const MonoLabel: React.FC<TypographyProps> = ({ children, className = "" }) => (
|
||||
<span className={`text-[10px] font-mono uppercase tracking-[0.2em] text-slate-400 block ${className}`}>
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
6
src/utils/cn.ts
Normal file
6
src/utils/cn.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { type ClassValue, clsx } from 'clsx';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
Reference in New Issue
Block a user