Import source: cshare-app/src/pages/LandingPage
Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality.
This commit is contained in:
@@ -0,0 +1,366 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { ShieldIcon, ZapIcon, GlobeIcon, ArrowDownIcon, EyeIcon, LinkIcon, ClockIcon, ChartIcon, CheckIcon, CopyIcon } from '../components/Icons';
|
||||
import Footer from '../components/Footer';
|
||||
import { useScrollReveal } from '../hooks/useScrollReveal';
|
||||
|
||||
const DEMO_FILES = [
|
||||
{ id: 'a', name: 'app-prototype-v2.fig', type: 'FILE', color: '#5E9EFF', size: '15.0 MB', initProgress: 100, initLink: 'cshare.app/preview/x7k2m' },
|
||||
{ id: 'b', name: 'Q4-report.pdf', type: 'PDF', color: '#E84848', size: '2.4 MB', initProgress: 62 },
|
||||
{ id: 'c', name: 'brand-assets.zip', type: 'ZIP', color: '#4A90FF', size: '52.0 MB', initProgress: 21 },
|
||||
];
|
||||
|
||||
function HeroPreview() {
|
||||
const [files, setFiles] = useState(() =>
|
||||
DEMO_FILES.map((f) => ({ ...f, progress: f.initProgress, link: f.initLink || null }))
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const id = setInterval(() => {
|
||||
setFiles((prev) =>
|
||||
prev.map((f) => {
|
||||
if (f.progress >= 100) return f;
|
||||
const np = Math.min(100, f.progress + Math.random() * 8 + 3);
|
||||
return {
|
||||
...f,
|
||||
progress: np,
|
||||
link: np >= 100
|
||||
? `cshare.app/preview/${Math.random().toString(36).slice(2, 7)}`
|
||||
: f.link,
|
||||
};
|
||||
})
|
||||
);
|
||||
}, 380);
|
||||
return () => clearInterval(id);
|
||||
}, []);
|
||||
|
||||
const doneCount = files.filter((f) => f.progress >= 100).length;
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
background: 'var(--bg-elevated)',
|
||||
border: '1px solid var(--border)',
|
||||
borderRadius: 'var(--radius-xl)',
|
||||
padding: '20px',
|
||||
boxShadow: '0 32px 80px rgba(0,0,0,0.55), 0 0 0 1px rgba(74,144,255,0.07)',
|
||||
animation: 'fadeInUp 0.55s 0.35s ease both',
|
||||
}}>
|
||||
{/* Panel header */}
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '10px',
|
||||
marginBottom: '14px',
|
||||
paddingBottom: '14px',
|
||||
borderBottom: '1px solid var(--border-subtle)',
|
||||
}}>
|
||||
<div style={{
|
||||
width: '8px',
|
||||
height: '8px',
|
||||
borderRadius: '50%',
|
||||
background: doneCount < 3 ? 'var(--accent)' : 'var(--success)',
|
||||
boxShadow: `0 0 10px ${doneCount < 3 ? 'rgba(74,144,255,0.7)' : 'rgba(56,200,122,0.7)'}`,
|
||||
animation: 'pulseGlow 2s ease infinite',
|
||||
flexShrink: 0,
|
||||
}} />
|
||||
<span style={{ fontFamily: 'var(--font-heading)', fontSize: '0.8125rem', fontWeight: 600, color: 'var(--text-secondary)' }}>
|
||||
{doneCount < 3 ? `${doneCount} of 3 ready to share` : 'All files ready'}
|
||||
</span>
|
||||
<span style={{ marginLeft: 'auto', fontSize: '0.6875rem', color: 'var(--text-muted)', fontFamily: 'var(--font-heading)' }}>
|
||||
just now
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* File rows */}
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
|
||||
{files.map((file) => (
|
||||
<div
|
||||
key={file.id}
|
||||
style={{
|
||||
padding: '12px 14px',
|
||||
background: 'var(--bg-surface)',
|
||||
borderRadius: 'var(--radius-md)',
|
||||
border: `1px solid ${file.link ? 'var(--accent-border)' : 'var(--border-subtle)'}`,
|
||||
transition: 'border-color 0.4s ease',
|
||||
}}
|
||||
>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
|
||||
<div style={{
|
||||
width: '34px', height: '34px', borderRadius: 'var(--radius-sm)',
|
||||
background: 'var(--bg-primary)', display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
fontFamily: 'var(--font-heading)', fontWeight: 700, fontSize: '0.5rem',
|
||||
color: file.color, letterSpacing: '0.05em', flexShrink: 0, border: '1px solid var(--border-subtle)',
|
||||
}}>
|
||||
{file.type}
|
||||
</div>
|
||||
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<div style={{ fontFamily: 'var(--font-heading)', fontWeight: 600, fontSize: '0.8125rem', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
|
||||
{file.name}
|
||||
</div>
|
||||
<div style={{ fontSize: '0.6875rem', color: 'var(--text-muted)', marginTop: '2px' }}>
|
||||
{file.size}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{file.progress >= 100 ? (
|
||||
<div style={{ width: '24px', height: '24px', borderRadius: '50%', background: 'var(--success-muted)', display: 'flex', alignItems: 'center', justifyContent: 'center', animation: 'scaleIn 0.3s ease', flexShrink: 0 }}>
|
||||
<CheckIcon size={11} style={{ color: 'var(--success)' }} />
|
||||
</div>
|
||||
) : (
|
||||
<span style={{ fontFamily: 'var(--font-heading)', fontWeight: 700, fontSize: '0.75rem', color: 'var(--accent)', minWidth: '32px', textAlign: 'right', flexShrink: 0 }}>
|
||||
{Math.round(file.progress)}%
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{file.progress < 100 && (
|
||||
<div style={{ marginTop: '10px', height: '3px', borderRadius: '2px', background: 'var(--bg-primary)', overflow: 'hidden' }}>
|
||||
<div style={{
|
||||
width: `${file.progress}%`, height: '100%', borderRadius: '2px',
|
||||
background: 'linear-gradient(90deg, var(--accent), var(--accent-hover))',
|
||||
transition: 'width 0.38s ease',
|
||||
boxShadow: '0 0 8px rgba(74,144,255,0.45)',
|
||||
}} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{file.link && (
|
||||
<div style={{
|
||||
marginTop: '10px', display: 'flex', alignItems: 'center', gap: '8px',
|
||||
padding: '7px 10px', background: 'var(--bg-primary)', borderRadius: 'var(--radius-sm)',
|
||||
border: '1px solid var(--accent-border)', animation: 'fadeIn 0.4s ease',
|
||||
}}>
|
||||
<span style={{ flex: 1, fontSize: '0.6875rem', color: 'var(--accent)', fontFamily: 'monospace', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
|
||||
{file.link}
|
||||
</span>
|
||||
<CopyIcon size={11} style={{ color: 'var(--text-muted)', flexShrink: 0 }} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function LandingPage() {
|
||||
const navigate = useNavigate();
|
||||
const sectionRef = useScrollReveal(0.1);
|
||||
|
||||
return (
|
||||
<div className="page-enter" style={{ minHeight: '100vh' }}>
|
||||
{/* ── Hero ──────────────────────────────────────────────── */}
|
||||
<section style={{ position: 'relative', overflow: 'hidden', padding: 'clamp(72px, 10vh, 120px) 32px clamp(72px, 10vh, 120px)' }}>
|
||||
{/* Dot-grid background */}
|
||||
<div style={{
|
||||
position: 'absolute', inset: 0,
|
||||
backgroundImage: 'radial-gradient(circle, rgba(42,42,50,0.85) 1px, transparent 1px)',
|
||||
backgroundSize: '32px 32px', pointerEvents: 'none',
|
||||
maskImage: 'radial-gradient(ellipse 90% 90% at 50% 40%, black 30%, transparent 100%)',
|
||||
WebkitMaskImage: 'radial-gradient(ellipse 90% 90% at 50% 40%, black 30%, transparent 100%)',
|
||||
}} />
|
||||
<div style={{ position: 'absolute', top: '-20%', right: '-8%', width: '650px', height: '650px', background: 'radial-gradient(circle, rgba(74,144,255,0.065) 0%, transparent 65%)', pointerEvents: 'none' }} />
|
||||
<div style={{ position: 'absolute', bottom: '-15%', left: '-5%', width: '420px', height: '420px', background: 'radial-gradient(circle, rgba(74,144,255,0.03) 0%, transparent 65%)', pointerEvents: 'none' }} />
|
||||
|
||||
<div className="hero-split">
|
||||
{/* Left — headline + CTA */}
|
||||
<div style={{ position: 'relative', zIndex: 1 }}>
|
||||
{/* Hero heading — Playfair Display italic */}
|
||||
<h1 style={{
|
||||
fontFamily: 'var(--font-display)',
|
||||
fontStyle: 'italic',
|
||||
fontWeight: 700,
|
||||
fontSize: 'clamp(2.75rem, 4.5vw, 5rem)',
|
||||
letterSpacing: '-0.02em',
|
||||
lineHeight: 1.08,
|
||||
marginBottom: '24px',
|
||||
animation: 'fadeInUp 0.5s 0.08s ease both',
|
||||
}}>
|
||||
Share files
|
||||
<br />
|
||||
without the
|
||||
<br />
|
||||
<span style={{
|
||||
background: 'linear-gradient(135deg, var(--accent) 0%, var(--accent-hover) 50%, var(--accent) 100%)',
|
||||
backgroundSize: '200% 200%',
|
||||
animation: 'gradientMove 4s ease infinite',
|
||||
WebkitBackgroundClip: 'text',
|
||||
WebkitTextFillColor: 'transparent',
|
||||
backgroundClip: 'text',
|
||||
}}>
|
||||
friction.
|
||||
</span>
|
||||
</h1>
|
||||
|
||||
<p style={{
|
||||
fontSize: '1.0625rem', color: 'var(--text-secondary)',
|
||||
maxWidth: '460px', lineHeight: 1.75, marginBottom: '36px',
|
||||
animation: 'fadeInUp 0.5s 0.16s ease both',
|
||||
}}>
|
||||
Upload, share, and track your files with links that expire.
|
||||
No sign-up walls, no bloated UIs, no files living forever on someone else's server.
|
||||
</p>
|
||||
|
||||
<div style={{ display: 'flex', gap: '12px', flexWrap: 'wrap', marginBottom: '32px', animation: 'fadeInUp 0.5s 0.24s ease both' }}>
|
||||
<button className="btn btn-primary btn-lg" onClick={() => navigate('/upload')}>
|
||||
Start Sharing
|
||||
<ArrowDownIcon size={16} style={{ transform: 'rotate(-90deg)' }} />
|
||||
</button>
|
||||
<button className="btn btn-secondary btn-lg" onClick={() => navigate('/dashboard')}>
|
||||
View Dashboard
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Honest feature pills */}
|
||||
<div style={{ display: 'flex', gap: '8px', flexWrap: 'wrap', animation: 'fadeInUp 0.5s 0.32s ease both' }}>
|
||||
{['No sign-up required', 'Auto-expiring links', 'Up to 500 MB/file'].map((tag) => (
|
||||
<span key={tag} style={{
|
||||
display: 'inline-flex', alignItems: 'center', gap: '6px',
|
||||
padding: '5px 12px', background: 'var(--bg-elevated)', border: '1px solid var(--border-subtle)',
|
||||
borderRadius: '999px', fontSize: '0.8125rem', color: 'var(--text-secondary)',
|
||||
fontFamily: 'var(--font-body)', fontWeight: 500,
|
||||
}}>
|
||||
<CheckIcon size={12} style={{ color: 'var(--success)' }} />
|
||||
{tag}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right — live upload preview */}
|
||||
<div className="hero-preview-col" style={{ position: 'relative', zIndex: 1 }}>
|
||||
<HeroPreview />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* ── Features ─────────────────────────────────────────────── */}
|
||||
<section ref={sectionRef} id="features" style={{ padding: 'clamp(60px, 8vh, 100px) 24px', maxWidth: '1120px', margin: '0 auto' }}>
|
||||
<div className="scroll-reveal" style={{ textAlign: 'center', marginBottom: '64px' }}>
|
||||
<div style={{ display: 'inline-flex', alignItems: 'center', gap: '8px', marginBottom: '16px' }}>
|
||||
<span style={{ color: 'var(--accent)', fontFamily: 'var(--font-heading)', fontWeight: 700, fontSize: '0.875rem' }}>//</span>
|
||||
<span style={{ color: 'var(--text-muted)', fontFamily: 'var(--font-heading)', fontWeight: 600, fontSize: '0.75rem', letterSpacing: '0.12em', textTransform: 'uppercase' }}>Features</span>
|
||||
</div>
|
||||
<h2 className="heading-md" style={{ marginBottom: '16px' }}>Built different, by design</h2>
|
||||
<p style={{ color: 'var(--text-secondary)', maxWidth: '480px', margin: '0 auto' }}>
|
||||
Every decision made to reduce friction and keep your files under your control.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(280px, 1fr))', gap: '20px' }}>
|
||||
{[
|
||||
{ icon: <ZapIcon size={24} />, title: 'Instant uploads', desc: 'Drag, drop, share. Your link is ready before you finish reading this sentence.' },
|
||||
{ icon: <ShieldIcon size={24} />, title: 'Auto-expiring links', desc: 'Every link has a shelf life. Set it and forget it — files disappear when you want them to.' },
|
||||
{ icon: <GlobeIcon size={24} />, title: 'No account needed', desc: "Share files without creating an account. Recipients don't need one either." },
|
||||
{ icon: <EyeIcon size={24} />, title: 'View tracking', desc: 'Know exactly who opened your link and when. Real-time analytics on every file you share.' },
|
||||
{ icon: <LinkIcon size={24} />, title: 'One-click sharing', desc: 'Copy a share link the instant your file is ready. No extra steps, no friction.' },
|
||||
{ icon: <ClockIcon size={24} />, title: 'Custom expiry', desc: 'Set links to expire in an hour, a day, a week — you decide when access ends.' },
|
||||
{ icon: <ChartIcon size={24} />, title: 'Download analytics', desc: 'See how many times your file has been downloaded, right from your dashboard.' },
|
||||
{ icon: <ShieldIcon size={24} />, title: 'End-to-end encryption', desc: 'Your files are encrypted in transit and at rest. Only people with the link can access them.' },
|
||||
{ icon: <ZapIcon size={24} />, title: '500MB file limit', desc: 'Share presentations, design files, datasets — up to half a gigabyte per upload.' },
|
||||
].map((feature, i) => (
|
||||
<div key={i} className="card card-interactive scroll-reveal" style={{ padding: '28px', transitionDelay: `${i * 0.05}s`, position: 'relative', overflow: 'hidden' }}>
|
||||
{/* Faint ordinal */}
|
||||
<span style={{
|
||||
position: 'absolute', top: '16px', right: '20px',
|
||||
fontFamily: 'var(--font-heading)', fontWeight: 800, fontSize: '0.6875rem',
|
||||
color: 'var(--border)', letterSpacing: '0.06em', userSelect: 'none',
|
||||
}}>
|
||||
{String(i + 1).padStart(2, '0')}
|
||||
</span>
|
||||
<div style={{
|
||||
width: '44px', height: '44px', borderRadius: 'var(--radius-md)',
|
||||
background: 'var(--accent-muted)', border: '1px solid var(--accent-border)',
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
color: 'var(--accent)', marginBottom: '16px', transition: 'all var(--transition-base)',
|
||||
}}>
|
||||
{feature.icon}
|
||||
</div>
|
||||
<h3 className="heading-sm" style={{ marginBottom: '6px' }}>{feature.title}</h3>
|
||||
<p style={{ color: 'var(--text-secondary)', fontSize: '0.875rem', lineHeight: 1.65 }}>{feature.desc}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* ── How it Works ─────────────────────────────────────────── */}
|
||||
<section id="how-it-works" style={{
|
||||
padding: 'clamp(60px, 8vh, 100px) 24px',
|
||||
background: 'var(--bg-elevated)',
|
||||
borderTop: '1px solid var(--border-subtle)',
|
||||
borderBottom: '1px solid var(--border-subtle)',
|
||||
position: 'relative',
|
||||
}}>
|
||||
<div style={{ maxWidth: '1120px', margin: '0 auto', position: 'relative', zIndex: 1 }}>
|
||||
<div style={{ textAlign: 'center', marginBottom: '64px' }}>
|
||||
<div style={{ display: 'inline-flex', alignItems: 'center', gap: '8px', marginBottom: '16px' }}>
|
||||
<span style={{ color: 'var(--accent)', fontFamily: 'var(--font-heading)', fontWeight: 700, fontSize: '0.875rem' }}>//</span>
|
||||
<span style={{ color: 'var(--text-muted)', fontFamily: 'var(--font-heading)', fontWeight: 600, fontSize: '0.75rem', letterSpacing: '0.12em', textTransform: 'uppercase' }}>How it Works</span>
|
||||
</div>
|
||||
<h2 className="heading-md" style={{ marginBottom: '16px' }}>Three steps. Zero friction.</h2>
|
||||
<p style={{ color: 'var(--text-secondary)', maxWidth: '480px', margin: '0 auto' }}>
|
||||
From file to shared link in under five seconds.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(280px, 1fr))', gap: '48px', position: 'relative' }}>
|
||||
<div className="steps-connector" style={{ position: 'absolute', top: '28px', left: 'calc(33.33% / 2)', right: 'calc(33.33% / 2)', height: '2px', background: 'linear-gradient(90deg, var(--accent-border), var(--accent), var(--accent-border))', opacity: 0.4, zIndex: 0 }} />
|
||||
{[
|
||||
{ title: 'Drop your file', desc: 'Drag and drop any file up to 500MB into the upload zone.' },
|
||||
{ title: 'Get a link', desc: 'A unique, time-limited share link is generated instantly.' },
|
||||
{ title: 'Share and track', desc: 'Send the link. See who viewed and downloaded it in your dashboard.' },
|
||||
].map((step, i) => (
|
||||
<div key={i} style={{ textAlign: 'center', position: 'relative', zIndex: 1 }}>
|
||||
{/* Giant decorative background number */}
|
||||
<div style={{
|
||||
position: 'absolute', top: '-24px', left: '50%', transform: 'translateX(-50%)',
|
||||
fontFamily: 'var(--font-heading)', fontWeight: 800, fontSize: '8rem',
|
||||
lineHeight: 1, color: 'var(--bg-surface)',
|
||||
userSelect: 'none', pointerEvents: 'none', zIndex: 0,
|
||||
letterSpacing: '-0.06em',
|
||||
}}>
|
||||
{i + 1}
|
||||
</div>
|
||||
<div style={{ position: 'relative', zIndex: 1 }}>
|
||||
<div style={{
|
||||
width: '56px', height: '56px', margin: '0 auto 20px', borderRadius: '50%',
|
||||
border: '2px solid var(--accent-border)', display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
fontFamily: 'var(--font-heading)', fontWeight: 700, fontSize: '1.25rem',
|
||||
color: 'var(--accent)', background: 'var(--bg-elevated)',
|
||||
animation: `pulseGlow 3s ease-in-out infinite ${i * 0.5}s`,
|
||||
}}>
|
||||
{i + 1}
|
||||
</div>
|
||||
<h3 className="heading-sm" style={{ marginBottom: '8px' }}>{step.title}</h3>
|
||||
<p style={{ color: 'var(--text-secondary)', fontSize: '0.9375rem', lineHeight: 1.65 }}>{step.desc}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* ── CTA ──────────────────────────────────────────────────── */}
|
||||
<section style={{ padding: 'clamp(80px, 12vh, 140px) 24px', textAlign: 'center', position: 'relative', overflow: 'hidden' }}>
|
||||
<div style={{ position: 'absolute', inset: 0, background: 'radial-gradient(ellipse 60% 50% at 50% 100%, rgba(74,144,255,0.06) 0%, transparent 70%)', pointerEvents: 'none' }} />
|
||||
<div style={{ position: 'relative', zIndex: 1, maxWidth: '560px', margin: '0 auto' }}>
|
||||
<div style={{ display: 'inline-flex', alignItems: 'center', gap: '8px', marginBottom: '16px' }}>
|
||||
<span style={{ color: 'var(--accent)', fontFamily: 'var(--font-heading)', fontWeight: 700, fontSize: '0.875rem' }}>//</span>
|
||||
<span style={{ color: 'var(--text-muted)', fontFamily: 'var(--font-heading)', fontWeight: 600, fontSize: '0.75rem', letterSpacing: '0.12em', textTransform: 'uppercase' }}>Get Started</span>
|
||||
</div>
|
||||
<h2 className="heading-md" style={{ marginBottom: '16px' }}>Ready to share smarter?</h2>
|
||||
<p style={{ color: 'var(--text-secondary)', marginBottom: '32px', lineHeight: 1.7 }}>
|
||||
No sign-up required. Upload your first file and see how simple file sharing should be.
|
||||
</p>
|
||||
<button className="btn btn-primary btn-lg" onClick={() => navigate('/upload')}>
|
||||
Start Sharing Now
|
||||
<ArrowDownIcon size={16} style={{ transform: 'rotate(-90deg)' }} />
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user