adiitya29 commited on
Commit
d6e81a8
Β·
1 Parent(s): 4f241a2

feat: add React landing page

Browse files
.gitignore CHANGED
@@ -178,3 +178,8 @@ models/*
178
  *.json
179
  *.csv
180
  !app/templates/*.json
 
 
 
 
 
 
178
  *.json
179
  *.csv
180
  !app/templates/*.json
181
+
182
+ # React / Frontend
183
+ landing page/node_modules/
184
+ landing page/dist/
185
+ .DS_Store
landing page/index.html ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Multilingual ASR β€” Speech to Text with Wav2Vec</title>
7
+ <meta name="description" content="An end-to-end Automatic Speech Recognition system powered by Meta's Wav2Vec 2.0 and Hugging Face. Upload audio, get text. Instantly." />
8
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
9
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" />
10
+ </head>
11
+ <body>
12
+ <div id="root"></div>
13
+ <script type="module" src="/src/main.jsx"></script>
14
+ </body>
15
+ </html>
landing page/src/App.jsx ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Nav from './components/Nav'
2
+ import Hero from './components/Hero'
3
+ import HowItWorks from './components/HowItWorks'
4
+ import TechStack from './components/TechStack'
5
+ import Footer from './components/Footer'
6
+
7
+ export default function App() {
8
+ return (
9
+ <>
10
+ {/* Background layers */}
11
+ <div className="bg-grid" />
12
+ <div className="orb orb-1" />
13
+ <div className="orb orb-2" />
14
+
15
+ {/* Page */}
16
+ <Nav />
17
+ <Hero />
18
+ <HowItWorks />
19
+ <TechStack />
20
+ <Footer />
21
+ </>
22
+ )
23
+ }
landing page/src/components/Footer.jsx ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ export default function Footer() {
2
+ return (
3
+ <footer className="footer" id="footer">
4
+ <div className="footer-inner">
5
+ <p className="footer-name">Multilingual ASR</p>
6
+ <p className="footer-sub">Wav2Vec 2.0 Β· FastAPI Β· Gradio Β· Hugging Face</p>
7
+ </div>
8
+ </footer>
9
+ )
10
+ }
landing page/src/components/Hero.jsx ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useEffect, useRef } from 'react'
2
+
3
+ const WAVES = [
4
+ { freq: 0.022, amp: 0.42, speed: 0.012, phase: 0.0, color: '#4f6ef7' },
5
+ { freq: 0.018, amp: 0.28, speed: 0.009, phase: 1.2, color: '#a855f7' },
6
+ { freq: 0.030, amp: 0.18, speed: 0.016, phase: 2.5, color: '#06d6a0' },
7
+ ]
8
+
9
+ function WaveformCanvas() {
10
+ const canvasRef = useRef(null)
11
+
12
+ useEffect(() => {
13
+ const canvas = canvasRef.current
14
+ const ctx = canvas.getContext('2d')
15
+ let animFrame
16
+ let t = 0
17
+
18
+ function resize() {
19
+ const dpr = window.devicePixelRatio || 1
20
+ const rect = canvas.getBoundingClientRect()
21
+ canvas.width = rect.width * dpr
22
+ canvas.height = rect.height * dpr
23
+ ctx.scale(dpr, dpr)
24
+ }
25
+
26
+ function draw() {
27
+ const w = canvas.getBoundingClientRect().width
28
+ const h = canvas.getBoundingClientRect().height
29
+ ctx.clearRect(0, 0, w, h)
30
+ WAVES.forEach(wave => {
31
+ ctx.beginPath()
32
+ const cx = w / 2, cy = h / 2
33
+ for (let x = 0; x <= w; x++) {
34
+ const envelope = 1 - Math.pow((x - cx) / cx, 2) * 0.9
35
+ const y = cy + Math.sin(x * wave.freq + t * wave.speed * 60 + wave.phase) * (h * wave.amp * envelope)
36
+ x === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y)
37
+ }
38
+ ctx.strokeStyle = wave.color
39
+ ctx.lineWidth = 1.8
40
+ ctx.globalAlpha = 0.65
41
+ ctx.stroke()
42
+ })
43
+ ctx.globalAlpha = 1
44
+ t++
45
+ animFrame = requestAnimationFrame(draw)
46
+ }
47
+
48
+ resize()
49
+ window.addEventListener('resize', resize, { passive: true })
50
+ draw()
51
+ return () => {
52
+ cancelAnimationFrame(animFrame)
53
+ window.removeEventListener('resize', resize)
54
+ }
55
+ }, [])
56
+
57
+ return (
58
+ <div className="waveform-container" aria-hidden="true">
59
+ <canvas ref={canvasRef} id="waveform-canvas" />
60
+ </div>
61
+ )
62
+ }
63
+
64
+ export default function Hero() {
65
+ return (
66
+ <section className="hero" id="hero">
67
+ <div className="hero-content">
68
+ <div className="badge">
69
+ <span className="badge-dot" />
70
+ Powered by Meta Wav2Vec 2.0
71
+ </div>
72
+ <h1 className="hero-title">
73
+ Speech to Text,<br />
74
+ <span className="gradient-text">Redefined.</span>
75
+ </h1>
76
+ <p className="hero-sub">
77
+ Upload any audio. Get accurate, instant transcriptions using one of the world's most capable
78
+ open-source acoustic models β€” no cloud API key, no per-request billing.
79
+ </p>
80
+ <div className="hero-actions">
81
+ <a
82
+ href="https://huggingface.co/spaces/adiitya29/Multilingual-ASR"
83
+ target="_blank"
84
+ rel="noreferrer"
85
+ className="btn btn-primary"
86
+ id="hero-launch-btn"
87
+ >
88
+ <span>Try the Live Demo</span>
89
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
90
+ <path d="M7 17L17 7M17 7H7M17 7v10"/>
91
+ </svg>
92
+ </a>
93
+ </div>
94
+ </div>
95
+ <WaveformCanvas />
96
+ </section>
97
+ )
98
+ }
landing page/src/components/HowItWorks.jsx ADDED
@@ -0,0 +1,111 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useEffect, useRef } from 'react'
2
+
3
+ const STEPS = [
4
+ {
5
+ num: '01',
6
+ title: 'Audio Preprocessing',
7
+ desc: <>Raw audio is loaded via <code>librosa</code> and resampled to exactly <strong>16kHz</strong> β€” the sampling rate the model was trained on.</>,
8
+ icon: (
9
+ <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8">
10
+ <path d="M9 19V5l12-2v14M9 9l12-2"/>
11
+ <circle cx="6" cy="18" r="3"/><circle cx="18" cy="16" r="3"/>
12
+ </svg>
13
+ ),
14
+ },
15
+ {
16
+ num: '02',
17
+ title: 'Feature Extraction',
18
+ desc: <>The Wav2Vec2 <strong>Processor</strong> normalizes waveform values and converts them into padded PyTorch tensors ready for inference.</>,
19
+ icon: (
20
+ <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8">
21
+ <rect x="2" y="3" width="20" height="14" rx="2"/>
22
+ <path d="M8 21h8M12 17v4"/>
23
+ </svg>
24
+ ),
25
+ },
26
+ {
27
+ num: '03',
28
+ title: 'Acoustic Model',
29
+ desc: <><code>Wav2Vec2ForCTC</code> (1.26GB, Large architecture) performs the forward pass using self-supervised learned speech representations.</>,
30
+ icon: (
31
+ <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8">
32
+ <ellipse cx="12" cy="12" rx="10" ry="10"/>
33
+ <path d="M12 2a15.3 15.3 0 014 10 15.3 15.3 0 01-4 10 15.3 15.3 0 01-4-10 15.3 15.3 0 014-10z"/>
34
+ <path d="M2 12h20"/>
35
+ </svg>
36
+ ),
37
+ },
38
+ {
39
+ num: '04',
40
+ title: 'CTC Decoding',
41
+ desc: 'Connectionist Temporal Classification (CTC) decodes raw logit tensors into the most probable character sequence, collapsing repeated tokens.',
42
+ icon: (
43
+ <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8">
44
+ <polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/>
45
+ </svg>
46
+ ),
47
+ },
48
+ {
49
+ num: '05',
50
+ title: 'Output & History',
51
+ desc: <>The transcript is stored in a local JSON history, made available for download as <code>.txt</code>, and the full history is exportable as <code>.csv</code>.</>,
52
+ icon: (
53
+ <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8">
54
+ <path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/>
55
+ <polyline points="14 2 14 8 20 8"/>
56
+ <line x1="16" y1="13" x2="8" y2="13"/>
57
+ <line x1="16" y1="17" x2="8" y2="17"/>
58
+ </svg>
59
+ ),
60
+ },
61
+ ]
62
+
63
+ function PipelineStep({ step, index }) {
64
+ const ref = useRef(null)
65
+
66
+ useEffect(() => {
67
+ const el = ref.current
68
+ const observer = new IntersectionObserver(
69
+ ([entry]) => {
70
+ if (entry.isIntersecting) {
71
+ setTimeout(() => el.classList.add('visible'), index * 80)
72
+ observer.unobserve(el)
73
+ }
74
+ },
75
+ { threshold: 0.12 }
76
+ )
77
+ observer.observe(el)
78
+ return () => observer.disconnect()
79
+ }, [index])
80
+
81
+ return (
82
+ <div ref={ref} className="pipeline-step">
83
+ <div className="step-icon">{step.icon}</div>
84
+ <div className="step-num">{step.num}</div>
85
+ <h3>{step.title}</h3>
86
+ <p>{step.desc}</p>
87
+ </div>
88
+ )
89
+ }
90
+
91
+ export default function HowItWorks() {
92
+ return (
93
+ <section className="section" id="how-it-works">
94
+ <div className="section-inner">
95
+ <div className="section-label">Under the Hood</div>
96
+ <h2 className="section-title">A Five-Stage Pipeline</h2>
97
+ <p className="section-sub">From raw audio bytes to structured text β€” every step is deliberate.</p>
98
+ <div className="pipeline">
99
+ {STEPS.map((step, i) => (
100
+ <>
101
+ <PipelineStep key={step.num} step={step} index={i} />
102
+ {i < STEPS.length - 1 && (
103
+ <div key={`conn-${i}`} className="pipeline-connector" aria-hidden="true" />
104
+ )}
105
+ </>
106
+ ))}
107
+ </div>
108
+ </div>
109
+ </section>
110
+ )
111
+ }
landing page/src/components/Nav.jsx ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useEffect, useState } from 'react'
2
+
3
+ export default function Nav() {
4
+ const [scrolled, setScrolled] = useState(false)
5
+
6
+ useEffect(() => {
7
+ const handler = () => setScrolled(window.scrollY > 20)
8
+ window.addEventListener('scroll', handler, { passive: true })
9
+ return () => window.removeEventListener('scroll', handler)
10
+ }, [])
11
+
12
+ return (
13
+ <nav className={`nav${scrolled ? ' scrolled' : ''}`} id="nav">
14
+ <div className="nav-inner">
15
+ <a href="#" className="nav-logo">
16
+ <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
17
+ <path d="M12 1v22M5 5.5l14 13M19 5.5L5 18.5M1 12h22"/>
18
+ </svg>
19
+ ASR
20
+ </a>
21
+ <div className="nav-links">
22
+ <a href="#how-it-works">How It Works</a>
23
+ <a href="#stack">Stack</a>
24
+ <a
25
+ href="https://huggingface.co/spaces/adiitya29/Multilingual-ASR"
26
+ target="_blank"
27
+ rel="noreferrer"
28
+ className="nav-cta"
29
+ id="nav-cta"
30
+ >
31
+ Launch App β†’
32
+ </a>
33
+ </div>
34
+ </div>
35
+ </nav>
36
+ )
37
+ }
landing page/src/components/TechStack.jsx ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useEffect, useRef } from 'react'
2
+
3
+ const CARDS = [
4
+ {
5
+ id: 'pytorch',
6
+ tag: 'Deep Learning',
7
+ title: 'PyTorch',
8
+ desc: <>Tensor operations, gradient management, and CPU-forced model execution to bypass Apple Silicon MPS instability with CTC ops.</>,
9
+ },
10
+ {
11
+ id: 'hf',
12
+ tag: 'Model Hub',
13
+ title: 'Hugging Face Transformers',
14
+ desc: <><code>Wav2Vec2ForCTC</code> and <code>Wav2Vec2Processor</code> from <code>facebook/wav2vec2-large-960h-lv60-self</code>.</>,
15
+ },
16
+ {
17
+ id: 'fastapi',
18
+ tag: 'API',
19
+ title: 'FastAPI',
20
+ desc: <>Async REST endpoint at <code>/api/transcribe</code>. The Gradio UI is mounted directly onto the FastAPI app β€” one unified server process.</>,
21
+ },
22
+ {
23
+ id: 'gradio',
24
+ tag: 'UI',
25
+ title: 'Gradio',
26
+ desc: 'Tabbed Blocks UI with lazy file reveal, live history Dataframe, and CSV export β€” all driven by Python callbacks.',
27
+ },
28
+ {
29
+ id: 'cicd',
30
+ tag: 'CI / CD',
31
+ title: 'GitHub Actions β†’ HF Spaces',
32
+ desc: <>Automated deploy pipeline via <code>sync_to_hub.yml</code>. Every push to <code>main</code> propagates to Hugging Face Spaces using a scoped token secret.</>,
33
+ },
34
+ {
35
+ id: 'librosa',
36
+ tag: 'DSP',
37
+ title: 'Librosa',
38
+ desc: 'Digital Signal Processing for audio file ingestion, arbitrary sample rate conversion, and mono-channel normalization.',
39
+ },
40
+ ]
41
+
42
+ function StackCard({ card, index }) {
43
+ const ref = useRef(null)
44
+
45
+ useEffect(() => {
46
+ const el = ref.current
47
+ const observer = new IntersectionObserver(
48
+ ([entry]) => {
49
+ if (entry.isIntersecting) {
50
+ setTimeout(() => el.classList.add('visible'), index * 80)
51
+ observer.unobserve(el)
52
+ }
53
+ },
54
+ { threshold: 0.12 }
55
+ )
56
+ observer.observe(el)
57
+ return () => observer.disconnect()
58
+ }, [index])
59
+
60
+ return (
61
+ <div ref={ref} className="stack-card" id={`stack-${card.id}`}>
62
+ <div className="stack-card-top">
63
+ <span className="stack-tag">{card.tag}</span>
64
+ </div>
65
+ <h3>{card.title}</h3>
66
+ <p>{card.desc}</p>
67
+ </div>
68
+ )
69
+ }
70
+
71
+ export default function TechStack() {
72
+ return (
73
+ <section className="section section-dark" id="stack">
74
+ <div className="section-inner">
75
+ <div className="section-label">Technology</div>
76
+ <h2 className="section-title">Built with Precision</h2>
77
+ <div className="stack-grid">
78
+ {CARDS.map((card, i) => (
79
+ <StackCard key={card.id} card={card} index={i} />
80
+ ))}
81
+ </div>
82
+ </div>
83
+ </section>
84
+ )
85
+ }
landing page/src/index.css ADDED
@@ -0,0 +1,287 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* ─── Reset & Tokens ──────────────────────────────────────── */
2
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
3
+
4
+ :root {
5
+ --bg: #07070f;
6
+ --bg-card: rgba(255,255,255,0.035);
7
+ --bg-card-hover: rgba(255,255,255,0.065);
8
+ --border: rgba(255,255,255,0.07);
9
+ --border-hover: rgba(255,255,255,0.18);
10
+ --text-primary: #f0f0f8;
11
+ --text-muted: #7a7a9a;
12
+ --text-faint: #44445a;
13
+ --accent-1: #4f6ef7;
14
+ --accent-2: #a855f7;
15
+ --accent-3: #06d6a0;
16
+ --glow-1: rgba(79, 110, 247, 0.35);
17
+ --glow-2: rgba(168, 85, 247, 0.25);
18
+ --radius: 14px;
19
+ --radius-sm: 8px;
20
+ --font-sans: 'Inter', sans-serif;
21
+ --font-mono: 'JetBrains Mono', monospace;
22
+ --nav-h: 64px;
23
+ --transition: 0.25s cubic-bezier(0.4, 0, 0.2, 1);
24
+ }
25
+
26
+ html { scroll-behavior: smooth; }
27
+ body {
28
+ background: var(--bg);
29
+ color: var(--text-primary);
30
+ font-family: var(--font-sans);
31
+ font-size: 16px;
32
+ line-height: 1.65;
33
+ overflow-x: hidden;
34
+ -webkit-font-smoothing: antialiased;
35
+ }
36
+
37
+ /* ─── Background ─────────────────────────────────────────── */
38
+ .bg-grid {
39
+ position: fixed; inset: 0; z-index: 0; pointer-events: none;
40
+ background-image:
41
+ linear-gradient(rgba(255,255,255,0.026) 1px, transparent 1px),
42
+ linear-gradient(90deg, rgba(255,255,255,0.026) 1px, transparent 1px);
43
+ background-size: 48px 48px;
44
+ mask-image: radial-gradient(ellipse 90% 80% at 50% 0%, black 30%, transparent 100%);
45
+ }
46
+ .orb {
47
+ position: fixed; z-index: 0; border-radius: 50%;
48
+ filter: blur(120px); pointer-events: none; opacity: 0.55;
49
+ }
50
+ .orb-1 {
51
+ width: 700px; height: 700px; top: -200px; left: -150px;
52
+ background: radial-gradient(circle, var(--accent-1), transparent 70%);
53
+ animation: orbFloat 18s ease-in-out infinite;
54
+ }
55
+ .orb-2 {
56
+ width: 600px; height: 600px; bottom: 10%; right: -150px;
57
+ background: radial-gradient(circle, var(--accent-2), transparent 70%);
58
+ animation: orbFloat 22s ease-in-out infinite reverse;
59
+ }
60
+ @keyframes orbFloat {
61
+ 0%, 100% { transform: translate(0, 0); }
62
+ 33% { transform: translate(40px, -30px); }
63
+ 66% { transform: translate(-20px, 30px); }
64
+ }
65
+
66
+ /* ─── Nav ────────────────────────────────────────────────── */
67
+ .nav {
68
+ position: fixed; top: 0; left: 0; right: 0; z-index: 100;
69
+ height: var(--nav-h);
70
+ border-bottom: 1px solid transparent;
71
+ transition: background var(--transition), border-color var(--transition), backdrop-filter var(--transition);
72
+ }
73
+ .nav.scrolled {
74
+ background: rgba(7,7,15,0.75);
75
+ backdrop-filter: blur(20px);
76
+ border-bottom-color: var(--border);
77
+ }
78
+ .nav-inner {
79
+ max-width: 1100px; margin: 0 auto; padding: 0 28px;
80
+ height: 100%; display: flex; align-items: center; justify-content: space-between;
81
+ }
82
+ .nav-logo {
83
+ display: flex; align-items: center; gap: 9px;
84
+ font-weight: 600; font-size: 1rem; color: var(--text-primary);
85
+ text-decoration: none; letter-spacing: 0.02em;
86
+ }
87
+ .nav-logo svg { color: var(--accent-1); }
88
+ .nav-links { display: flex; align-items: center; gap: 32px; }
89
+ .nav-links a {
90
+ color: var(--text-muted); text-decoration: none; font-size: 0.9rem; font-weight: 500;
91
+ transition: color var(--transition);
92
+ }
93
+ .nav-links a:hover { color: var(--text-primary); }
94
+ .nav-cta {
95
+ padding: 8px 18px !important;
96
+ background: rgba(79, 110, 247, 0.12) !important;
97
+ border: 1px solid rgba(79, 110, 247, 0.35) !important;
98
+ border-radius: 8px !important;
99
+ color: var(--accent-1) !important;
100
+ transition: background var(--transition), box-shadow var(--transition) !important;
101
+ }
102
+ .nav-cta:hover {
103
+ background: rgba(79, 110, 247, 0.22) !important;
104
+ box-shadow: 0 0 18px var(--glow-1) !important;
105
+ color: #fff !important;
106
+ }
107
+
108
+ /* ─── Hero ───────────────────────────────────────────────── */
109
+ .hero {
110
+ position: relative; z-index: 1;
111
+ min-height: 100vh;
112
+ display: flex; flex-direction: column;
113
+ align-items: center; justify-content: center;
114
+ padding: calc(var(--nav-h) + 64px) 28px 80px;
115
+ text-align: center; overflow: hidden;
116
+ }
117
+ .hero-content { max-width: 760px; }
118
+
119
+ .badge {
120
+ display: inline-flex; align-items: center; gap: 7px;
121
+ padding: 6px 14px; border-radius: 100px;
122
+ border: 1px solid rgba(79,110,247,0.3);
123
+ background: rgba(79,110,247,0.08);
124
+ font-size: 0.78rem; font-weight: 500; letter-spacing: 0.04em;
125
+ color: #818cf8; margin-bottom: 32px;
126
+ animation: fadeInUp 0.6s ease both;
127
+ }
128
+ .badge-dot {
129
+ width: 6px; height: 6px; border-radius: 50%;
130
+ background: var(--accent-1);
131
+ box-shadow: 0 0 8px var(--accent-1);
132
+ animation: pulse 2s ease infinite;
133
+ }
134
+ @keyframes pulse { 0%,100% { opacity:1; } 50% { opacity:0.4; } }
135
+
136
+ .hero-title {
137
+ font-size: clamp(2.8rem, 7vw, 5.5rem);
138
+ font-weight: 700; letter-spacing: -0.03em; line-height: 1.08;
139
+ margin-bottom: 24px; animation: fadeInUp 0.7s 0.1s ease both;
140
+ }
141
+ .gradient-text {
142
+ background: linear-gradient(135deg, var(--accent-1) 0%, var(--accent-2) 55%, var(--accent-3) 100%);
143
+ -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text;
144
+ }
145
+ .hero-sub {
146
+ font-size: 1.1rem; color: var(--text-muted); max-width: 560px; margin: 0 auto 40px;
147
+ font-weight: 400; animation: fadeInUp 0.8s 0.2s ease both;
148
+ }
149
+ .hero-actions {
150
+ display: flex; gap: 14px; justify-content: center; flex-wrap: wrap;
151
+ animation: fadeInUp 0.9s 0.3s ease both;
152
+ }
153
+ .btn {
154
+ display: inline-flex; align-items: center; gap: 8px;
155
+ padding: 13px 26px; border-radius: var(--radius-sm);
156
+ font-size: 0.95rem; font-weight: 600; text-decoration: none;
157
+ transition: all var(--transition); cursor: pointer; border: none;
158
+ }
159
+ .btn-primary {
160
+ background: linear-gradient(135deg, var(--accent-1), var(--accent-2));
161
+ color: #fff;
162
+ box-shadow: 0 4px 24px rgba(79,110,247,0.4), inset 0 1px 0 rgba(255,255,255,0.1);
163
+ }
164
+ .btn-primary:hover {
165
+ transform: translateY(-2px);
166
+ box-shadow: 0 8px 36px rgba(79,110,247,0.55), inset 0 1px 0 rgba(255,255,255,0.15);
167
+ }
168
+ .btn-ghost {
169
+ background: var(--bg-card); color: var(--text-primary); border: 1px solid var(--border);
170
+ }
171
+ .btn-ghost:hover {
172
+ background: var(--bg-card-hover); border-color: var(--border-hover); transform: translateY(-2px);
173
+ }
174
+ .waveform-container {
175
+ width: 100%; max-width: 900px; height: 100px; margin-top: 72px;
176
+ animation: fadeInUp 1s 0.5s ease both; opacity: 0.6;
177
+ }
178
+ #waveform-canvas { width: 100%; height: 100%; }
179
+
180
+ /* ─── Sections ───────────────────────────────────────────── */
181
+ .section { position: relative; z-index: 1; padding: 120px 28px; }
182
+ .section-dark { background: rgba(0,0,0,0.3); }
183
+ .section-inner { max-width: 1100px; margin: 0 auto; }
184
+ .section-label {
185
+ font-size: 0.75rem; font-weight: 600; letter-spacing: 0.14em;
186
+ text-transform: uppercase; color: var(--accent-1); margin-bottom: 14px;
187
+ }
188
+ .section-title {
189
+ font-size: clamp(1.9rem, 4vw, 2.9rem); font-weight: 700;
190
+ letter-spacing: -0.025em; margin-bottom: 14px;
191
+ }
192
+ .section-sub {
193
+ color: var(--text-muted); font-size: 1.05rem; max-width: 480px; margin-bottom: 72px;
194
+ }
195
+
196
+ /* ─── Pipeline ───────────────────────────────────────────── */
197
+ .pipeline { display: flex; align-items: flex-start; gap: 0; flex-wrap: wrap; }
198
+ .pipeline-step {
199
+ flex: 1; min-width: 160px; max-width: 220px;
200
+ position: relative; padding: 28px 20px;
201
+ border: 1px solid var(--border); border-radius: var(--radius);
202
+ background: var(--bg-card); backdrop-filter: blur(12px);
203
+ transition: border-color var(--transition), background var(--transition), transform var(--transition), opacity 0.5s ease;
204
+ opacity: 0; transform: translateY(30px);
205
+ }
206
+ .pipeline-step.visible { opacity: 1; transform: translateY(0); }
207
+ .pipeline-step:hover {
208
+ border-color: var(--border-hover); background: var(--bg-card-hover); transform: translateY(-4px);
209
+ }
210
+ .step-icon {
211
+ width: 40px; height: 40px; border-radius: 10px;
212
+ background: linear-gradient(135deg, rgba(79,110,247,0.15), rgba(168,85,247,0.15));
213
+ border: 1px solid rgba(79,110,247,0.25);
214
+ display: flex; align-items: center; justify-content: center;
215
+ margin-bottom: 16px; color: var(--accent-1);
216
+ }
217
+ .step-num {
218
+ font-family: var(--font-mono); font-size: 0.7rem; font-weight: 500;
219
+ color: var(--accent-2); letter-spacing: 0.1em; margin-bottom: 10px;
220
+ }
221
+ .pipeline-step h3 { font-size: 0.95rem; font-weight: 600; margin-bottom: 10px; }
222
+ .pipeline-step p { font-size: 0.82rem; color: var(--text-muted); line-height: 1.6; }
223
+ .pipeline-connector {
224
+ flex: 0 0 28px; height: 1px;
225
+ background: linear-gradient(90deg, var(--accent-1), var(--accent-2));
226
+ align-self: center; margin-top: -30px; opacity: 0.4;
227
+ }
228
+
229
+ /* ─── Stack Grid ─────────────────────────────────────────── */
230
+ .stack-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 18px; }
231
+ .stack-card {
232
+ padding: 28px 28px 32px; border: 1px solid var(--border); border-radius: var(--radius);
233
+ background: var(--bg-card); backdrop-filter: blur(12px);
234
+ transition: border-color var(--transition), background var(--transition), transform var(--transition), box-shadow var(--transition), opacity 0.5s ease;
235
+ opacity: 0; transform: translateY(24px); position: relative; overflow: hidden;
236
+ }
237
+ .stack-card::before {
238
+ content: ''; position: absolute; inset: 0;
239
+ background: linear-gradient(135deg, rgba(79,110,247,0.04), transparent 60%);
240
+ opacity: 0; transition: opacity var(--transition);
241
+ }
242
+ .stack-card:hover::before { opacity: 1; }
243
+ .stack-card.visible { opacity: 1; transform: translateY(0); }
244
+ .stack-card:hover {
245
+ border-color: rgba(79,110,247,0.35); background: var(--bg-card-hover);
246
+ transform: translateY(-3px); box-shadow: 0 8px 40px rgba(79,110,247,0.1);
247
+ }
248
+ .stack-card-top { margin-bottom: 14px; }
249
+ .stack-tag {
250
+ font-family: var(--font-mono); font-size: 0.72rem; font-weight: 500;
251
+ color: var(--accent-3); letter-spacing: 0.08em; text-transform: uppercase;
252
+ padding: 3px 9px; border-radius: 4px;
253
+ background: rgba(6, 214, 160, 0.08); border: 1px solid rgba(6, 214, 160, 0.2);
254
+ }
255
+ .stack-card h3 { font-size: 1.05rem; font-weight: 600; margin-bottom: 10px; }
256
+ .stack-card p { font-size: 0.875rem; color: var(--text-muted); line-height: 1.65; }
257
+ code {
258
+ font-family: var(--font-mono); font-size: 0.82em; color: #a5b4fc;
259
+ background: rgba(79,110,247,0.1); padding: 1px 6px; border-radius: 4px;
260
+ }
261
+
262
+ /* ─── Footer ─────────────────────────────────────────────── */
263
+ .footer {
264
+ position: relative; z-index: 1;
265
+ border-top: 1px solid var(--border); padding: 52px 28px; text-align: center;
266
+ }
267
+ .footer-inner { max-width: 1100px; margin: 0 auto; }
268
+ .footer-name { font-size: 1rem; font-weight: 600; margin-bottom: 6px; }
269
+ .footer-sub {
270
+ font-size: 0.82rem; color: var(--text-faint); margin-bottom: 0;
271
+ font-family: var(--font-mono); letter-spacing: 0.04em;
272
+ }
273
+
274
+ /* ─── Animations ─────────────────────────────────────────── */
275
+ @keyframes fadeInUp {
276
+ from { opacity: 0; transform: translateY(28px); }
277
+ to { opacity: 1; transform: translateY(0); }
278
+ }
279
+
280
+ @media (max-width: 900px) {
281
+ .pipeline { flex-direction: column; gap: 2px; }
282
+ .pipeline-step { max-width: 100%; }
283
+ .pipeline-connector {
284
+ width: 1px; height: 24px; flex: 0 0 24px; align-self: flex-start; margin: 0 34px;
285
+ background: linear-gradient(180deg, var(--accent-1), var(--accent-2));
286
+ }
287
+ }
landing page/src/main.jsx ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react'
2
+ import ReactDOM from 'react-dom/client'
3
+ import App from './App.jsx'
4
+ import './index.css'
5
+
6
+ ReactDOM.createRoot(document.getElementById('root')).render(
7
+ <React.StrictMode>
8
+ <App />
9
+ </React.StrictMode>
10
+ )
landing page/vite.config.js ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ import { defineConfig } from 'vite'
2
+ import react from '@vitejs/plugin-react'
3
+
4
+ export default defineConfig({
5
+ plugins: [react()],
6
+ })