feat: replace bento grid with format cloud / tag wall in features section
All 70+ formats displayed as color-coded badges in a flowing cloud layout. Popular formats are larger, all badges animate in with staggered entrance. Category legend at top with colored dots. Hover effects on each badge.
This commit is contained in:
+123
-115
@@ -51,55 +51,68 @@ const flowSteps = [
|
||||
{ inputIcon: '\u{1F3B5}', inputLabel: '.FLAC', outputIcon: '\u{1F3A7}', outputLabel: '.MP3' },
|
||||
];
|
||||
|
||||
/* ─── Bento Features ─── */
|
||||
/* ─── Format Cloud Data ─── */
|
||||
|
||||
const bentoFeatures = [
|
||||
{
|
||||
icon: '\u{1F5BC}',
|
||||
title: 'Images',
|
||||
desc: 'Convert between any image format with zero quality loss.',
|
||||
accent: '#f472b6',
|
||||
accentLight: 'rgba(244,114,182,0.08)',
|
||||
formats: ['PNG', 'JPG', 'WebP', 'GIF', 'AVIF', 'SVG', 'PSD', 'HEIC', 'BMP', 'TIFF', 'ICO'],
|
||||
// Bento: tall left column
|
||||
gridArea: 'images',
|
||||
},
|
||||
{
|
||||
icon: '\u{1F4C4}',
|
||||
title: 'Documents',
|
||||
desc: 'Full formatting preservation across office and web formats.',
|
||||
accent: '#60a5fa',
|
||||
accentLight: 'rgba(96,165,250,0.08)',
|
||||
formats: ['DOCX', 'PDF', 'MD', 'HTML', 'TXT', 'RTF', 'PPTX', 'EPUB'],
|
||||
gridArea: 'docs',
|
||||
},
|
||||
{
|
||||
icon: '\u{1F3B5}',
|
||||
title: 'Audio',
|
||||
desc: 'FFmpeg WebAssembly powered.',
|
||||
accent: '#a78bfa',
|
||||
accentLight: 'rgba(167,139,250,0.08)',
|
||||
formats: ['MP3', 'WAV', 'OGG', 'FLAC', 'AAC', 'M4A'],
|
||||
gridArea: 'audio',
|
||||
},
|
||||
{
|
||||
icon: '\u{1F3AC}',
|
||||
title: 'Video',
|
||||
desc: 'Full transcoding in your browser.',
|
||||
accent: '#fb923c',
|
||||
accentLight: 'rgba(251,146,60,0.08)',
|
||||
formats: ['MP4', 'WebM', 'AVI', 'MOV', 'MKV'],
|
||||
gridArea: 'video',
|
||||
},
|
||||
{
|
||||
icon: '\u{1F4CA}',
|
||||
title: 'Data & Fonts',
|
||||
desc: 'Smart structure preservation for structured data and typography.',
|
||||
accent: '#34d399',
|
||||
accentLight: 'rgba(52,211,153,0.08)',
|
||||
formats: ['CSV', 'JSON', 'XML', 'YAML', 'XLSX', 'TSV', 'TOML', 'TTF', 'OTF', 'WOFF2'],
|
||||
gridArea: 'data',
|
||||
},
|
||||
type FormatCategory = 'image' | 'document' | 'audio' | 'video' | 'data';
|
||||
|
||||
const categoryMeta: Record<FormatCategory, { label: string; color: string; colorLight: string }> = {
|
||||
image: { label: 'Images', color: '#f472b6', colorLight: 'rgba(244,114,182,0.10)' },
|
||||
document: { label: 'Documents', color: '#60a5fa', colorLight: 'rgba(96,165,250,0.10)' },
|
||||
audio: { label: 'Audio', color: '#a78bfa', colorLight: 'rgba(167,139,250,0.10)' },
|
||||
video: { label: 'Video', color: '#fb923c', colorLight: 'rgba(251,146,60,0.10)' },
|
||||
data: { label: 'Data & Fonts', color: '#34d399', colorLight: 'rgba(52,211,153,0.10)' },
|
||||
};
|
||||
|
||||
const allFormats: { name: string; cat: FormatCategory; popular?: boolean }[] = [
|
||||
// Images
|
||||
{ name: 'PNG', cat: 'image', popular: true },
|
||||
{ name: 'JPG', cat: 'image', popular: true },
|
||||
{ name: 'WebP', cat: 'image', popular: true },
|
||||
{ name: 'GIF', cat: 'image' },
|
||||
{ name: 'AVIF', cat: 'image' },
|
||||
{ name: 'SVG', cat: 'image', popular: true },
|
||||
{ name: 'PSD', cat: 'image' },
|
||||
{ name: 'HEIC', cat: 'image', popular: true },
|
||||
{ name: 'BMP', cat: 'image' },
|
||||
{ name: 'TIFF', cat: 'image' },
|
||||
{ name: 'ICO', cat: 'image' },
|
||||
// Documents
|
||||
{ name: 'PDF', cat: 'document', popular: true },
|
||||
{ name: 'DOCX', cat: 'document', popular: true },
|
||||
{ name: 'MD', cat: 'document' },
|
||||
{ name: 'HTML', cat: 'document', popular: true },
|
||||
{ name: 'TXT', cat: 'document' },
|
||||
{ name: 'RTF', cat: 'document' },
|
||||
{ name: 'PPTX', cat: 'document', popular: true },
|
||||
{ name: 'EPUB', cat: 'document' },
|
||||
// Audio
|
||||
{ name: 'MP3', cat: 'audio', popular: true },
|
||||
{ name: 'WAV', cat: 'audio', popular: true },
|
||||
{ name: 'OGG', cat: 'audio' },
|
||||
{ name: 'FLAC', cat: 'audio', popular: true },
|
||||
{ name: 'AAC', cat: 'audio' },
|
||||
{ name: 'M4A', cat: 'audio' },
|
||||
// Video
|
||||
{ name: 'MP4', cat: 'video', popular: true },
|
||||
{ name: 'WebM', cat: 'video' },
|
||||
{ name: 'AVI', cat: 'video' },
|
||||
{ name: 'MOV', cat: 'video', popular: true },
|
||||
{ name: 'MKV', cat: 'video', popular: true },
|
||||
// Data & Fonts
|
||||
{ name: 'CSV', cat: 'data', popular: true },
|
||||
{ name: 'JSON', cat: 'data', popular: true },
|
||||
{ name: 'XML', cat: 'data' },
|
||||
{ name: 'YAML', cat: 'data', popular: true },
|
||||
{ name: 'XLSX', cat: 'data', popular: true },
|
||||
{ name: 'TSV', cat: 'data' },
|
||||
{ name: 'TOML', cat: 'data' },
|
||||
{ name: 'INI', cat: 'data' },
|
||||
{ name: 'NDJSON', cat: 'data' },
|
||||
{ name: 'SQL', cat: 'data' },
|
||||
{ name: 'TTF', cat: 'data' },
|
||||
{ name: 'OTF', cat: 'data' },
|
||||
{ name: 'WOFF', cat: 'data' },
|
||||
{ name: 'WOFF2', cat: 'data' },
|
||||
];
|
||||
|
||||
/* ─── Animation Variants ─── */
|
||||
@@ -454,7 +467,7 @@ export default function LandingPage() {
|
||||
<ConversionFlow />
|
||||
</section>
|
||||
|
||||
{/* ──── FEATURES — BENTO GRID ──── */}
|
||||
{/* ──── FEATURES — FORMAT CLOUD ──── */}
|
||||
<section
|
||||
id="features"
|
||||
className="relative z-10 flex flex-col items-center gap-10 px-6 py-20"
|
||||
@@ -473,77 +486,72 @@ export default function LandingPage() {
|
||||
Every format you need
|
||||
</h2>
|
||||
<p className="text-[17px] text-text-mid leading-relaxed max-w-[520px]">
|
||||
70+ file formats across 5 categories, all converted instantly with zero quality loss.
|
||||
70+ file formats across 5 categories, all converted instantly in your browser.
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
{/* Bento Grid — asymmetric masonry layout */}
|
||||
<div
|
||||
className="w-full max-w-[1060px] grid gap-4 md:gap-5"
|
||||
style={{
|
||||
gridTemplateColumns: 'repeat(12, 1fr)',
|
||||
gridTemplateRows: 'auto auto auto',
|
||||
gridTemplateAreas: `
|
||||
"images images images images images docs docs docs docs docs docs docs"
|
||||
"audio audio audio audio video video video video data data data data"
|
||||
"audio audio audio audio video video video video data data data data"
|
||||
`,
|
||||
{/* Category legend */}
|
||||
<motion.div
|
||||
className="flex flex-wrap items-center justify-center gap-x-5 gap-y-2"
|
||||
initial={{ opacity: 0 }}
|
||||
whileInView={{ opacity: 1 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ delay: 0.2 }}
|
||||
>
|
||||
{(Object.entries(categoryMeta) as [FormatCategory, typeof categoryMeta[FormatCategory]][]).map(([key, meta]) => (
|
||||
<div key={key} className="flex items-center gap-2">
|
||||
<div className="w-2.5 h-2.5 rounded-full" style={{ background: meta.color }} />
|
||||
<span className="font-mono text-[11px] font-semibold text-text-mid tracking-wide">{meta.label}</span>
|
||||
</div>
|
||||
))}
|
||||
</motion.div>
|
||||
|
||||
{/* Format cloud */}
|
||||
<motion.div
|
||||
className="w-full max-w-[820px] flex flex-wrap items-center justify-center gap-2.5"
|
||||
initial="hidden"
|
||||
whileInView="visible"
|
||||
viewport={{ once: true, margin: '-40px' }}
|
||||
variants={{
|
||||
hidden: {},
|
||||
visible: { transition: { staggerChildren: 0.02 } },
|
||||
}}
|
||||
>
|
||||
{bentoFeatures.map((feat, i) => (
|
||||
<motion.div
|
||||
key={feat.title}
|
||||
className="relative bg-white rounded-[20px] overflow-hidden shadow-[0_1px_3px_rgba(160,120,80,0.06)] hover:shadow-[0_12px_40px_rgba(160,120,80,0.12)] hover:-translate-y-0.5 transition-all duration-300 group"
|
||||
style={{
|
||||
gridArea: feat.gridArea,
|
||||
borderLeft: `3px solid ${feat.accent}`,
|
||||
}}
|
||||
initial={{ opacity: 0, y: 28 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true, margin: '-40px' }}
|
||||
transition={{ duration: 0.5, delay: i * 0.08, ease: [0.16, 1, 0.3, 1] as const }}
|
||||
>
|
||||
{/* Top accent bar */}
|
||||
<div
|
||||
className="absolute top-0 left-0 right-0 h-[2px]"
|
||||
style={{ background: feat.accent, opacity: 0.4 }}
|
||||
/>
|
||||
|
||||
<div className="p-6 md:p-7 h-full flex flex-col">
|
||||
{/* Header row */}
|
||||
<div className="flex items-start gap-3.5 mb-3">
|
||||
<div
|
||||
className="w-11 h-11 rounded-xl flex items-center justify-center text-[20px] flex-shrink-0"
|
||||
style={{ background: feat.accentLight }}
|
||||
>
|
||||
{feat.icon}
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-serif font-bold text-[18px] text-text-dark leading-tight">{feat.title}</h3>
|
||||
<p className="text-[13px] text-text-mid leading-relaxed mt-0.5">{feat.desc}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Format badges — grow to fill space */}
|
||||
<div className="flex flex-wrap gap-1.5 mt-auto pt-3">
|
||||
{feat.formats.map((f) => (
|
||||
<span
|
||||
key={f}
|
||||
className="px-2.5 py-[5px] font-mono text-[10px] font-bold rounded-lg border transition-colors duration-200"
|
||||
style={{
|
||||
borderColor: `${feat.accent}25`,
|
||||
color: feat.accent,
|
||||
background: feat.accentLight,
|
||||
}}
|
||||
>
|
||||
.{f}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
{allFormats.map((fmt) => {
|
||||
const meta = categoryMeta[fmt.cat];
|
||||
return (
|
||||
<motion.span
|
||||
key={fmt.name}
|
||||
className={`inline-flex items-center font-mono font-bold rounded-xl border cursor-default select-none transition-all duration-200 hover:-translate-y-0.5 hover:shadow-md ${
|
||||
fmt.popular
|
||||
? 'px-4 py-2 text-[13px] tracking-wide'
|
||||
: 'px-3 py-1.5 text-[11px] tracking-wider'
|
||||
}`}
|
||||
style={{
|
||||
color: meta.color,
|
||||
background: meta.colorLight,
|
||||
borderColor: `${meta.color}20`,
|
||||
}}
|
||||
variants={{
|
||||
hidden: { opacity: 0, scale: 0.7, y: 12 },
|
||||
visible: {
|
||||
opacity: 1,
|
||||
scale: 1,
|
||||
y: 0,
|
||||
transition: { duration: 0.4, ease: [0.16, 1, 0.3, 1] as const },
|
||||
},
|
||||
}}
|
||||
whileHover={{
|
||||
scale: 1.08,
|
||||
background: `${meta.color}20`,
|
||||
borderColor: `${meta.color}50`,
|
||||
}}
|
||||
>
|
||||
.{fmt.name}
|
||||
</motion.span>
|
||||
);
|
||||
})}
|
||||
</motion.div>
|
||||
|
||||
{/* Total count callout */}
|
||||
<motion.p
|
||||
|
||||
Reference in New Issue
Block a user