www.rrpropertybandung.com
RR Pro: ai animasi generator

www.rrpropertybandung.com

Text to Animation Script Generator Thumbnail Aplikasi

Login Portal

Masuk untuk membuat skrip animasi

atau

Data Login Dummy (Mode Preview):

  • Peserta: peserta / edudigital
  • Admin: admin / edudigital
Thumbnail Aplikasi

Login Portal

Masuk untuk membuat skrip animasi

atau

Data Login Dummy (Mode Preview):

  • Peserta: peserta / edudigital
  • Admin: admin / edudigital
"; // State Management let currentUser = null; // DOM Elements const pageLogin = document.getElementById('page-login'); const pageGenerator = document.getElementById('page-generator'); const pageAdmin = document.getElementById('page-admin'); const userInfo = document.getElementById('user-info'); const btnLogout = document.getElementById('btn-logout'); // -- FUNGSI ROUTING & AUTH -- document.getElementById('form-login').addEventListener('submit', async (e) => { e.preventDefault(); const u = document.getElementById('username').value.trim(); const p = document.getElementById('password').value; const btn = document.getElementById('btn-login-submit'); const err = document.getElementById('login-error'); btn.innerHTML = ' Memeriksa...'; btn.disabled = true; err.classList.add('hidden'); try { let response; if (IS_PREVIEW) { // Dummy Login Logic await new Promise(r => setTimeout(r, 800)); if (u === 'admin' && p === 'edudigital') { response = { success: true, role: 'admin', username: u }; } else if (u === 'peserta' && p === 'edudigital') { response = { success: true, role: 'peserta', username: u }; } else { response = { success: false, message: "Username atau password salah!" }; } } else { // Live GAS Logic const payload = { action: "login", username: u, password: p }; const res = await fetch(GAS_URL, { method: "POST", headers: { "Content-Type": "text/plain;charset=utf-8" }, body: JSON.stringify(payload) }); response = await res.json(); } if (response.success) { currentUser = response; showPage(response.role); } else { err.innerText = response.message || "Gagal login."; err.classList.remove('hidden'); } } catch (error) { err.innerText = "Terjadi kesalahan jaringan."; err.classList.remove('hidden'); } finally { btn.innerHTML = 'Masuk Sekarang'; btn.disabled = false; } }); // FUNGSI LOGIN GOOGLE (Simulasi UI Frontend untuk bypass sebagai Peserta) document.getElementById('btn-google-login').addEventListener('click', async () => { const btn = document.getElementById('btn-google-login'); const err = document.getElementById('login-error'); btn.innerHTML = ' Menghubungkan...'; btn.disabled = true; err.classList.add('hidden'); try { // Simulasi proses loading otentikasi Google await new Promise(r => setTimeout(r, 1200)); // Set login otomatis sebagai peserta const response = { success: true, role: 'peserta', username: 'Google_User' }; currentUser = response; showPage(response.role); } catch (error) { err.innerText = "Gagal terhubung dengan layanan Google."; err.classList.remove('hidden'); } finally { btn.innerHTML = 'Google Masuk dengan Google'; btn.disabled = false; } }); btnLogout.addEventListener('click', () => { currentUser = null; document.getElementById('form-login').reset(); showPage('login'); }); function showPage(role) { pageLogin.classList.add('hidden'); pageGenerator.classList.add('hidden'); pageAdmin.classList.add('hidden'); userInfo.classList.add('hidden'); btnLogout.classList.add('hidden'); if (role === 'login') { pageLogin.classList.remove('hidden'); } else { userInfo.innerText = `Halo, ${currentUser.username.toUpperCase()}`; userInfo.classList.remove('hidden'); btnLogout.classList.remove('hidden'); if (role === 'admin') { pageAdmin.classList.remove('hidden'); fetchHistory(); } else if (role === 'peserta') { pageGenerator.classList.remove('hidden'); } } } // -- FUNGSI GENERATOR SKRIP -- document.getElementById('form-generator').addEventListener('submit', async (e) => { e.preventDefault(); const payload = { action: "generate", username: currentUser.username, judul: document.getElementById('input-judul').value, ringkasan: document.getElementById('input-ringkasan').value, prompt: document.getElementById('input-prompt').value }; const btn = document.getElementById('btn-generate'); const emptyState = document.getElementById('output-empty'); const loadingState = document.getElementById('output-loading'); const resultState = document.getElementById('output-result'); btn.disabled = true; btn.innerHTML = ' Memproses...'; emptyState.classList.add('hidden'); resultState.classList.add('hidden'); loadingState.classList.remove('hidden'); try { let data; if (IS_PREVIEW) { await new Promise(r => setTimeout(r, 2000)); data = generateDummyScript(payload); } else { const res = await fetch(GAS_URL, { method: "POST", headers: { "Content-Type": "text/plain;charset=utf-8" }, body: JSON.stringify(payload) }); data = await res.json(); } if (data.success) { renderScript(data.script); loadingState.classList.add('hidden'); resultState.classList.remove('hidden'); } else { alert("Gagal generate: " + data.message); loadingState.classList.add('hidden'); emptyState.classList.remove('hidden'); } } catch (error) { alert("Error: " + error.message); loadingState.classList.add('hidden'); emptyState.classList.remove('hidden'); } finally { btn.disabled = false; btn.innerHTML = ' Generate Script'; } }); function renderScript(script) { document.getElementById('res-judul').innerText = script.judul; document.getElementById('res-ringkasan').innerText = script.ringkasan; document.getElementById('res-narasi').innerText = script.narasi; const scenesContainer = document.getElementById('res-scenes'); scenesContainer.innerHTML = ''; script.scenes.forEach((scene, index) => { const html = `
Scene ${index + 1}: ${scene.judulScene}
Deskripsi Visual: ${scene.visual}
Dialog Karakter:
"${scene.dialog}"
Gerakan Karakter: ${scene.gerakanKarakter}
Gerakan Kamera: ${scene.gerakanKamera}
Efek Suara (SFX): ${scene.sfx}
Musik Latar: ${scene.bgm}
`; scenesContainer.innerHTML += html; }); } function copyToClipboard() { const judul = document.getElementById('res-judul').innerText; const text = document.getElementById('script-content').innerText; const fullText = judul + "\n\n" + text; // Fallback eksekusi copy menggunakan textarea (Andal untuk iFrame & Blogger) const textArea = document.createElement("textarea"); textArea.value = fullText; textArea.style.position = "fixed"; textArea.style.left = "-999999px"; textArea.style.top = "-999999px"; document.body.appendChild(textArea); textArea.focus(); textArea.select(); const copyBtn = document.getElementById('btn-copy'); const originalHTML = copyBtn.innerHTML; try { document.execCommand('copy'); // Feedback sukses merubah tampilan tombol (Tanpa Alert) copyBtn.innerHTML = ' Tersalin!'; copyBtn.classList.remove('text-gray-700', 'bg-gray-100', 'hover:bg-gray-200'); copyBtn.classList.add('text-green-700', 'bg-green-100', 'hover:bg-green-200'); } catch (err) { console.error('Gagal menyalin teks', err); // Feedback gagal copyBtn.innerHTML = ' Gagal'; copyBtn.classList.remove('text-gray-700'); copyBtn.classList.add('text-red-600'); } // Kembalikan tombol ke bentuk semula setelah 2 detik setTimeout(() => { copyBtn.innerHTML = originalHTML; copyBtn.className = "bg-gray-100 hover:bg-gray-200 text-gray-700 px-4 py-2 rounded-lg font-medium text-sm flex items-center transition shadow-sm"; }, 2000); document.body.removeChild(textArea); } // -- FUNGSI ADMIN -- async function fetchHistory() { const tbody = document.getElementById('admin-table-body'); const loader = document.getElementById('admin-loading'); tbody.innerHTML = ''; loader.classList.remove('hidden'); try { let data; if (IS_PREVIEW) { await new Promise(r => setTimeout(r, 1000)); data = { success: true, history: [ { tanggal: new Date().toLocaleString(), user: 'peserta', judul: 'Petualangan Budi', status: 'Sukses' }, { tanggal: new Date(Date.now() - 86400000).toLocaleString(), user: 'peserta', judul: 'Misteri Hutan Berduri', status: 'Sukses' } ] }; } else { const res = await fetch(GAS_URL, { method: "POST", headers: { "Content-Type": "text/plain;charset=utf-8" }, body: JSON.stringify({ action: "getHistory" }) }); data = await res.json(); } if (data.success) { data.history.forEach(item => { tbody.innerHTML += ` ${item.tanggal} ${item.user} ${item.judul} ${item.status} `; }); } } catch (error) { tbody.innerHTML = `Gagal memuat data`; } finally { loader.classList.add('hidden'); } } // -- DATA DUMMY GENERATOR (Untuk Preview) -- function generateDummyScript(payload) { return { success: true, script: { judul: payload.judul, ringkasan: payload.ringkasan, narasi: `Di sebuah dunia imajinasi berdasarkan ide "${payload.prompt.substring(0, 30)}...", cerita dimulai dengan penuh kejutan. Perjalanan karakter utama dipenuhi rintangan namun berakhir dengan pelajaran berharga.`, scenes: [ { judulScene: "Awal Petualangan", visual: "Langit cerah dengan matahari pagi. Karakter utama berdiri menatap horizon yang luas.", dialog: "Hari ini adalah hari yang kutunggu-tunggu. Aku siap!", gerakanKarakter: "Karakter meregangkan tangan, tersenyum lebar, dan mulai melangkah maju dengan semangat.", gerakanKamera: "Zoom out perlahan (Wide Shot) untuk memperlihatkan seberapa luas dunia di depan karakter.", sfx: "Suara burung berkicau, hembusan angin sepoi-sepoi.", bgm: "Musik orkestra ringan, ceria, membangkitkan semangat." }, { judulScene: "Menghadapi Tantangan", visual: "Suasana berubah menjadi sedikit gelap di dalam hutan / ruangan. Bayangan panjang terlihat di tanah.", dialog: "Tunggu, apa itu di depan sana? Sepertinya aku tidak sendirian.", gerakanKarakter: "Berhenti mendadak, menunduk sedikit, dan melihat ke sekeliling dengan waspada.", gerakanKamera: "Close up ke wajah karakter (Ekspresi tegang), lalu cut ke Over-The-Shoulder shot.", sfx: "Suara ranting patah, detak jantung pelan.", bgm: "Musik suspense tempo lambat, nada minor." }, { judulScene: "Penyelesaian", visual: "Pemandangan kembali terang. Karakter memegang sebuah objek/mencapai tujuan dengan bangga.", dialog: "Akhirnya, semua usaha ini membuahkan hasil yang manis.", gerakanKarakter: "Mengangkat tangan ke atas sebagai tanda kemenangan (Victory Pose), lalu tertawa lega.", gerakanKamera: "Dolly in (mendekat perlahan) ke karakter, diikuti transisi Fade to Black.", sfx: "Suara sorakan kecil / suara gemerincing magis.", bgm: "Musik epik penutup (Outro), menggelegar dan bahagia." } ] } }; }