ADD LANDING PAGE
This commit is contained in:
+40
@@ -0,0 +1,40 @@
|
||||
# Dependencies
|
||||
node_modules/
|
||||
|
||||
# Build output
|
||||
dist/
|
||||
|
||||
# Environment variables
|
||||
.env
|
||||
.env.local
|
||||
.env.production
|
||||
.env.staging
|
||||
|
||||
# IDE / Editor
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# OS files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
Desktop.ini
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Cache
|
||||
.cache/
|
||||
.parcel-cache/
|
||||
.eslintcache
|
||||
|
||||
# Vite
|
||||
*.local
|
||||
|
||||
# Debug
|
||||
debug/
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/png" href="/favicon.png" />
|
||||
<link rel="apple-touch-icon" href="/icon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="description" content="Nova40 - Transform your identity in 40 days. Build habits, journal with AI insights, and track your personal growth journey." />
|
||||
<meta name="keywords" content="Nova40, habit tracker, identity transformation, 40 days, personal growth, AI journaling, self improvement" />
|
||||
<meta name="author" content="Nova40" />
|
||||
|
||||
<!-- Open Graph -->
|
||||
<meta property="og:title" content="Nova40 - 40 Days Identity Transformation" />
|
||||
<meta property="og:description" content="Transform your identity in 40 days with AI-powered habits, journaling, and personal growth tracking." />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content="https://nova40.app" />
|
||||
<meta property="og:image" content="https://nova40.app/og-image.png" />
|
||||
|
||||
<!-- Twitter Card -->
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:title" content="Nova40 - 40 Days Identity Transformation" />
|
||||
<meta name="twitter:description" content="Transform your identity in 40 days with AI-powered habits, journaling, and personal growth tracking." />
|
||||
|
||||
<!-- App Links -->
|
||||
<meta name="apple-itunes-app" content="app-id=YOUR_APP_ID" />
|
||||
<meta name="google-play-app" content="app-id=com.nova40.app" />
|
||||
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
|
||||
|
||||
<title>Nova40 - Transform Your Identity in 40 Days</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.jsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
Generated
+2667
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "nova40-landing",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-router-dom": "^6.23.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-react": "^4.3.1",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"postcss": "^8.4.38",
|
||||
"tailwindcss": "^3.4.4",
|
||||
"vite": "^5.3.1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 4.9 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.1 MiB |
@@ -0,0 +1,87 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
||||
<defs>
|
||||
<!-- Core glow gradient -->
|
||||
<radialGradient id="coreGlow" cx="50%" cy="50%" r="50%">
|
||||
<stop offset="0%" stop-color="#FFFFFF"/>
|
||||
<stop offset="30%" stop-color="#80F0FF"/>
|
||||
<stop offset="70%" stop-color="#00CFFF"/>
|
||||
<stop offset="100%" stop-color="#0080CC"/>
|
||||
</radialGradient>
|
||||
<!-- Outer glow -->
|
||||
<radialGradient id="outerGlow" cx="50%" cy="50%" r="50%">
|
||||
<stop offset="0%" stop-color="#00E5FF" stop-opacity="0.6"/>
|
||||
<stop offset="50%" stop-color="#CC44FF" stop-opacity="0.2"/>
|
||||
<stop offset="100%" stop-color="#CC44FF" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<!-- Ray gradient -->
|
||||
<linearGradient id="rayGrad" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" stop-color="#FF44CC" stop-opacity="0.9"/>
|
||||
<stop offset="100%" stop-color="#FF44CC" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<!-- Background gradient -->
|
||||
<linearGradient id="bgGrad" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" stop-color="#0D1230"/>
|
||||
<stop offset="100%" stop-color="#080C1A"/>
|
||||
</linearGradient>
|
||||
<!-- Blur filter for glow -->
|
||||
<filter id="softGlow" x="-50%" y="-50%" width="200%" height="200%">
|
||||
<feGaussianBlur stdDeviation="15" result="blur"/>
|
||||
<feComposite in="SourceGraphic" in2="blur" operator="over"/>
|
||||
</filter>
|
||||
<filter id="bigGlow" x="-100%" y="-100%" width="300%" height="300%">
|
||||
<feGaussianBlur stdDeviation="30"/>
|
||||
</filter>
|
||||
<filter id="rayGlow" x="-50%" y="-50%" width="200%" height="200%">
|
||||
<feGaussianBlur stdDeviation="4"/>
|
||||
</filter>
|
||||
</defs>
|
||||
|
||||
<!-- Background -->
|
||||
<rect width="512" height="512" rx="100" fill="url(#bgGrad)"/>
|
||||
|
||||
<!-- Stars -->
|
||||
<circle cx="80" cy="90" r="1.5" fill="white" opacity="0.5"/>
|
||||
<circle cx="430" cy="70" r="1" fill="white" opacity="0.4"/>
|
||||
<circle cx="400" cy="400" r="1.5" fill="white" opacity="0.3"/>
|
||||
<circle cx="60" cy="380" r="1" fill="white" opacity="0.5"/>
|
||||
<circle cx="150" cy="50" r="1" fill="white" opacity="0.3"/>
|
||||
<circle cx="350" cy="450" r="1" fill="white" opacity="0.4"/>
|
||||
<circle cx="450" cy="200" r="1" fill="white" opacity="0.3"/>
|
||||
<circle cx="70" cy="250" r="1.5" fill="white" opacity="0.4"/>
|
||||
<circle cx="200" cy="440" r="1" fill="white" opacity="0.3"/>
|
||||
<circle cx="440" cy="130" r="1" fill="white" opacity="0.5"/>
|
||||
|
||||
<!-- Outer glow behind everything -->
|
||||
<circle cx="256" cy="256" r="120" fill="url(#outerGlow)" filter="url(#bigGlow)"/>
|
||||
|
||||
<!-- Starburst rays -->
|
||||
<g transform="translate(256,256)" filter="url(#rayGlow)">
|
||||
<!-- 16 rays at various angles -->
|
||||
<line x1="0" y1="0" x2="160" y2="0" stroke="#FF44CC" stroke-width="2.5" opacity="0.8" transform="rotate(0)"/>
|
||||
<line x1="0" y1="0" x2="130" y2="0" stroke="#FF44CC" stroke-width="2" opacity="0.7" transform="rotate(22.5)"/>
|
||||
<line x1="0" y1="0" x2="155" y2="0" stroke="#FF44CC" stroke-width="2.5" opacity="0.8" transform="rotate(45)"/>
|
||||
<line x1="0" y1="0" x2="125" y2="0" stroke="#FF44CC" stroke-width="2" opacity="0.7" transform="rotate(67.5)"/>
|
||||
<line x1="0" y1="0" x2="160" y2="0" stroke="#FF44CC" stroke-width="2.5" opacity="0.8" transform="rotate(90)"/>
|
||||
<line x1="0" y1="0" x2="130" y2="0" stroke="#FF44CC" stroke-width="2" opacity="0.7" transform="rotate(112.5)"/>
|
||||
<line x1="0" y1="0" x2="155" y2="0" stroke="#FF44CC" stroke-width="2.5" opacity="0.8" transform="rotate(135)"/>
|
||||
<line x1="0" y1="0" x2="125" y2="0" stroke="#FF44CC" stroke-width="2" opacity="0.7" transform="rotate(157.5)"/>
|
||||
<line x1="0" y1="0" x2="160" y2="0" stroke="#FF44CC" stroke-width="2.5" opacity="0.8" transform="rotate(180)"/>
|
||||
<line x1="0" y1="0" x2="130" y2="0" stroke="#FF44CC" stroke-width="2" opacity="0.7" transform="rotate(202.5)"/>
|
||||
<line x1="0" y1="0" x2="155" y2="0" stroke="#FF44CC" stroke-width="2.5" opacity="0.8" transform="rotate(225)"/>
|
||||
<line x1="0" y1="0" x2="125" y2="0" stroke="#FF44CC" stroke-width="2" opacity="0.7" transform="rotate(247.5)"/>
|
||||
<line x1="0" y1="0" x2="160" y2="0" stroke="#FF44CC" stroke-width="2.5" opacity="0.8" transform="rotate(270)"/>
|
||||
<line x1="0" y1="0" x2="130" y2="0" stroke="#FF44CC" stroke-width="2" opacity="0.7" transform="rotate(292.5)"/>
|
||||
<line x1="0" y1="0" x2="155" y2="0" stroke="#FF44CC" stroke-width="2.5" opacity="0.8" transform="rotate(315)"/>
|
||||
<line x1="0" y1="0" x2="125" y2="0" stroke="#FF44CC" stroke-width="2" opacity="0.7" transform="rotate(337.5)"/>
|
||||
</g>
|
||||
|
||||
<!-- Orbital ring -->
|
||||
<ellipse cx="256" cy="256" rx="170" ry="55" fill="none" stroke="#00D4FF" stroke-width="3" opacity="0.8" transform="rotate(-25, 256, 256)"/>
|
||||
<ellipse cx="256" cy="256" rx="170" ry="55" fill="none" stroke="#00E5FF" stroke-width="1.5" opacity="0.4" transform="rotate(-25, 256, 256)" filter="url(#rayGlow)"/>
|
||||
|
||||
<!-- Core sphere -->
|
||||
<circle cx="256" cy="256" r="55" fill="url(#coreGlow)" filter="url(#softGlow)"/>
|
||||
<!-- Core highlight -->
|
||||
<circle cx="245" cy="244" r="25" fill="white" opacity="0.15"/>
|
||||
<circle cx="240" cy="240" r="12" fill="white" opacity="0.2"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.0 KiB |
+28
@@ -0,0 +1,28 @@
|
||||
import React from 'react'
|
||||
import StarField from './components/StarField'
|
||||
import Navbar from './components/Navbar'
|
||||
import Hero from './components/Hero'
|
||||
import Stats from './components/Stats'
|
||||
import Features from './components/Features'
|
||||
import HowItWorks from './components/HowItWorks'
|
||||
import Testimonial from './components/Testimonial'
|
||||
import Download from './components/Download'
|
||||
import Footer from './components/Footer'
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<div className="min-h-screen relative">
|
||||
<StarField count={70} />
|
||||
<Navbar />
|
||||
<main className="relative z-10">
|
||||
<Hero />
|
||||
<Stats />
|
||||
<Features />
|
||||
<HowItWorks />
|
||||
<Testimonial />
|
||||
<Download />
|
||||
</main>
|
||||
<Footer />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
import React from 'react'
|
||||
import Nova40Icon from './Nova40Icon'
|
||||
import useReveal from '../hooks/useReveal'
|
||||
|
||||
export default function Download() {
|
||||
const ref = useReveal()
|
||||
|
||||
return (
|
||||
<section id="download" className="py-20 sm:py-28 relative" ref={ref}>
|
||||
<div className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="reveal relative glass-card p-10 sm:p-16 text-center overflow-hidden">
|
||||
{/* Background effects */}
|
||||
<div className="absolute top-0 left-1/2 -translate-x-1/2 w-[500px] h-[500px] bg-nova-primary/10 rounded-full blur-[150px]" />
|
||||
<div className="absolute bottom-0 right-0 w-[300px] h-[300px] bg-nova-accent/5 rounded-full blur-[100px]" />
|
||||
|
||||
{/* Decorative orbit */}
|
||||
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[400px] h-[400px] border border-nova-primary/10 rounded-full animate-orbit" />
|
||||
|
||||
<div className="relative">
|
||||
<div className="flex justify-center mb-6">
|
||||
<div className="relative">
|
||||
<Nova40Icon size={96} rounded />
|
||||
<div className="absolute -inset-4 bg-nova-primary/15 rounded-2xl blur-xl -z-10 animate-pulse-slow" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 className="text-3xl sm:text-5xl font-bold mb-4">
|
||||
Ready to become{' '}
|
||||
<br className="hidden sm:block" />
|
||||
<span className="text-shimmer">someone new?</span>
|
||||
</h2>
|
||||
<p className="text-nova-textSecondary text-lg max-w-lg mx-auto mb-10">
|
||||
Download Nova40 for free and start your 40-day identity transformation journey today.
|
||||
</p>
|
||||
|
||||
<div className="flex flex-col sm:flex-row gap-4 justify-center items-center">
|
||||
{/* App Store */}
|
||||
<a
|
||||
href="https://apps.apple.com/app/nova40/id0000000000"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="w-full sm:w-auto inline-flex items-center justify-center gap-3 px-8 py-4 bg-white/5 hover:bg-white/10 border border-nova-border hover:border-nova-primary/50 rounded-2xl transition-all group hover:scale-105 hover:shadow-lg hover:shadow-nova-primary/10"
|
||||
>
|
||||
<svg className="w-9 h-9 text-white" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M18.71 19.5c-.83 1.24-1.71 2.45-3.05 2.47-1.34.03-1.77-.79-3.29-.79-1.53 0-2 .77-3.27.82-1.31.05-2.3-1.32-3.14-2.53C4.25 17 2.94 12.45 4.7 9.39c.87-1.52 2.43-2.48 4.12-2.51 1.28-.02 2.5.87 3.29.87.78 0 2.26-1.07 3.8-.91.65.03 2.47.26 3.64 1.98-.09.06-2.17 1.28-2.15 3.81.03 3.02 2.65 4.03 2.68 4.04-.03.07-.42 1.44-1.38 2.83M13 3.5c.73-.83 1.94-1.46 2.94-1.5.13 1.17-.34 2.35-1.04 3.19-.69.85-1.83 1.51-2.95 1.42-.15-1.15.41-2.35 1.05-3.11z"/>
|
||||
</svg>
|
||||
<div className="text-left">
|
||||
<p className="text-[10px] text-nova-textSecondary leading-tight">Download on the</p>
|
||||
<p className="text-white font-bold text-lg leading-tight">App Store</p>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
{/* Google Play */}
|
||||
<a
|
||||
href="https://play.google.com/store/apps/details?id=com.nova40.app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="w-full sm:w-auto inline-flex items-center justify-center gap-3 px-8 py-4 bg-white/5 hover:bg-white/10 border border-nova-border hover:border-nova-accent/50 rounded-2xl transition-all group hover:scale-105 hover:shadow-lg hover:shadow-nova-accent/10"
|
||||
>
|
||||
<svg className="w-9 h-9 text-white" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M3.609 1.814L13.792 12 3.61 22.186a.996.996 0 01-.61-.92V2.734a1 1 0 01.609-.92zm10.89 10.893l2.302 2.302-10.937 6.333 8.635-8.635zm3.199-3.198l2.807 1.626a1 1 0 010 1.73l-2.808 1.626L15.206 12l2.492-2.491zM5.864 2.658L16.8 8.99l-2.3 2.3-8.636-8.632z"/>
|
||||
</svg>
|
||||
<div className="text-left">
|
||||
<p className="text-[10px] text-nova-textSecondary leading-tight">Get it on</p>
|
||||
<p className="text-white font-bold text-lg leading-tight">Google Play</p>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<p className="text-nova-textMuted text-xs mt-6">Free download. No credit card required.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
import React from 'react'
|
||||
import useReveal from '../hooks/useReveal'
|
||||
|
||||
const features = [
|
||||
{
|
||||
emoji: '🧠',
|
||||
title: 'AI-Powered Identity',
|
||||
description: 'Write your story and let AI help you discover your new identity and build a personalized habit plan.',
|
||||
color: 'from-nova-primary/20 to-purple-500/20',
|
||||
borderColor: 'hover:border-nova-primary/50',
|
||||
glowColor: 'rgba(108, 99, 255, 0.15)',
|
||||
},
|
||||
{
|
||||
emoji: '🎯',
|
||||
title: 'Daily Habit Tracking',
|
||||
description: 'Track up to 5 daily habits with mood check-ins and identity alignment to stay consistent.',
|
||||
color: 'from-nova-accent/20 to-cyan-500/20',
|
||||
borderColor: 'hover:border-nova-accent/50',
|
||||
glowColor: 'rgba(0, 229, 255, 0.15)',
|
||||
},
|
||||
{
|
||||
emoji: '📝',
|
||||
title: 'Smart Journaling',
|
||||
description: 'Write daily reflections and receive AI-generated insights, summaries, and motivational quotes.',
|
||||
color: 'from-nova-success/20 to-emerald-500/20',
|
||||
borderColor: 'hover:border-nova-success/50',
|
||||
glowColor: 'rgba(0, 230, 118, 0.15)',
|
||||
},
|
||||
{
|
||||
emoji: '📖',
|
||||
title: 'Habit History',
|
||||
description: 'Look back on every completed day — mood, habits, AI reflections, and journal entries all in one timeline.',
|
||||
color: 'from-nova-warning/20 to-amber-500/20',
|
||||
borderColor: 'hover:border-nova-warning/50',
|
||||
glowColor: 'rgba(255, 215, 64, 0.15)',
|
||||
},
|
||||
{
|
||||
emoji: '📊',
|
||||
title: 'Visual Progress',
|
||||
description: 'Watch your journey unfold on a beautiful orbit map with heat calendar and detailed statistics.',
|
||||
color: 'from-nova-error/20 to-rose-500/20',
|
||||
borderColor: 'hover:border-nova-error/50',
|
||||
glowColor: 'rgba(255, 82, 82, 0.15)',
|
||||
},
|
||||
{
|
||||
emoji: '✨',
|
||||
title: 'Transformation Story',
|
||||
description: 'Complete 40 days and receive an AI-written transformation story celebrating your journey.',
|
||||
color: 'from-nova-primaryLight/20 to-indigo-500/20',
|
||||
borderColor: 'hover:border-nova-primaryLight/50',
|
||||
glowColor: 'rgba(139, 133, 255, 0.15)',
|
||||
},
|
||||
]
|
||||
|
||||
export default function Features() {
|
||||
const ref = useReveal()
|
||||
|
||||
return (
|
||||
<section id="features" className="py-20 sm:py-28 relative" ref={ref}>
|
||||
{/* Section divider glow */}
|
||||
<div className="absolute top-0 left-1/2 -translate-x-1/2 w-[600px] h-px bg-gradient-to-r from-transparent via-nova-primary/40 to-transparent" />
|
||||
|
||||
<div className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="text-center mb-16 reveal">
|
||||
<p className="text-nova-accent text-sm font-semibold tracking-widest uppercase mb-3">Features</p>
|
||||
<h2 className="text-3xl sm:text-5xl font-bold mb-5">
|
||||
Everything you need to{' '}
|
||||
<span className="text-shimmer">transform</span>
|
||||
</h2>
|
||||
<p className="text-nova-textSecondary text-lg max-w-2xl mx-auto">
|
||||
Nova40 combines proven habit science with AI technology to guide your 40-day personal transformation.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{features.map((feature, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className={`reveal reveal-delay-${i % 3 + 1} glass-card p-7 ${feature.borderColor} transition-all duration-500 group hover:-translate-y-2 hover:shadow-2xl relative overflow-hidden cursor-default`}
|
||||
>
|
||||
{/* Hover glow */}
|
||||
<div
|
||||
className="absolute -top-20 -right-20 w-40 h-40 rounded-full blur-[60px] opacity-0 group-hover:opacity-100 transition-opacity duration-500"
|
||||
style={{ background: feature.glowColor }}
|
||||
/>
|
||||
|
||||
<div className="relative">
|
||||
<div className={`w-14 h-14 rounded-2xl bg-gradient-to-br ${feature.color} flex items-center justify-center mb-5 group-hover:scale-110 transition-transform duration-300`}>
|
||||
<span className="text-2xl">{feature.emoji}</span>
|
||||
</div>
|
||||
<h3 className="text-white font-bold text-lg mb-2">{feature.title}</h3>
|
||||
<p className="text-nova-textSecondary text-sm leading-relaxed">{feature.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
import React from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
import Nova40Icon from './Nova40Icon'
|
||||
|
||||
export default function Footer() {
|
||||
return (
|
||||
<footer className="relative border-t border-nova-border py-12">
|
||||
{/* Top glow */}
|
||||
<div className="absolute top-0 left-1/2 -translate-x-1/2 w-[400px] h-px bg-gradient-to-r from-transparent via-nova-primary/30 to-transparent" />
|
||||
|
||||
<div className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-8 mb-10">
|
||||
{/* Brand */}
|
||||
<div className="sm:col-span-2 lg:col-span-1">
|
||||
<div className="flex items-center gap-2.5 mb-4">
|
||||
<Nova40Icon size={40} rounded />
|
||||
<span className="text-xl font-bold text-white">Nova40</span>
|
||||
</div>
|
||||
<p className="text-nova-textSecondary text-sm leading-relaxed mb-4">
|
||||
Transform your identity in 40 days through habits, journaling, and AI-powered insights.
|
||||
</p>
|
||||
<p className="text-nova-accent text-xs font-medium">40 days is all it takes.</p>
|
||||
</div>
|
||||
|
||||
{/* Product */}
|
||||
<div>
|
||||
<h4 className="text-white font-bold text-sm mb-4 uppercase tracking-wider text-xs">Product</h4>
|
||||
<ul className="space-y-2.5">
|
||||
<li><a href="#features" className="text-nova-textSecondary hover:text-white text-sm transition-colors hover:translate-x-1 inline-block">Features</a></li>
|
||||
<li><a href="#how-it-works" className="text-nova-textSecondary hover:text-white text-sm transition-colors hover:translate-x-1 inline-block">How It Works</a></li>
|
||||
<li><a href="#download" className="text-nova-textSecondary hover:text-white text-sm transition-colors hover:translate-x-1 inline-block">Download</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* Legal */}
|
||||
<div>
|
||||
<h4 className="text-white font-bold text-sm mb-4 uppercase tracking-wider text-xs">Legal</h4>
|
||||
<ul className="space-y-2.5">
|
||||
<li><Link to="/privacy-policy" className="text-nova-textSecondary hover:text-white text-sm transition-colors hover:translate-x-1 inline-block">Privacy Policy</Link></li>
|
||||
<li><Link to="/terms-conditions" className="text-nova-textSecondary hover:text-white text-sm transition-colors hover:translate-x-1 inline-block">Terms & Conditions</Link></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* Contact */}
|
||||
<div>
|
||||
<h4 className="text-white font-bold text-sm mb-4 uppercase tracking-wider text-xs">Contact</h4>
|
||||
<ul className="space-y-2.5">
|
||||
<li>
|
||||
<a href="mailto:support@nova40.app" className="text-nova-textSecondary hover:text-nova-accent text-sm transition-colors inline-flex items-center gap-2">
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
|
||||
</svg>
|
||||
support@nova40.app
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border-t border-nova-border pt-8 flex flex-col sm:flex-row items-center justify-between gap-4">
|
||||
<p className="text-nova-textMuted text-xs">
|
||||
© {new Date().getFullYear()} Nova40. All rights reserved.
|
||||
</p>
|
||||
<div className="flex items-center gap-1 text-nova-textMuted text-xs">
|
||||
Made with <span className="text-nova-error mx-0.5 animate-pulse">♥</span> for personal growth
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import Nova40Icon from './Nova40Icon'
|
||||
|
||||
function Meteor({ delay, top, left }) {
|
||||
return (
|
||||
<div
|
||||
className="meteor"
|
||||
style={{
|
||||
top: `${top}%`,
|
||||
left: `${left}%`,
|
||||
animationDelay: `${delay}s`,
|
||||
animationDuration: `${2 + Math.random() * 2}s`,
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function TypeWriter({ texts, className }) {
|
||||
const [index, setIndex] = useState(0)
|
||||
const [text, setText] = useState('')
|
||||
const [deleting, setDeleting] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
const current = texts[index]
|
||||
const speed = deleting ? 40 : 80
|
||||
|
||||
if (!deleting && text === current) {
|
||||
const pause = setTimeout(() => setDeleting(true), 2000)
|
||||
return () => clearTimeout(pause)
|
||||
}
|
||||
if (deleting && text === '') {
|
||||
setDeleting(false)
|
||||
setIndex((i) => (i + 1) % texts.length)
|
||||
return
|
||||
}
|
||||
|
||||
const timer = setTimeout(() => {
|
||||
setText(deleting ? current.slice(0, text.length - 1) : current.slice(0, text.length + 1))
|
||||
}, speed)
|
||||
|
||||
return () => clearTimeout(timer)
|
||||
}, [text, deleting, index, texts])
|
||||
|
||||
return (
|
||||
<span className={className}>
|
||||
{text}
|
||||
<span className="animate-pulse text-nova-accent">|</span>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
export default function Hero() {
|
||||
return (
|
||||
<section className="relative min-h-screen flex items-center justify-center pt-28 sm:pt-16 overflow-hidden">
|
||||
{/* Background Effects — more vibrant */}
|
||||
<div className="absolute inset-0">
|
||||
<div className="absolute top-1/4 left-1/2 -translate-x-1/2 w-[600px] h-[600px] bg-nova-primary/15 rounded-full blur-[150px] animate-pulse-slow" />
|
||||
<div className="absolute bottom-1/3 left-1/4 w-[400px] h-[400px] bg-nova-accent/10 rounded-full blur-[120px]" />
|
||||
<div className="absolute top-1/3 right-1/4 w-[300px] h-[300px] bg-[#FF44CC]/5 rounded-full blur-[100px]" />
|
||||
</div>
|
||||
|
||||
{/* Shooting stars */}
|
||||
<Meteor delay={0} top={10} left={70} />
|
||||
<Meteor delay={3} top={5} left={90} />
|
||||
<Meteor delay={6} top={15} left={50} />
|
||||
<Meteor delay={9} top={8} left={80} />
|
||||
|
||||
<div className="relative z-10 max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
|
||||
<div className="flex flex-col lg:flex-row items-center gap-12 lg:gap-16">
|
||||
{/* Text Content */}
|
||||
<div className="flex-1 text-center lg:text-left">
|
||||
{/* Badge */}
|
||||
<div className="inline-flex items-center gap-2 px-4 py-2 bg-nova-primary/10 border border-nova-primary/30 rounded-full mb-6 backdrop-blur-sm">
|
||||
<span className="relative flex h-2.5 w-2.5">
|
||||
<span className="ping-slow absolute inline-flex h-full w-full rounded-full bg-nova-success opacity-75" />
|
||||
<span className="relative inline-flex rounded-full h-2.5 w-2.5 bg-nova-success" />
|
||||
</span>
|
||||
<span className="text-nova-primaryLight text-xs font-semibold tracking-wide uppercase">40-Day Transformation</span>
|
||||
</div>
|
||||
|
||||
<h1 className="text-5xl sm:text-6xl lg:text-7xl font-bold leading-[1.1] mb-6">
|
||||
Become
|
||||
<br />
|
||||
<span className="text-shimmer">
|
||||
someone new
|
||||
</span>
|
||||
</h1>
|
||||
|
||||
<p className="text-nova-textSecondary text-lg sm:text-xl max-w-lg mx-auto lg:mx-0 mb-4 leading-relaxed">
|
||||
Nova40 is a 40-day journey that helps you transform your identity through daily habits, AI-powered journaling, and real self-reflection.
|
||||
</p>
|
||||
|
||||
{/* Typewriter */}
|
||||
<p className="text-nova-textMuted text-sm mb-8 h-6">
|
||||
I want to become{' '}
|
||||
<TypeWriter
|
||||
texts={['more disciplined', 'a better version of me', 'mentally stronger', 'someone who never gives up', 'truly confident']}
|
||||
className="text-nova-accent font-medium"
|
||||
/>
|
||||
</p>
|
||||
|
||||
<div className="flex flex-col sm:flex-row gap-4 justify-center lg:justify-start">
|
||||
<a
|
||||
href="#download"
|
||||
className="group inline-flex items-center justify-center gap-2 px-8 py-4 bg-gradient-to-r from-nova-primary to-nova-primaryLight hover:from-nova-primaryLight hover:to-nova-primary rounded-full text-white font-bold transition-all glow-primary hover:scale-105 text-lg"
|
||||
>
|
||||
<svg className="w-5 h-5 group-hover:animate-bounce" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M5 20h14v-2H5v2zM19 9h-4V3H9v6H5l7 7 7-7z"/>
|
||||
</svg>
|
||||
Download Free
|
||||
</a>
|
||||
<a
|
||||
href="#how-it-works"
|
||||
className="inline-flex items-center justify-center gap-2 px-8 py-4 border border-nova-border hover:border-nova-primary/60 rounded-full text-white font-medium transition-all hover:bg-nova-surface/50"
|
||||
>
|
||||
See How It Works
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{/* Social proof */}
|
||||
<div className="flex items-center gap-6 mt-10 justify-center lg:justify-start">
|
||||
<div className="flex -space-x-2">
|
||||
{['#6C63FF', '#00E5FF', '#00E676', '#FFD740'].map((c, i) => (
|
||||
<div key={i} className="w-8 h-8 rounded-full border-2 border-nova-bg flex items-center justify-center text-[10px] font-bold" style={{ background: c, zIndex: 4 - i }}>
|
||||
{['A', 'R', 'M', 'S'][i]}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="text-left">
|
||||
<p className="text-white text-sm font-semibold">1,000+ users</p>
|
||||
<p className="text-nova-textMuted text-xs">already transforming</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Phone Mockup */}
|
||||
<div className="flex-shrink-0 relative phone-float">
|
||||
<div className="relative w-[280px] sm:w-[300px]">
|
||||
{/* Decorative orbit rings */}
|
||||
<div className="absolute -inset-12 border border-nova-primary/10 rounded-full animate-orbit" />
|
||||
<div className="absolute -inset-20 border border-nova-accent/5 rounded-full" style={{ animation: 'orbit 30s linear infinite reverse' }} />
|
||||
|
||||
{/* Floating dots */}
|
||||
<div className="absolute -top-4 -right-4 w-4 h-4 bg-nova-accent rounded-full glow-accent animate-float" />
|
||||
<div className="absolute -bottom-2 -left-6 w-3 h-3 bg-nova-primary rounded-full glow-primary" style={{ animation: 'float 4s ease-in-out 1s infinite' }} />
|
||||
<div className="absolute top-1/3 -right-8 w-2 h-2 bg-nova-warning rounded-full" style={{ animation: 'float 5s ease-in-out 2s infinite' }} />
|
||||
|
||||
{/* Phone frame */}
|
||||
<div className="relative bg-gradient-to-b from-nova-surfaceLight to-nova-surface border-2 border-nova-border rounded-[3rem] p-3 shadow-2xl shadow-nova-primary/10">
|
||||
{/* Notch */}
|
||||
<div className="absolute top-3 left-1/2 -translate-x-1/2 w-24 h-5 bg-nova-bg rounded-full z-10" />
|
||||
|
||||
<div className="bg-nova-bg rounded-[2.5rem] overflow-hidden aspect-[9/19.5] flex flex-col items-center justify-center p-6 relative">
|
||||
{/* Subtle glow inside phone */}
|
||||
<div className="absolute top-1/3 left-1/2 -translate-x-1/2 w-40 h-40 bg-nova-primary/10 rounded-full blur-[60px]" />
|
||||
|
||||
{/* Stars inside phone screen */}
|
||||
{[...Array(18)].map((_, i) => {
|
||||
const size = i < 10 ? 1 + Math.random() * 1.5 : 2 + Math.random() * 1.5
|
||||
const hasGlow = i >= 14
|
||||
return (
|
||||
<span
|
||||
key={`ps-${i}`}
|
||||
className="absolute rounded-full"
|
||||
style={{
|
||||
width: size,
|
||||
height: size,
|
||||
top: `${5 + Math.random() * 90}%`,
|
||||
left: `${5 + Math.random() * 90}%`,
|
||||
background: hasGlow ? 'rgba(255,255,255,0.9)' : 'rgba(255,255,255,0.6)',
|
||||
boxShadow: hasGlow ? `0 0 ${size * 3}px rgba(108,99,255,0.6)` : 'none',
|
||||
animation: `starTwinkle ${2 + Math.random() * 3}s ease-in-out ${Math.random() * 4}s infinite`,
|
||||
}}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
|
||||
{/* Nova40 Icon */}
|
||||
<div className="relative mb-4">
|
||||
<Nova40Icon size={64} rounded={false} />
|
||||
<div className="absolute -inset-2 bg-nova-primary/20 rounded-full blur-xl -z-10" />
|
||||
</div>
|
||||
|
||||
<p className="text-white text-sm font-bold mb-0.5">Day 17 of 40</p>
|
||||
<p className="text-nova-accent text-[10px] font-medium mb-3">42% Complete</p>
|
||||
|
||||
{/* Progress bar */}
|
||||
<div className="w-full h-1.5 bg-nova-surfaceLight rounded-full mb-6 overflow-hidden">
|
||||
<div className="w-[42%] h-full bg-gradient-to-r from-nova-primary via-nova-accent to-nova-success rounded-full relative">
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-transparent to-white/30 animate-pulse" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Mini orbit */}
|
||||
<div className="relative w-28 h-28 mb-4">
|
||||
<div className="absolute inset-0 border border-nova-primary/30 rounded-full" />
|
||||
<div className="absolute inset-3 border border-nova-accent/15 rounded-full" />
|
||||
<div className="absolute inset-0 flex items-center justify-center">
|
||||
<div className="w-5 h-5 bg-gradient-to-br from-nova-primary to-nova-accent rounded-full animate-pulse-slow" />
|
||||
</div>
|
||||
{[...Array(8)].map((_, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="absolute w-2 h-2 rounded-full transition-colors"
|
||||
style={{
|
||||
background: i < 5 ? '#00E5FF' : 'rgba(28, 35, 69, 0.8)',
|
||||
boxShadow: i < 5 ? '0 0 6px rgba(0, 229, 255, 0.5)' : 'none',
|
||||
top: `${50 + 42 * Math.sin((i * Math.PI * 2) / 8)}%`,
|
||||
left: `${50 + 42 * Math.cos((i * Math.PI * 2) / 8)}%`,
|
||||
transform: 'translate(-50%, -50%)',
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Stats mini cards */}
|
||||
<div className="flex gap-2 w-full">
|
||||
<div className="flex-1 bg-nova-surface/80 rounded-lg px-2 py-1.5 text-center border border-nova-border">
|
||||
<p className="text-nova-accent text-xs font-bold">85%</p>
|
||||
<p className="text-nova-textMuted text-[8px]">Focus</p>
|
||||
</div>
|
||||
<div className="flex-1 bg-nova-surface/80 rounded-lg px-2 py-1.5 text-center border border-nova-border">
|
||||
<p className="text-nova-success text-xs font-bold">92%</p>
|
||||
<p className="text-nova-textMuted text-[8px]">Discipline</p>
|
||||
</div>
|
||||
<div className="flex-1 bg-nova-surface/80 rounded-lg px-2 py-1.5 text-center border border-nova-border">
|
||||
<p className="text-nova-warning text-xs font-bold">78%</p>
|
||||
<p className="text-nova-textMuted text-[8px]">Consistency</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Scroll indicator */}
|
||||
<div className="absolute bottom-8 left-1/2 -translate-x-1/2 flex flex-col items-center gap-2 animate-bounce z-10">
|
||||
<p className="text-nova-textMuted text-xs">Scroll to explore</p>
|
||||
<svg className="w-4 h-4 text-nova-textMuted" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 14l-7 7m0 0l-7-7" />
|
||||
</svg>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
import React from 'react'
|
||||
import useReveal from '../hooks/useReveal'
|
||||
|
||||
const steps = [
|
||||
{
|
||||
number: '01',
|
||||
emoji: '📖',
|
||||
title: 'Write Your Story',
|
||||
description: 'Tell us who you want to become. Our AI analyzes your story and creates a personalized identity transformation plan with habits tailored for you.',
|
||||
accent: 'bg-nova-primary',
|
||||
glow: 'bg-nova-primary/20',
|
||||
line: 'from-nova-primary',
|
||||
},
|
||||
{
|
||||
number: '02',
|
||||
emoji: '🔥',
|
||||
title: 'Build Daily Habits',
|
||||
description: 'Choose up to 5 daily habits that align with your new identity. Track your mood, check alignment, and build unstoppable consistency.',
|
||||
accent: 'bg-nova-accent',
|
||||
glow: 'bg-nova-accent/20',
|
||||
line: 'from-nova-accent',
|
||||
},
|
||||
{
|
||||
number: '03',
|
||||
emoji: '🪞',
|
||||
title: 'Reflect & Grow',
|
||||
description: 'Journal daily, review your habit history, and receive AI-powered reflections that deepen your self-awareness and push you further.',
|
||||
accent: 'bg-nova-success',
|
||||
glow: 'bg-nova-success/20',
|
||||
line: 'from-nova-success',
|
||||
},
|
||||
{
|
||||
number: '04',
|
||||
emoji: '🚀',
|
||||
title: 'Transform in 40 Days',
|
||||
description: 'Complete the journey and unlock your AI-generated transformation story — a celebration of the new version of you.',
|
||||
accent: 'bg-nova-warning',
|
||||
glow: 'bg-nova-warning/20',
|
||||
line: 'from-nova-warning',
|
||||
},
|
||||
]
|
||||
|
||||
export default function HowItWorks() {
|
||||
const ref = useReveal()
|
||||
|
||||
return (
|
||||
<section id="how-it-works" className="py-20 sm:py-28 relative" ref={ref}>
|
||||
{/* Background gradient band */}
|
||||
<div className="absolute inset-0 bg-gradient-to-b from-transparent via-nova-surface/40 to-transparent" />
|
||||
<div className="absolute top-0 left-1/2 -translate-x-1/2 w-[600px] h-px bg-gradient-to-r from-transparent via-nova-accent/30 to-transparent" />
|
||||
|
||||
<div className="relative max-w-5xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="text-center mb-16 reveal">
|
||||
<p className="text-nova-primary text-sm font-semibold tracking-widest uppercase mb-3">How It Works</p>
|
||||
<h2 className="text-3xl sm:text-5xl font-bold mb-5">
|
||||
4 steps to a{' '}
|
||||
<span className="text-shimmer">new you</span>
|
||||
</h2>
|
||||
<p className="text-nova-textSecondary text-lg max-w-2xl mx-auto">
|
||||
A simple process — big transformation. Just show up for 40 days.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Timeline */}
|
||||
<div className="relative">
|
||||
{/* Vertical connector line — desktop only */}
|
||||
<div className="hidden md:block absolute left-1/2 -translate-x-1/2 top-0 bottom-0 w-px bg-gradient-to-b from-nova-primary via-nova-accent via-nova-success to-nova-warning opacity-30" />
|
||||
|
||||
<div className="space-y-12 md:space-y-16">
|
||||
{steps.map((step, i) => {
|
||||
const isLeft = i % 2 === 0
|
||||
return (
|
||||
<div key={i} className={`reveal reveal-delay-${i + 1} flex flex-col md:flex-row items-center gap-6 md:gap-12 ${isLeft ? '' : 'md:flex-row-reverse'}`}>
|
||||
{/* Content card */}
|
||||
<div className={`flex-1 glass-card p-6 sm:p-8 relative overflow-hidden group hover:border-nova-primary/30 transition-all duration-500 ${isLeft ? 'md:text-right' : 'md:text-left'}`}>
|
||||
{/* Background glow on hover */}
|
||||
<div className={`absolute -bottom-10 ${isLeft ? '-right-10' : '-left-10'} w-32 h-32 ${step.glow} rounded-full blur-[50px] opacity-0 group-hover:opacity-100 transition-opacity duration-500`} />
|
||||
|
||||
<div className="relative">
|
||||
<span className="text-3xl mb-3 block">{step.emoji}</span>
|
||||
<h3 className="text-white font-bold text-xl mb-2">{step.title}</h3>
|
||||
<p className="text-nova-textSecondary text-sm leading-relaxed">{step.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Center dot */}
|
||||
<div className="relative flex-shrink-0 z-10">
|
||||
<div className={`w-14 h-14 ${step.accent} rounded-2xl flex items-center justify-center font-bold text-white text-lg shadow-lg relative`}>
|
||||
{step.number}
|
||||
<div className={`absolute -inset-2 ${step.accent} rounded-2xl opacity-20 blur-sm`} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Spacer for alignment */}
|
||||
<div className="flex-1 hidden md:block" />
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
import React from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
import StarField from './StarField'
|
||||
|
||||
export default function LegalLayout({ title, updated, children }) {
|
||||
return (
|
||||
<div className="min-h-screen relative">
|
||||
<StarField count={40} />
|
||||
{/* Header */}
|
||||
<nav className="fixed top-0 left-0 right-0 z-50 bg-nova-bg/80 backdrop-blur-xl border-b border-nova-border">
|
||||
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 h-16 flex items-center">
|
||||
<Link to="/" className="flex items-center gap-2 text-nova-textSecondary hover:text-white transition-colors">
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
|
||||
</svg>
|
||||
<span className="text-sm">Back to Home</span>
|
||||
</Link>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
{/* Content */}
|
||||
<main className="pt-24 pb-16 max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<h1 className="text-3xl sm:text-4xl font-bold text-white mb-2">{title}</h1>
|
||||
<p className="text-nova-textMuted text-sm mb-10">Last updated: {updated}</p>
|
||||
<div className="space-y-8">
|
||||
{children}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
{/* Footer */}
|
||||
<footer className="border-t border-nova-border py-8">
|
||||
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 flex flex-col sm:flex-row items-center justify-between gap-4">
|
||||
<p className="text-nova-textMuted text-sm">© {new Date().getFullYear()} Nova40</p>
|
||||
<div className="flex gap-4">
|
||||
<Link to="/privacy-policy" className="text-nova-textMuted hover:text-nova-textSecondary text-sm transition-colors">Privacy Policy</Link>
|
||||
<Link to="/terms-conditions" className="text-nova-textMuted hover:text-nova-textSecondary text-sm transition-colors">Terms & Conditions</Link>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function Section({ title, children }) {
|
||||
return (
|
||||
<section>
|
||||
<h2 className="text-white font-semibold text-lg mb-3">{title}</h2>
|
||||
<div className="space-y-3">{children}</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
export function Para({ children }) {
|
||||
return <p className="text-nova-textSecondary text-sm leading-relaxed">{children}</p>
|
||||
}
|
||||
|
||||
export function BulletList({ items }) {
|
||||
return (
|
||||
<ul className="space-y-2 pl-1">
|
||||
{items.map((item, i) => (
|
||||
<li key={i} className="flex gap-2 text-sm">
|
||||
<span className="text-nova-primary mt-0.5">•</span>
|
||||
<span className="text-nova-textSecondary">{item}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
import React, { useState } from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
import Nova40Icon from './Nova40Icon'
|
||||
|
||||
export default function Navbar() {
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
return (
|
||||
<nav className="fixed top-0 left-0 right-0 z-50 bg-nova-bg/80 backdrop-blur-xl border-b border-nova-border">
|
||||
<div className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="flex items-center justify-between h-16">
|
||||
{/* Logo */}
|
||||
<Link to="/" className="flex items-center gap-2">
|
||||
<Nova40Icon size={36} rounded />
|
||||
<span className="text-lg font-bold text-white">Nova40</span>
|
||||
</Link>
|
||||
|
||||
{/* Desktop Nav */}
|
||||
<div className="hidden md:flex items-center gap-8">
|
||||
<a href="#features" className="text-nova-textSecondary hover:text-white transition-colors text-sm">Features</a>
|
||||
<a href="#how-it-works" className="text-nova-textSecondary hover:text-white transition-colors text-sm">How It Works</a>
|
||||
<a href="#download" className="text-nova-textSecondary hover:text-white transition-colors text-sm">Download</a>
|
||||
<a
|
||||
href="#download"
|
||||
className="px-4 py-2 bg-nova-primary hover:bg-nova-primaryLight rounded-full text-white text-sm font-medium transition-colors"
|
||||
>
|
||||
Get the App
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{/* Mobile Toggle */}
|
||||
<button
|
||||
onClick={() => setOpen(!open)}
|
||||
className="md:hidden text-white p-2"
|
||||
aria-label="Toggle menu"
|
||||
>
|
||||
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
{open ? (
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||
) : (
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" />
|
||||
)}
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Mobile Menu */}
|
||||
{open && (
|
||||
<div className="md:hidden pb-4 border-t border-nova-border mt-2 pt-4">
|
||||
<div className="flex flex-col gap-3">
|
||||
<a href="#features" onClick={() => setOpen(false)} className="text-nova-textSecondary hover:text-white transition-colors text-sm px-2 py-1">Features</a>
|
||||
<a href="#how-it-works" onClick={() => setOpen(false)} className="text-nova-textSecondary hover:text-white transition-colors text-sm px-2 py-1">How It Works</a>
|
||||
<a href="#download" onClick={() => setOpen(false)} className="text-nova-textSecondary hover:text-white transition-colors text-sm px-2 py-1">Download</a>
|
||||
<a
|
||||
href="#download"
|
||||
onClick={() => setOpen(false)}
|
||||
className="mt-2 px-4 py-2 bg-nova-primary hover:bg-nova-primaryLight rounded-full text-white text-sm font-medium transition-colors text-center"
|
||||
>
|
||||
Get the App
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
import React from 'react'
|
||||
|
||||
/**
|
||||
* Nova40 Icon — matches the mobile app icon:
|
||||
* Glowing cyan sphere with magenta starburst rays and an orbital ring
|
||||
* on a dark navy background.
|
||||
*/
|
||||
export default function Nova40Icon({ size = 48, className = '', rounded = true }) {
|
||||
return (
|
||||
<div
|
||||
className={`relative flex-shrink-0 ${className}`}
|
||||
style={{ width: size, height: size }}
|
||||
>
|
||||
<img
|
||||
src="/icon.png"
|
||||
alt="Nova40"
|
||||
width={size}
|
||||
height={size}
|
||||
className={`w-full h-full object-cover ${rounded ? 'rounded-xl' : ''}`}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Animated CSS-only version of the Nova40 icon for decorative use
|
||||
*/
|
||||
export function Nova40IconAnimated({ size = 64, className = '' }) {
|
||||
const coreSize = size * 0.3
|
||||
const rayLength = size * 0.38
|
||||
const ringRx = size * 0.42
|
||||
const ringRy = size * 0.14
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`relative flex-shrink-0 ${className}`}
|
||||
style={{ width: size, height: size }}
|
||||
>
|
||||
<svg viewBox="0 0 100 100" className="w-full h-full" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<radialGradient id="iconCoreGlow" cx="50%" cy="50%" r="50%">
|
||||
<stop offset="0%" stopColor="#FFFFFF" />
|
||||
<stop offset="30%" stopColor="#80F0FF" />
|
||||
<stop offset="70%" stopColor="#00CFFF" />
|
||||
<stop offset="100%" stopColor="#0080CC" />
|
||||
</radialGradient>
|
||||
<filter id="iconSoftGlow" x="-50%" y="-50%" width="200%" height="200%">
|
||||
<feGaussianBlur stdDeviation="3" />
|
||||
</filter>
|
||||
<filter id="iconRayGlow" x="-50%" y="-50%" width="200%" height="200%">
|
||||
<feGaussianBlur stdDeviation="1" />
|
||||
</filter>
|
||||
</defs>
|
||||
|
||||
{/* Outer glow */}
|
||||
<circle cx="50" cy="50" r="25" fill="#00E5FF" opacity="0.15" filter="url(#iconSoftGlow)" />
|
||||
<circle cx="50" cy="50" r="30" fill="#CC44FF" opacity="0.08" filter="url(#iconSoftGlow)" />
|
||||
|
||||
{/* Starburst rays */}
|
||||
<g filter="url(#iconRayGlow)">
|
||||
{Array.from({ length: 16 }).map((_, i) => {
|
||||
const angle = (i * 360) / 16
|
||||
const len = i % 2 === 0 ? 38 : 30
|
||||
const rad = (angle * Math.PI) / 180
|
||||
return (
|
||||
<line
|
||||
key={i}
|
||||
x1="50"
|
||||
y1="50"
|
||||
x2={50 + len * Math.cos(rad)}
|
||||
y2={50 + len * Math.sin(rad)}
|
||||
stroke="#FF44CC"
|
||||
strokeWidth={i % 2 === 0 ? '0.8' : '0.5'}
|
||||
opacity={i % 2 === 0 ? 0.8 : 0.6}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</g>
|
||||
|
||||
{/* Orbital ring */}
|
||||
<ellipse
|
||||
cx="50" cy="50" rx="42" ry="14"
|
||||
fill="none" stroke="#00D4FF" strokeWidth="0.8" opacity="0.8"
|
||||
transform="rotate(-25, 50, 50)"
|
||||
/>
|
||||
|
||||
{/* Core sphere */}
|
||||
<circle cx="50" cy="50" r="12" fill="url(#iconCoreGlow)">
|
||||
<animate attributeName="r" values="12;13;12" dur="3s" repeatCount="indefinite" />
|
||||
</circle>
|
||||
{/* Highlight */}
|
||||
<circle cx="47" cy="47" r="5" fill="white" opacity="0.15" />
|
||||
</svg>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
import React, { useMemo } from 'react'
|
||||
|
||||
/**
|
||||
* StarField — matches the mobile app's StarField.js
|
||||
* 3 tiers of stars: small (60%), medium (25%), large with glow halo (15%)
|
||||
* Each star twinkles independently with CSS animation.
|
||||
*/
|
||||
export default function StarField({ count = 60 }) {
|
||||
const stars = useMemo(() => {
|
||||
const items = []
|
||||
const smallCount = Math.floor(count * 0.6)
|
||||
const medCount = Math.floor(count * 0.25)
|
||||
const glowCount = count - smallCount - medCount
|
||||
|
||||
// Small stars
|
||||
for (let i = 0; i < smallCount; i++) {
|
||||
items.push({
|
||||
id: i,
|
||||
x: Math.random() * 100,
|
||||
y: Math.random() * 100,
|
||||
size: 1.5 + Math.random() * 2, // 1.5–3.5px
|
||||
delay: Math.random() * 5,
|
||||
duration: 2 + Math.random() * 2.5, // 2–4.5s
|
||||
glow: false,
|
||||
})
|
||||
}
|
||||
|
||||
// Medium stars
|
||||
for (let i = 0; i < medCount; i++) {
|
||||
items.push({
|
||||
id: smallCount + i,
|
||||
x: Math.random() * 100,
|
||||
y: Math.random() * 100,
|
||||
size: 2.5 + Math.random() * 2, // 2.5–4.5px
|
||||
delay: Math.random() * 5,
|
||||
duration: 2 + Math.random() * 2.5,
|
||||
glow: false,
|
||||
})
|
||||
}
|
||||
|
||||
// Large glowing stars
|
||||
for (let i = 0; i < glowCount; i++) {
|
||||
items.push({
|
||||
id: smallCount + medCount + i,
|
||||
x: Math.random() * 100,
|
||||
y: Math.random() * 100,
|
||||
size: 3 + Math.random() * 3, // 3–6px
|
||||
delay: Math.random() * 4,
|
||||
duration: 2.5 + Math.random() * 3, // 2.5–5.5s
|
||||
glow: true,
|
||||
})
|
||||
}
|
||||
|
||||
return items
|
||||
}, [count])
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 pointer-events-none z-0 overflow-hidden" aria-hidden="true">
|
||||
{stars.map((star) => (
|
||||
<div
|
||||
key={star.id}
|
||||
className="absolute"
|
||||
style={{
|
||||
left: `${star.x}%`,
|
||||
top: `${star.y}%`,
|
||||
}}
|
||||
>
|
||||
{/* Glow halo for large stars */}
|
||||
{star.glow && (
|
||||
<span
|
||||
className="absolute rounded-full"
|
||||
style={{
|
||||
width: star.size * 4,
|
||||
height: star.size * 4,
|
||||
top: -(star.size * 1.5),
|
||||
left: -(star.size * 1.5),
|
||||
background: 'rgba(108, 99, 255, 0.2)',
|
||||
animation: `starTwinkle ${star.duration}s ease-in-out ${star.delay}s infinite`,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{/* Star dot */}
|
||||
<span
|
||||
className="absolute rounded-full"
|
||||
style={{
|
||||
width: star.size,
|
||||
height: star.size,
|
||||
background: star.glow
|
||||
? 'rgba(255, 255, 255, 0.85)'
|
||||
: 'rgba(255, 255, 255, 0.6)',
|
||||
boxShadow: star.glow
|
||||
? `0 0 ${star.size * 2}px rgba(108, 99, 255, 0.5), 0 0 ${star.size}px rgba(255, 255, 255, 0.5)`
|
||||
: 'none',
|
||||
animation: `starTwinkle ${star.duration}s ease-in-out ${star.delay}s infinite`,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{/* Inject keyframes */}
|
||||
<style>{`
|
||||
@keyframes starTwinkle {
|
||||
0%, 100% { opacity: 0.15; }
|
||||
50% { opacity: 1; }
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
import React, { useEffect, useState, useRef } from 'react'
|
||||
import useReveal from '../hooks/useReveal'
|
||||
|
||||
function Counter({ end, suffix = '', duration = 2000 }) {
|
||||
const [count, setCount] = useState(0)
|
||||
const ref = useRef(null)
|
||||
const started = useRef(false)
|
||||
|
||||
useEffect(() => {
|
||||
const observer = new IntersectionObserver(
|
||||
([entry]) => {
|
||||
if (entry.isIntersecting && !started.current) {
|
||||
started.current = true
|
||||
const step = end / (duration / 16)
|
||||
let current = 0
|
||||
const timer = setInterval(() => {
|
||||
current += step
|
||||
if (current >= end) {
|
||||
setCount(end)
|
||||
clearInterval(timer)
|
||||
} else {
|
||||
setCount(Math.floor(current))
|
||||
}
|
||||
}, 16)
|
||||
}
|
||||
},
|
||||
{ threshold: 0.5 }
|
||||
)
|
||||
if (ref.current) observer.observe(ref.current)
|
||||
return () => observer.disconnect()
|
||||
}, [end, duration])
|
||||
|
||||
return (
|
||||
<span ref={ref}>
|
||||
{count.toLocaleString()}{suffix}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
const stats = [
|
||||
{ value: 40, suffix: '', label: 'Days to Transform', color: 'text-nova-primary' },
|
||||
{ value: 5, suffix: '', label: 'Daily Habits', color: 'text-nova-accent' },
|
||||
{ value: 40, suffix: '+', label: 'Days of History', color: 'text-nova-warning' },
|
||||
{ value: 100, suffix: '%', label: 'Free to Use', color: 'text-nova-success' },
|
||||
]
|
||||
|
||||
export default function Stats() {
|
||||
const ref = useReveal()
|
||||
|
||||
return (
|
||||
<section className="py-16 relative" ref={ref}>
|
||||
<div className="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="reveal glass-card p-8 sm:p-10">
|
||||
<div className="grid grid-cols-2 lg:grid-cols-4 gap-8">
|
||||
{stats.map((stat, i) => (
|
||||
<div key={i} className="text-center">
|
||||
<p className={`text-4xl sm:text-5xl font-bold ${stat.color} mb-2`}>
|
||||
<Counter end={stat.value} suffix={stat.suffix} />
|
||||
</p>
|
||||
<p className="text-nova-textSecondary text-sm font-medium">{stat.label}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import React from 'react'
|
||||
import { Nova40IconAnimated } from './Nova40Icon'
|
||||
import useReveal from '../hooks/useReveal'
|
||||
|
||||
const quotes = [
|
||||
{
|
||||
text: "You don't need to be perfect. Just show up — one day at a time. That's the whole secret.",
|
||||
author: 'The Nova40 Philosophy',
|
||||
},
|
||||
{
|
||||
text: 'Every day you show up, you cast a vote for the person you want to become.',
|
||||
author: 'James Clear',
|
||||
},
|
||||
{
|
||||
text: "Your identity isn't fixed. In 40 days, you can rewrite the story of who you are.",
|
||||
author: 'Nova40',
|
||||
},
|
||||
]
|
||||
|
||||
export default function Testimonial() {
|
||||
const ref = useReveal()
|
||||
|
||||
return (
|
||||
<section className="py-20 sm:py-28 relative" ref={ref}>
|
||||
<div className="absolute top-0 left-1/2 -translate-x-1/2 w-[600px] h-px bg-gradient-to-r from-transparent via-nova-primary/30 to-transparent" />
|
||||
|
||||
<div className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="text-center mb-12 reveal">
|
||||
<p className="text-nova-warning text-sm font-semibold tracking-widest uppercase mb-3">Philosophy</p>
|
||||
<h2 className="text-3xl sm:text-4xl font-bold">
|
||||
Words that{' '}
|
||||
<span className="text-nova-warning">ignite</span>
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
{quotes.map((q, i) => (
|
||||
<div key={i} className={`reveal reveal-delay-${i + 1} glass-card p-8 relative overflow-hidden group hover:border-nova-primary/30 transition-all duration-500 hover:-translate-y-1`}>
|
||||
{/* Glow */}
|
||||
<div className="absolute -top-16 -right-16 w-32 h-32 bg-nova-primary/10 rounded-full blur-[50px] opacity-0 group-hover:opacity-100 transition-opacity duration-500" />
|
||||
|
||||
<div className="relative">
|
||||
<div className="text-nova-primary/40 text-5xl font-serif leading-none mb-4">"</div>
|
||||
<blockquote className="text-white font-medium leading-relaxed mb-6 text-[15px]">
|
||||
{q.text}
|
||||
</blockquote>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-8 h-px bg-gradient-to-r from-nova-primary to-transparent" />
|
||||
<p className="text-nova-textMuted text-xs font-medium">{q.author}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Center icon */}
|
||||
<div className="flex justify-center mt-12 reveal">
|
||||
<Nova40IconAnimated size={48} />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { useEffect, useRef } from 'react'
|
||||
|
||||
export default function useReveal() {
|
||||
const ref = useRef(null)
|
||||
|
||||
useEffect(() => {
|
||||
const el = ref.current
|
||||
if (!el) return
|
||||
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
entries.forEach((entry) => {
|
||||
if (entry.isIntersecting) {
|
||||
entry.target.classList.add('visible')
|
||||
}
|
||||
})
|
||||
},
|
||||
{ threshold: 0.15, rootMargin: '0px 0px -40px 0px' }
|
||||
)
|
||||
|
||||
const children = el.querySelectorAll('.reveal')
|
||||
children.forEach((child) => observer.observe(child))
|
||||
|
||||
// Also observe the container itself if it has reveal class
|
||||
if (el.classList.contains('reveal')) {
|
||||
observer.observe(el)
|
||||
}
|
||||
|
||||
return () => observer.disconnect()
|
||||
}, [])
|
||||
|
||||
return ref
|
||||
}
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
body {
|
||||
@apply bg-nova-bg text-nova-text font-sans antialiased;
|
||||
}
|
||||
}
|
||||
|
||||
@layer components {
|
||||
.glass-card {
|
||||
@apply bg-nova-surface/60 backdrop-blur-xl border border-nova-border rounded-2xl;
|
||||
}
|
||||
.glow-primary {
|
||||
box-shadow: 0 0 30px rgba(108, 99, 255, 0.3);
|
||||
}
|
||||
.glow-accent {
|
||||
box-shadow: 0 0 30px rgba(0, 229, 255, 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
/* Scroll reveal animation */
|
||||
.reveal {
|
||||
opacity: 0;
|
||||
transform: translateY(40px);
|
||||
transition: opacity 0.8s cubic-bezier(0.16, 1, 0.3, 1), transform 0.8s cubic-bezier(0.16, 1, 0.3, 1);
|
||||
}
|
||||
.reveal.visible {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
.reveal-delay-1 { transition-delay: 0.1s; }
|
||||
.reveal-delay-2 { transition-delay: 0.2s; }
|
||||
.reveal-delay-3 { transition-delay: 0.3s; }
|
||||
.reveal-delay-4 { transition-delay: 0.4s; }
|
||||
.reveal-delay-5 { transition-delay: 0.5s; }
|
||||
|
||||
/* Shooting stars / meteors */
|
||||
@keyframes meteor {
|
||||
0% {
|
||||
transform: translateX(0) translateY(0) rotate(-45deg);
|
||||
opacity: 1;
|
||||
}
|
||||
70% { opacity: 1; }
|
||||
100% {
|
||||
transform: translateX(-600px) translateY(600px) rotate(-45deg);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
.meteor {
|
||||
position: absolute;
|
||||
width: 2px;
|
||||
height: 80px;
|
||||
background: linear-gradient(to bottom, rgba(108, 99, 255, 0), rgba(108, 99, 255, 0.6), rgba(255, 255, 255, 0.8));
|
||||
border-radius: 999px;
|
||||
animation: meteor 3s linear infinite;
|
||||
}
|
||||
|
||||
/* Gradient text shimmer */
|
||||
@keyframes shimmer {
|
||||
0% { background-position: -200% center; }
|
||||
100% { background-position: 200% center; }
|
||||
}
|
||||
.text-shimmer {
|
||||
background: linear-gradient(90deg, #6C63FF, #00E5FF, #FF44CC, #6C63FF);
|
||||
background-size: 200% auto;
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
animation: shimmer 4s linear infinite;
|
||||
}
|
||||
|
||||
/* Floating badge pulse ring */
|
||||
@keyframes ping-slow {
|
||||
0% { transform: scale(1); opacity: 0.6; }
|
||||
100% { transform: scale(2); opacity: 0; }
|
||||
}
|
||||
.ping-slow {
|
||||
animation: ping-slow 2s cubic-bezier(0, 0, 0.2, 1) infinite;
|
||||
}
|
||||
|
||||
/* Counter number animation */
|
||||
@keyframes countUp {
|
||||
from { opacity: 0; transform: translateY(10px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
/* Phone float */
|
||||
@keyframes phoneFloat {
|
||||
0%, 100% { transform: translateY(0) rotate(0deg); }
|
||||
25% { transform: translateY(-8px) rotate(0.5deg); }
|
||||
75% { transform: translateY(4px) rotate(-0.5deg); }
|
||||
}
|
||||
.phone-float {
|
||||
animation: phoneFloat 5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
/* Scrollbar */
|
||||
::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
::-webkit-scrollbar-track {
|
||||
background: #0A0E1A;
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #1E2548;
|
||||
border-radius: 3px;
|
||||
}
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: #6C63FF;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom/client'
|
||||
import { BrowserRouter, Routes, Route } from 'react-router-dom'
|
||||
import App from './App'
|
||||
import PrivacyPolicy from './pages/PrivacyPolicy'
|
||||
import TermsConditions from './pages/TermsConditions'
|
||||
import './index.css'
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')).render(
|
||||
<React.StrictMode>
|
||||
<BrowserRouter>
|
||||
<Routes>
|
||||
<Route path="/" element={<App />} />
|
||||
<Route path="/privacy-policy" element={<PrivacyPolicy />} />
|
||||
<Route path="/terms-conditions" element={<TermsConditions />} />
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
</React.StrictMode>,
|
||||
)
|
||||
@@ -0,0 +1,97 @@
|
||||
import React from 'react'
|
||||
import LegalLayout, { Section, Para, BulletList } from '../components/LegalLayout'
|
||||
|
||||
export default function PrivacyPolicy() {
|
||||
return (
|
||||
<LegalLayout title="Privacy Policy" updated="April 29, 2026">
|
||||
<Section title="1. Introduction">
|
||||
<Para>
|
||||
Nova40 ("we", "our", "us") is committed to protecting your privacy. This Privacy Policy explains how we collect, use, and safeguard your information when you use our mobile application.
|
||||
</Para>
|
||||
</Section>
|
||||
|
||||
<Section title="2. Information We Collect">
|
||||
<Para>We collect the following types of information:</Para>
|
||||
<BulletList items={[
|
||||
'Account information (email address, password)',
|
||||
'Identity and habit data you create within the app',
|
||||
'Daily journal entries, mood data, and reflections',
|
||||
'Game scores and progress statistics',
|
||||
'Device information for crash reporting (via Firebase Crashlytics)',
|
||||
]} />
|
||||
</Section>
|
||||
|
||||
<Section title="3. How We Use Your Information">
|
||||
<Para>Your information is used to:</Para>
|
||||
<BulletList items={[
|
||||
'Provide and maintain the Nova40 service',
|
||||
'Generate AI-powered identity reflections and habit suggestions',
|
||||
'Track your 40-day transformation progress',
|
||||
'Improve app stability through crash reports',
|
||||
'Send important service notifications',
|
||||
]} />
|
||||
</Section>
|
||||
|
||||
<Section title="4. AI-Generated Content">
|
||||
<Para>
|
||||
Nova40 uses artificial intelligence (Google Gemini / Claude AI) to generate personalized identity statements, habit suggestions, and daily reflections. Your story and journal entries are sent to AI services for processing. We do not store AI-processed data on third-party servers beyond the generation session.
|
||||
</Para>
|
||||
</Section>
|
||||
|
||||
<Section title="5. Data Storage">
|
||||
<Para>
|
||||
Your data is stored securely using Supabase (cloud database) and locally on your device via AsyncStorage. Data stored locally remains on your device and is not transmitted unless you explicitly use cloud features.
|
||||
</Para>
|
||||
</Section>
|
||||
|
||||
<Section title="6. Data Sharing">
|
||||
<Para>We do not sell, trade, or share your personal information with third parties, except:</Para>
|
||||
<BulletList items={[
|
||||
'Firebase (Google) — for crash reporting and analytics',
|
||||
'AI service providers — for generating personalized content',
|
||||
'When required by law or legal process',
|
||||
]} />
|
||||
</Section>
|
||||
|
||||
<Section title="7. Data Security">
|
||||
<Para>
|
||||
We implement industry-standard security measures to protect your data, including encrypted connections (HTTPS), secure authentication, and row-level security policies on our database.
|
||||
</Para>
|
||||
</Section>
|
||||
|
||||
<Section title="8. Your Rights">
|
||||
<Para>You have the right to:</Para>
|
||||
<BulletList items={[
|
||||
'Access your personal data',
|
||||
'Delete your account and all associated data',
|
||||
'Export your journal and progress data',
|
||||
'Opt out of analytics collection',
|
||||
]} />
|
||||
</Section>
|
||||
|
||||
<Section title="9. Data Retention">
|
||||
<Para>
|
||||
We retain your data as long as your account is active. When you delete your account or reset your journey, associated data is permanently removed from our systems.
|
||||
</Para>
|
||||
</Section>
|
||||
|
||||
<Section title="10. Children's Privacy">
|
||||
<Para>
|
||||
Nova40 is not intended for children under 13 years of age. We do not knowingly collect information from children under 13.
|
||||
</Para>
|
||||
</Section>
|
||||
|
||||
<Section title="11. Changes to This Policy">
|
||||
<Para>
|
||||
We may update this Privacy Policy from time to time. Changes will be posted within the app and take effect immediately upon posting.
|
||||
</Para>
|
||||
</Section>
|
||||
|
||||
<Section title="12. Contact Us">
|
||||
<Para>
|
||||
If you have questions about this Privacy Policy, please contact us at: support@nova40.app
|
||||
</Para>
|
||||
</Section>
|
||||
</LegalLayout>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
import React from 'react'
|
||||
import LegalLayout, { Section, Para, BulletList } from '../components/LegalLayout'
|
||||
|
||||
export default function TermsConditions() {
|
||||
return (
|
||||
<LegalLayout title="Terms & Conditions" updated="April 29, 2026">
|
||||
<Section title="1. Acceptance of Terms">
|
||||
<Para>
|
||||
By downloading, installing, or using Nova40, you agree to be bound by these Terms and Conditions. If you do not agree, please do not use the application.
|
||||
</Para>
|
||||
</Section>
|
||||
|
||||
<Section title="2. Description of Service">
|
||||
<Para>
|
||||
Nova40 is a 40-day identity transformation app that helps users build habits, track progress, and reflect on personal growth through AI-powered insights, journaling, and gamification.
|
||||
</Para>
|
||||
</Section>
|
||||
|
||||
<Section title="3. User Accounts">
|
||||
<Para>To use Nova40, you must:</Para>
|
||||
<BulletList items={[
|
||||
'Create an account with a valid email address',
|
||||
'Maintain the security of your account credentials',
|
||||
'Be at least 13 years of age',
|
||||
]} />
|
||||
<Para>You are responsible for all activity that occurs under your account.</Para>
|
||||
</Section>
|
||||
|
||||
<Section title="4. User Content">
|
||||
<Para>
|
||||
You retain ownership of all content you create in Nova40, including your stories, journal entries, and reflections. By using the AI features, you grant us permission to process your content through third-party AI services for the purpose of generating personalized responses.
|
||||
</Para>
|
||||
</Section>
|
||||
|
||||
<Section title="5. AI-Generated Content">
|
||||
<Para>
|
||||
Nova40 uses artificial intelligence to generate identity statements, habit suggestions, reflections, and transformation stories. You acknowledge that:
|
||||
</Para>
|
||||
<BulletList items={[
|
||||
'AI-generated content is for guidance only, not professional advice',
|
||||
'AI responses may not always be accurate or appropriate',
|
||||
'You should not rely solely on AI content for mental health decisions',
|
||||
]} />
|
||||
</Section>
|
||||
|
||||
<Section title="6. Acceptable Use">
|
||||
<Para>You agree not to:</Para>
|
||||
<BulletList items={[
|
||||
'Use the app for any illegal or unauthorized purpose',
|
||||
'Attempt to hack, reverse engineer, or disrupt the service',
|
||||
'Submit content that is harmful, abusive, or violates others\' rights',
|
||||
'Create multiple accounts to abuse free features',
|
||||
]} />
|
||||
</Section>
|
||||
|
||||
<Section title="7. Intellectual Property">
|
||||
<Para>
|
||||
The Nova40 app, including its design, code, logos, and branding, is owned by Nova40 and protected by intellectual property laws. You may not copy, modify, or distribute any part of the application without written permission.
|
||||
</Para>
|
||||
</Section>
|
||||
|
||||
<Section title="8. Disclaimer of Warranties">
|
||||
<Para>
|
||||
Nova40 is provided "as is" without warranties of any kind. We do not guarantee that the app will be uninterrupted, error-free, or that AI-generated content will meet your expectations.
|
||||
</Para>
|
||||
</Section>
|
||||
|
||||
<Section title="9. Limitation of Liability">
|
||||
<Para>
|
||||
To the maximum extent permitted by law, Nova40 shall not be liable for any indirect, incidental, or consequential damages arising from your use of the application.
|
||||
</Para>
|
||||
</Section>
|
||||
|
||||
<Section title="10. Health Disclaimer">
|
||||
<Para>
|
||||
Nova40 is a personal development tool and is not a substitute for professional medical, psychological, or therapeutic advice. If you are experiencing mental health issues, please consult a qualified professional.
|
||||
</Para>
|
||||
</Section>
|
||||
|
||||
<Section title="11. Data and Privacy">
|
||||
<Para>
|
||||
Your use of Nova40 is also governed by our Privacy Policy. Please review it to understand how we collect and use your information.
|
||||
</Para>
|
||||
</Section>
|
||||
|
||||
<Section title="12. Termination">
|
||||
<Para>
|
||||
We reserve the right to suspend or terminate your account at any time for violation of these terms. You may delete your account at any time through the app settings.
|
||||
</Para>
|
||||
</Section>
|
||||
|
||||
<Section title="13. Changes to Terms">
|
||||
<Para>
|
||||
We may update these Terms and Conditions from time to time. Continued use of the app after changes constitutes acceptance of the updated terms.
|
||||
</Para>
|
||||
</Section>
|
||||
|
||||
<Section title="14. Governing Law">
|
||||
<Para>
|
||||
These terms are governed by the laws of the Republic of Indonesia. Any disputes shall be resolved in the courts of Indonesia.
|
||||
</Para>
|
||||
</Section>
|
||||
|
||||
<Section title="15. Contact">
|
||||
<Para>
|
||||
For questions about these Terms, contact us at: support@nova40.app
|
||||
</Para>
|
||||
</Section>
|
||||
</LegalLayout>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
content: [
|
||||
"./index.html",
|
||||
"./src/**/*.{js,ts,jsx,tsx}",
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
nova: {
|
||||
bg: '#0A0E1A',
|
||||
surface: '#131831',
|
||||
surfaceLight: '#1C2345',
|
||||
primary: '#6C63FF',
|
||||
primaryLight: '#8B85FF',
|
||||
accent: '#00E5FF',
|
||||
success: '#00E676',
|
||||
warning: '#FFD740',
|
||||
error: '#FF5252',
|
||||
text: '#FFFFFF',
|
||||
textSecondary: '#8A8FB5',
|
||||
textMuted: '#4A5078',
|
||||
border: '#1E2548',
|
||||
}
|
||||
},
|
||||
fontFamily: {
|
||||
sans: ['Inter', 'system-ui', '-apple-system', 'sans-serif'],
|
||||
},
|
||||
animation: {
|
||||
'pulse-slow': 'pulse 3s cubic-bezier(0.4, 0, 0.6, 1) infinite',
|
||||
'orbit': 'orbit 20s linear infinite',
|
||||
'float': 'float 6s ease-in-out infinite',
|
||||
'glow': 'glow 2s ease-in-out infinite alternate',
|
||||
},
|
||||
keyframes: {
|
||||
orbit: {
|
||||
'0%': { transform: 'rotate(0deg)' },
|
||||
'100%': { transform: 'rotate(360deg)' },
|
||||
},
|
||||
float: {
|
||||
'0%, 100%': { transform: 'translateY(0)' },
|
||||
'50%': { transform: 'translateY(-10px)' },
|
||||
},
|
||||
glow: {
|
||||
'0%': { boxShadow: '0 0 20px rgba(108, 99, 255, 0.4)' },
|
||||
'100%': { boxShadow: '0 0 40px rgba(108, 99, 255, 0.8)' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
})
|
||||
Reference in New Issue
Block a user