224 lines
8.7 KiB
TypeScript
224 lines
8.7 KiB
TypeScript
import { useState } from 'react';
|
|
import { motion } from 'motion/react';
|
|
import { ArrowRight } from 'lucide-react';
|
|
import { VideoFilled } from '../icons/FilledIcons';
|
|
import { SectionWrapper } from '../report/ui/SectionWrapper';
|
|
import type { ContentStrategyData } from '../../types/plan';
|
|
|
|
interface ContentStrategyProps {
|
|
data: ContentStrategyData;
|
|
}
|
|
|
|
const tabItems = [
|
|
{ key: 'pillars', label: 'Content Pillars', labelKr: '콘텐츠 필러' },
|
|
{ key: 'types', label: 'Content Types', labelKr: '콘텐츠 유형' },
|
|
{ key: 'workflow', label: 'Production Workflow', labelKr: '제작 워크플로우' },
|
|
{ key: 'repurposing', label: 'Repurposing', labelKr: '콘텐츠 재활용' },
|
|
] as const;
|
|
|
|
type TabKey = (typeof tabItems)[number]['key'];
|
|
|
|
const channelColorMap: Record<string, string> = {
|
|
YouTube: 'bg-[#FFF0F0] text-[#7C3A4B]',
|
|
Instagram: 'bg-[#FFF0F0] text-[#7C3A4B]',
|
|
Blog: 'bg-[#EFF0FF] text-[#3A3F7C]',
|
|
'블로그': 'bg-[#EFF0FF] text-[#3A3F7C]',
|
|
'네이버블로그': 'bg-[#F3F0FF] text-[#4A3A7C]',
|
|
'네이버': 'bg-[#F3F0FF] text-[#4A3A7C]',
|
|
Facebook: 'bg-[#EFF0FF] text-[#3A3F7C]',
|
|
'홈페이지': 'bg-slate-100 text-slate-700',
|
|
Website: 'bg-slate-100 text-slate-700',
|
|
TikTok: 'bg-[#F3F0FF] text-[#4A3A7C]',
|
|
'카카오': 'bg-[#FFF6ED] text-[#7C5C3A]',
|
|
};
|
|
|
|
function getChannelBadgeClass(channel: string): string {
|
|
for (const [key, value] of Object.entries(channelColorMap)) {
|
|
if (channel.toLowerCase().includes(key.toLowerCase())) return value;
|
|
}
|
|
return 'bg-slate-100 text-slate-700';
|
|
}
|
|
|
|
export default function ContentStrategy({ data }: ContentStrategyProps) {
|
|
const [activeTab, setActiveTab] = useState<TabKey>('pillars');
|
|
|
|
return (
|
|
<SectionWrapper
|
|
id="content-strategy"
|
|
title="Content Strategy"
|
|
subtitle="콘텐츠 마케팅 전략"
|
|
>
|
|
{/* Tabs */}
|
|
<div className="flex flex-wrap gap-2 mb-8">
|
|
{tabItems.map((tab) => (
|
|
<button
|
|
key={tab.key}
|
|
onClick={() => setActiveTab(tab.key)}
|
|
className={`rounded-full px-4 py-2 text-sm font-medium transition-all ${
|
|
activeTab === tab.key
|
|
? 'bg-gradient-to-r from-[#4F1DA1] to-[#021341] text-white shadow-lg'
|
|
: 'bg-white border border-slate-200 text-slate-600 hover:bg-slate-50'
|
|
}`}
|
|
>
|
|
{tab.label}
|
|
</button>
|
|
))}
|
|
</div>
|
|
|
|
{/* Tab 1: Content Pillars */}
|
|
{activeTab === 'pillars' && (
|
|
<motion.div
|
|
className="grid md:grid-cols-2 gap-6"
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ duration: 0.4 }}
|
|
>
|
|
{data.pillars.map((pillar, i) => (
|
|
<motion.div
|
|
key={pillar.title}
|
|
className="rounded-2xl border border-slate-100 bg-white shadow-sm p-6 border-l-4"
|
|
style={{ borderLeftColor: pillar.color }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ duration: 0.4, delay: i * 0.1 }}
|
|
>
|
|
<h4 className="font-serif text-xl font-bold text-[#0A1128] mb-2">
|
|
{pillar.title}
|
|
</h4>
|
|
<p className="text-sm text-slate-600 mb-3">{pillar.description}</p>
|
|
<span className="inline-block rounded-full bg-slate-100 px-3 py-1 text-xs font-medium text-slate-700 mb-4">
|
|
{pillar.relatedUSP}
|
|
</span>
|
|
<ul className="space-y-2">
|
|
{pillar.exampleTopics.map((topic, j) => (
|
|
<li key={j} className="flex items-start gap-2 text-sm text-slate-700">
|
|
<span
|
|
className="shrink-0 w-2 h-2 rounded-full mt-2"
|
|
style={{ backgroundColor: pillar.color }}
|
|
/>
|
|
{topic}
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</motion.div>
|
|
))}
|
|
</motion.div>
|
|
)}
|
|
|
|
{/* Tab 2: Content Types */}
|
|
{activeTab === 'types' && (
|
|
<motion.div
|
|
className="rounded-2xl overflow-hidden border border-slate-100"
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ duration: 0.4 }}
|
|
>
|
|
<div className="grid grid-cols-4 bg-[#0A1128] text-white">
|
|
<div className="px-6 py-4 text-sm font-semibold">Format</div>
|
|
<div className="px-6 py-4 text-sm font-semibold">Channels</div>
|
|
<div className="px-6 py-4 text-sm font-semibold">Frequency</div>
|
|
<div className="px-6 py-4 text-sm font-semibold">Purpose</div>
|
|
</div>
|
|
{data.typeMatrix.map((row, i) => (
|
|
<motion.div
|
|
key={row.format}
|
|
className={`grid grid-cols-4 ${i % 2 === 0 ? 'bg-white' : 'bg-slate-50'}`}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ duration: 0.3, delay: i * 0.04 }}
|
|
>
|
|
<div className="px-6 py-4 text-sm font-medium text-[#0A1128]">
|
|
{row.format}
|
|
</div>
|
|
<div className="px-6 py-4 flex flex-wrap gap-2">
|
|
{row.channels.map((ch) => (
|
|
<span
|
|
key={ch}
|
|
className={`inline-flex items-center rounded-full px-3 py-1 text-xs font-medium ${getChannelBadgeClass(ch)}`}
|
|
>
|
|
{ch}
|
|
</span>
|
|
))}
|
|
</div>
|
|
<div className="px-6 py-4 text-sm text-slate-700">{row.frequency}</div>
|
|
<div className="px-6 py-4 text-sm text-slate-600">{row.purpose}</div>
|
|
</motion.div>
|
|
))}
|
|
</motion.div>
|
|
)}
|
|
|
|
{/* Tab 3: Production Workflow */}
|
|
{activeTab === 'workflow' && (
|
|
<motion.div
|
|
className="flex md:flex-row flex-col gap-4 items-stretch"
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ duration: 0.4 }}
|
|
>
|
|
{data.workflow.map((step, i) => (
|
|
<div key={step.step} className="flex md:flex-row flex-col items-center gap-4 flex-1">
|
|
<motion.div
|
|
className="flex-1 w-full rounded-2xl border border-slate-100 bg-white shadow-sm p-5"
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ duration: 0.4, delay: i * 0.1 }}
|
|
>
|
|
<div className="w-10 h-10 rounded-full bg-gradient-to-r from-[#4F1DA1] to-[#021341] text-white font-bold flex items-center justify-center mb-3">
|
|
{step.step}
|
|
</div>
|
|
<h4 className="font-semibold text-[#0A1128] mb-1">{step.name}</h4>
|
|
<p className="text-sm text-slate-600 mb-3">{step.description}</p>
|
|
<div className="flex flex-wrap gap-2">
|
|
<span className="rounded-full bg-[#F3F0FF] text-[#4A3A7C] px-2 py-1 text-xs">
|
|
{step.owner}
|
|
</span>
|
|
<span className="rounded-full bg-slate-100 text-slate-600 px-2 py-1 text-xs">
|
|
{step.duration}
|
|
</span>
|
|
</div>
|
|
</motion.div>
|
|
{i < data.workflow.length - 1 && (
|
|
<ArrowRight
|
|
size={20}
|
|
className="text-slate-300 shrink-0 hidden md:block"
|
|
/>
|
|
)}
|
|
</div>
|
|
))}
|
|
</motion.div>
|
|
)}
|
|
|
|
{/* Tab 4: Repurposing */}
|
|
{activeTab === 'repurposing' && (
|
|
<motion.div
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ duration: 0.4 }}
|
|
>
|
|
{/* Source Card */}
|
|
<div className="rounded-2xl bg-gradient-to-r from-[#4F1DA1] to-[#021341] p-6 text-white mb-6">
|
|
<div className="flex items-center gap-3">
|
|
<VideoFilled size={28} className="text-white/60" />
|
|
<h4 className="font-serif text-xl font-bold">{data.repurposingSource}</h4>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Connector */}
|
|
<div className="flex justify-center mb-6">
|
|
<div className="w-px h-8 bg-gradient-to-b from-[#4F1DA1] to-slate-200" />
|
|
</div>
|
|
|
|
{/* Outputs Grid */}
|
|
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-3">
|
|
{data.repurposingOutputs.map((output, i) => (
|
|
<motion.div
|
|
key={`${output.format}-${i}`}
|
|
className="rounded-xl border border-slate-100 bg-white p-4"
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ duration: 0.3, delay: i * 0.05 }}
|
|
>
|
|
<p className="font-semibold text-sm text-[#0A1128] mb-1">{output.format}</p>
|
|
<p className="text-xs text-slate-500 mb-1">{output.channel}</p>
|
|
<p className="text-xs text-slate-600">{output.description}</p>
|
|
</motion.div>
|
|
))}
|
|
</div>
|
|
</motion.div>
|
|
)}
|
|
</SectionWrapper>
|
|
);
|
|
}
|