fix: 플랜 페이지 전체 섹션 투명 현상 완전 해결 — initial opacity:0 전수 제거

- PlanHeader: motion.div/h1/p의 initial opacity:0 → plain HTML로 교체
  (배경 blob 애니메이션은 opacity 무관하여 유지)
- RepurposingProposal: 카드 motion.div → plain div, 확장 accordion도 plain div
- ChannelStrategy, ContentStrategy, AssetCollection, PlanCTA,
  BrandingGuide, WorkflowTracker, StrategyAdjustmentSection:
  initial={{ opacity: 0, ... }} 라인 전수 제거
- 이 환경의 framer-motion이 animate prop 완료 실패 → opacity:0에서 멈춤

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
main
Haewon Kam 2026-04-14 16:06:21 +09:00
parent eaf215ea6d
commit 6b3b950ad0
9 changed files with 21 additions and 87 deletions

View File

@ -91,7 +91,6 @@ export default function AssetCollection({ data }: AssetCollectionProps) {
key={asset.id} key={asset.id}
className="rounded-2xl border border-slate-100 bg-white shadow-sm p-5 cursor-pointer hover:shadow-[3px_4px_12px_rgba(0,0,0,0.06)] hover:border-[#D5CDF5] transition-all" className="rounded-2xl border border-slate-100 bg-white shadow-sm p-5 cursor-pointer hover:shadow-[3px_4px_12px_rgba(0,0,0,0.06)] hover:border-[#D5CDF5] transition-all"
onClick={() => setSelectedAsset({ kind: 'asset', data: asset })} onClick={() => setSelectedAsset({ kind: 'asset', data: asset })}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.4, delay: i * 0.05 }} transition={{ duration: 0.4, delay: i * 0.05 }}
> >
@ -153,7 +152,6 @@ export default function AssetCollection({ data }: AssetCollectionProps) {
key={video.title} key={video.title}
className="min-w-[280px] rounded-2xl border border-slate-100 bg-white shadow-sm p-5 shrink-0 cursor-pointer hover:shadow-[3px_4px_12px_rgba(0,0,0,0.06)] hover:border-[#D5CDF5] transition-all" className="min-w-[280px] rounded-2xl border border-slate-100 bg-white shadow-sm p-5 shrink-0 cursor-pointer hover:shadow-[3px_4px_12px_rgba(0,0,0,0.06)] hover:border-[#D5CDF5] transition-all"
onClick={() => setSelectedAsset({ kind: 'youtube', data: video })} onClick={() => setSelectedAsset({ kind: 'youtube', data: video })}
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }} animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.4, delay: i * 0.1 }} transition={{ duration: 0.4, delay: i * 0.1 }}
> >

View File

