+ {/* Progress */}
+
+
+ Question {step + 1} of {totalSteps}
+ {progressPct}%
+
+
+
+
+ {/* Step 1: Risk */}
+ {step === 0 && (
+
+
How spicy is your risk appetite? 🌶️
+
+ {([
+ { key: 'low', label: 'Low • Cozy 🔒' },
+ { key: 'medium', label: 'Medium • Balanced ⚖️' },
+ { key: 'high', label: 'High • YOLO 🚀' },
+ ] as const).map((opt) => (
+
+ ))}
+
+
+ )}
+
+ {/* Step 2: Horizon */}
+ {step === 1 && (
+
+
Whats your ideal holding period? ⏳
+
+ {([
+ { key: 'short', label: 'Short • Quick flings 💫' },
+ { key: 'medium', label: 'Medium • Steady dates 🗓️' },
+ { key: 'long', label: 'Long • Serious only 💍' },
+ ] as const).map((opt) => (
+
+ ))}
+
+
+ )}
+
+ {/* Step 3: Vibe */}
+ {step === 2 && (
+
+
Pick your vibe 🎭
+
+ {([
+ { key: 'serious', label: 'Serious • Safe & sound 🛡️' },
+ { key: 'balanced', label: 'Balanced • A lil fun ⚖️' },
+ { key: 'degen', label: 'Degen • Memes only 🐸' },
+ ] as const).map((opt) => (
+
+ ))}
+
+
+ )}
+
+ {/* Step 4: Favorite sector (UI only) */}
+ {step === 3 && (
+
+
What makes your heart race? 💘
+
+ {([
+ { key: 'memes', label: 'Memecoins 🐶' },
+ { key: 'bluechips', label: 'Blue chips 🟦' },
+ { key: 'stables', label: 'Stables 💵' },
+ { key: 'narratives', label: 'Hot narratives 🔥' },
+ ] as const).map((opt) => (
+
+ ))}
+
+
+ )}
+
+ {/* Step 5: Volatility tolerance (UI only) */}
+ {step === 4 && (
+
+
How much volatility can you cuddle with? 🧸
+
+ {([
+ { key: 'low', label: 'Low • Keep me comfy 🛌' },
+ { key: 'medium', label: 'Medium • Spice it up 🌶️' },
+ { key: 'high', label: 'High • Chaos lover ⚡' },
+ ] as const).map((opt) => (
+
+ ))}
+
+
+ )}
+
+ {/* Step 6: Trade frequency (UI only) */}
+ {step === 5 && (
+
+
How often do you like to trade? 💃
+
+ {([
+ { key: 'scalp', label: 'Scalp • Minute-by-minute 🧠' },
+ { key: 'swing', label: 'Swing • A few days 🪩' },
+ { key: 'position', label: 'Position • Weeks+ 🧭' },
+ ] as const).map((opt) => (
+
+ ))}
+
+
+ )}
+
+ {/* Step 7: Chain preference (UI only) */}
+ {step === 6 && (
+
+
Pick your date spot (chain) 🗺️
+
+ {([
+ { key: 'monad', label: 'Monad 💜' },
+ { key: 'eth', label: 'Ethereum 🧿' },
+ { key: 'alt', label: 'Alt L1/L2 🌀' },
+ ] as const).map((opt) => (
+
+ ))}
+
+
+ )}
+
+ {/* Controls */}
+
+
+ {step < totalSteps - 1 ? (
+
+ ) : (
+
+ )}
+
+
+ )
+}
+
+
diff --git a/app/src/features/compatibility/data.ts b/app/src/features/compatibility/data.ts
new file mode 100644
index 000000000..ddb15334b
--- /dev/null
+++ b/app/src/features/compatibility/data.ts
@@ -0,0 +1,216 @@
+export type QuizAnswer = {
+ risk: 'low' | 'medium' | 'high'
+ horizon: 'short' | 'medium' | 'long'
+ vibe: 'serious' | 'balanced' | 'degen'
+ // Optional UI-driven extras
+ sector?: 'memes' | 'bluechips' | 'stables' | 'narratives'
+ volatility?: 'low' | 'medium' | 'high'
+ frequency?: 'scalp' | 'swing' | 'position'
+ chainPref?: 'monad' | 'eth' | 'alt'
+}
+
+export type TokenProfile = {
+ symbol: string
+ name: string
+ bio: string
+ liquidity: 'High' | 'Medium' | 'Low'
+ feeBps: number
+ vector: number[]
+}
+
+// Simple trait encoding: [risk, volatility, memeScore, stability, yieldBias]
+export const TOKENS: TokenProfile[] = [
+ {
+ symbol: 'ETH',
+ name: 'Ethereum',
+ bio: 'The steady date — I like stability and long nights hodling.',
+ liquidity: 'High',
+ feeBps: 5,
+ vector: [0.4, 0.4, 0.2, 0.8, 0.5],
+ },
+ {
+ symbol: 'USDC',
+ name: 'USD Coin',
+ bio: 'The sensible one — Perfect for short date nights. No surprises.',
+ liquidity: 'High',
+ feeBps: 1,
+ vector: [0.1, 0.1, 0.0, 1.0, 0.3],
+ },
+ {
+ symbol: 'PEPE',
+ name: 'PEPE',
+ bio: 'The crazy one — Loud, meme-y and might moon. Bring snacks.',
+ liquidity: 'Medium',
+ feeBps: 30,
+ vector: [0.9, 0.95, 1.0, 0.1, 0.4],
+ },
+ {
+ symbol: 'USDT',
+ name: 'Tether USD',
+ bio: 'The practical date — dependable and everywhere.',
+ liquidity: 'High',
+ feeBps: 1,
+ vector: [0.1, 0.1, 0.0, 1.0, 0.3],
+ },
+ {
+ symbol: 'WBTC',
+ name: 'Wrapped Bitcoin',
+ bio: 'The OG — slow and steady, big energy.',
+ liquidity: 'Medium',
+ feeBps: 5,
+ vector: [0.5, 0.5, 0.1, 0.7, 0.4],
+ },
+ {
+ symbol: 'WSOL',
+ name: 'Wrapped SOL',
+ bio: 'The sprinter — fast moves and sunny vibes.',
+ liquidity: 'Medium',
+ feeBps: 5,
+ vector: [0.6, 0.6, 0.2, 0.6, 0.5],
+ },
+]
+
+export function userVectorFromAnswers(a: QuizAnswer): number[] {
+ const risk = a.risk === 'low' ? 0.2 : a.risk === 'medium' ? 0.5 : 0.9
+ const volatility = risk
+ const meme = a.vibe === 'serious' ? 0.1 : a.vibe === 'balanced' ? 0.5 : 0.95
+ const stability = 1 - volatility * 0.8
+ const yieldBias = a.horizon === 'short' ? 0.3 : a.horizon === 'medium' ? 0.6 : 0.8
+ return [risk, volatility, meme, stability, yieldBias]
+}
+
+export function cosineSimilarity(a: number[], b: number[]): number {
+ const dot = a.reduce((sum, ai, i) => sum + ai * b[i], 0)
+ const magA = Math.hypot(...a)
+ const magB = Math.hypot(...b)
+ return magA && magB ? dot / (magA * magB) : 0
+}
+
+export function scoreTokens(userVec: number[]): { token: TokenProfile; score: number }[] {
+ return TOKENS.map((t) => ({ token: t, score: cosineSimilarity(userVec, t.vector) }))
+ .sort((x, y) => y.score - x.score)
+}
+
+// -----------------------------
+// Discrete survey weighting model (custom token list from user)
+// -----------------------------
+
+type Percent = number // 0-100
+export type DiscreteToken = {
+ symbol: string
+ name: string
+ weights: {
+ risk: { low: Percent; medium: Percent; high: Percent }
+ horizon: { short: Percent; medium: Percent; long: Percent }
+ vibe: { serious: Percent; balanced: Percent; degen: Percent }
+ }
+}
+
+// Update swipe cards to supported Monad tokens so we can swap reliably
+export const DISCRETE_TOKENS: DiscreteToken[] = [
+ { symbol: 'WMON', name: 'Wrapped MON', weights: { risk: { low: 20, medium: 60, high: 20 }, horizon: { short: 20, medium: 60, long: 20 }, vibe: { serious: 30, balanced: 50, degen: 20 } } },
+ { symbol: 'WOOL', name: 'Wrapped WOOL', weights: { risk: { low: 40, medium: 50, high: 10 }, horizon: { short: 30, medium: 50, long: 20 }, vibe: { serious: 35, balanced: 50, degen: 15 } } },
+ { symbol: 'USDC', name: 'USD Coin', weights: { risk: { low: 80, medium: 20, high: 0 }, horizon: { short: 60, medium: 35, long: 5 }, vibe: { serious: 70, balanced: 30, degen: 0 } } },
+ { symbol: 'USDT', name: 'Tether USD', weights: { risk: { low: 80, medium: 20, high: 0 }, horizon: { short: 60, medium: 35, long: 5 }, vibe: { serious: 60, balanced: 35, degen: 5 } } },
+ { symbol: 'WETH', name: 'Wrapped ETH', weights: { risk: { low: 30, medium: 60, high: 10 }, horizon: { short: 20, medium: 50, long: 30 }, vibe: { serious: 50, balanced: 45, degen: 5 } } },
+ { symbol: 'WBTC', name: 'Wrapped BTC', weights: { risk: { low: 30, medium: 60, high: 10 }, horizon: { short: 15, medium: 40, long: 45 }, vibe: { serious: 60, balanced: 35, degen: 5 } } },
+ { symbol: 'WSOL', name: 'Wrapped SOL', weights: { risk: { low: 25, medium: 60, high: 15 }, horizon: { short: 25, medium: 55, long: 20 }, vibe: { serious: 40, balanced: 50, degen: 10 } } },
+]
+
+export function scoreDiscreteTokens(a: QuizAnswer): { symbol: string; name: string; scorePct: number }[] {
+ // Base from risk/horizon/vibe
+ const base = DISCRETE_TOKENS.map((t) => {
+ const r = t.weights.risk[a.risk]
+ const h = t.weights.horizon[a.horizon]
+ const v = t.weights.vibe[a.vibe]
+ const baseScore = (r + h + v) / 3 // 0..100
+
+ // Bonuses from extra answers (lightweight heuristic)
+ let bonus = 0
+ // Sector fit
+ const sectorFit: Record