o2o-castad-frontend/ado2-marketing-intelligence.../App.tsx

233 lines
12 KiB
TypeScript
Executable File

import React, { useMemo } from 'react';
import { ArrowLeft, Sparkles, MapPin, Target, Zap, LayoutGrid, Users, Crown, TrendingUp } from 'lucide-react';
import { LALA_CABIN_DATA } from './constants';
import { GeometricChart } from './components/GeometricChart';
import { KeywordBubble } from './components/KeywordBubble';
import { BrandData, USP } from './types';
// Logic to calculate scores based on ICP (Ideal Customer Profile) signals
const calculateDynamicScores = (data: BrandData): USP[] => {
// 1. Extract Demand Signals from Targets (Needs + Triggers)
const marketSignals = data.targets.flatMap(t => [...t.needs, ...t.triggers]).map(s => s.replace(/\s+/g, ''));
// High value keywords that represent the "Core Value" of this specific property type
const coreKeywords = ['프라이빗', '감성', '독채', '캐빈', '조명', '불멍', 'Private', 'Mood'];
return data.usps.map(usp => {
let calculatedScore = 65; // Base score
const contentStr = (usp.label + usp.subLabel + usp.description).replace(/\s+/g, '');
// 2. Cross-reference USP with Market Signals
marketSignals.forEach(signal => {
if (contentStr.includes(signal)) calculatedScore += 4;
});
// 3. Boost based on Core Keywords (Weighted Importance)
coreKeywords.forEach(keyword => {
if (contentStr.includes(keyword)) calculatedScore += 6;
});
// 4. Special Boost based on specific Marketing Pillars (checking English SubLabels)
if (usp.subLabel === 'CONCEPT') calculatedScore += 10;
if (usp.subLabel === 'PRIVACY') calculatedScore += 8;
if (usp.subLabel === 'NIGHT MOOD') calculatedScore += 8;
if (usp.subLabel === 'PHOTO SPOT') calculatedScore += 5;
return {
...usp,
score: Math.min(Math.round(calculatedScore), 99) // Cap at 99
};
});
};
export default function App() {
const rawData = LALA_CABIN_DATA;
// Calculate scores on mount (or when data changes)
const scoredUSPs = useMemo(() => calculateDynamicScores(rawData), [rawData]);
// Find the top selling point
const topUSP = useMemo(() => [...scoredUSPs].sort((a, b) => b.score - a.score)[0], [scoredUSPs]);
return (
<div className="min-h-screen bg-brand-bg text-brand-text pb-24 selection:bg-brand-accent/30 font-sans">
{/* Top Navigation */}
<div className="p-6">
<button className="flex items-center gap-2 px-4 py-2 rounded-full border border-brand-muted/30 text-brand-muted hover:text-brand-accent hover:border-brand-accent transition-all text-sm group">
<ArrowLeft size={16} className="group-hover:-translate-x-1 transition-transform" />
<span></span>
</button>
</div>
{/* Main Header */}
<div className="text-center mb-10 px-4">
<div className="flex justify-center mb-4">
<div className="relative">
<Sparkles className="text-brand-accent w-8 h-8 animate-pulse relative z-10" />
</div>
</div>
<h1 className="text-4xl font-bold mb-3 tracking-tight text-white"> </h1>
<p className="text-brand-muted text-lg max-w-xl mx-auto">
<span className="text-brand-accent font-semibold">AI </span> .
</p>
</div>
{/* Grid Container */}
<div className="max-w-7xl mx-auto px-4 md:px-8 grid grid-cols-1 lg:grid-cols-2 gap-8">
{/* LEFT COLUMN: Identity & Text Analysis */}
<div className="space-y-6">
{/* Main Identity Card */}
<div className="bg-brand-card rounded-3xl p-8 border border-brand-muted/10 shadow-lg relative overflow-hidden group hover:border-brand-accent/20 transition-all duration-500">
<div className="absolute top-0 left-0 w-1.5 h-full bg-gradient-to-b from-brand-accent to-brand-purple"></div>
<div className="mb-4 flex items-center gap-2">
<span className="text-brand-accent font-bold text-sm uppercase tracking-wider flex items-center gap-2">
<LayoutGrid size={14} />
</span>
</div>
<h2 className="text-3xl font-bold mb-2 text-white tracking-tight">{rawData.name}</h2>
<div className="flex items-start gap-2 text-brand-muted text-sm mb-6">
<MapPin size={14} className="mt-0.5 shrink-0 text-brand-accent" />
<div>
<p>{rawData.address}</p>
<p className="opacity-70">{rawData.subAddress}</p>
</div>
</div>
<div className="space-y-5 text-gray-300 leading-relaxed border-t border-white/5 pt-5">
<div>
<h3 className="text-white font-semibold mb-2 text-sm text-brand-muted"> </h3>
<p className="text-sm opacity-90 leading-6">{rawData.locationAnalysis}</p>
</div>
<div>
<h3 className="text-white font-semibold mb-2 text-sm text-brand-muted"> </h3>
<p className="text-sm opacity-90 leading-6">{rawData.conceptAnalysis}</p>
</div>
</div>
</div>
{/* Positioning & Strategy Card */}
<div className="bg-brand-card rounded-3xl p-8 border border-brand-muted/10">
<h3 className="text-xl font-bold mb-6 flex items-center gap-2 text-white">
<Target size={20} className="text-brand-purple" />
</h3>
<div className="grid grid-cols-1 gap-4">
<div className="bg-brand-bg/50 p-5 rounded-xl border border-brand-muted/20 hover:border-brand-accent/30 transition-colors group">
<span className="block text-xs text-brand-muted mb-1 group-hover:text-brand-accent transition-colors"> </span>
<span className="font-bold text-lg text-white">{rawData.positioning.category}</span>
</div>
<div className="bg-gradient-to-r from-brand-bg/50 to-brand-cardHover p-5 rounded-xl border border-brand-muted/20 border-l-4 border-l-brand-accent">
<span className="block text-xs text-brand-accent mb-1 font-semibold"> (Core Value)</span>
<span className="font-semibold text-white">{rawData.positioning.coreValue}</span>
</div>
</div>
</div>
{/* Target Audience Card */}
<div className="bg-brand-card rounded-3xl p-8 border border-brand-muted/10">
<h3 className="text-xl font-bold mb-6 flex items-center gap-2 text-white">
<Users size={20} className="text-brand-purple" />
</h3>
<div className="space-y-4">
{rawData.targets.map((target, idx) => (
<div key={idx} className="flex flex-col sm:flex-row sm:items-center gap-4 p-4 bg-brand-bg/30 rounded-xl border border-white/5 hover:border-brand-accent/20 transition-all group">
<div className="min-w-[120px]">
<div className="font-bold text-white group-hover:text-brand-accent transition-colors">{target.segment}</div>
<div className="text-xs text-brand-muted">{target.age}</div>
</div>
<div className="flex-1">
<div className="flex flex-wrap gap-1.5 mb-2">
{target.needs.map((need, i) => (
<span key={i} className="text-[10px] px-2 py-0.5 bg-brand-accent/10 text-brand-accent rounded-sm font-medium">{need}</span>
))}
</div>
<p className="text-xs text-gray-400 border-t border-white/5 pt-2 mt-2">
<span className="text-brand-muted">Trigger:</span> {target.triggers.join(', ')}
</p>
</div>
</div>
))}
</div>
</div>
</div>
{/* RIGHT COLUMN: Visuals & Keywords */}
<div className="space-y-6">
{/* Chart Card */}
<div className="bg-brand-card rounded-3xl p-8 border border-brand-muted/10 min-h-[500px] flex flex-col relative overflow-hidden">
<div className="flex justify-between items-center mb-2 z-10">
<h3 className="text-xl font-bold text-white"> (USP)</h3>
<div className="flex items-center gap-1 text-xs text-brand-accent bg-brand-accent/10 px-2 py-1 rounded">
<TrendingUp size={12} />
<span>AI Data Analysis</span>
</div>
</div>
<div className="flex-1 flex items-center justify-center relative z-10 -my-4">
<GeometricChart data={scoredUSPs} />
</div>
{/* Core Competitiveness Highlight */}
{topUSP && (
<div className="mt-2 mb-6 p-4 rounded-xl bg-gradient-to-r from-brand-accent/10 to-transparent border border-brand-accent/20 relative overflow-hidden">
<div className="absolute top-0 right-0 p-2 opacity-10">
<Crown size={64} className="text-brand-accent" />
</div>
<div className="relative z-10">
<div className="text-xs text-brand-accent font-bold uppercase tracking-wider mb-1 flex items-center gap-1">
<Crown size={12} /> Core Competitiveness
</div>
<div className="flex justify-between items-end">
<div>
<div className="text-lg font-bold text-white">{topUSP.label}</div>
<div className="text-xs text-brand-accent/80 font-mono tracking-wider">{topUSP.subLabel}</div>
</div>
{/* Score removed */}
</div>
</div>
</div>
)}
<div className="grid grid-cols-2 gap-3 z-10">
{scoredUSPs.filter(u => u.label !== topUSP.label).slice(0, 4).map((usp, idx) => (
<div key={idx} className="p-3 rounded-xl bg-brand-bg/40 border border-white/5 hover:bg-brand-bg/60 transition-colors">
<div className="flex justify-between items-start mb-1">
<div className="text-xs text-brand-muted font-bold uppercase tracking-tight">{usp.subLabel}</div>
{/* Score removed */}
</div>
<div className="text-sm font-bold text-white mb-1">{usp.label}</div>
<div className="text-xs text-gray-400 leading-tight line-clamp-1">{usp.description}</div>
</div>
))}
</div>
</div>
{/* Keywords Card */}
<div className="bg-brand-card rounded-3xl p-8 border border-brand-muted/10 relative overflow-hidden">
<h3 className="text-xl font-bold mb-6 text-center text-white"> </h3>
<div className="flex flex-wrap justify-center gap-3 relative z-10">
{rawData.keywords.map((keyword, idx) => (
<KeywordBubble key={idx} text={keyword} />
))}
</div>
</div>
</div>
</div>
{/* Floating Action Button (Sticky Bottom) */}
<div className="fixed bottom-8 left-0 right-0 flex justify-center z-50 pointer-events-none">
<button className="pointer-events-auto bg-brand-purple hover:bg-brand-purpleHover text-white font-bold py-3 px-12 rounded-full shadow-2xl shadow-brand-purple/40 transform hover:scale-105 transition-all duration-300 flex items-center gap-2">
<Sparkles size={18} />
</button>
</div>
</div>
);
}