| |
| let currentScript = [{ |
| credit: "μΆμ² μ
λ ₯\n@username", |
| idiom: "β ν΅μ¬ νν", |
| meaning: "; λ»μ μ
λ ₯νμΈμ", |
| en: "", |
| ko: "", |
| pron: "", |
| prompt_top: "stand-up comedian, highly detailed", |
| prompt_bottom: "audience reacting, cinematic lighting" |
| }]; |
| let assetStates = {}; |
| let currentPreviewTurn = 0; |
|
|
| |
| let styleConfig = { |
| english: { |
| font: 'SUIT', size: 80, baseColor: '#FFFFFF', bgColor: '#000000', bgOpacity: 0.85, |
| highlights: { |
| 1: { color: '#FFF200', desc: 'μ£Όμ (λ
Έλ)' }, |
| 2: { color: '#00E5FF', desc: 'ν¨ν΄(μμ)' }, |
| 3: { color: '#FF3B30', desc: 'μ£Όμ(λΉ¨κ°)' }, |
| 4: { color: '#4CD964', desc: 'μ΄ν(μ΄λ‘)' }, |
| 5: { color: '#FF2D55', desc: 'λμμ€(νν¬)' } |
| } |
| }, |
| korean: { font: 'SeoulNamsanM', size: 55, color: '#FFFFFF', outlineColor: '#000000', outlineWidth: 3 }, |
| pronunciation: { font: 'SchoolSafeDictation', size: 65, color: '#FF3B30', outlineColor: '#000000', outlineWidth: 2 } |
| }; |
|
|
| |
| function parseAsteriskToHTML(text) { |
| if (!text) return ""; |
| let html = text; |
| html = html.replace(/\*{5}(.*?)\*{5}/g, `<span class="font-bold" style="color: ${styleConfig.english.highlights[5].color};">$1</span>`); |
| html = html.replace(/\*{4}(.*?)\*{4}/g, `<span class="font-bold" style="color: ${styleConfig.english.highlights[4].color};">$1</span>`); |
| html = html.replace(/\*{3}(.*?)\*{3}/g, `<span class="font-bold underline" style="color: ${styleConfig.english.highlights[3].color};">$1</span>`); |
| html = html.replace(/\*{2}(.*?)\*{2}/g, `<span class="font-bold" style="color: ${styleConfig.english.highlights[2].color};">$1</span>`); |
| html = html.replace(/\*(.*?)\*/g, `<span class="font-bold" style="color: ${styleConfig.english.highlights[1].color};">$1</span>`); |
| return html; |
| } |
|
|
| |
| async function generateScript() { |
| const topic = document.getElementById('topicInput').value; |
| if(!topic) { alert("μ£Όμ λ₯Ό μ
λ ₯ν΄μ£ΌμΈμ."); return; } |
| |
| const btn = event.currentTarget; |
| btn.innerHTML = '<i class="fa-solid fa-circle-notch fa-spin"></i> μμ± μ€...'; |
| btn.disabled = true; |
|
|
| try { |
| const response = await fetch('/api/studio/generate-script', { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify({ topic: topic }) |
| }); |
|
|
| if (!response.ok) throw new Error(`μλ² μ€λ₯: ${response.status}`); |
| const data = await response.json(); |
| |
| currentScript = data.dialogue.map((turn) => ({ |
| credit: turn.credit || "μ§λ―Έ μΉ΄\n@jimmycarr", |
| idiom: turn.idiom || "β ν΅μ¬ νν", |
| meaning: turn.meaning || "; λ»μ μ
λ ₯νμΈμ", |
| en: turn.en || "", |
| ko: turn.ko || "", |
| pron: turn.ko_pron || turn.pron || "", |
| prompt_top: turn.image_prompt_top || turn.image_prompt || "stand-up comedian, highly detailed", |
| prompt_bottom: turn.image_prompt_bottom || "audience reacting, cinematic lighting" |
| })); |
| |
| renderScriptPanel(); |
| renderAssetPanel(); |
| } catch (error) { |
| console.error("λλ³Έ μμ± μλ¬:", error); |
| |
| currentScript = [ |
| { |
| credit: "μ§λ―Έ μΉ΄\n@jimmycarr", idiom: "β test the water(s)", meaning: "; λΆμκΈ°λ₯Ό μ΄νΌλ€ / λ°μμ λ 보λ€", |
| en: "I'll just, I'll *test the water* first.", ko: "κ·Έλ₯ λΆμκΈ° ν λ² λ³Όκ²", pron: "μμμ μ€, μμν
μ€λ μλ΄ νΌμ€λ", |
| prompt_top: "stand-up comedian holding a microphone", prompt_bottom: "crowd of people smiling" |
| }, |
| { |
| credit: "μ§λ―Έ μΉ΄\n@jimmycarr", idiom: "β make a move", meaning: "; νλμ μ·¨νλ€ / λ€κ°κ°λ€", |
| en: "Before I actually **make a move**, you know?", ko: "본격μ μΌλ‘ νλνκΈ° μ μ λ§μ΄μΌ.", pron: "λΉν¬ μμ΄ μ‘μΈμΌλ¦¬ λ©μ΄μ»€ 무λΈ, μ λ
Έ?", |
| prompt_top: "man looking thoughtful, cinematic lighting", prompt_bottom: "close up of a person's eyes" |
| }, |
| { |
| credit: "μ§λ―Έ μΉ΄\n@jimmycarr", idiom: "β play it safe", meaning: "; μμ νκ² κ°λ€ / 무리νμ§ μλ€", |
| en: "It's always better to ***play it safe***.", ko: "μΈμ λ μμ νκ² κ°λ κ² λ«μ§.", pron: "μ΄μΈ μ¬μ¨μ΄μ¦ λ² λ¬ ν¬ νλ μ΄ μ μΈμ΄ν.", |
| prompt_top: "a cautious person, dark background", prompt_bottom: "audience nodding in agreement" |
| }, |
| { |
| credit: "μ§λ―Έ μΉ΄\n@jimmycarr", idiom: "β go all in", meaning: "; μ¬μΈνλ€ / λ€ κ±Έλ€", |
| en: "Then I'll just ****go all in****.", ko: "κ·Έλ¬κ³ λμ μΉ λ€ κ±Έμ΄λ²λ¦΄ κ±°μΌ.", pron: "λ΄ μμμ μ€ κ³ μ¬ μΈ.", |
| prompt_top: "casino chips, aggressive lighting", prompt_bottom: "people cheering loudly" |
| } |
| ]; |
| renderScriptPanel(); |
| renderAssetPanel(); |
| } finally { |
| btn.innerHTML = '<i class="fa-solid fa-wand-magic-sparkles"></i> AI μμ±'; |
| btn.disabled = false; |
| } |
| } |
|
|
| |
| function renderScriptPanel() { |
| const container = document.getElementById('scriptContainer'); |
| container.innerHTML = ""; |
| |
| currentScript.forEach((turn, idx) => { |
| |
| const html = ` |
| <div class="bg-neutral-800 rounded-xl p-4 border border-neutral-700 shadow-md relative space-y-3"> |
| <div class="absolute -left-2 -top-2 bg-neutral-700 text-white text-xs w-6 h-6 flex items-center justify-center rounded-full font-mono font-bold shadow-md">${idx+1}</div> |
| |
| <button onclick="deleteScriptTurn(${idx})" class="absolute -right-2 -top-2 bg-neutral-900 border border-neutral-700 text-neutral-400 hover:bg-red-600 hover:text-white hover:border-red-500 w-6 h-6 flex items-center justify-center rounded-full transition-colors shadow-md text-[10px]" title="μ΄ λ¨κ³ μμ "> |
| <i class="fa-solid fa-xmark"></i> |
| </button> |
| |
| <div class="grid grid-cols-2 gap-2 pt-1"> |
| <div> |
| <label class="block text-[10px] text-gray-500 font-bold mb-1">π·οΈ μλ¨ μΆμ² νκΈ° (Credit)</label> |
| <textarea onchange="updateScriptData(${idx}, 'credit', this.value)" class="w-full bg-neutral-950 border border-neutral-700 rounded p-1.5 text-xs text-gray-300 font-mono h-12 leading-tight resize-none">${turn.credit}</textarea> |
| </div> |
| <div class="space-y-1.5"> |
| <div> |
| <label class="block text-[10px] text-yellow-500 font-bold">β ν΅μ¬ νν (Idiom)</label> |
| <input onchange="updateScriptData(${idx}, 'idiom', this.value)" type="text" class="w-full bg-neutral-950 border border-neutral-700 rounded px-2 py-1 text-xs text-white font-bold" value="${turn.idiom}"> |
| </div> |
| <div> |
| <label class="block text-[10px] text-neutral-400 font-bold">π¬ νν λ» (Meaning)</label> |
| <input onchange="updateScriptData(${idx}, 'meaning', this.value)" type="text" class="w-full bg-neutral-950 border border-neutral-700 rounded px-2 py-1 text-xs text-neutral-400" value="${turn.meaning}"> |
| </div> |
| </div> |
| </div> |
| |
| <div class="space-y-2 border-t border-neutral-700/60 pt-2"> |
| <div> |
| <label class="block text-[10px] text-blue-400 font-bold mb-0.5">πΊπΈ μμ΄ λ¬Έμ₯ (Sentence)</label> |
| <textarea id="textarea_en_${idx}" onchange="updateScriptData(${idx}, 'en', this.value)" class="w-full bg-neutral-950 border border-neutral-700 rounded p-2 text-xs text-white resize-none font-medium" rows="2">${turn.en}</textarea> |
| </div> |
| <div> |
| <label class="block text-[10px] text-emerald-400 font-bold mb-0.5">π°π· νκ΅μ΄ ν΄μ (Translation)</label> |
| <input id="textarea_ko_${idx}" onchange="updateScriptData(${idx}, 'ko', this.value)" type="text" class="w-full bg-neutral-950 border border-neutral-700 rounded px-2 py-1.5 text-xs text-gray-300" value="${turn.ko}"> |
| </div> |
| <div> |
| <label class="block text-[10px] text-purple-400 font-bold mb-0.5">π£οΈ νκΈ λ°μ νκΈ° (Pronunciation)</label> |
| <input id="textarea_pron_${idx}" onchange="updateScriptData(${idx}, 'pron', this.value)" type="text" class="w-full bg-neutral-950 border border-neutral-700 rounded px-2 py-1.5 text-xs text-purple-300 font-mono" value="${turn.pron}"> |
| </div> |
| </div> |
| </div> |
| `; |
| container.insertAdjacentHTML('beforeend', html); |
| }); |
| } |
|
|
| |
| function deleteScriptTurn(idx) { |
| if (currentScript.length <= 1) { |
| alert("μ΅μ 1κ°μ λ¨κ³λ μ μ§ν΄μΌ ν©λλ€."); |
| return; |
| } |
| |
| if (confirm(`μ λ§λ‘ ${idx + 1}λ¨κ³λ₯Ό μμ νμκ² μ΅λκΉ?`)) { |
| |
| currentScript.splice(idx, 1); |
| |
| |
| const newAssetStates = {}; |
| currentScript.forEach((_, newIdx) => { |
| newAssetStates[newIdx] = assetStates[newIdx >= idx ? newIdx + 1 : newIdx]; |
| }); |
| assetStates = newAssetStates; |
|
|
| |
| if (currentPreviewTurn >= currentScript.length) { |
| currentPreviewTurn = Math.max(0, currentScript.length - 1); |
| } |
| |
| localStorage.setItem('studio_current_script', JSON.stringify(currentScript)); |
| |
| renderScriptPanel(); |
| renderAssetPanel(); |
| updatePreviewScreen(); |
| } |
| } |
|
|
| function updateScriptData(idx, field, value) { |
| currentScript[idx][field] = value; |
| localStorage.setItem('studio_current_script', JSON.stringify(currentScript)); |
| updatePreviewScreen(); |
| } |
|
|
| |
| function addScriptTurn() { |
| |
| currentScript.push({ |
| credit: "μΆμ² μ
λ ₯\n@username", |
| idiom: "β ν΅μ¬ νν", |
| meaning: "; λ»μ μ
λ ₯νμΈμ", |
| en: "", |
| ko: "", |
| pron: "", |
| prompt_top: "stand-up comedian, highly detailed", |
| prompt_bottom: "audience reacting, cinematic lighting" |
| }); |
| |
| |
| renderScriptPanel(); |
| renderAssetPanel(); |
| |
| |
| setTimeout(() => { |
| const container = document.getElementById('scriptContainer'); |
| if (container) { |
| container.scrollTo({ top: container.scrollHeight, behavior: 'smooth' }); |
| } |
| }, 50); |
| } |
|
|
| |
| function applyThemePreset(theme) { |
| if (theme === 'variety') { |
| |
| styleConfig.korean.color = '#FFFFFF'; |
| styleConfig.korean.outlineColor = '#000000'; |
| styleConfig.pronunciation.color = '#FF3B30'; |
| styleConfig.pronunciation.outlineColor = '#000000'; |
| } else if (theme === 'docu') { |
| |
| styleConfig.korean.color = '#E5E5E5'; |
| styleConfig.korean.outlineColor = 'transparent'; |
| styleConfig.pronunciation.color = '#A3A3A3'; |
| styleConfig.pronunciation.outlineColor = 'transparent'; |
| } else if (theme === 'trendy') { |
| |
| styleConfig.korean.color = '#00E5FF'; |
| styleConfig.korean.outlineColor = '#1A0B2E'; |
| styleConfig.pronunciation.color = '#FF2D55'; |
| styleConfig.pronunciation.outlineColor = '#1A0B2E'; |
| } |
| |
| |
| updatePreviewScreen(); |
| } |