diff --git a/movie_editor/frontend/index.html b/movie_editor/frontend/index.html index 0e10d199..a0f92573 100644 --- a/movie_editor/frontend/index.html +++ b/movie_editor/frontend/index.html @@ -118,6 +118,7 @@ + diff --git a/movie_editor/frontend/store.js b/movie_editor/frontend/store.js index bf28c713..3351557b 100644 --- a/movie_editor/frontend/store.js +++ b/movie_editor/frontend/store.js @@ -622,6 +622,7 @@ refreshPreview(); await loadModels(); _validateSceneRenders(); + window.WelcomePage?.close?.(); } async function newProject(name) { @@ -636,7 +637,10 @@ _selectionAnchorId = null; await refreshProjectList(); if (state.projects[0]) await loadProject(state.projects[0].id); - else notify(); + else { + notify(); + if (window.WelcomePage) window.WelcomePage.open(); + } } function downloadProject() { @@ -2869,9 +2873,8 @@ await loadModels(); window.addEventListener("funpack-models-changed", loadModels); await refreshProjectList(); - if (state.projects[0]) await loadProject(state.projects[0].id); - else await newProject("My first montage"); notify(); + if (window.WelcomePage) window.WelcomePage.open(); } window.Store = { diff --git a/movie_editor/frontend/styles.css b/movie_editor/frontend/styles.css index d3fcd549..acb775ed 100644 --- a/movie_editor/frontend/styles.css +++ b/movie_editor/frontend/styles.css @@ -873,6 +873,96 @@ body.tl-reordering { cursor: grabbing; user-select: none; } .empty-stage { color: var(--faint); padding: 30px; text-align: center; width: 100%; } +/* ───────────────────────── WELCOME (first load) ───────────────────────── */ +.welcome-overlay { + position: fixed; + left: 0; right: 0; bottom: 0; + top: var(--menubar-h); + z-index: 350; + display: flex; + align-items: center; + justify-content: center; + padding: 24px; + background: + radial-gradient(900px 480px at 50% 20%, rgba(243,169,60,.08) 0%, transparent 55%), + radial-gradient(700px 400px at 80% 80%, rgba(87,208,198,.06) 0%, transparent 50%), + rgba(8,7,6,.92); + backdrop-filter: blur(6px); + animation: fade .2s ease; +} +.welcome-card { + width: min(520px, 96vw); + padding: 34px 32px 28px; + border-radius: 16px; + border: 1px solid var(--line); + background: linear-gradient(165deg, #1a1712 0%, #12100d 100%); + box-shadow: var(--shadow), 0 0 0 1px rgba(255,255,255,.03) inset; + animation: pop .18s ease; +} +.welcome-brand { + display: flex; + align-items: baseline; + gap: 10px; + flex-wrap: wrap; + margin-bottom: 12px; +} +.welcome-mark { color: var(--amber); font-size: 14px; filter: drop-shadow(0 0 8px var(--amber-soft)); } +.welcome-title { font-family: var(--font-display); font-weight: 800; font-size: 28px; letter-spacing: .2px; } +.welcome-sub { + font-family: var(--font-display); + font-weight: 500; + font-size: 11px; + letter-spacing: 2.5px; + text-transform: uppercase; + color: var(--faint); +} +.welcome-lead { + margin: 0 0 22px; + color: var(--muted); + font-size: 14px; + line-height: 1.55; + max-width: 42em; +} +.welcome-actions { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; } +.welcome-btn { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 4px; + text-align: left; + padding: 14px 15px; + border-radius: 11px; + border: 1px solid var(--line); + background: var(--ink-1); + cursor: pointer; + transition: border-color .12s, background .12s, transform .08s; +} +.welcome-btn:hover:not(.disabled) { + border-color: color-mix(in srgb, var(--amber) 35%, var(--line)); + background: var(--ink-2); + transform: translateY(-1px); +} +.welcome-btn.primary { + border-color: color-mix(in srgb, var(--amber) 45%, var(--line)); + background: color-mix(in srgb, var(--amber) 10%, var(--ink-1)); +} +.welcome-btn.primary:hover:not(.disabled) { + background: color-mix(in srgb, var(--amber) 16%, var(--ink-2)); +} +.welcome-btn.ghost { opacity: .88; } +.welcome-btn.disabled { + opacity: .45; + cursor: default; + transform: none; +} +.welcome-btn-label { font-weight: 600; font-size: 14px; color: var(--text); } +.welcome-btn.primary .welcome-btn-label { color: var(--amber-2); } +.welcome-btn-hint { font-size: 11px; color: var(--faint); line-height: 1.35; } +@media (max-width: 520px) { + .welcome-actions { grid-template-columns: 1fr; } + .welcome-card { padding: 24px 20px 20px; } +} + /* ───────────────────────── MODALS (Models settings) ───────────────────────── */ .modal-overlay { position: fixed; inset: 0; z-index: 500; background: rgba(6,5,4,.66); backdrop-filter: blur(3px); display: flex; align-items: flex-start; justify-content: center; padding: 7vh 16px; animation: fade .15s ease; } @keyframes fade { from { opacity: 0; } } diff --git a/movie_editor/frontend/welcome.js b/movie_editor/frontend/welcome.js new file mode 100644 index 00000000..aa558d5f --- /dev/null +++ b/movie_editor/frontend/welcome.js @@ -0,0 +1,103 @@ +// First-load welcome screen: pick a project before entering the editor. +(function () { + const { el } = window.dom; + const S = window.Store; + let overlay = null; + + const loadFileInput = (function () { + const inp = document.createElement("input"); + inp.type = "file"; + inp.accept = ".json,.funpack_project.json,application/json"; + inp.style.display = "none"; + inp.onchange = async () => { + const file = inp.files?.[0]; + inp.value = ""; + if (!file) return; + await S.importProject(file); + }; + document.body.appendChild(inp); + return inp; + })(); + + function latestProject() { + const list = S.get().projects || []; + return list.length ? list[0] : null; + } + + function promptNewProject() { + if (window.SlotPicker?.openPrompt) { + window.SlotPicker.openPrompt({ + title: "New project", + value: "Untitled montage", + placeholder: "Project name", + onPick: (name) => S.newProject(name), + }); + return; + } + const name = prompt("Project name:", "Untitled montage"); + if (name != null) S.newProject(name || "Untitled montage"); + } + + function openLatest() { + const p = latestProject(); + if (!p) return; + S.loadProject(p.id); + } + + function loadProjectFile() { + loadFileInput.click(); + } + + function welcomeTour() { + alert("Welcome tour is coming soon."); + } + + function close() { + if (overlay) { + overlay.remove(); + overlay = null; + } + } + + function open() { + close(); + overlay = el("div", "welcome-overlay"); + const card = el("div", "welcome-card"); + + const brand = el("div", "welcome-brand"); + brand.append(el("span", "welcome-mark", "◉")); + brand.append(el("span", "welcome-title", "FunPack")); + brand.append(el("span", "welcome-sub", "Cutting Room")); + card.append(brand); + + card.append(el("p", "welcome-lead", + "Build multi-scene montages with FunPack Studio and preview them on a real timeline.")); + + const latest = latestProject(); + const actions = el("div", "welcome-actions"); + + const mkBtn = (label, cls, hint, disabled, fn) => { + const b = el("button", "welcome-btn" + (cls ? " " + cls : "") + (disabled ? " disabled" : "")); + b.type = "button"; + b.append(el("span", "welcome-btn-label", label)); + if (hint) b.append(el("span", "welcome-btn-hint", hint)); + if (!disabled && fn) b.onclick = fn; + return b; + }; + + actions.append( + mkBtn("New Project", "primary", "Start a blank montage", false, promptNewProject), + mkBtn("Open Latest Project", "", + latest ? latest.name : "No saved projects yet", + !latest, openLatest), + mkBtn("Load Project", "", "Import a .json project file", false, loadProjectFile), + mkBtn("Welcome Tour", "ghost", "Coming soon", false, welcomeTour), + ); + card.append(actions); + + overlay.append(card); + document.body.append(overlay); + } + + window.WelcomePage = { open, close, isOpen: () => !!overlay }; +})();