diff --git a/frontend/src/App.js b/frontend/src/App.js index 0b2067a..79aadf2 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -1,5 +1,6 @@ import React, { useState, useEffect, useRef } from 'react'; import axios from 'axios'; +import LandingView from './components/LandingView'; import UploadView from './components/UploadView'; import QueryView from './components/QueryView'; import './App.css'; @@ -11,7 +12,7 @@ const API_BASE_URL = : 'https://documindrex.onrender.com'); export default function App() { - const [view, setView] = useState('upload'); + const [view, setView] = useState('landing'); const [transitionClass, setTransitionClass] = useState('visible'); const [documentId, setDocumentId] = useState(null); const [documentName, setDocumentName] = useState(''); @@ -101,6 +102,14 @@ export default function App() { }, 300); }; + if (view === 'landing') { + return ( +
+ transitionTo('upload')} /> +
+ ); + } + return (
diff --git a/frontend/src/components/LandingView.css b/frontend/src/components/LandingView.css new file mode 100644 index 0000000..bd0df44 --- /dev/null +++ b/frontend/src/components/LandingView.css @@ -0,0 +1,436 @@ +@import url('https://fonts.googleapis.com/css2?family=Syne:wght@400;600;700;800&family=DM+Sans:ital,opsz,wght@0,9..40,300;0,9..40,400;0,9..40,500;1,9..40,300&display=swap'); + +/* ─── Shell ──────────────────────────────────────────────── */ + +.landing { + position: relative; + min-height: 100vh; + display: flex; + flex-direction: column; + background: #060606; + color: #ffffff; + font-family: 'DM Sans', -apple-system, sans-serif; + overflow: hidden; +} + +/* Grain overlay */ +.landing__noise { + position: absolute; + inset: 0; + z-index: 0; + pointer-events: none; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='200' height='200' filter='url(%23g)'/%3E%3C/svg%3E"); + background-repeat: repeat; + opacity: 0.028; +} + +/* Sage-green ambient glow — top right */ +.landing__glow { + position: absolute; + top: -160px; + right: -120px; + width: 780px; + height: 780px; + border-radius: 50%; + background: radial-gradient(circle, rgba(100, 148, 100, 0.20) 0%, transparent 62%); + pointer-events: none; + z-index: 0; +} + +/* ─── Nav ────────────────────────────────────────────────── */ + +.landing__nav { + position: relative; + z-index: 10; + display: flex; + align-items: center; + justify-content: space-between; + padding: 1.2rem 2.5rem; + border-bottom: 1px solid rgba(255, 255, 255, 0.05); +} + +.landing__logo { + display: flex; + align-items: center; + gap: 0.55rem; + font-family: 'Syne', sans-serif; + font-weight: 700; + font-size: 0.95rem; + letter-spacing: 0.01em; + color: inherit; +} + +.landing__logo-mark { + font-size: 1.15rem; + color: rgba(110, 168, 110, 0.95); + line-height: 1; +} + +.landing__nav-links { + display: flex; + align-items: center; + gap: 1.75rem; + font-size: 0.84rem; + color: rgba(255, 255, 255, 0.5); +} + +.landing__nav-links a { + color: inherit; + text-decoration: none; + transition: color 180ms ease; +} +.landing__nav-links a:hover { color: rgba(255, 255, 255, 0.88); } + +.landing__nav-cta { + font-size: 0.83rem; + font-family: 'DM Sans', sans-serif; + padding: 0.45rem 1.15rem; + border-radius: 999px; + border: 1px solid rgba(255, 255, 255, 0.14); + background: rgba(255, 255, 255, 0.04); + color: rgba(255, 255, 255, 0.78); + cursor: pointer; + transition: border-color 180ms, background 180ms, color 180ms; +} +.landing__nav-cta:hover { + border-color: rgba(255, 255, 255, 0.32); + background: rgba(255, 255, 255, 0.08); + color: #fff; +} + +/* ─── Hero ───────────────────────────────────────────────── */ + +.landing__hero { + position: relative; + z-index: 1; + flex: 1; + display: flex; + align-items: center; + justify-content: center; + min-height: 580px; +} + +/* ─── Rail SVG ───────────────────────────────────────────── */ + +.landing__rails-svg { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + pointer-events: none; + z-index: 2; +} + +/* ─── Station nodes ──────────────────────────────────────── */ +/* Each station: circle bead ON the line + label beside it */ + +.landing__station { + position: absolute; + z-index: 3; + display: flex; + align-items: center; + gap: 0.55rem; +} + +/* Left-top: Parser — at the right end of left-top rail */ +.landing__station--lt { + top: 33%; + left: 13%; + transform: translateY(-50%); +} + +/* Left-bottom: Embeddings — at the right end of left-bottom rail */ +.landing__station--lb { + top: 67%; + left: 8%; + transform: translateY(-50%); +} + +/* Right-top: Retrieval — at the left end of right-top rail */ +.landing__station--rt { + top: 28%; + right: 13%; + transform: translateY(-50%); +} + +/* Right-bottom: Citations — at the left end of right-bottom rail */ +.landing__station--rb { + top: 67%; + right: 11%; + transform: translateY(-50%); +} + +/* Circle bead — dark fill so the line "passes through" it */ +.landing__station-circle { + flex-shrink: 0; + width: 30px; + height: 30px; + border-radius: 50%; + border: 1px solid rgba(255, 255, 255, 0.10); + background: #090909; + display: flex; + align-items: center; + justify-content: center; + font-size: 0.7rem; + color: rgba(255, 255, 255, 0.48); +} + +.landing__station-text { + display: flex; + flex-direction: column; + gap: 0.14rem; +} + +.landing__station-text--right { + text-align: right; +} + +.landing__station-name { + font-size: 0.8rem; + color: rgba(255, 255, 255, 0.7); + font-family: 'DM Sans', sans-serif; + font-weight: 400; + white-space: nowrap; +} + +.landing__station-sub { + font-size: 0.69rem; + color: rgba(255, 255, 255, 0.28); + font-family: 'DM Sans', sans-serif; + white-space: nowrap; +} + +/* ─── Center content ─────────────────────────────────────── */ + +.landing__center { + position: relative; + z-index: 4; + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + gap: 1.2rem; + max-width: 620px; + padding: 0 2rem; +} + +.landing__play { + width: 38px; + height: 38px; + border-radius: 50%; + border: 1px solid rgba(255, 255, 255, 0.14); + display: flex; + align-items: center; + justify-content: center; + font-size: 0.62rem; + color: rgba(255, 255, 255, 0.42); + margin-bottom: 0.2rem; +} + +.landing__badge { + display: inline-flex; + align-items: center; + gap: 0.5rem; + padding: 0.32rem 1rem 0.32rem 0.72rem; + border-radius: 999px; + border: 1px solid rgba(255, 255, 255, 0.09); + background: rgba(255, 255, 255, 0.03); + font-size: 0.78rem; + color: rgba(255, 255, 255, 0.5); + backdrop-filter: blur(8px); + -webkit-backdrop-filter: blur(8px); +} + +.landing__badge-dot { + font-size: 0.68rem; + color: rgba(110, 168, 110, 0.9); + line-height: 1; +} + +.landing__heading { + font-family: 'Syne', sans-serif; + font-size: clamp(2.8rem, 7vw, 5.8rem); + font-weight: 800; + letter-spacing: -0.04em; + line-height: 1.0; + margin: 0; + color: #ffffff; +} + +.landing__slogan { + font-size: 0.95rem; + font-family: 'DM Sans', sans-serif; + font-weight: 400; + color: rgba(255, 255, 255, 0.38); + letter-spacing: 0.01em; + margin: -0.4rem 0 0; +} + +.landing__sub { + font-size: 0.88rem; + color: rgba(255, 255, 255, 0.38); + line-height: 1.75; + font-weight: 300; + margin: 0; +} + +.landing__ctas { + display: flex; + align-items: center; + gap: 0.7rem; + margin-top: 0.5rem; +} + +.landing__cta-dark { + padding: 0.62rem 1.55rem; + border-radius: 999px; + border: 1px solid rgba(255, 255, 255, 0.14); + background: rgba(22, 22, 22, 0.92); + color: rgba(255, 255, 255, 0.9); + font-size: 0.86rem; + font-family: 'DM Sans', sans-serif; + font-weight: 500; + cursor: pointer; + transition: background 180ms, border-color 180ms; + text-decoration: none; + display: inline-flex; + align-items: center; +} +.landing__cta-dark:hover { + background: rgba(40, 40, 40, 0.95); + border-color: rgba(255, 255, 255, 0.28); +} + +.landing__cta-light { + padding: 0.62rem 1.55rem; + border-radius: 999px; + border: 1px solid rgba(255, 255, 255, 0.1); + background: rgba(255, 255, 255, 0.93); + color: #0a0a0a; + font-size: 0.86rem; + font-family: 'DM Sans', sans-serif; + font-weight: 500; + cursor: pointer; + transition: background 180ms; + text-decoration: none; + display: inline-flex; + align-items: center; +} +.landing__cta-light:hover { background: rgba(245, 245, 245, 0.95); } + +/* ─── Bar visualizer ─────────────────────────────────────── */ + +.landing__bars { + position: absolute; + bottom: 68px; + left: 50%; + transform: translateX(-50%); + display: flex; + align-items: flex-end; + gap: 5px; + height: 88px; + z-index: 2; +} + +.landing__bar { + width: 3px; + height: var(--h, 50%); + background: rgba(255, 255, 255, 0.09); + border-radius: 2px 2px 0 0; + animation: bar-breathe 3.5s ease-in-out infinite; + animation-delay: var(--d, 0s); + transform-origin: bottom; +} + +@keyframes bar-breathe { + 0%, 100% { opacity: 0.3; transform: scaleY(0.6); } + 50% { opacity: 0.8; transform: scaleY(1); } +} + +/* ─── Bottom indicators ──────────────────────────────────── */ + +.landing__scroll { + position: absolute; + bottom: 1.75rem; + left: 2.5rem; + display: flex; + align-items: center; + gap: 0.7rem; + z-index: 5; +} + +.landing__scroll-icon { + width: 30px; + height: 30px; + border-radius: 50%; + border: 1px solid rgba(255, 255, 255, 0.18); + display: flex; + align-items: center; + justify-content: center; + font-size: 0.68rem; + color: rgba(255, 255, 255, 0.55); +} + +.landing__scroll-text { + font-size: 0.75rem; + color: rgba(255, 255, 255, 0.32); + font-family: 'DM Sans', sans-serif; +} + +.landing__section-label { + position: absolute; + bottom: 1.75rem; + right: 2.5rem; + display: flex; + flex-direction: column; + align-items: flex-end; + gap: 0.4rem; + font-size: 0.76rem; + color: rgba(255, 255, 255, 0.32); + font-family: 'DM Sans', sans-serif; + z-index: 5; +} + +.landing__section-line { + width: 26px; + height: 1px; + background: rgba(255, 255, 255, 0.2); +} + +/* ─── Tech strip ─────────────────────────────────────────── */ + +.landing__strip { + position: relative; + z-index: 5; + display: flex; + align-items: center; + gap: 2.5rem; + padding: 1.15rem 2.5rem; + border-top: 1px solid rgba(255, 255, 255, 0.055); + background: rgba(0, 0, 0, 0.25); +} + +.landing__strip-label { + font-size: 0.72rem; + color: rgba(255, 255, 255, 0.2); + font-family: 'DM Sans', sans-serif; + white-space: nowrap; +} + +.landing__strip-logos { + display: flex; + align-items: center; + gap: 2.25rem; + flex-wrap: wrap; +} + +.landing__strip-logo { + font-size: 0.8rem; + font-family: 'Syne', sans-serif; + font-weight: 600; + letter-spacing: 0.02em; + color: rgba(255, 255, 255, 0.2); + transition: color 180ms ease; + cursor: default; +} +.landing__strip-logo:hover { color: rgba(255, 255, 255, 0.45); } diff --git a/frontend/src/components/LandingView.jsx b/frontend/src/components/LandingView.jsx new file mode 100644 index 0000000..f062d86 --- /dev/null +++ b/frontend/src/components/LandingView.jsx @@ -0,0 +1,158 @@ +import React from 'react'; +import './LandingView.css'; + +export default function LandingView({ onOpenApp }) { + return ( +
+