Text to Animation Script Generator
Login Portal
Masuk untuk membuat skrip animasi
Data Login Dummy (Mode Preview):
Peserta: peserta / edudigital
Admin: admin / edudigital
AI sedang merangkai skrip animasi...
Belum Ada Skrip
Isi form di samping dan klik Generate untuk membuat skrip animasi profesional Anda.
Admin Dashboard
Riwayat Generate Skrip
Refresh
Tanggal
User
Judul Video
Status
Login Portal
Masuk untuk membuat skrip animasi
Username
Password
Masuk Sekarang
Masuk dengan Google
Data Login Dummy (Mode Preview):
Peserta: peserta / edudigital
Admin: admin / edudigital
Parameter Cerita
Judul Video
Ringkasan Cerita Singkat
Ide / Prompt Lengkap
Generate Script
AI sedang merangkai skrip animasi...
Belum Ada Skrip
Isi form di samping dan klik Generate untuk membuat skrip animasi profesional Anda.
Admin Dashboard
Riwayat Generate Skrip
Refresh
Tanggal
User
Judul Video
Status
";
// 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 = '
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."
}
]
}
};
}