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}
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 })}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.4, delay: i * 0.05 }}
>
@ -153,7 +152,6 @@ export default function AssetCollection({ data }: AssetCollectionProps) {
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"
onClick={() => setSelectedAsset({ kind: 'youtube', data: video })}
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.4, delay: i * 0.1 }}
>

View File

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

View File

@ -53,7 +53,6 @@ export default function ChannelStrategy({ channels }: ChannelStrategyProps) {
<motion.div
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"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: index * 0.1 }}
>

View File

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

View File

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

View File

@ -28,7 +28,7 @@ export default function PlanHeader({
}: PlanHeaderProps) {
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">
{/* Animated blobs */}
{/* Animated blobs — position only, no opacity */}
<motion.div
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] }}
@ -47,46 +47,21 @@ export default function PlanHeader({
<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">
{/* Left: Text content */}
<motion.div
className="flex-1 text-center md:text-left"
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 }}
>
{/* Left: Text content — plain div, no opacity animation */}
<div className="flex-1 text-center md:text-left">
<p className="text-xs font-semibold text-[#6C5CE7] mb-4 tracking-widest uppercase">
Marketing Execution Plan
</motion.p>
</p>
<motion.h1
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 }}
>
<h1 className="font-serif text-4xl md:text-5xl font-bold text-[#0A1128] mb-3">
{clinicName}
</motion.h1>
</h1>
<motion.p
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 }}
>
<p className="text-xl text-slate-600 mb-8">
{clinicNameEn}
</motion.p>
</p>
<motion.div
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 }}
>
<div className="flex flex-wrap gap-3 justify-center md:justify-start">
<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" />
{formatDate(date)}
@ -95,23 +70,18 @@ export default function PlanHeader({
<GlobeFilled size={14} className="text-slate-400" />
{targetUrl}
</span>
</motion.div>
</motion.div>
</div>
</div>
{/* Right: 90 Days badge */}
<motion.div
className="shrink-0"
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.6, delay: 0.3 }}
>
<div className="shrink-0">
<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">
90
</span>
<span className="text-sm text-white/60">Days</span>
</div>
</motion.div>
</div>
</div>
</div>
</section>

View File

@ -1,5 +1,4 @@
import { useState } from 'react';
import { motion } from 'motion/react';
import {
YoutubeFilled,
InstagramFilled,
@ -63,12 +62,9 @@ export default function RepurposingProposal({ proposals }: RepurposingProposalPr
const isExpanded = expandedIdx === idx;
return (
<motion.div
<div
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"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.4, delay: idx * 0.08 }}
>
{/* Card Header — click to expand */}
<button
@ -120,26 +116,17 @@ export default function RepurposingProposal({ proposals }: RepurposingProposalPr
{/* Expanded: Repurpose outputs */}
{isExpanded && (
<motion.div
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="border-t border-slate-100">
<div className="px-6 py-4">
<div className="flex items-center gap-2 mb-4">
<BoltFilled size={14} className="text-[#6C5CE7]" />
<p className="text-xs font-semibold text-slate-500 uppercase tracking-widest">REPURPOSE AS</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
{item.outputs.map((output, oIdx) => (
<motion.div
{item.outputs.map((output) => (
<div
key={output.format}
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">
<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-slate-500 leading-relaxed">{output.description}</p>
</div>
</motion.div>
</div>
))}
</div>
</div>
</motion.div>
</div>
)}
</motion.div>
</div>
);
})}
</div>

View File

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

View File

@ -101,7 +101,6 @@ function WorkflowCard({ item, onStageChange, onNotesChange }: {
<motion.div
layout
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 }}
exit={{ opacity: 0, y: -8 }}
transition={{ duration: 0.3 }}
@ -144,7 +143,6 @@ function WorkflowCard({ item, onStageChange, onNotesChange }: {
<AnimatePresence>
{expanded && (
<motion.div
initial={{ opacity: 0, height: 0 }}
animate={{ opacity: 1, height: 'auto' }}
exit={{ opacity: 0, height: 0 }}
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} ${
isActive ? 'ring-2 ring-white/40' : ''
} ${activeStageFilter && !isActive ? 'opacity-40' : ''}`}
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.2 }}
>
@ -366,7 +363,6 @@ export default function WorkflowTracker({ data }: WorkflowTrackerProps) {
<AnimatePresence mode="popLayout">
{filtered.length === 0 ? (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
className="text-center py-10 text-white/30 text-sm"
>