diff --git a/src/pages/music/MusicStudio.css b/src/pages/music/MusicStudio.css index 7a6ebe1..98f8233 100644 --- a/src/pages/music/MusicStudio.css +++ b/src/pages/music/MusicStudio.css @@ -2110,6 +2110,243 @@ cursor: not-allowed; } +/* ═══════════════════════════════════════════════════ + LYRICS TAB +═══════════════════════════════════════════════════ */ +.ms-lyrics-tab { + display: grid; + gap: 24px; +} + +.ms-lyrics-tab__head { + margin-bottom: 4px; +} + +.ms-lyrics-tab__title { + font-family: var(--ms-ff-disp); + font-size: 28px; + letter-spacing: 0.06em; + margin: 0 0 8px; + color: var(--ms-text); +} + +.ms-lyrics-tab__desc { + font-size: 12px; + color: var(--ms-muted); + margin: 0; + line-height: 1.6; +} + +/* ── Input area ── */ +.ms-lyrics-tab__form { + display: grid; + gap: 14px; +} + +.ms-lyrics-tab__input-wrap { + display: grid; + gap: 8px; + background: var(--ms-surface); + border: 1px solid var(--ms-line); + border-radius: 12px; + padding: 14px; + transition: border-color 0.2s; +} + +.ms-lyrics-tab__input-wrap:focus-within { + border-color: var(--ms-accent); +} + +.ms-lyrics-tab__input { + width: 100%; + background: transparent; + border: none; + outline: none; + color: var(--ms-text); + font-family: var(--ms-ff-body); + font-size: 14px; + line-height: 1.6; + resize: vertical; + min-height: 60px; +} + +.ms-lyrics-tab__input::placeholder { + color: var(--ms-dim); +} + +.ms-lyrics-tab__input-footer { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; +} + +.ms-lyrics-tab__count { + font-family: var(--ms-ff-mono); + font-size: 10px; + color: var(--ms-dim); + letter-spacing: 0.06em; +} + +/* ── Accent button ── */ +.ms-btn--accent { + padding: 8px 20px; + font-size: 13px; + font-family: var(--ms-ff-body); + font-weight: 600; + background: var(--ms-accent); + color: #0c0b09; + border: none; + border-radius: 8px; + cursor: pointer; + transition: opacity 0.2s, transform 0.15s; + display: flex; + align-items: center; + gap: 6px; +} + +.ms-btn--accent:hover:not(:disabled) { + opacity: 0.9; + transform: translateY(-1px); +} + +.ms-btn--accent:disabled { + opacity: 0.4; + cursor: not-allowed; +} + +.ms-btn--accent.is-loading { + pointer-events: none; +} + +.ms-btn__spinner { + display: inline-block; + width: 14px; + height: 14px; + border: 2px solid rgba(12,11,9,0.2); + border-top-color: #0c0b09; + border-radius: 50%; + animation: ms-spin 0.7s linear infinite; +} + +@keyframes ms-spin { + to { transform: rotate(360deg); } +} + +/* ── Empty state ── */ +.ms-lyrics-tab__empty { + text-align: center; + padding: 48px 20px; + border: 1px dashed var(--ms-line); + border-radius: 16px; + background: var(--ms-surface); +} + +.ms-lyrics-tab__empty-icon { + font-size: 40px; + display: block; + margin-bottom: 12px; + opacity: 0.4; +} + +.ms-lyrics-tab__empty p { + color: var(--ms-muted); + font-size: 13px; + margin: 0 0 6px; +} + +.ms-lyrics-tab__empty-hint { + font-family: var(--ms-ff-mono); + font-size: 10px !important; + color: var(--ms-dim) !important; + letter-spacing: 0.04em; +} + +/* ── Loading state ── */ +.ms-lyrics-tab__loading { + text-align: center; + padding: 32px 20px; + border: 1px solid var(--ms-line); + border-radius: 16px; + background: var(--ms-surface); +} + +.ms-lyrics-tab__loading p { + color: var(--ms-muted); + font-size: 12px; + font-family: var(--ms-ff-mono); + margin: 12px 0 0; + letter-spacing: 0.04em; +} + +.ms-lyrics-tab__loading-bar { + height: 3px; + width: 60%; + margin: 0 auto; + border-radius: 2px; + background: linear-gradient(90deg, transparent, var(--ms-accent), transparent); + background-size: 200% 100%; + animation: ms-shimmer 1.2s ease-in-out infinite; +} + +/* ── Results list ── */ +.ms-lyrics-tab__results { + display: grid; + gap: 16px; +} + +/* ── Lyrics Card ── */ +.ms-lyrics-card { + background: var(--ms-surface); + border: 1px solid var(--ms-line); + border-radius: 14px; + overflow: hidden; + transition: border-color 0.2s; +} + +.ms-lyrics-card:hover { + border-color: color-mix(in srgb, var(--ms-accent) 50%, transparent); +} + +.ms-lyrics-card__header { + padding: 14px 16px 10px; + border-bottom: 1px solid var(--ms-line-2); +} + +.ms-lyrics-card__title { + font-family: var(--ms-ff-disp); + font-size: 18px; + letter-spacing: 0.04em; + color: var(--ms-text); + margin: 0 0 4px; +} + +.ms-lyrics-card__prompt { + font-family: var(--ms-ff-mono); + font-size: 10px; + color: var(--ms-dim); + letter-spacing: 0.04em; +} + +.ms-lyrics-card__text { + padding: 14px 16px; + margin: 0; + font-family: var(--ms-ff-mono); + font-size: 12px; + line-height: 1.8; + color: rgba(255,255,255,0.75); + white-space: pre-wrap; + max-height: 400px; + overflow-y: auto; +} + +.ms-lyrics-card__actions { + display: flex; + gap: 6px; + padding: 8px 16px 12px; + border-top: 1px solid var(--ms-line-2); +} + /* ═══════════════════════════════════════════════════ REDUCED MOTION ═══════════════════════════════════════════════════ */ diff --git a/src/pages/music/MusicStudio.jsx b/src/pages/music/MusicStudio.jsx index 6bc1af0..b4fbb88 100644 --- a/src/pages/music/MusicStudio.jsx +++ b/src/pages/music/MusicStudio.jsx @@ -607,6 +607,133 @@ const Library = ({ tracks, onDelete, onRefresh, onExtend, onVocalRemoval, isGene ); }; +/* ───────────────────────────────────────────── + Lyrics Tab +───────────────────────────────────────────── */ +const LyricsTab = ({ onUseInCreate }) => { + const [lyrPrompt, setLyrPrompt] = useState(''); + const [lyrLoading, setLyrLoading] = useState(false); + const [lyrResults, setLyrResults] = useState([]); // [{text, title}] + const [lyrError, setLyrError] = useState(null); + const [copied, setCopied] = useState(null); // index + + const handleGenerate = async () => { + if (!lyrPrompt.trim() || lyrLoading) return; + setLyrLoading(true); + setLyrError(null); + try { + const res = await generateMusicLyrics(lyrPrompt.trim()); + if (res?.text) { + setLyrResults((prev) => [{ text: res.text, title: res.title || '', prompt: lyrPrompt.trim() }, ...prev]); + } else { + setLyrError('가사 생성 결과가 없습니다'); + } + } catch (e) { + setLyrError(e.message || '가사 생성에 실패했습니다'); + } finally { + setLyrLoading(false); + } + }; + + const handleCopy = (text, idx) => { + navigator.clipboard.writeText(text).then(() => { + setCopied(idx); + setTimeout(() => setCopied(null), 2000); + }); + }; + + return ( +
+ 원하는 분위기, 주제, 스타일을 설명하면 AI가 가사를 작성합니다. +
+프롬프트를 입력하고 가사를 생성해보세요
++ 생성된 가사는 [Verse], [Chorus] 등 섹션 태그가 포함됩니다 +
+AI가 가사를 작성하고 있습니다...
+{item.text}
+