diff --git a/web/components/home/home.tsx b/web/components/home/home.tsx
index 3eaee77f..712d8175 100644
--- a/web/components/home/home.tsx
+++ b/web/components/home/home.tsx
@@ -1,53 +1,237 @@
-import {useEffect} from 'react'
+import {discordLink, githubRepo} from 'common/constants'
+import Link from 'next/link'
+import {useEffect, useRef} from 'react'
import {Col} from 'web/components/layout/col'
import {SignUpButton} from 'web/components/nav/sidebar'
import {useUser} from 'web/hooks/use-user'
import {useT} from 'web/lib/locale'
-export function AboutBox(props: {title: string; text: string}) {
- const {title, text} = props
+// ─── Types ────────────────────────────────────────────────────────────────────
+
+interface FeatureCardProps {
+ icon: string
+ title: string
+ text: string
+}
+
+interface SocialAvatarProps {
+ letter: string
+ gradient: string
+}
+
+// ─── Sub-components ───────────────────────────────────────────────────────────
+
+function EyebrowBadge({children}: {children: React.ReactNode}) {
return (
-
-
{title}
-
{text}
+
+
+ {children}
)
}
+function FeatureCard({icon, title, text}: FeatureCardProps) {
+ return (
+
+ {/* Left accent bar */}
+
+
+
+ {icon}
+
+
{title}
+
{text}
+
+ )
+}
+
+function SocialAvatar({letter, gradient}: SocialAvatarProps) {
+ return (
+
+ {letter}
+
+ )
+}
+
+function SocialProof({label}: {label: React.ReactNode}) {
+ const avatars: SocialAvatarProps[] = [
+ {letter: 'S', gradient: 'linear-gradient(135deg, #C17F3E, #8B5E3C)'},
+ {letter: 'R', gradient: 'linear-gradient(135deg, #6B8F71, #4A7055)'},
+ {letter: 'T', gradient: 'linear-gradient(135deg, #8B5E3C, #6B3E22)'},
+ {letter: 'L', gradient: 'linear-gradient(135deg, #C17F3E, #D4955A)'},
+ ]
+
+ return (
+
+
+ {avatars.map((av) => (
+
+ ))}
+
+
{label}
+
+ )
+}
+
+function QuoteBlock({children}: {children: React.ReactNode}) {
+ return (
+
+ )
+}
+
+function OpenSourceStrip({
+ title,
+ description,
+ badges,
+}: {
+ title: string
+ description: string
+ badges: {label: string; url: string; primary?: boolean}[]
+}) {
+ return (
+
+
+
{title}
+
{description}
+
+
+ {badges.map((b) => (
+
+ {b.label}
+
+ ))}
+
+
+ )
+}
+
+// ─── Typewriter hook ──────────────────────────────────────────────────────────
+
+function useTypewriter(words: string[]) {
+ const elRef = useRef
(null)
+
+ useEffect(() => {
+ const el = elRef.current
+ if (!el) return
+
+ let wordIndex = 0
+ let charIndex = 0
+ let deleting = false
+ let timeoutId: ReturnType
+
+ function tick() {
+ const word = words[wordIndex]
+
+ if (!el) return
+
+ if (!deleting) {
+ el.textContent = word.substring(0, charIndex + 1)
+ charIndex++
+ if (charIndex === word.length) {
+ deleting = true
+ timeoutId = setTimeout(tick, 1800)
+ return
+ }
+ } else {
+ el.textContent = word.substring(0, charIndex - 1)
+ charIndex--
+ if (charIndex === 0) {
+ deleting = false
+ wordIndex = (wordIndex + 1) % words.length
+ }
+ }
+
+ timeoutId = setTimeout(tick, deleting ? 60 : 120)
+ }
+
+ timeoutId = setTimeout(tick, 600)
+ return () => clearTimeout(timeoutId)
+ }, [words])
+
+ return elRef
+}
+
+// ─── Main component ───────────────────────────────────────────────────────────
+
export function LoggedOutHome() {
const user = useUser()
const t = useT()
- const typewriterText = t('home.typewriter', 'Search.')
+ const typewriterWords = [
+ t('home.typewriter.search', 'Search.'),
+ t('home.typewriter.connect', 'Connect.'),
+ // t('home.typewriter.belong', 'Belong.'),
+ ]
- useEffect(() => {
- const text = typewriterText
- const el = document.getElementById('typewriter')
- if (!el) return
+ const typewriterRef = useTypewriter(typewriterWords)
- let i = 0
- let timeoutId: any
- el.textContent = ''
+ const features: FeatureCardProps[] = [
+ {
+ icon: '🔍',
+ title: t('home.feature1.title', 'Radically Transparent'),
+ text: t(
+ 'home.feature1.text',
+ 'No algorithms. Every profile fully searchable. You decide who to discover — not a black box.',
+ ),
+ },
+ {
+ icon: '🎯',
+ title: t('home.feature2.title', 'Built for Depth'),
+ text: t(
+ 'home.feature2.text',
+ 'Filter by values, interests, goals, and keywords — from "stoicism" to "sustainable living." Surface connections that truly matter.',
+ ),
+ },
+ {
+ icon: '🌍',
+ title: t('home.feature3.title', 'Community Owned'),
+ text: t(
+ 'home.feature3.text',
+ 'Free forever. No ads, no subscriptions. Built by the people who use it, for the benefit of everyone.',
+ ),
+ },
+ ]
- function typeWriter() {
- if (i < text.length && el) {
- el.textContent = text.substring(0, i + 1)
- i++
- timeoutId = setTimeout(typeWriter, 150)
- }
- }
-
- const startId = setTimeout(typeWriter, 500)
-
- return () => {
- clearTimeout(timeoutId)
- clearTimeout(startId)
- if (el) el.textContent = text
- }
- }, [typewriterText])
+ const openSourceBadges = [
+ {label: t('home.strip.github', '⭐ GitHub'), url: githubRepo},
+ {label: t('home.strip.discord', '📖 Discord'), url: discordLink},
+ {label: t('home.strip.join', 'Join Now →'), url: '/register', primary: true},
+ ]
return (
<>
+ {/* Mobile sign-up CTA */}
{user === null && (
)}
-
- {t('home.title', "Don't Swipe.")}
-
-
-
- |
-
-
-
-
- {t(
- 'home.subtitle',
- 'Find people who share your values, ideas, and intentions — not just your photos.',
- )}
-
-
-
-
-
-
-
+
+
+ {/* ── Hero ── */}
+
+
+ {t('home.eyebrow', 'Free forever · Open source · No matching algorithms')}
+
+
+
+ {t('home.title', "Don't Swipe.")}
+
+
+ {/* Typewriter line */}
+
+
+ |
-
-
- {t(
- 'home.bottom',
- 'Compass is to human connection what Linux is to software, Wikipedia is to knowledge, and Firefox is to browsing — a public digital good designed to serve people, not profit.',
- )}
-
+
+
+ {t('home.subtitle', 'Find people who share your ')}
+ {t('home.subtitle.values', 'values')}
+ {', '}
+ {t('home.subtitle.ideas', 'ideas')}
+ {t('home.subtitle.and', ', and ')}
+ {t('home.subtitle.intentions', 'intentions')}
+ {t('home.subtitle.end', ' — not just your photos.')}
+
+
+ {/* CTAs */}
+
+ {/**/}
+ {/* {t('home.cta.primary', 'Explore People →')}*/}
+ {/* */}
+
+ {t('home.cta.secondary', 'Learn how it works')}
+
-
+
+ {/* Social proof */}
+
+ {t('home.proof.prefix', 'Joined by ')}
+ {t('home.proof.count', '600+')}
+ {t('home.proof.suffix', ' real people worldwide')}
+ >
+ }
+ />
+
+
+ {/* Divider */}
+
+
+ {/* ── Features ── */}
+
+
+ {t('home.features.label', 'Why Compass')}
+
+
+ {t('home.features.title', 'Built different. On purpose.')}
+
+
+ {features.map((f) => (
+
+ ))}
+
+
+
+ {/* ── Quote ── */}
+
+ {t('home.quote.prefix', 'Compass is to human connection what ')}
+ {t('home.quote.linux', 'Linux')}
+ {t('home.quote.linux_suffix', ' is to software, ')}
+ {t('home.quote.wikipedia', 'Wikipedia')}
+ {t('home.quote.wikipedia_suffix', ' is to knowledge, and ')}
+ {t('home.quote.firefox', 'Firefox')}
+ {t('home.quote.end', ' is to browsing — a public digital good designed to ')}
+
+ {t('home.quote.mission', 'serve people, not profit.')}
+
+
+
+ {/* ── Open source strip ── */}
+
-
+
+ {/* Mobile bottom spacing */}
+
>
)
}
diff --git a/web/pages/about.tsx b/web/pages/about.tsx
index 42ece596..a02bda5c 100644
--- a/web/pages/about.tsx
+++ b/web/pages/about.tsx
@@ -9,7 +9,7 @@ import {useT} from 'web/lib/locale'
export const AboutBlock = (props: {text: ReactNode; title: string}) => {
const {text, title} = props
return (
-
+
@@ -131,7 +131,7 @@ export default function About() {
{t('about.help.title', 'Help Compass')}
-
+
-
+
{t('about.dev.title', 'Develop the App')}
@@ -161,7 +161,7 @@ export default function About() {
-
+
{t('about.join.title', 'Join the Community')}
@@ -182,7 +182,7 @@ export default function About() {
{/**/}
-
+
{t('about.donate.title', 'Donate')}
@@ -227,7 +227,7 @@ export default function About() {
{/**/}
-
+
{t('about.final.title', 'Tell Your Friends and Family')}
diff --git a/web/styles/globals.css b/web/styles/globals.css
index 14455674..2b16af44 100644
--- a/web/styles/globals.css
+++ b/web/styles/globals.css
@@ -135,7 +135,7 @@
--color-canvas-800: 50 50 50;
--color-canvas-900: 68 52 34; /* Sidebar pressed */
--color-canvas-950: 44 36 22; /* Sidebar - Dark Espresso (#2C2416) */
- --color-canvas-1000: 0 0 0; /* black */
+ --color-canvas-1000: 44 36 22; /* black */
/* Primary - Warm Amber */
--color-primary-50: 250 243 233; /* #FAF3E9 */
@@ -249,6 +249,7 @@
--color-canvas-200: 68 52 34; /* UI Elements/Tags */
--color-canvas-300: 78 62 44;
--color-canvas-950: 15 13 10; /* Deepest depth (Sidebar/Bottom) */
+ --color-canvas-1000: 247 244 239; /* Deepest depth (Sidebar/Bottom) */
/* Dark Mode Primary - Luminous Amber */
--color-primary-50: 43 21 8; /* Deepest Shadow (Old 950) */
diff --git a/web/tailwind.config.js b/web/tailwind.config.js
index 10a9bf9c..904e8bd8 100644
--- a/web/tailwind.config.js
+++ b/web/tailwind.config.js
@@ -332,6 +332,7 @@ module.exports = {
800: 'rgb(var(--color-canvas-800) /
)',
900: 'rgb(var(--color-canvas-900) / )',
950: 'rgb(var(--color-canvas-950) / )',
+ 1000: 'rgb(var(--color-canvas-1000) / )',
},
primary: {