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 };
+})();