@ -157,7 +157,6 @@ function VisualIdentityTab({ data }: { data: BrandGuide }) {
return ( return (
<motion.div <motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.4 }} transition={{ duration: 0.4 }}
className="space-y-8" className="space-y-8"
@ -268,7 +267,6 @@ function VisualIdentityTab({ data }: { data: BrandGuide }) {
function ToneVoiceTab({ tone }: { tone: BrandGuide['toneOfVoice'] }) { function ToneVoiceTab({ tone }: { tone: BrandGuide['toneOfVoice'] }) {
return ( return (
<motion.div <motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.4 }} transition={{ duration: 0.4 }}
className="space-y-6" className="space-y-6"
@ -339,7 +337,6 @@ function ToneVoiceTab({ tone }: { tone: BrandGuide['toneOfVoice'] }) {
function ChannelRulesTab({ channels }: { channels: BrandGuide['channelBranding'] }) { function ChannelRulesTab({ channels }: { channels: BrandGuide['channelBranding'] }) {
return ( return (
<motion.div <motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.4 }} transition={{ duration: 0.4 }}
> >
@ -396,7 +393,6 @@ function BrandConsistencyTab({ inconsistencies }: { inconsistencies: BrandIncons
return ( return (
<motion.div <motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.4 }} transition={{ duration: 0.4 }}
> >

View File

@ -53,7 +53,6 @@ export default function ChannelStrategy({ channels }: ChannelStrategyProps) {
<motion.div <motion.div
key={ch.channelId} key={ch.channelId}
className="bg-white rounded-2xl p-6 shadow-[3px_4px_12px_rgba(0,0,0,0.06)] hover:shadow-[4px_6px_16px_rgba(0,0,0,0.09)] transition-shadow" className="bg-white rounded-2xl p-6 shadow-[3px_4px_12px_rgba(0,0,0,0.06)] hover:shadow-[4px_6px_16px_rgba(0,0,0,0.09)] transition-shadow"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: index * 0.1 }} transition={{ duration: 0.5, delay: index * 0.1 }}
> >

View File

@ -69,7 +69,6 @@ export default function ContentStrategy({ data }: ContentStrategyProps) {
{activeTab === 'pillars' && ( {activeTab === 'pillars' && (
<motion.div <motion.div
className="grid md:grid-cols-2 gap-6" className="grid md:grid-cols-2 gap-6"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.4 }} transition={{ duration: 0.4 }}
> >
@ -78,7 +77,6 @@ export default function ContentStrategy({ data }: ContentStrategyProps) {
key={pillar.title} key={pillar.title}
className="rounded-2xl border border-slate-100 bg-white shadow-sm p-6 border-l-4" className="rounded-2xl border border-slate-100 bg-white shadow-sm p-6 border-l-4"
style={{ borderLeftColor: pillar.color }} style={{ borderLeftColor: pillar.color }}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.4, delay: i * 0.1 }} transition={{ duration: 0.4, delay: i * 0.1 }}
> >
@ -109,7 +107,6 @@ export default function ContentStrategy({ data }: ContentStrategyProps) {
{activeTab === 'types' && ( {activeTab === 'types' && (
<motion.div <motion.div
className="rounded-2xl overflow-hidden border border-slate-100" className="rounded-2xl overflow-hidden border border-slate-100"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.4 }} transition={{ duration: 0.4 }}
> >
@ -123,7 +120,6 @@ export default function ContentStrategy({ data }: ContentStrategyProps) {
<motion.div <motion.div
key={row.format} key={row.format}
className={`grid grid-cols-4 ${i % 2 === 0 ? 'bg-white' : 'bg-slate-50'}`} className={`grid grid-cols-4 ${i % 2 === 0 ? 'bg-white' : 'bg-slate-50'}`}
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3, delay: i * 0.04 }} transition={{ duration: 0.3, delay: i * 0.04 }}
> >
@ -151,7 +147,6 @@ export default function ContentStrategy({ data }: ContentStrategyProps) {
{activeTab === 'workflow' && ( {activeTab === 'workflow' && (
<motion.div <motion.div
className="flex md:flex-row flex-col gap-4 items-stretch" className="flex md:flex-row flex-col gap-4 items-stretch"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.4 }} transition={{ duration: 0.4 }}
> >
@ -159,7 +154,6 @@ export default function ContentStrategy({ data }: ContentStrategyProps) {
<div key={step.step} className="flex md:flex-row flex-col items-center gap-4 flex-1"> <div key={step.step} className="flex md:flex-row flex-col items-center gap-4 flex-1">
<motion.div <motion.div
className="flex-1 w-full rounded-2xl border border-slate-100 bg-white shadow-sm p-5" className="flex-1 w-full rounded-2xl border border-slate-100 bg-white shadow-sm p-5"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.4, delay: i * 0.1 }} transition={{ duration: 0.4, delay: i * 0.1 }}
> >
@ -191,7 +185,6 @@ export default function ContentStrategy({ data }: ContentStrategyProps) {
{/* Tab 4: Repurposing */} {/* Tab 4: Repurposing */}
{activeTab === 'repurposing' && ( {activeTab === 'repurposing' && (
<motion.div <motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.4 }} transition={{ duration: 0.4 }}
> >
@ -214,7 +207,6 @@ export default function ContentStrategy({ data }: ContentStrategyProps) {
<motion.div <motion.div
key={`${output.format}-${i}`} key={`${output.format}-${i}`}
className="rounded-xl border border-slate-100 bg-white p-4" className="rounded-xl border border-slate-100 bg-white p-4"
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3, delay: i * 0.05 }} transition={{ duration: 0.3, delay: i * 0.05 }}
> >

View File

@ -11,7 +11,6 @@ export default function PlanCTA() {
return ( return (
<motion.section <motion.section
className="py-16 md:py-20 px-6" className="py-16 md:py-20 px-6"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, ease: 'easeOut' }} transition={{ duration: 0.6, ease: 'easeOut' }}
> >

View File

@ -28,7 +28,7 @@ export default function PlanHeader({
}: PlanHeaderProps) { }: PlanHeaderProps) {
return ( return (
<section className="relative overflow-hidden bg-[radial-gradient(ellipse_at_top_left,#e0e7ff,transparent_50%),radial-gradient(ellipse_at_bottom_right,#fce7f3,transparent_50%),radial-gradient(ellipse_at_center,#f5f3ff,transparent_60%)] py-20 md:py-28 px-6"> <section className="relative overflow-hidden bg-[radial-gradient(ellipse_at_top_left,#e0e7ff,transparent_50%),radial-gradient(ellipse_at_bottom_right,#fce7f3,transparent_50%),radial-gradient(ellipse_at_center,#f5f3ff,transparent_60%)] py-20 md:py-28 px-6">
{/* Animated blobs */} {/* Animated blobs — position only, no opacity */}
<motion.div <motion.div
className="absolute top-10 left-10 w-72 h-72 rounded-full bg-[#6C5CE7]/10 blur-3xl" className="absolute top-10 left-10 w-72 h-72 rounded-full bg-[#6C5CE7]/10 blur-3xl"
animate={{ x: [0, 30, 0], y: [0, -20, 0] }} animate={{ x: [0, 30, 0], y: [0, -20, 0] }}
@ -47,46 +47,21 @@ export default function PlanHeader({
<div className="relative max-w-7xl mx-auto"> <div className="relative max-w-7xl mx-auto">
<div className="flex flex-col md:flex-row items-center md:items-start justify-between gap-10"> <div className="flex flex-col md:flex-row items-center md:items-start justify-between gap-10">
{/* Left: Text content */} {/* Left: Text content — plain div, no opacity animation */}
<motion.div <div className="flex-1 text-center md:text-left">
className="flex-1 text-center md:text-left" <p className="text-xs font-semibold text-[#6C5CE7] mb-4 tracking-widest uppercase">
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5 }}
>
<motion.p
className="text-xs font-semibold text-[#6C5CE7] mb-4 tracking-widest uppercase"
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: 0.1 }}
>
Marketing Execution Plan Marketing Execution Plan
</motion.p> </p>
<motion.h1 <h1 className="font-serif text-4xl md:text-5xl font-bold text-[#0A1128] mb-3">
className="font-serif text-4xl md:text-5xl font-bold text-[#0A1128] mb-3"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: 0.2 }}
>
{clinicName} {clinicName}
</motion.h1> </h1>
<motion.p <p className="text-xl text-slate-600 mb-8">
className="text-xl text-slate-600 mb-8"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: 0.3 }}
>
{clinicNameEn} {clinicNameEn}
</motion.p> </p>
<motion.div <div className="flex flex-wrap gap-3 justify-center md:justify-start">
className="flex flex-wrap gap-3 justify-center md:justify-start"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: 0.4 }}
>
<span className="inline-flex items-center gap-2 rounded-full bg-white/60 backdrop-blur-sm border border-white/40 px-3 py-1 text-sm font-medium text-slate-700"> <span className="inline-flex items-center gap-2 rounded-full bg-white/60 backdrop-blur-sm border border-white/40 px-3 py-1 text-sm font-medium text-slate-700">
<CalendarFilled size={14} className="text-slate-400" /> <CalendarFilled size={14} className="text-slate-400" />
{formatDate(date)} {formatDate(date)}
@ -95,23 +70,18 @@ export default function PlanHeader({
<GlobeFilled size={14} className="text-slate-400" /> <GlobeFilled size={14} className="text-slate-400" />
{targetUrl} {targetUrl}
</span> </span>
</motion.div> </div>
</motion.div> </div>
{/* Right: 90 Days badge */} {/* Right: 90 Days badge */}
<motion.div <div className="shrink-0">
className="shrink-0"
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.6, delay: 0.3 }}
>
<div className="w-32 h-32 rounded-full bg-gradient-to-r from-[#4F1DA1] to-[#021341] flex flex-col items-center justify-center shadow-lg"> <div className="w-32 h-32 rounded-full bg-gradient-to-r from-[#4F1DA1] to-[#021341] flex flex-col items-center justify-center shadow-lg">
<span className="text-4xl font-bold text-white leading-none"> <span className="text-4xl font-bold text-white leading-none">
90 90
</span> </span>
<span className="text-sm text-white/60">Days</span> <span className="text-sm text-white/60">Days</span>
</div> </div>
</motion.div> </div>
</div> </div>
</div> </div>
</section> </section>

View File

@ -1,5 +1,4 @@
import { useState } from 'react'; import { useState } from 'react';
import { motion } from 'motion/react';
import { import {
YoutubeFilled, YoutubeFilled,
InstagramFilled, InstagramFilled,
@ -63,12 +62,9 @@ export default function RepurposingProposal({ proposals }: RepurposingProposalPr
const isExpanded = expandedIdx === idx; const isExpanded = expandedIdx === idx;
return ( return (
<motion.div <div
key={item.sourceVideo.title} key={item.sourceVideo.title}
className="rounded-2xl border border-slate-100 bg-white shadow-[3px_4px_12px_rgba(0,0,0,0.06)] overflow-hidden" className="rounded-2xl border border-slate-100 bg-white shadow-[3px_4px_12px_rgba(0,0,0,0.06)] overflow-hidden"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.4, delay: idx * 0.08 }}
> >
{/* Card Header — click to expand */} {/* Card Header — click to expand */}
<button <button
@ -120,26 +116,17 @@ export default function RepurposingProposal({ proposals }: RepurposingProposalPr
{/* Expanded: Repurpose outputs */} {/* Expanded: Repurpose outputs */}
{isExpanded && ( {isExpanded && (
<motion.div <div className="border-t border-slate-100">
initial={{ opacity: 0, height: 0 }}
animate={{ opacity: 1, height: 'auto' }}
exit={{ opacity: 0, height: 0 }}
transition={{ duration: 0.2 }}
className="border-t border-slate-100"
>
<div className="px-6 py-4"> <div className="px-6 py-4">
<div className="flex items-center gap-2 mb-4"> <div className="flex items-center gap-2 mb-4">
<BoltFilled size={14} className="text-[#6C5CE7]" /> <BoltFilled size={14} className="text-[#6C5CE7]" />
<p className="text-xs font-semibold text-slate-500 uppercase tracking-widest">REPURPOSE AS</p> <p className="text-xs font-semibold text-slate-500 uppercase tracking-widest">REPURPOSE AS</p>
</div> </div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-3"> <div className="grid grid-cols-1 md:grid-cols-2 gap-3">
{item.outputs.map((output, oIdx) => ( {item.outputs.map((output) => (
<motion.div <div
key={output.format} key={output.format}
className="flex items-start gap-3 p-4 rounded-xl bg-slate-50 border border-slate-100" className="flex items-start gap-3 p-4 rounded-xl bg-slate-50 border border-slate-100"
initial={{ opacity: 0, x: -8 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: oIdx * 0.06 }}
> >
<div className="w-8 h-8 rounded-lg bg-gradient-to-br from-[#6C5CE7]/10 to-[#4F1DA1]/10 flex items-center justify-center shrink-0"> <div className="w-8 h-8 rounded-lg bg-gradient-to-br from-[#6C5CE7]/10 to-[#4F1DA1]/10 flex items-center justify-center shrink-0">
<ChannelIcon channel={output.channel} size={16} /> <ChannelIcon channel={output.channel} size={16} />
@ -149,13 +136,13 @@ export default function RepurposingProposal({ proposals }: RepurposingProposalPr
<p className="text-xs text-[#6C5CE7] font-medium mb-1">{output.channel}</p> <p className="text-xs text-[#6C5CE7] font-medium mb-1">{output.channel}</p>
<p className="text-xs text-slate-500 leading-relaxed">{output.description}</p> <p className="text-xs text-slate-500 leading-relaxed">{output.description}</p>
</div> </div>
</motion.div> </div>
))} ))}
</div> </div>
</div> </div>
</motion.div> </div>
)} )}
</motion.div> </div>
); );
})} })}
</div> </div>

View File

@ -176,7 +176,6 @@ export default function StrategyAdjustmentSection({ clinicId, planId }: Strategy
{overallAssessment && ( {overallAssessment && (
<motion.div <motion.div
className="bg-purple-50 border border-purple-100 rounded-2xl p-4 mb-6" className="bg-purple-50 border border-purple-100 rounded-2xl p-4 mb-6"
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
> >
<p className="text-sm text-purple-800">{overallAssessment}</p> <p className="text-sm text-purple-800">{overallAssessment}</p>
@ -192,7 +191,6 @@ export default function StrategyAdjustmentSection({ clinicId, planId }: Strategy
<motion.div <motion.div
key={i} key={i}
className="bg-white rounded-xl p-4 border border-slate-100" className="bg-white rounded-xl p-4 border border-slate-100"
initial={{ opacity: 0, x: -10 }}
animate={{ opacity: 1, x: 0 }} animate={{ opacity: 1, x: 0 }}
transition={{ delay: i * 0.05 }} transition={{ delay: i * 0.05 }}
> >
@ -237,7 +235,6 @@ export default function StrategyAdjustmentSection({ clinicId, planId }: Strategy
<motion.div <motion.div
key={i} key={i}
className={`${colors.bg} rounded-xl p-4 border border-slate-100`} className={`${colors.bg} rounded-xl p-4 border border-slate-100`}
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ delay: i * 0.08 }} transition={{ delay: i * 0.08 }}
> >

View File

@ -101,7 +101,6 @@ function WorkflowCard({ item, onStageChange, onNotesChange }: {
<motion.div <motion.div
layout layout
className="bg-white rounded-2xl border border-slate-100 shadow-[3px_4px_12px_rgba(0,0,0,0.06)] overflow-hidden" className="bg-white rounded-2xl border border-slate-100 shadow-[3px_4px_12px_rgba(0,0,0,0.06)] overflow-hidden"
initial={{ opacity: 0, y: 16 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -8 }} exit={{ opacity: 0, y: -8 }}
transition={{ duration: 0.3 }} transition={{ duration: 0.3 }}
@ -144,7 +143,6 @@ function WorkflowCard({ item, onStageChange, onNotesChange }: {
<AnimatePresence> <AnimatePresence>
{expanded && ( {expanded && (
<motion.div <motion.div
initial={{ opacity: 0, height: 0 }}
animate={{ opacity: 1, height: 'auto' }} animate={{ opacity: 1, height: 'auto' }}
exit={{ opacity: 0, height: 0 }} exit={{ opacity: 0, height: 0 }}
transition={{ duration: 0.2 }} transition={{ duration: 0.2 }}
@ -341,7 +339,6 @@ export default function WorkflowTracker({ data }: WorkflowTrackerProps) {
className={`flex items-center gap-2 px-3 py-1.5 rounded-full border text-xs font-medium transition-all ${color.bg} ${color.text} ${color.border} ${ className={`flex items-center gap-2 px-3 py-1.5 rounded-full border text-xs font-medium transition-all ${color.bg} ${color.text} ${color.border} ${
isActive ? 'ring-2 ring-white/40' : '' isActive ? 'ring-2 ring-white/40' : ''
} ${activeStageFilter && !isActive ? 'opacity-40' : ''}`} } ${activeStageFilter && !isActive ? 'opacity-40' : ''}`}
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }} animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.2 }} transition={{ duration: 0.2 }}
> >
@ -366,7 +363,6 @@ export default function WorkflowTracker({ data }: WorkflowTrackerProps) {
<AnimatePresence mode="popLayout"> <AnimatePresence mode="popLayout">
{filtered.length === 0 ? ( {filtered.length === 0 ? (
<motion.div <motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }} animate={{ opacity: 1 }}
className="text-center py-10 text-white/30 text-sm" className="text-center py-10 text-white/30 text-sm"
> >