Nuzwa's picture
Update index.html
9dd37a6 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Veo3 JSON Prompt Generator Pro — Smart Mode</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;600;700&display=swap" rel="stylesheet">
<style>
:root {
--primary-color-start: #4F46E5; /* Indigo */
--primary-color-end: #8B5CF6; /* Violet */
--background-color: #f8f9fc;
--container-bg: #ffffff;
--text-color: #1f2937;
--label-color: #374151;
--border-color: #d1d5db;
--shadow-color: rgba(0, 0, 0, 0.08);
}
* { box-sizing: border-box; }
body {
font-family: 'Poppins', sans-serif;
background-color: var(--background-color);
color: var(--text-color);
margin: 0;
padding: 1rem;
display: flex;
justify-content: center;
align-items: flex-start;
min-height: 100vh;
}
.container {
width: 100%;
max-width: 820px;
background-color: var(--container-bg);
padding: 2rem;
border-radius: 16px;
box-shadow: 0 10px 30px var(--shadow-color);
border: 1px solid #e5e7eb;
}
@media (max-width: 600px) { .container { padding: 1.25rem; } }
h1 {
font-size: 2rem; font-weight: 700; margin: 0 0 .75rem 0; text-align: center;
background: linear-gradient(90deg, var(--primary-color-start), var(--primary-color-end));
-webkit-background-clip: text; -webkit-text-fill-color: transparent;
}
.subtitle { text-align:center; font-size:.9rem; color:#6b7280; margin-bottom: .75rem; }
label { font-weight: 600; display:block; margin-top: 1rem; margin-bottom: .4rem; color: var(--label-color); font-size:.95rem; }
select, textarea, input[type="text"] {
width: 100%; padding: .8rem 1rem; border-radius: 10px; border: 1px solid var(--border-color);
font-size: 1rem; font-family: 'Poppins', sans-serif; background: #f9fafb; transition: all .2s ease-in-out;
}
/* Focus states for inputs */
select:focus,
textarea:focus,
input[type="text"]:focus {
outline: none;
border-color: var(--primary-color-start);
box-shadow: 0 0 0 3px rgba(79,70,229,.15);
background: #fff;
}
/* === Unified dropdown styling for all <select> === */
select {
-webkit-appearance: none; /* iOS/Safari */
-moz-appearance: none;
appearance: none;
padding-right: 2.25rem; /* space for chevron */
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="none"><path d="M6 8l4 4 4-4" stroke="%234F46E5" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>');
background-repeat: no-repeat;
background-position: right .75rem center;
background-size: 14px;
}
/* old Edge/IE arrow */
select::-ms-expand { display: none; }
/* disabled look */
select:disabled { opacity: .6; cursor: not-allowed; }
.row { display:grid; grid-template-columns: 1fr 1fr; gap: .75rem 1rem; }
.row > div { width:100%; }
/* Toolbar layout */
.toolbar { display:flex; gap:1rem; align-items:center; justify-content:center; flex-wrap: wrap; margin:.35rem 0 1rem; }
.toolbar .group { display:flex; gap:.5rem; align-items:center; }
.toolbar select { width: auto; min-width: 12ch; }
.actions { display:grid; grid-template-columns: 1fr 1fr; gap:.75rem; margin-top:1.25rem; }
button {
background-image: linear-gradient(90deg, var(--primary-color-start), var(--primary-color-end)); color:#fff; border:none; padding:.9rem 1.2rem; font-size:1.05rem; font-weight:700; border-radius:10px; cursor:pointer; width:100%;
transition: transform .18s ease, box-shadow .18s ease; box-shadow: 0 4px 15px rgba(0,0,0,.10);
}
button:hover { transform: translateY(-2px); box-shadow: 0 7px 20px rgba(0,0,0,.15); }
button.secondary { background:#111827; }
pre { background:#0f172a; color:#e5e7eb; padding:1rem 1.25rem; white-space:pre-wrap; word-break:break-word; border-radius:12px; margin-top:1rem; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; font-size:.92rem; line-height:1.6; border:1px solid #1f2937; }
.note { font-size:.8rem; color:#6b7280; margin-top:.25rem; }
.dimmed { opacity:.55; }
.toast { position: fixed; right: 16px; bottom: 16px; background: #111827; color: #fff; padding: 10px 12px; border-radius: 10px; opacity: 0; transform: translateY(10px); box-shadow: 0 8px 20px rgba(0,0,0,0.2); transition: opacity .2s ease, transform .2s ease; font-size: .9rem; z-index:1000; }
.toast.show { opacity:1; transform: translateY(0); }
</style>
</head>
<body>
<div class="container">
<h1>🎬 Veo3 JSON Prompt Generator <span style="font-size: 1rem; font-weight: 400;">(Pro)</span></h1>
<div class="subtitle">Slect Options • Smart Mode • Copy‑ready JSON Prompt</div>
<div class="toolbar">
<div class="group">
<label for="qualityPreset" style="margin:0">Quality</label>
<select id="qualityPreset" aria-label="Quality Preset">
<option value="pro" selected>Pro (default)</option>
<option value="draft">Draft / Fast</option>
<option value="cinematic">Cinematic</option>
</select>
</div>
</div>
<div class="row">
<div>
<label for="domain">🎯 Select Domain</label>
<select id="domain">
<option value="education">Education</option>
<option value="advertisement" selected>Advertisement</option>
<option value="product_demo">Product Demo</option>
<option value="creator_pov">Creator POV</option>
</select>
</div>
<div>
<label for="characterType">🎭 Character / Product</label>
<select id="characterType">
<optgroup label="Characters">
<option>Pakistani Male</option>
<option selected>Pakistani Female</option>
<option>Arab Male</option>
<option>Arab Female</option>
<option>South Asian Coach</option>
<option>Young Creator (Female)</option>
<option>Young Creator (Male)</option>
</optgroup>
<optgroup label="Products">
<option>Smartphone</option>
<option>Perfume</option>
<option>Luxury Car</option>
<option>Skincare Product</option>
</optgroup>
<option value="custom">🛠️ Custom Character or Product</option>
</select>
<div id="customCharacterTypeWrapper" style="display:none; margin-top:.5rem;">
<input type="text" id="customCharacterType" placeholder="Enter custom character or product" />
</div>
</div>
</div>
<div>
<label for="taskFocus">📆 Scene Title / Task Focus</label>
<select id="taskFocus">
<option>Lesson Planning</option>
<option>Quiz Generator</option>
<option>Feedback Automation</option>
<option selected>Product Reveal</option>
<option>How It Works</option>
<option>From Chaos to Clarity</option>
<option>Before vs. After</option>
<option>Transformation Moment</option>
<option>A Day in the Life</option>
<option>Problem → Solution</option>
<option>Behind the Scenes</option>
<option>AI to the Rescue</option>
<option>Time-Saver Workflow</option>
<option value="custom">🛠️ Custom</option>
</select>
<div id="customTaskWrapper" style="display:none; margin-top:.5rem;">
<input type="text" id="customTask" placeholder="Enter your custom task focus" />
</div>
</div>
<div class="row">
<div>
<label for="variant">🎞️ Video Type / Variant</label>
<select id="variant">
<option value="cinematic" selected>🎬 Cinematic</option>
<option value="POV">📸 POV</option>
<option value="meme">😂 Meme</option>
<option value="teaser">🎯 Teaser</option>
<option value="tutorial">📚 Tutorial</option>
<option value="BTS/documentary">🎥 Behind the Scenes</option>
<option value="montage">🌀 Montage</option>
<option value="product-hero">💡 Product Hero</option>
<option value="ASMR">🔊 ASMR</option>
<option value="drone-flythrough">🚁 Drone Flythrough</option>
<option value="timelapse">⏳ Timelapse</option>
<option value="kinetic-typography">🔠 Kinetic Typography</option>
<option value="stop-motion">🎞️ Stop Motion</option>
<option value="comedy-sketch">🤣 Comedy Sketch</option>
<option value="custom">✍️ Custom Variant</option>
</select>
<div id="customVariantWrapper" style="display:none; margin-top:.5rem;">
<input type="text" id="customVariant" placeholder="Enter custom video type" />
</div>
</div>
<div>
<label for="aspectRatio">🖼️ Aspect Ratio</label>
<select id="aspectRatio">
<option>16:9</option>
<option selected>9:16</option>
<option>1:1</option>
</select>
<div class="note">Smart Mode: platforms auto‑map from aspect ratio</div>
</div>
</div>
<div class="row">
<div>
<label for="platform">🌐 Platform</label>
<select id="platform">
<option value="tiktok">TikTok</option>
<option value="facebook_reels">Facebook Reels</option>
<option value="instagram_reels">Instagram Reels</option>
<option value="shorts">YouTube Shorts</option>
<option value="stories">Stories</option>
<option value="facebook">Facebook Feed</option>
<option value="x">X (Twitter)</option>
<option value="linkedin">LinkedIn</option>
<option value="snap">Snap Spotlight</option>
<option value="youtube">YouTube (Long)</option>
</select>
</div>
<div>
<label for="description">🧠 Narrative Structure</label>
<select id="description">
<option>Setting → Action → Reveal</option>
<option>Reveal-first → Context</option>
<option>Problem → Build → Payoff</option>
<option>Before/After Transform</option>
<option>Countdown → Pop → Logo-out</option>
</select>
</div>
</div>
<div id="lookPresetBlock" style="margin-top:.5rem;">
<label style="display:flex; align-items:center; gap:8px; font-weight:600;">
<input type="checkbox" id="useLookPreset" checked /> Enable Character Look Preset
</label>
<div id="lookPresetFields" class="row" style="margin-top:0.5rem;">
<div>
<label for="skinTone">🧑🏽‍ Skin Tone</label>
<select id="skinTone">
<option>light wheatish</option>
<option selected>wheatish</option>
<option>medium tan</option>
<option>warm brown</option>
<option>deep brown</option>
</select>
</div>
<div id="headCoverWrap">
<label for="headCover">🧕 Head Covering (if applicable)</label>
<select id="headCover">
<option value="auto" selected>Auto</option>
<option value="none">None</option>
<option value="dupatta">Dupatta (draped)</option>
<option value="hijab_wrap">Hijab (wrap)</option>
<option value="shayla">Shayla</option>
<option value="niqab">Niqab (eyes visible)</option>
</select>
</div>
</div>
<div class="note">Presets add culturally accurate wardrobe & grooming to JSON.</div>
</div>
<div class="row">
<div>
<label for="visualStyle">🎨 Visual Style</label>
<select id="visualStyle">
<option>Photorealistic</option>
<option>Glossy Commercial</option>
<option selected>Cinematic / Filmic Grain</option>
<option>HDR High-Contrast</option>
<option>Low-Key Noir</option>
<option>High-Key Clean</option>
<option>Cyberpunk / Neon</option>
<option>Retro VHS</option>
<option>Natural Documentary</option>
<option>Hyper-Real Macro</option>
<option>Tilt-Shift</option>
<option>Stylized (Toon/Anime/Cel-Shade)</option>
</select>
</div>
<div>
<label for="camera">🎥 Camera & Motion (optional)</label>
<select id="camera">
<option value="auto" selected>🎯 Auto Based on Scene</option>
<option value="dolly">Dolly</option>
<option value="slider">Slider</option>
<option value="handheld">Handheld (Micro Judder)</option>
<option value="crane">Crane / Jib</option>
<option value="rack-focus">Rack Focus</option>
<option value="360 orbit">360 Orbit</option>
<option value="fpv">FPV / Drone</option>
<option value="whip-pan">Whip Pan</option>
<option value="smash-zoom">Smash Zoom</option>
</select>
</div>
</div>
<div class="row">
<div>
<label for="background">🌆 Background Environment</label>
<select id="background">
<option value="auto" selected>🎯 Auto Based on Task</option>
<option value="studio">Studio Cyclorama</option>
<option value="warehouse">Industrial Warehouse</option>
<option value="garage">Neon Garage</option>
<option value="showroom">Marble Showroom</option>
<option value="night_city">City Night Street</option>
<option value="forest">Misty Forest</option>
<option value="lab">Clean Lab</option>
</select>
</div>
<div>
<label for="lightingMood">💡 Lighting & Mood</label>
<select id="lightingMood">
<option>Golden Hour Beams</option>
<option>Hard Rim / Silhouette</option>
<option selected>Soft Wrap Key</option>
<option>Top Light High-Contrast</option>
<option>Backlight + Haze</option>
<option>Neon Mix (Cyan / Magenta)</option>
<option>Strobe on Beats</option>
<option>Moonlight Blue</option>
<option>Neutral Daylight</option>
<option>🛠️ Auto Select</option>
</select>
</div>
</div>
<div class="row">
<div>
<label for="audioTrack">🎵 Background Music</label>
<select id="audioTrack">
<option>Hybrid Orchestral</option>
<option>Trap</option>
<option>Hip-Hop</option>
<option>Synthwave</option>
<option>Techno</option>
<option>Glitch-hop</option>
<option selected>Cinematic Pulses</option>
</select>
</div>
<div>
<label for="sfx">🔊 Sound Effects (SFX)</label>
<select id="sfx">
<option>Braaam</option>
<option selected>Whoosh / Riser</option>
<option>Strap Snap</option>
<option>Plastic Tear</option>
<option>Metallic Clinks</option>
<option>Angle-Grinder Sparks</option>
<option>Tire Chirp</option>
<option>Heartbeat Thump</option>
</select>
</div>
</div>
<div id="voiceoverBlock" style="margin-top:.5rem;">
<label style="display:flex; align-items:center; gap:8px; font-weight:600;">
<input type="checkbox" id="useVoiceover" checked /> Enable Voiceover
</label>
<div id="voFields" class="row" style="margin-top:0.5rem;">
<div>
<label for="voLanguage">🎙 Language</label>
<select id="voLanguage">
<option value="ur-PK-roman" selected>Urdu (Roman Urdu)</option>
<option value="ur-PK">Urdu (Nastaliq)</option>
<option value="en-US">English (US)</option>
<option value="en-GB">English (UK)</option>
<option value="ar-Gulf">Arabic (Gulf)</option>
<option value="ar-Levant">Arabic (Levant)</option>
</select>
</div>
<div>
<label for="voGender">👤 Voice</label>
<select id="voGender">
<option value="female" selected>Female</option>
<option value="male">Male</option>
<option value="youth">Youth</option>
</select>
</div>
<div>
<label for="voTone">🎚 Tone</label>
<select id="voTone">
<option value="cinematic" selected>Cinematic</option>
<option value="friendly">Friendly</option>
<option value="authoritative">Authoritative</option>
<option value="educational">Educational</option>
<option value="inspirational">Inspirational</option>
<option value="playful">Playful</option>
</select>
</div>
<div>
<label for="voPace">⏱ Pace</label>
<select id="voPace">
<option value="slow">Slow</option>
<option value="medium" selected>Medium</option>
<option value="fast">Fast</option>
</select>
</div>
</div>
<label for="voScript" style="margin-top:.5rem;">📝 Voiceover Script (optional)</label>
<textarea id="voScript" rows="3" placeholder="Write your VO script here... or leave empty to generate later"></textarea>
</div>
<div class="row">
<div>
<label for="colorPalette">🎨 Color Palette</label>
<select id="colorPalette">
<option>Graphite / Obsidian / Silver</option>
<option selected>Amber / Gold / Copper</option>
<option>Steel Blue / Cyan / Teal</option>
<option>Neon Accents (Magenta / Cyan / Lime)</option>
<option>Monochrome</option>
<option>Duotone</option>
<option>Triadic (e.g. graphite + amber + red)</option>
</select>
</div>
<div>
<label for="hook">🚀 Hook / Opening Action</label>
<select id="hook">
<option>Bolt Snap (Slow-mo)</option>
<option>Sparks Hit Lens</option>
<option>Seal Rip Recoil</option>
<option>Lights Power-on Cascade</option>
<option selected>Tarp Whip-Off</option>
<option>Confetti Pop</option>
<option>Macro Finger Press</option>
<option>Countdown Flash</option>
<option>Smash-Zoom Gag</option>
</select>
</div>
</div>
<div class="row">
<div>
<label for="finale">🎬 Finale / Closing Action</label>
<select id="finale">
<option selected>Hero Push-in → Blackout</option>
<option>Butterfly Doors + 360 Orbit</option>
<option>Taillight Bloom → Logo-out</option>
<option>Freeze Frame + End Card</option>
<option>Speed-Ramp Drive-by</option>
<option>Reverse Reveal Cloth Fall</option>
</select>
</div>
<div>
<label for="keywords">🔑 Keywords (comma-separated)</label>
<textarea id="keywords" rows="2" placeholder="e.g., macro details, handheld realism, clean logo"></textarea>
</div>
</div>
<div>
<label for="negativePrompt">🚫 Negative Prompt (Exclude Things)</label>
<textarea id="negativePrompt" rows="3" placeholder="e.g., no logos, no text overlays, no watermark, no extra limbs"></textarea>
<div class="note">Smart Mode auto-adds clean negatives if left empty.</div>
</div>
<div class="actions">
<button id="generateBtn" onclick="generatePrompt()">🚀 Generate Prompt</button>
<button class="secondary" id="copyBtn" onclick="copyToClipboard()">📋 Copy Prompt</button>
</div>
<pre id="output" aria-live="polite">{ /* Your JSON prompt will appear here */ }</pre>
</div>
<div id="toast" class="toast" role="status" aria-live="polite">Copied!</div>
<script>
// ===== Helpers & Baseline =====
const $ = (id) => document.getElementById(id);
function showToast(msg="Copied!"){
const t = $("toast"); if(!t) return; t.textContent = msg; t.classList.add("show"); setTimeout(()=>t.classList.remove("show"), 1200);
}
let _lastJSON = "";
let _typeTimer = null;
function typewriter(el, text, speed = 8){
if (!el) return;
if (_typeTimer) { clearInterval(_typeTimer); _typeTimer = null; }
el.textContent = "";
let i = 0;
_typeTimer = setInterval(()=>{
el.textContent += text[i++] || "";
if (i >= text.length) { clearInterval(_typeTimer); _typeTimer = null; }
}, speed);
}
function copyToClipboard(){
const text = _lastJSON || $("output").textContent || "";
navigator.clipboard.writeText(text).then(()=>showToast("Copied!"));
}
// Fallback generatePrompt so our override can call it safely
if (typeof window.generatePrompt !== 'function') {
window.generatePrompt = function(){ /* no-op baseline */ };
}
</script>
<script>
// ====== SMART MAPPINGS ======
const AR_TO_PLATFORMS = {
"9:16": ["tiktok","instagram_reels","facebook_reels","shorts","snap","stories"],
"16:9": ["youtube","facebook","x","linkedin"],
"1:1": ["instagram_feed","facebook"]
};
const AUTO_PRESETS = {
cinematic: {
camera: ["dolly","rack-focus","360 orbit"],
lighting: "Backlight + Haze",
bg: "showroom",
music: "Cinematic Pulses",
sfx: "Whoosh / Riser"
},
"product-hero": {
camera: ["slider","macro"],
lighting: "Top Light High-Contrast",
bg: "studio",
music: "Hybrid Orchestral",
sfx: "Metallic Clinks"
},
tutorial: {
camera: ["handheld","rack-focus"],
lighting: "Soft Wrap Key",
bg: "lab",
music: "Cinematic Pulses",
sfx: "Metallic Clinks"
},
"BTS/documentary": {
camera: ["handheld"],
lighting: "Neutral Daylight",
bg: "warehouse",
music: "Cinematic Pulses",
sfx: "Whoosh / Riser"
},
meme: {
camera: ["smash-zoom","whip-pan"],
lighting: "High-Key Clean",
bg: "auto",
music: "Trap",
sfx: "Whoosh / Riser"
}
};
const DOMAIN_HINTS = {
education: { lighting: "Neutral Daylight", bg: "lab" },
advertisement:{ lighting: "Top Light High-Contrast", bg: "showroom" },
product_demo:{ lighting: "High-Key Clean", bg: "studio" },
creator_pov: { lighting: "Soft Wrap Key", bg: "night_city" }
};
const QUALITY_PRESET = {
draft: { fps: 24, grain: "off", denoise: "on", duration: "8–10s" },
pro: { fps: 30, grain: "light",denoise: "auto",duration: "10–12s" },
cinematic: { fps: 30, grain: "gentle filmic", denoise: "auto", duration: "12–14s" }
};
const el = (id)=>document.getElementById(id);
const aspectEl = el("aspectRatio");
const platEl = el("platform");
const varEl = el("variant");
const domEl = el("domain");
const qualEl = el("qualityPreset") || { value:"pro" };
function syncPlatformsFromAspect() {
if (!aspectEl || !platEl) return;
const ar = aspectEl.value;
const rec = AR_TO_PLATFORMS[ar] || [];
platEl.dataset.recommended = JSON.stringify(rec);
// set a sensible default if empty
if (!platEl.value || !rec.includes(platEl.value)) {
platEl.value = rec[0] || platEl.value || "tiktok";
}
}
function setIfAuto(selectEl, val){
if (!selectEl) return;
const cur = (selectEl.value||"").toLowerCase();
if (cur === "auto" || cur === "🎯 auto based on scene".toLowerCase() || cur === "🛠️ auto select".toLowerCase() || cur === "") {
selectEl.value = val;
}
}
function autoFromVariantAndDomain() {
const v = (varEl?.value || "cinematic").toLowerCase();
const d = (domEl?.value || "advertisement").toLowerCase();
const vAuto = AUTO_PRESETS[v] || {};
const dAuto = DOMAIN_HINTS[d] || {};
const cameraEl = el("camera");
const lightingEl = el("lightingMood");
const bgEl = el("background");
const musicEl = el("audioTrack");
const sfxEl = el("sfx");
const cameraVal = Array.isArray(vAuto.camera) ? vAuto.camera[0] : vAuto.camera;
setIfAuto(cameraEl, cameraVal || "auto");
setIfAuto(lightingEl, vAuto.lighting || dAuto.lighting || "🛠️ Auto Select");
setIfAuto(bgEl, vAuto.bg || dAuto.bg || "auto");
setIfAuto(musicEl, vAuto.music || "Cinematic Pulses");
setIfAuto(sfxEl, vAuto.sfx || "Whoosh / Riser");
}
function cleanObj(obj){
const isArr = Array.isArray(obj);
const acc = isArr ? [] : {};
Object.entries(obj).forEach(([k,v])=>{
if (v === undefined || v === null) return;
if (typeof v === 'string' && v.trim() === '') return;
if (typeof v === 'string' && /^(auto|🛠️ Auto Select|🎯 Auto Based on Scene)$/i.test(v.trim())) return;
if (Array.isArray(v) && v.length===0) return;
acc[k] = (v && typeof v === 'object' && !Array.isArray(v)) ? cleanObj(v) : v;
});
return acc;
}
const _oldGenerate = window.generatePrompt;
window.generatePrompt = function(){
autoFromVariantAndDomain();
syncPlatformsFromAspect();
const qp = QUALITY_PRESET[qualEl.value] || QUALITY_PRESET.pro;
const variant = (el("variant")?.value === "custom" ? el("customVariant")?.value : el("variant")?.value) || "cinematic";
const ar = el("aspectRatio")?.value || "16:9";
const domain = el("domain")?.value || "advertisement";
const scene = (el("taskFocus")?.value === "custom" ? el("customTask")?.value : el("taskFocus")?.value) || "Product Reveal";
const lookOn = el("useLookPreset")?.checked;
const voOn = el("useVoiceover")?.checked;
let subjectLook = null;
if (lookOn) {
const type = (el("characterType")?.value === "custom" ? el("customCharacterType")?.value : el("characterType")?.value);
const tone = el("skinTone")?.value;
const hc = el("headCover")?.value;
subjectLook = { type, skin_tone: tone, head_cover: hc };
}
let voiceover = null;
if (voOn) {
voiceover = {
language: el("voLanguage")?.value,
voice: el("voGender")?.value,
tone: el("voTone")?.value,
pace: el("voPace")?.value,
script: (el("voScript")?.value || "").trim()
};
}
const camera = el("camera")?.value;
const lighting = el("lightingMood")?.value;
const bg = el("background")?.value;
const music = el("audioTrack")?.value;
const sfx = el("sfx")?.value;
let platforms = [];
if (platEl?.dataset?.recommended){
const rec = JSON.parse(platEl.dataset.recommended);
platforms = platEl.value ? [platEl.value] : rec;
}
const negatives = (el("negativePrompt")?.value || "no logos, no text overlays, no watermark").trim();
const meta = {
aspect_ratio: ar,
duration: qp.duration,
fps: qp.fps,
grain: qp.grain,
denoise: qp.denoise,
platforms
};
const out = cleanObj({
variant,
domain,
scene_title: scene,
meta,
subject_look: subjectLook,
visual_style: el("visualStyle")?.value || "Cinematic / Filmic Grain",
camera,
background: bg,
lighting,
music,
sfx,
color_palette: el("colorPalette")?.value,
hook: el("hook")?.value,
finale: el("finale")?.value,
voiceover,
keywords: (el("keywords")?.value||"").trim(),
negative_prompt: negatives
});
const pretty = JSON.stringify(out, null, 2);
_lastJSON = pretty;
typewriter(document.getElementById("output"), pretty, 6); // typewriting effect
if (typeof _oldGenerate === "function") { try { _oldGenerate(); } catch(e){} }
};
// Events
aspectEl?.addEventListener("change", syncPlatformsFromAspect);
domEl?.addEventListener("change", ()=>{ autoFromVariantAndDomain(); });
// Task Focus Custom Input
const taskFocus = el('taskFocus');
const customTaskWrap = el('customTaskWrapper');
taskFocus?.addEventListener('change', ()=>{
customTaskWrap.style.display = taskFocus.value === 'custom' ? 'block' : 'none';
});
// Character/Product Custom Input
const characterType = el('characterType');
const customCharacterWrap = el('customCharacterTypeWrapper');
characterType?.addEventListener('change', () => {
customCharacterWrap.style.display = characterType.value === 'custom' ? 'block' : 'none';
});
// Variant Custom Input & Auto-preset trigger
const customVariantWrap = el('customVariantWrapper');
varEl?.addEventListener("change", ()=>{
autoFromVariantAndDomain();
customVariantWrap.style.display = varEl.value === 'custom' ? 'block' : 'none';
});
document.addEventListener("DOMContentLoaded", ()=>{
autoFromVariantAndDomain();
syncPlatformsFromAspect();
generatePrompt(); // prefill output with typing
});
</script>
</body>
</html>