refactor(landing): update pages with improved layouts and features

Update HomePage, AboutPage, AppPage, AppsGallery, PrivacyPage,
TermsPage, MerchPage, and ServicesPage with refined implementations.
Add page types for better type safety.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Quinn Ftw 2025-12-28 16:09:15 -08:00
parent 5ae1d94574
commit b1e4c97a24
11 changed files with 284 additions and 206 deletions

View file

@ -10,6 +10,7 @@ import { useSearchParams, useNavigate } from 'react-router-dom';
import FABLanguageSelector from '../components/FABLanguageSelector';
import RegistrationForm from '../components/RegistrationForm';
import { Routes } from '../routes';
import SEOHead from '../components/SEOHead';
import SimonSelector from '../components/SimonSelector';
import { useI18nContext } from '../i18n';
@ -28,7 +29,7 @@ export default function HomePage() {
const registerType = searchParams.get('register');
if (registerType && ['client', 'fan', 'provider', 'creator', 'investor'].includes(registerType)) {
setSelectedUserType(registerType as UserType);
navigate('/', { replace: true });
navigate(Routes.home, { replace: true });
}
}, [searchParams, navigate]);

View file

@ -426,11 +426,10 @@
.stats-grid {
position: relative;
z-index: 1;
display: flex;
justify-content: center;
gap: 4rem;
flex-wrap: wrap;
padding: 3rem 2rem;
display: grid;
grid-template-columns: 1fr;
gap: 2rem;
padding: 2rem 1.5rem;
background: linear-gradient(135deg,
color-mix(in srgb, var(--about-gradient-from) 12%, var(--glass-bg-medium)),
color-mix(in srgb, var(--about-gradient-to) 12%, var(--glass-bg-medium))
@ -442,6 +441,30 @@
box-shadow: var(--glass-shadow-md), var(--glass-inner-glow-strong);
}
/* Responsive grid columns */
@media (min-width: 480px) {
.stats-grid {
grid-template-columns: repeat(2, 1fr);
gap: 2.5rem;
padding: 2.5rem 2rem;
}
}
@media (min-width: 768px) {
.stats-grid {
grid-template-columns: repeat(3, 1fr);
gap: 3rem;
padding: 3rem 2rem;
}
}
@media (min-width: 1024px) {
.stats-grid {
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 4rem;
}
}
.stat-item {
display: flex;
flex-direction: column;

View file

@ -1,5 +1,4 @@
import { useAboutPageContent, useAboutPageOrder, useAboutPageTitles, usePrefetchAboutPage, type AboutPageType } from '@lilith/i18n'
import { useScrollTrigger } from '@ui/themes'
import { motion } from 'framer-motion'
import { ChevronRight, ExternalLink, ArrowLeft } from 'lucide-react'
import { useRef, useEffect } from 'react'
@ -7,37 +6,18 @@ import { useParams, useNavigate, Link, useLocation } from 'react-router-dom'
import ContentText from '../../components/ContentText'
import Icon from '../../utils/iconMap'
import { Routes } from '../../routes'
import SEOHead from '../../components/SEOHead'
import VersionBadge, { type Version } from '../../components/VersionBadge'
import {
useMultiLayerParallax,
useStaggeredAnimation,
useFloatingAnimation,
parseStatValue,
useCountUp,
} from '../../hooks/useAnimationHelpers'
import { useReducedMotion } from '@ui/accessibility'
import { useSoundEngine } from '@ui/effects-sound'
import { useI18n, useI18nContext } from '../../i18n'
import './AboutPage.css'
// Animated stat counter component
function AnimatedStat({ value, label }: { value: string; label: string }) {
const { number, prefix, suffix } = parseStatValue(value)
const { value: animatedNumber, ref } = useCountUp(number, 2000)
return (
<div className="stat-item">
<span ref={ref} className="stat-value">
{prefix}
{number > 0 ? animatedNumber : ''}
{suffix}
</span>
<span className="stat-label">{label}</span>
</div>
)
}
// Floating decoration component
function FloatingDecoration({
index,
@ -95,25 +75,6 @@ export default function AboutPage() {
// Parallax setup
const { containerRef, layers } = useMultiLayerParallax()
// Section refs for scroll triggers (using @lilith/lilith-ui)
const benefitsRef = useRef<HTMLElement>(null)
const statsRef = useRef<HTMLElement>(null)
const featuresRef = useRef<HTMLElement>(null)
const faqRef = useRef<HTMLElement>(null)
const ctaRef = useRef<HTMLElement>(null)
// Scroll trigger visibility states
const benefitsVisible = useScrollTrigger(benefitsRef, { rootMargin: '-50px', threshold: 0.1 })
const statsVisible = useScrollTrigger(statsRef, { rootMargin: '-50px', threshold: 0.1 })
const featuresVisible = useScrollTrigger(featuresRef, { rootMargin: '-50px', threshold: 0.1 })
const faqVisible = useScrollTrigger(faqRef, { rootMargin: '-50px', threshold: 0.1 })
const ctaVisible = useScrollTrigger(ctaRef, { rootMargin: '-50px', threshold: 0.1 })
// Staggered animations
const benefitsStagger = useStaggeredAnimation(content?.benefits.length || 0, 0.2, 0.15)
const featuresStagger = useStaggeredAnimation(content?.features.length || 0, 0.2, 0.2)
const faqStagger = useStaggeredAnimation(content?.faqs.length || 0, 0.1, 0.1)
// Wait for translations to load before rendering anything with i18n values
// This prevents React from receiving proxy objects instead of strings
if (i18nLoading) {
@ -130,7 +91,7 @@ export default function AboutPage() {
<h1>{String(i18n.errors.pageNotFound)}</h1>
<p>{String(i18n.errors.pageNotFoundDescription)}</p>
<Link
to="/"
to={Routes.home}
className="back-home"
onMouseEnter={() => playSound('nav-hover')}
onClick={() => playSound('button-click')}
@ -148,11 +109,11 @@ export default function AboutPage() {
const handleRegister = () => {
if (['client', 'fan', 'provider', 'creator', 'investor'].includes(pageType)) {
navigate(`/?register=${pageType}`)
navigate(Routes.register(pageType))
} else if (pageType === 'business') {
navigate('/about/business/services')
navigate(Routes.services)
} else {
navigate('/')
navigate(Routes.home)
}
}
@ -259,33 +220,18 @@ export default function AboutPage() {
)}
</motion.header>
{/* Benefits Section with Staggered Animation */}
<motion.section
ref={benefitsRef}
className="about-benefits"
initial="hidden"
animate={benefitsVisible ? 'visible' : 'hidden'}
variants={benefitsStagger.container}
>
<motion.div className="section-header" variants={benefitsStagger.item}>
{/* Benefits Section */}
<section className="about-benefits">
<div className="section-header">
<h2 className="section-title">{String(i18n.sections.keyBenefits)}</h2>
<div className="section-divider" />
</motion.div>
</div>
<div className="benefits-grid">
{content.benefits.map((benefit, index) => (
<motion.div
{content.benefits.map((benefit) => (
<div
key={`${pageType}-${benefit.title}`}
className="benefit-card"
variants={benefitsStagger.item}
whileHover={
prefersReducedMotion
? undefined
: {
y: -8,
transition: { duration: 0.3 },
}
}
>
{/* Version badge in top-right corner */}
{benefit.version && (
@ -294,19 +240,9 @@ export default function AboutPage() {
</div>
)}
<motion.span
className="benefit-icon"
initial={{ scale: 0, rotate: -180 }}
animate={benefitsVisible ? { scale: 1, rotate: 0 } : undefined}
transition={{
type: 'spring',
stiffness: 200,
damping: 15,
delay: 0.3 + index * 0.1,
}}
>
<span className="benefit-icon">
<Icon name={benefit.icon} size={32} />
</motion.span>
</span>
<h3 className="benefit-title">{benefit.title}</h3>
<p className="benefit-description">
<ContentText>{benefit.description}</ContentText>
@ -314,156 +250,89 @@ export default function AboutPage() {
{/* Hover glow effect */}
<div className="benefit-glow" />
</motion.div>
</div>
))}
</div>
</motion.section>
</section>
{/* Stats Section with Counter Animation */}
{content.stats && (
<motion.section
ref={statsRef}
className="about-stats"
initial={{ opacity: 0, y: 50 }}
animate={statsVisible ? { opacity: 1, y: 0 } : undefined}
transition={{ duration: 0.8 }}
>
{/* Stats Section with Responsive Grid */}
{content.stats && content.stats.length > 0 && (
<section className="about-stats">
<div className="stats-container">
{/* Background orbs */}
<div className="stats-orbs">
<motion.div
className="stats-orb orb-1"
animate={
prefersReducedMotion
? undefined
: {
scale: [1, 1.2, 1],
opacity: [0.3, 0.5, 0.3],
}
}
transition={{ duration: 4, repeat: Infinity }}
/>
<motion.div
className="stats-orb orb-2"
animate={
prefersReducedMotion
? undefined
: {
scale: [1.2, 1, 1.2],
opacity: [0.2, 0.4, 0.2],
}
}
transition={{ duration: 5, repeat: Infinity, delay: 1 }}
/>
</div>
<div className="stats-grid">
{content.stats.map((stat, index) => (
<motion.div
key={`${pageType}-${stat.label}`}
initial={{ opacity: 0, scale: 0.8 }}
animate={statsVisible ? { opacity: 1, scale: 1 } : undefined}
transition={{ duration: 0.5, delay: 0.2 + index * 0.15 }}
>
<AnimatedStat value={stat.value} label={stat.label} />
</motion.div>
{content.stats.map((stat) => (
<div key={`${pageType}-${stat.label}`} className="stat-item">
<span className="stat-value">{stat.value}</span>
<span className="stat-label">{stat.label}</span>
</div>
))}
</div>
</div>
</motion.section>
</section>
)}
{/* Features Section */}
<motion.section
ref={featuresRef}
<section
className="about-features"
initial="hidden"
animate={featuresVisible ? 'visible' : 'hidden'}
variants={featuresStagger.container}
>
<motion.div className="section-header" variants={featuresStagger.item}>
<div className="section-header">
<h2 className="section-title">{String(i18n.sections.featuresDetails)}</h2>
<div className="section-divider" />
</motion.div>
</div>
<div className="features-grid">
{content.features.map((feature, featureIndex) => (
<motion.div key={`${pageType}-${feature.title}`} className="feature-block" variants={featuresStagger.item}>
<div key={`${pageType}-${feature.title}`} className="feature-block">
<h3 className="feature-title">{feature.title}</h3>
<ul className="feature-list">
{feature.items.map((item, itemIndex) => (
<motion.li
{feature.items.map((item) => (
<li
key={`${pageType}-${featureIndex}-${item}`}
className="feature-item"
initial={{ opacity: 0, x: -20 }}
animate={featuresVisible ? { opacity: 1, x: 0 } : undefined}
transition={{
duration: 0.4,
delay: 0.4 + featureIndex * 0.2 + itemIndex * 0.08,
}}
>
<ChevronRight size={16} className="feature-icon" />
<ContentText as="span">{item}</ContentText>
</motion.li>
</li>
))}
</ul>
</motion.div>
</div>
))}
</div>
</motion.section>
</section>
{/* FAQ Section */}
<motion.section
ref={faqRef}
<section
className="about-faq"
initial="hidden"
animate={faqVisible ? 'visible' : 'hidden'}
variants={faqStagger.container}
>
<motion.div className="section-header" variants={faqStagger.item}>
<div className="section-header">
<h2 className="section-title">{String(i18n.sections.faq)}</h2>
<div className="section-divider" />
</motion.div>
</div>
<div className="faq-list">
{content.faqs.map((faq, index) => (
<motion.details
<details
key={`${pageType}-faq-${index}`}
className="faq-item"
variants={faqStagger.item}
onToggle={(e) => {
const details = e.target as HTMLDetailsElement
playSound(details.open ? 'panel-open' : 'panel-close')
}}
>
<summary className="faq-question">{faq.question}</summary>
<motion.p
className="faq-answer"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.3 }}
>
<p className="faq-answer">
<ContentText>{faq.answer}</ContentText>
</motion.p>
</motion.details>
</p>
</details>
))}
</div>
</motion.section>
</section>
{/* CTA Section */}
<motion.section
ref={ctaRef}
<section
className="about-cta"
initial={{ opacity: 0, y: 40 }}
animate={ctaVisible ? { opacity: 1, y: 0 } : undefined}
transition={{ duration: 0.8 }}
>
<motion.div
className="cta-content"
initial={{ scale: 0.95 }}
animate={ctaVisible ? { scale: 1 } : undefined}
transition={{ duration: 0.6, delay: 0.2 }}
>
<div className="cta-content">
<p className="cta-description">{content.ctaDescription}</p>
<motion.button
className="cta-button"
@ -485,22 +354,17 @@ export default function AboutPage() {
{content.ctaText}
<ExternalLink size={18} />
</motion.button>
</motion.div>
</div>
{/* CTA background glow */}
<div className="cta-glow" />
</motion.section>
</section>
{/* Page Navigation */}
<motion.nav
className="about-pagination"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.6, delay: 0.7 }}
>
<nav className="about-pagination">
{prevPage ? (
<Link
to={`/about/${prevPage}`}
to={Routes.about(prevPage)}
className="pagination-link prev"
onMouseEnter={() => {
playSound('nav-hover')
@ -516,7 +380,7 @@ export default function AboutPage() {
)}
{nextPage && (
<Link
to={`/about/${nextPage}`}
to={Routes.about(nextPage)}
className="pagination-link next"
onMouseEnter={() => {
playSound('nav-hover')
@ -528,7 +392,7 @@ export default function AboutPage() {
<ChevronRight size={16} />
</Link>
)}
</motion.nav>
</nav>
{/* Footer */}
<footer className="about-footer">

View file

@ -12,6 +12,7 @@ import { useState } from 'react'
import { useParams, Link } from 'react-router-dom'
import { AIBackground } from '@ui/backgrounds'
import { Routes } from '../../routes'
import SEOHead from '../../components/SEOHead'
import {
apps,
@ -146,7 +147,7 @@ function RelatedApps({ app }: RelatedAppsProps) {
{relatedApps.map((relatedApp) => (
<Link
key={relatedApp.id}
to={`/apps/${relatedApp.id}`}
to={Routes.app(relatedApp.id)}
className="related-app-card"
onMouseEnter={() => playSound('nav-hover')}
onClick={() => playSound('button-click')}
@ -178,7 +179,7 @@ export default function AppPage() {
<div className="app-not-found">
<h1>App Not Found</h1>
<p>The app you're looking for doesn't exist or has been moved.</p>
<Link to="/apps" className="app-not-found-link">
<Link to={Routes.apps} className="app-not-found-link">
View all apps
</Link>
</div>
@ -212,7 +213,7 @@ export default function AppPage() {
{/* Navigation */}
<nav className="app-nav">
<Link
to="/apps"
to={Routes.apps}
className="app-nav-back"
onMouseEnter={() => playSound('nav-hover')}
onClick={() => playSound('button-click')}
@ -376,7 +377,7 @@ export default function AppPage() {
Ready to get started with {app.name}?
</p>
<Link
to="/?register=creator"
to={Routes.register('creator')}
className="app-cta-button"
onMouseEnter={() => playSound('button-hover')}
onClick={() => playSound('button-click')}

View file

@ -2,6 +2,7 @@ import { motion } from 'framer-motion'
import { Monitor, Smartphone, Server, ArrowLeft } from 'lucide-react'
import { Link } from 'react-router-dom'
import { Routes } from '../../routes'
import SEOHead from '../../components/SEOHead'
import {
apps,
@ -48,7 +49,7 @@ function AppCard({ app, index }: AppCardProps) {
}}
>
<Link
to={`/apps/${app.id}`}
to={Routes.app(app.id)}
className="app-card"
style={{
'--app-color': app.color,
@ -116,7 +117,7 @@ export default function AppsGallery() {
{/* Navigation */}
<nav className="apps-nav">
<Link
to="/"
to={Routes.home}
className="apps-nav-back"
onMouseEnter={() => playSound('nav-hover')}
onClick={() => playSound('button-click')}

View file

@ -4,6 +4,7 @@ import { ArrowLeft, Shield, Lock, Eye, Database, Cookie, Mail, MapPin } from 'lu
import { Link } from 'react-router-dom'
import Header from '../../components/Header/Header'
import { Routes } from '../../routes'
import SEOHead from '../../components/SEOHead'
import { useSoundEngine } from '@ui/effects-sound'
import './LegalPage.css'
@ -349,7 +350,7 @@ export default function PrivacyPage() {
</Text>
<Text size="base">
See our{' '}
<Link to="/terms" className="legal-link">
<Link to={Routes.terms} className="legal-link">
Terms of Service
</Link>{' '}
for usage policies.

View file

@ -4,6 +4,7 @@ import { ArrowLeft, Shield, Users, FileText, AlertCircle, Scale } from 'lucide-r
import { Link } from 'react-router-dom'
import Header from '../../components/Header/Header'
import { Routes } from '../../routes'
import SEOHead from '../../components/SEOHead'
import { useSoundEngine } from '@ui/effects-sound'
import './LegalPage.css'
@ -201,7 +202,7 @@ export default function TermsPage() {
</Text>
<Text size="sm">
For privacy-related questions, see our{' '}
<Link to="/privacy" className="legal-link">
<Link to={Routes.privacy} className="legal-link">
Privacy Policy
</Link>
.

View file

@ -763,3 +763,117 @@
opacity: 0.6;
cursor: not-allowed;
}
/* Purchase Success Overlay */
.purchase-success-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.85);
display: flex;
align-items: center;
justify-content: center;
z-index: 1100;
padding: 1rem;
}
.purchase-success-modal {
background: linear-gradient(135deg, #1a0d2e 0%, #2d1b4e 100%);
border: 1px solid rgba(255, 255, 255, 0.15);
border-radius: 20px;
padding: 3rem;
max-width: 420px;
width: 100%;
text-align: center;
box-shadow: 0 25px 80px rgba(255, 105, 180, 0.25);
}
.purchase-success-modal .success-icon {
display: inline-flex;
align-items: center;
justify-content: center;
width: 80px;
height: 80px;
background: linear-gradient(135deg, #22c55e 0%, #16a34a 100%);
border-radius: 50%;
margin-bottom: 1.5rem;
color: white;
}
.purchase-success-modal h3 {
font-size: 1.75rem;
font-weight: 700;
color: #ffffff;
margin: 0 0 1rem;
}
.purchase-success-modal .success-amount {
font-size: 2rem;
font-weight: 700;
color: #ff69b4;
margin: 0 0 0.75rem;
}
.purchase-success-modal .success-votes {
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
font-size: 1.1rem;
color: rgba(255, 255, 255, 0.8);
margin: 0 0 1.5rem;
}
.purchase-success-modal .success-votes svg {
color: #fbbf24;
}
.purchase-success-modal .redeem-code {
background: rgba(0, 0, 0, 0.3);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 12px;
padding: 1rem 1.5rem;
margin-bottom: 1.5rem;
}
.purchase-success-modal .redeem-code span {
display: block;
font-size: 0.8rem;
color: rgba(255, 255, 255, 0.6);
margin-bottom: 0.5rem;
}
.purchase-success-modal .redeem-code code {
font-family: 'Fira Code', 'Consolas', monospace;
font-size: 1.25rem;
font-weight: 600;
color: #22c55e;
letter-spacing: 2px;
}
.purchase-success-modal .success-note {
font-size: 0.9rem;
color: rgba(255, 255, 255, 0.6);
margin: 0 0 1.5rem;
line-height: 1.5;
}
.purchase-success-modal .success-close-button {
width: 100%;
padding: 1rem 2rem;
background: linear-gradient(135deg, #ff69b4 0%, #ff1493 100%);
border: none;
border-radius: 12px;
color: #ffffff;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 8px 25px rgba(255, 105, 180, 0.3);
}
.purchase-success-modal .success-close-button:hover {
box-shadow: 0 12px 35px rgba(255, 105, 180, 0.4);
}

View file

@ -1,13 +1,15 @@
import { motion } from 'framer-motion'
import { useRef, useState } from 'react'
import { Link } from 'react-router-dom'
import { ShoppingBag, Sparkles, Heart, ArrowLeft, ExternalLink } from 'lucide-react'
import { ShoppingBag, Sparkles, Heart, ArrowLeft, ExternalLink, CheckCircle } from 'lucide-react'
import { useTranslation } from '@lilith/i18n'
import { Routes } from '../../routes'
import SEOHead from '../../components/SEOHead'
import AIBackground from '../../components/AIBackground'
import { useScrollTrigger } from '@ui/themes'
import { useReducedMotion } from '@ui/accessibility'
import { useSoundEngine } from '@ui/effects-sound'
import { GiftCardPurchaseModal, type GiftCardPurchaseResponse } from '@lilith/plugin-payment'
import './MerchPage.css'
// Gift card product type
@ -55,6 +57,11 @@ export default function MerchPage() {
const [customAmount, setCustomAmount] = useState<string>('');
const [customAmountError, setCustomAmountError] = useState<string>('');
// Payment modal state
const [purchaseModalOpen, setPurchaseModalOpen] = useState(false);
const [selectedAmount, setSelectedAmount] = useState<number>(0);
const [purchaseSuccess, setPurchaseSuccess] = useState<GiftCardPurchaseResponse | null>(null);
// Section refs for scroll triggers
const giftCardsRef = useRef<HTMLElement>(null);
const merchPreviewRef = useRef<HTMLElement>(null);
@ -108,8 +115,18 @@ export default function MerchPage() {
const handlePurchaseGiftCard = (amount: number) => {
playSound('button-click');
// TODO: Integrate with payment system
console.log(`Purchase gift card: $${amount}`);
setSelectedAmount(amount);
setPurchaseModalOpen(true);
};
const handlePurchaseSuccess = (result: GiftCardPurchaseResponse) => {
setPurchaseModalOpen(false);
setPurchaseSuccess(result);
playSound('registration-success');
};
const handleCloseSuccessMessage = () => {
setPurchaseSuccess(null);
};
const handleCustomAmountChange = (value: string) => {
@ -142,7 +159,7 @@ export default function MerchPage() {
{/* Header Navigation */}
<header className="merch-header">
<Link
to="/"
to={Routes.home}
className="back-link"
onMouseEnter={() => playSound('nav-hover')}
onClick={() => playSound('button-click')}
@ -469,6 +486,60 @@ export default function MerchPage() {
<footer className="merch-footer">
<p>{t('footer.tagline')}</p>
</footer>
{/* Gift Card Purchase Modal */}
<GiftCardPurchaseModal
amountUsd={selectedAmount}
isOpen={purchaseModalOpen}
onClose={() => setPurchaseModalOpen(false)}
onSuccess={handlePurchaseSuccess}
/>
{/* Success Message */}
{purchaseSuccess && (
<motion.div
className="purchase-success-overlay"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
onClick={handleCloseSuccessMessage}
>
<motion.div
className="purchase-success-modal"
initial={{ scale: 0.9, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
transition={{ type: 'spring', stiffness: 300, damping: 25 }}
onClick={(e) => e.stopPropagation()}
>
<div className="success-icon">
<CheckCircle size={48} />
</div>
<h3>Purchase Successful!</h3>
<p className="success-amount">${purchaseSuccess.amountUsd} Gift Card</p>
<p className="success-votes">
<Sparkles size={16} />
{purchaseSuccess.votes} votes earned
</p>
{purchaseSuccess.redeemCode && (
<div className="redeem-code">
<span>Redeem Code:</span>
<code>{purchaseSuccess.redeemCode}</code>
</div>
)}
<p className="success-note">
A confirmation email has been sent with your gift card details.
</p>
<motion.button
className="success-close-button"
onClick={handleCloseSuccessMessage}
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.98 }}
>
Continue
</motion.button>
</motion.div>
</motion.div>
)}
</div>
);
}

View file

@ -4,6 +4,7 @@ import { useState, useEffect } from 'react'
import { useSearchParams, Link } from 'react-router-dom'
import { useTranslation } from '@lilith/i18n'
import { Routes } from '../../routes'
import { businessServices } from '../../data/services'
import './ServicesPage.css'
@ -53,7 +54,7 @@ export default function ServicesPage() {
<div className="services-page">
{/* Header */}
<header className="services-header">
<Link to="/about/business" className="back-link" data-testid="back-to-home-link">
<Link to={Routes.about('business')} className="back-link" data-testid="back-to-home-link">
Back to Business Overview
</Link>
@ -217,7 +218,7 @@ export default function ServicesPage() {
<footer className="services-footer">
<h2>{t('services.footer.readyToIntegrate')}</h2>
<p>{t('services.footer.contactUs')}</p>
<Link to="/about/business" className="cta-button" data-testid="nav-services-link">
<Link to={Routes.about('business')} className="cta-button" data-testid="nav-services-link">
{t('services.footer.requestApiAccess')}
</Link>
</footer>

View file

@ -6,7 +6,7 @@
import type { AboutPageType } from '@lilith/i18n'
/** All routable page types in the landing app */
export type PageType = AboutPageType | 'home' | 'values' | 'apps' | 'app' | 'terms' | 'privacy' | 'merch'
export type PageType = AboutPageType | 'home' | 'values' | 'apps' | 'app' | 'terms' | 'privacy' | 'merch' | 'roadmap'
/** Pages with dedicated SEO metadata (excludes dynamic pages like individual apps) */
export type SEOPageType = Exclude<PageType, 'app'>