feat: 스튜디오 페이지 + Suno API 프록시 + 팩 상세 가격 최상단 재구성

- TopNav: 홈/샘플/팩 상세/스튜디오 4개 링크 구조
- /services/music: 컴팩트 헤더 + PRICING 최상단 배치 (상세 포맷)
- /studio: Suno Generate UI (simple/custom 모드, 태그 프리셋, 폴링)
- /api/studio/generate, /api/studio/status: Suno API 프록시
This commit is contained in:
2026-04-15 03:27:17 +09:00
parent 3aeec8b323
commit a362f7b387
5 changed files with 704 additions and 145 deletions

View File

@@ -118,89 +118,89 @@ export default function MusicServicePage() {
return (
<div className="min-h-full bg-slate-950 text-white">
{/* HERO */}
<section
className="relative overflow-hidden px-6 py-24 lg:px-14 lg:py-32"
style={{
background:
'radial-gradient(circle at 25% 20%, #2e1065 0%, #020617 55%), radial-gradient(circle at 80% 80%, #164e63 0%, transparent 50%)',
}}
>
<div
className="absolute inset-0 opacity-[0.05] pointer-events-none mix-blend-overlay"
style={{
backgroundImage:
"url(\"data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='120' height='120'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9'/></filter><rect width='100%' height='100%' filter='url(%23n)' opacity='0.5'/></svg>\")",
}}
/>
<div className="relative max-w-5xl mx-auto">
<div className="flex items-center gap-3 mb-8">
{/* 상세 페이지 헤더 (컴팩트) */}
<section className="px-6 pt-14 pb-8 lg:px-14 bg-slate-950 border-b border-white/5">
<div className="max-w-6xl mx-auto">
<div className="flex items-center gap-3 mb-4">
<span className="inline-flex h-2 w-2 rounded-full bg-violet-400 animate-pulse" />
<span className="kx-label">AI MUSIC PACK · v1</span>
<span className="kx-label">AI MUSIC PACK · v1 · </span>
</div>
<h1
className="kx-display text-[2.8rem] md:text-[4rem] lg:text-[5.5rem] font-extrabold leading-[1.02] mb-6"
style={{ wordBreak: 'keep-all' }}
>
<span className="text-white"> .</span>
<br />
<span className="kx-gradient-text">
.
</span>
<h1 className="kx-display text-3xl md:text-5xl font-extrabold leading-tight mb-3" style={{ wordBreak: 'keep-all' }}>
AI
</h1>
<p
className="text-slate-300 text-lg md:text-xl leading-relaxed mb-3 max-w-2xl"
style={{ wordBreak: 'keep-all' }}
>
AI로 , <span className="text-white font-semibold"> </span> .
<p className="text-slate-300 md:text-lg max-w-3xl">
Suno + MV + + 릿 PDF + .
4 AI .
</p>
<p className="text-slate-400 text-base mb-10 max-w-2xl">
<span className="text-white">4 AI </span> · Suno Pro .
</p>
<div className="flex flex-wrap gap-3 mb-10">
<a
href="#pricing"
className="inline-flex items-center gap-2 bg-violet-600 hover:bg-violet-500 text-white px-8 py-4 rounded-xl font-bold text-sm transition-colors shadow-[0_0_40px_rgba(139,92,246,0.45)]"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M17 8l4 4m0 0l-4 4m4-4H3" />
</svg>
</a>
<a
href="#samples"
className="inline-flex items-center gap-2 border border-white/20 hover:border-white/50 text-white/90 hover:text-white px-8 py-4 rounded-xl font-semibold text-sm transition-all"
>
</a>
</div>
<div className="flex flex-wrap gap-5 text-xs text-slate-400">
<span className="flex items-center gap-1.5"> </span>
<span className="flex items-center gap-1.5"> </span>
<span className="flex items-center gap-1.5"> Suno Pro </span>
</div>
</div>
</section>
{/* Bottom waveform */}
<div className="absolute bottom-0 left-0 right-0 h-32 opacity-40 pointer-events-none">
<svg viewBox="0 0 1200 120" preserveAspectRatio="none" className="w-full h-full">
<path
d="M0,60 Q150,10 300,60 T600,60 T900,60 T1200,60 L1200,120 L0,120 Z"
fill="url(#wg)"
/>
<defs>
<linearGradient id="wg" x1="0%" x2="100%">
<stop offset="0%" stopColor="#a78bfa" stopOpacity="0.5" />
<stop offset="50%" stopColor="#22d3ee" stopOpacity="0.3" />
<stop offset="100%" stopColor="#a78bfa" stopOpacity="0.5" />
</linearGradient>
</defs>
</svg>
{/* PRICING — 상세 최상단 */}
<section id="pricing" className="px-6 py-14 lg:px-14 bg-slate-950">
<div className="max-w-6xl mx-auto">
<div className="flex items-end justify-between flex-wrap gap-3 mb-8">
<div>
<p className="font-mono text-xs text-violet-300/70 tracking-widest uppercase mb-1">Pricing · 1 </p>
<h2 className="text-2xl md:text-3xl font-extrabold">3 , </h2>
</div>
<Link href="/services/music/samples" className="text-sm text-violet-300 hover:text-violet-200 underline underline-offset-4">
</Link>
</div>
<div className="grid md:grid-cols-3 gap-5 items-stretch">
{(Object.keys(TIERS) as Tier[]).map((key) => {
const t = TIERS[key];
return (
<div
key={key}
className={`relative rounded-3xl p-8 flex flex-col border transition-all ${
t.highlight
? 'border-violet-400 bg-gradient-to-br from-violet-900/40 to-slate-900 shadow-[0_0_60px_rgba(139,92,246,0.35)] md:scale-[1.03] md:-translate-y-2'
: 'border-white/10 bg-white/[0.02] hover:border-white/30'
}`}
>
{t.highlight && (
<div className="absolute -top-3 left-1/2 -translate-x-1/2">
<span className="inline-flex items-center gap-1 bg-gradient-to-r from-violet-500 to-pink-500 text-white text-[10px] font-extrabold px-3 py-1.5 rounded-full uppercase tracking-wider">
🔥
</span>
</div>
)}
<h3 className="font-extrabold text-2xl mb-1">{t.name}</h3>
<p className="text-sm text-slate-400 mb-6">{t.desc}</p>
<div className="mb-6">
<span className="text-4xl font-extrabold font-mono">{t.price}</span>
<span className="text-xs text-slate-500 ml-2">1 </span>
</div>
<ul className="space-y-3 text-sm text-slate-200 mb-8 flex-1">
{t.features.map((f) => (
<li key={f} className="flex gap-2.5">
<svg className="w-4 h-4 text-emerald-400 flex-shrink-0 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24" strokeWidth={2.5}>
<path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
</svg>
<span className="leading-relaxed">{f}</span>
</li>
))}
</ul>
<button
onClick={() => setSelectedTier(key)}
className={`w-full py-4 rounded-xl font-extrabold text-sm transition-colors ${
t.highlight
? 'bg-violet-500 hover:bg-violet-400 text-white'
: 'bg-white/10 hover:bg-white/20 text-white'
}`}
>
{t.name}
</button>
</div>
);
})}
</div>
<p className="text-xs text-slate-500 text-center mt-8">
<Link href="/legal/refund" className="underline hover:text-white"> </Link> .
.
</p>
</div>
</section>
@@ -327,73 +327,6 @@ export default function MusicServicePage() {
</div>
</section>
{/* PRICING */}
<section id="pricing" className="px-6 py-24 lg:px-14 bg-slate-950">
<div className="max-w-6xl mx-auto">
<p className="font-mono text-xs text-violet-300/70 tracking-widest uppercase mb-2 text-center">
Pricing
</p>
<h2 className="text-3xl md:text-4xl font-extrabold text-center mb-4">
3 , .
</h2>
<p className="text-center text-slate-400 mb-14"> .</p>
<div className="grid md:grid-cols-3 gap-5 items-stretch">
{(Object.keys(TIERS) as Tier[]).map((key) => {
const t = TIERS[key];
return (
<div
key={key}
className={`relative rounded-3xl p-8 flex flex-col border transition-all ${
t.highlight
? 'border-violet-400 bg-gradient-to-br from-violet-900/40 to-slate-900 shadow-[0_0_60px_rgba(139,92,246,0.35)] md:scale-[1.03] md:-translate-y-2'
: 'border-white/10 bg-white/[0.02] hover:border-white/30'
}`}
>
{t.highlight && (
<div className="absolute -top-3 left-1/2 -translate-x-1/2">
<span className="inline-flex items-center gap-1 bg-gradient-to-r from-violet-500 to-pink-500 text-white text-[10px] font-extrabold px-3 py-1.5 rounded-full uppercase tracking-wider">
🔥 80%
</span>
</div>
)}
<h3 className="font-extrabold text-2xl mb-1">{t.name}</h3>
<p className="text-sm text-slate-400 mb-6">{t.desc}</p>
<div className="mb-6">
<span className="text-4xl font-extrabold font-mono">{t.price}</span>
<span className="text-xs text-slate-500 ml-2">1 </span>
</div>
<ul className="space-y-3 text-sm text-slate-200 mb-8 flex-1">
{t.features.map((f) => (
<li key={f} className="flex gap-2.5">
<svg className="w-4 h-4 text-emerald-400 flex-shrink-0 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24" strokeWidth={2.5}>
<path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
</svg>
<span className="leading-relaxed">{f}</span>
</li>
))}
</ul>
<button
onClick={() => setSelectedTier(key)}
className={`w-full py-4 rounded-xl font-extrabold text-sm transition-colors ${
t.highlight
? 'bg-violet-500 hover:bg-violet-400 text-white'
: 'bg-white/10 hover:bg-white/20 text-white'
}`}
>
{t.name}
</button>
</div>
);
})}
</div>
<p className="text-xs text-slate-500 text-center mt-8">
<Link href="/legal/refund" className="underline hover:text-white"> </Link> .
.
</p>
</div>
</section>
{/* B2B */}
<section className="px-6 py-16 lg:px-14 bg-gradient-to-br from-slate-900 to-slate-950 border-y border-white/5">
<div className="max-w-5xl mx-auto">