233 lines
12 KiB
TypeScript
Executable File
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>
|
|
);
|
|
} |