/** * Landing Page Shell * * Composable landing page with an optional fixed background and scrolling * glassmorphic content overlay. The shell renders Hero, CTA, and Footer. * Additional sections are imported and placed in the JSX stack. */ import { useEffect, useState, useRef, useCallback } from 'react' import { useNavigate } from 'react-router-dom' import { motion, AnimatePresence, useScroll, useTransform } from 'framer-motion' import { ArrowRight, Menu, X, ChevronDown, Play, Github, Twitter, Linkedin, Mail, Sparkles, ChevronRight, Plus, Minus, } from 'lucide-react' import { useUser } from 'deepspace' import { Button, cn } from '@/components/ui' import { Typewriter, ScrollReveal, GlassCard, BrowserMockup, } from '../components/landing/primitives' // ============================================================================ // CSS injection // ============================================================================ const LANDING_CSS = `@layer base { .fixed-bg-layer { position: fixed; inset: 0; z-index: 0; overflow: hidden; } .scrollable-content-layer { position: relative; z-index: 1; height: 100dvh; height: 100vh; overflow-y: auto; overflow-x: hidden; scroll-behavior: smooth; } @supports (height: 100dvh) { .scrollable-content-layer { height: 100dvh; } } } @keyframes gradient-shimmer { 0% { background-position: 0% 50%; } 50% { background-position: 100% 50%; } 100% { background-position: 0% 50%; } } @keyframes border-rotate { 0% { --border-angle: 0deg; } 100% { --border-angle: 360deg; } } @layer base { .landing-shimmer-text { background-size: 200% 200%; animation: gradient-shimmer 6s ease-in-out infinite; } .landing-gradient-border { position: relative; } .landing-gradient-border::before { content: ''; position: absolute; inset: -1px; border-radius: inherit; padding: 1px; background: conic-gradient( from var(--border-angle, 0deg), transparent 25%, rgba(139, 92, 246, 0.5) 50%, transparent 75% ); mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0); mask-composite: exclude; animation: border-rotate 4s linear infinite; } .landing-noise-overlay { position: fixed; inset: 0; z-index: 2; pointer-events: none; opacity: 0.035; background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E"); background-repeat: repeat; background-size: 128px 128px; } } @property --border-angle { syntax: ""; initial-value: 0deg; inherits: false; }` let landingCssInjected = false function useLandingCSS() { useEffect(() => { if (landingCssInjected) return landingCssInjected = true const style = document.createElement('style') style.textContent = LANDING_CSS document.head.appendChild(style) return () => { document.head.removeChild(style) landingCssInjected = false } }, []) } // ============================================================================ // Configuration // ============================================================================ const LANDING_BG_URL = '' const APP_NAME = 'My App' const HERO_HEADLINE = 'Welcome to My App' const HERO_SUBHEADLINE = 'A simple, powerful way to get things done. Explore what\'s possible and start building today.' const HERO_IMAGE = '' const NAV_SECTIONS = [ { id: 'features', label: 'Features' }, { id: 'faq', label: 'FAQ' }, ] as const const NAV_LINKS = NAV_SECTIONS.map(s => ({ label: s.label, href: `#${s.id}` })) const SHOWCASE_ITEMS = [ { label: 'Overview', title: 'See everything at a glance', description: 'A clear, organized view of what matters most. Stay on top of your work without the clutter.', image: '', }, { label: 'Workflow', title: 'Work the way you want', description: 'Flexible tools that adapt to your process. Set things up once and let the app handle the rest.', image: '', }, ] const FAQ_ITEMS = [ { question: 'How do I get started?', answer: 'Just launch the app and follow the guided setup. You\'ll be up and running in under a minute -- no complicated configuration needed.' }, { question: 'Is it free to use?', answer: 'Yes, the core features are completely free. Premium features are available for teams and power users who need more.' }, { question: 'Can I customize it?', answer: 'Absolutely. The app is designed to be flexible -- adjust settings, layouts, and workflows to match exactly how you like to work.' }, { question: 'How does collaboration work?', answer: 'Invite your team and work together in real time. Everyone sees changes instantly, and permissions keep things organized.' }, { question: 'Where can I get help?', answer: 'Check out the built-in help section, browse the documentation, or reach out to the community. Support is always available.' }, ] const FOOTER_LINKS = { Product: ['Features', 'Pricing', 'Changelog', 'Documentation'], Company: ['About', 'Blog', 'Careers'], Resources: ['Community', 'Help Center', 'API Reference'], Legal: ['Privacy', 'Terms'], } // ============================================================================ // Navbar // ============================================================================ function useActiveSection( sectionIds: readonly string[], scrollRoot: React.RefObject, ): string | null { const [active, setActive] = useState(null) useEffect(() => { const root = scrollRoot.current if (!root) return const calculate = () => { const rootRect = root.getBoundingClientRect() const triggerY = rootRect.top + rootRect.height * 0.3 const entries = sectionIds .map(id => ({ id, el: document.getElementById(id) })) .filter((e): e is { id: string; el: HTMLElement } => e.el !== null) .sort((a, b) => a.el.getBoundingClientRect().top - b.el.getBoundingClientRect().top) let current: string | null = null for (const { id, el } of entries) { if (el.getBoundingClientRect().top <= triggerY) { current = id } } setActive(current) } calculate() root.addEventListener('scroll', calculate, { passive: true }) return () => root.removeEventListener('scroll', calculate) }, [sectionIds, scrollRoot]) return active } function LandingNav({ isScrolled, scrollRoot }: { isScrolled: boolean scrollRoot: React.RefObject }) { const [mobileOpen, setMobileOpen] = useState(false) const navigate = useNavigate() const sectionIds = NAV_SECTIONS.map(s => s.id) const activeSection = useActiveSection(sectionIds, scrollRoot) const scrollTo = (href: string) => { setMobileOpen(false) const target = document.querySelector(href) as HTMLElement | null const container = scrollRoot.current if (target && container) { const targetTop = target.getBoundingClientRect().top - container.getBoundingClientRect().top + container.scrollTop container.scrollTo({ top: targetTop, behavior: 'smooth' }) } } const mobileDropdown = ( {mobileOpen && (
{NAV_LINKS.map(link => { const isActive = activeSection === link.href.replace('#', '') return ( ) })}
)} ) return ( <>
{APP_NAME}
{NAV_LINKS.map(link => { const isActive = activeSection === link.href.replace('#', '') return ( ) })}
{mobileDropdown}
{isScrolled && (
{APP_NAME}
{NAV_LINKS.map(link => { const isActive = activeSection === link.href.replace('#', '') return ( ) })}
{mobileDropdown}
)} ) } // ============================================================================ // Hero // ============================================================================ function HeroSection() { const { user } = useUser() const navigate = useNavigate() const [typewriterDone, setTypewriterDone] = useState(false) const headline = user?.name ? `Welcome, ${user.name}` : HERO_HEADLINE const heroRef = useRef(null) const { scrollYProgress } = useScroll({ target: heroRef, offset: ['start start', 'end start'] }) const mockupY = useTransform(scrollYProgress, [0, 1], [0, 80]) return (
Now Available

