feat: Add CHORUS teaser website with mobile-responsive design

- Created complete Next.js 15 teaser website with CHORUS brand styling
- Implemented mobile-responsive 3D logo (128px mobile, 512px desktop)
- Added proper Exo font loading via Next.js Google Fonts for iOS/Chrome compatibility
- Built comprehensive early access form with GDPR compliance and rate limiting
- Integrated PostgreSQL database with complete schema for lead capture
- Added scroll indicators that auto-hide when scrolling begins
- Optimized mobile modal forms with proper scrolling and submit button access
- Deployed via Docker Swarm with Traefik SSL termination at chorus.services
- Includes database migrations, consent tracking, and email notifications

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
tony
2025-08-26 13:57:30 +10:00
parent 630d1c26ad
commit c8fb816775
236 changed files with 17525 additions and 0 deletions

View File

@@ -0,0 +1,276 @@
'use client'
import { useState, FormEvent } from 'react'
import { EarlyAccessLead, useEarlyAccessCapture } from '../hooks/useEarlyAccessCapture'
import { LeadSourceType } from '../hooks/useEarlyAccessCapture'
interface EarlyAccessFormProps {
isOpen: boolean
onClose: () => void
leadSource: LeadSourceType
}
export default function EarlyAccessForm({ isOpen, onClose, leadSource }: EarlyAccessFormProps) {
const { submitEarlyAccess, isSubmitting, submitStatus, errorMessage } = useEarlyAccessCapture()
const [formData, setFormData] = useState<EarlyAccessLead>({
firstName: '',
lastName: '',
email: '',
companyName: '',
companyRole: '',
interestLevel: 'general_interest',
leadSource: leadSource,
gdprConsent: false,
marketingConsent: false,
})
const handleSubmit = async (e: FormEvent) => {
e.preventDefault()
if (!formData.gdprConsent) {
alert('Please accept the privacy policy to continue.')
return
}
const result = await submitEarlyAccess(formData)
if (result.success) {
// Reset form on success
setFormData({
firstName: '',
lastName: '',
email: '',
companyName: '',
companyRole: '',
interestLevel: 'general_interest',
leadSource: leadSource,
gdprConsent: false,
marketingConsent: false,
})
}
}
const handleInputChange = (field: keyof EarlyAccessLead, value: string | boolean) => {
setFormData(prev => ({
...prev,
[field]: value
}))
}
if (!isOpen) return null
return (
<div
className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/80 backdrop-blur-sm overflow-y-auto"
onClick={onClose}
>
<div
className="relative w-full max-w-md bg-gradient-to-b from-mulberry-900 to-carbon-900 text-white p-4 sm:p-chorus-xl rounded-lg border border-mulberry-700/50 shadow-2xl my-8 max-h-[90vh] overflow-y-auto"
onClick={(e) => e.stopPropagation()}
>
{/* Close Button */}
<button
onClick={onClose}
className="absolute top-4 right-4 text-mulberry-300 hover:text-white text-2xl font-light transition-colors duration-200"
>
×
</button>
{/* Form Header */}
<h3 className="text-h3 font-semibold text-white mb-chorus-sm">
{leadSource === 'request_early_access'
? 'Request Early Access to CHORUS'
: 'Join the CHORUS Waitlist'}
</h3>
<p className="text-sm text-mulberry-200 font-light mb-4 sm:mb-chorus-xl">
{leadSource === 'request_early_access'
? 'Get priority access to contextual AI orchestration'
: 'Be notified when CHORUS becomes available'}
</p>
{/* Success State */}
{submitStatus === 'success' ? (
<div className="text-center py-chorus-xl">
<div className="text-5xl text-eucalyptus-400 mb-chorus-lg animate-bounce">
</div>
<h4 className="text-lg font-semibold text-eucalyptus-300 mb-chorus-sm">
{leadSource === 'request_early_access'
? 'Request submitted successfully!'
: 'Welcome to the waitlist!'}
</h4>
<p className="text-sm text-mulberry-200 font-light">
{leadSource === 'request_early_access'
? 'We\'ll prioritize your request and contact you soon.'
: 'We\'ll notify you when CHORUS becomes available.'}
</p>
</div>
) : (
/* Form */
<form onSubmit={handleSubmit}>
{/* Name Fields */}
<div className="grid grid-cols-1 sm:grid-cols-2 gap-chorus-sm sm:gap-chorus-md mb-chorus-md">
<div>
<label className="block text-sm font-medium text-mulberry-200 mb-chorus-xs">
First Name *
</label>
<input
type="text"
required
value={formData.firstName}
onChange={(e) => handleInputChange('firstName', e.target.value)}
className="form-input"
placeholder="John"
/>
</div>
<div>
<label className="block text-sm font-medium text-mulberry-200 mb-chorus-xs">
Last Name *
</label>
<input
type="text"
required
value={formData.lastName}
onChange={(e) => handleInputChange('lastName', e.target.value)}
className="form-input"
placeholder="Doe"
/>
</div>
</div>
{/* Email */}
<div className="mb-chorus-md">
<label className="block text-sm font-medium text-mulberry-200 mb-chorus-xs">
Email Address *
</label>
<input
type="email"
required
value={formData.email}
onChange={(e) => handleInputChange('email', e.target.value)}
className="form-input"
placeholder="john@company.com"
/>
</div>
{/* Company Information */}
<div className="grid grid-cols-1 sm:grid-cols-2 gap-chorus-sm sm:gap-chorus-md mb-chorus-md">
<div>
<label className="block text-sm font-medium text-mulberry-200 mb-chorus-xs">
Company
</label>
<input
type="text"
value={formData.companyName || ''}
onChange={(e) => handleInputChange('companyName', e.target.value)}
className="form-input"
placeholder="Company Name"
/>
</div>
<div>
<label className="block text-sm font-medium text-mulberry-200 mb-chorus-xs">
Role
</label>
<input
type="text"
value={formData.companyRole || ''}
onChange={(e) => handleInputChange('companyRole', e.target.value)}
className="form-input"
placeholder="CTO, Director, etc."
/>
</div>
</div>
{/* Interest Level */}
<div className="mb-4 sm:mb-chorus-lg">
<label className="block text-sm font-medium text-mulberry-200 mb-chorus-xs">
Primary Interest
</label>
<select
value={formData.interestLevel}
onChange={(e) => handleInputChange('interestLevel', e.target.value as any)}
className="form-input"
>
<option value="general_interest">General Interest</option>
<option value="technical_evaluation">Technical Evaluation</option>
<option value="strategic_demo">Strategic Demo</option>
</select>
</div>
{/* GDPR Consent */}
<div style={{ marginBottom: '1.5rem', fontSize: '0.85rem' }}>
<label style={{ display: 'flex', alignItems: 'flex-start', gap: '0.5rem', marginBottom: '0.75rem' }}>
<input
type="checkbox"
checked={formData.gdprConsent}
onChange={(e) => handleInputChange('gdprConsent', e.target.checked)}
required
/>
<span>
I agree to the privacy policy and consent to processing my personal data for early access communications. *
</span>
</label>
<label style={{ display: 'flex', alignItems: 'flex-start', gap: '0.5rem' }}>
<input
type="checkbox"
checked={formData.marketingConsent}
onChange={(e) => handleInputChange('marketingConsent', e.target.checked)}
/>
<span>
I would like to receive updates about CHORUS Services and related products.
</span>
</label>
</div>
{/* Error Message */}
{submitStatus === 'error' && errorMessage && (
<div style={{
padding: '0.75rem',
backgroundColor: 'rgba(239, 68, 68, 0.1)',
border: '1px solid #dc2626',
color: '#fca5a5',
fontSize: '0.85rem',
marginBottom: '1rem'
}}>
{errorMessage}
</div>
)}
{/* Submit Button */}
<button
type="submit"
disabled={isSubmitting || !formData.gdprConsent}
className="btn-primary"
style={{
width: '100%',
opacity: (isSubmitting || !formData.gdprConsent) ? 0.5 : 1,
cursor: (isSubmitting || !formData.gdprConsent) ? 'not-allowed' : 'pointer'
}}
>
{isSubmitting
? (leadSource === 'request_early_access' ? 'Submitting Request...' : 'Joining Waitlist...')
: (leadSource === 'request_early_access' ? 'Submit Request' : 'Join Waitlist')
}
</button>
</form>
)}
{/* Footer */}
<p style={{
fontSize: '0.75rem',
opacity: 0.6,
textAlign: 'center',
marginTop: '1.5rem',
paddingTop: '1.5rem',
borderTop: '1px solid #444'
}}>
By joining our waitlist, you'll receive exclusive early access and product updates.
We respect your privacy and won't spam you.
</p>
</div>
</div>
)
}