setTypewriterDone(true)} />

{HERO_SUBHEADLINE}
) } // ============================================================================ // Showcase // ============================================================================ function ShowcaseSection() { return (
{SHOWCASE_ITEMS.map((item, idx) => { const isReversed = idx % 2 !== 0 return (
{item.label}

{item.title}

{item.description}

) })}
) } // ============================================================================ // FAQ // ============================================================================ function FAQItem({ item, isOpen, onToggle }: { item: typeof FAQ_ITEMS[number]; isOpen: boolean; onToggle: () => void }) { return (
{isOpen && (

{item.answer}

)}
) } function FAQSection() { const [openIndex, setOpenIndex] = useState(null) const handleToggle = useCallback((index: number) => { setOpenIndex(prev => prev === index ? null : index) }, []) return (
FAQ

Common questions

Everything you need to know to get started.

{FAQ_ITEMS.map((item, idx) => ( handleToggle(idx)} /> ))}
) } // ============================================================================ // CTA // ============================================================================ function CTASection() { const navigate = useNavigate() return (

Ready to get started?

Jump in and start exploring. No setup required.

) } // ============================================================================ // Footer // ============================================================================ function Footer() { return (
{APP_NAME}

{HERO_SUBHEADLINE}

{[ { icon: Twitter, label: 'Twitter' }, { icon: Github, label: 'GitHub' }, { icon: Linkedin, label: 'LinkedIn' }, { icon: Mail, label: 'Email' }, ].map(social => ( ))}
{Object.entries(FOOTER_LINKS).map(([heading, links]) => (

{heading}

    {links.map(link => (
  • {link}
  • ))}
))}
© {new Date().getFullYear()} {APP_NAME}. All rights reserved.
) } // ============================================================================ // Page Shell // ============================================================================ export default function LandingPage() { useLandingCSS() const containerRef = useRef(null) const [isScrolled, setIsScrolled] = useState(false) useEffect(() => { const el = containerRef.current if (!el) return const handleScroll = () => setIsScrolled(el.scrollTop > 50) el.addEventListener('scroll', handleScroll, { passive: true }) return () => el.removeEventListener('scroll', handleScroll) }, []) return (
{LANDING_BG_URL && (
)}
) }