diff --git a/easy_gen/frontend/app.js b/easy_gen/frontend/app.js
index 23fd7743..992d37a6 100644
--- a/easy_gen/frontend/app.js
+++ b/easy_gen/frontend/app.js
@@ -276,7 +276,7 @@
}
// First run (or a wizard interrupted by an install restart) gets the setup flow;
// everyone else gets the project picker they already know.
- if (!loaded && !window.Onboarding?.maybeOpen()) window.ProjectMenu.open({ dismissable: false });
+ if (!loaded) window.Onboarding?.maybeOpen();
// After the project is in place, so a run targeting it re-attaches fully (result and
// all) instead of falling back to the queue-watch path. Not awaited — it runs for as
// long as the generation does.
diff --git a/movie_editor/frontend/index.html b/movie_editor/frontend/index.html
index d515eb2c..e7c874fd 100644
--- a/movie_editor/frontend/index.html
+++ b/movie_editor/frontend/index.html
@@ -136,7 +136,6 @@
-
diff --git a/movie_editor/frontend/inspector.js b/movie_editor/frontend/inspector.js
index 08d8d254..6262785f 100644
--- a/movie_editor/frontend/inspector.js
+++ b/movie_editor/frontend/inspector.js
@@ -137,7 +137,7 @@
wrap.append(el("span", null, m.label));
const sel = el("select"); sel.dataset.k = "sc-" + kind + "mode";
- [["project", "Inherit project global"], ["timeline", "Inherit timeline (trim)"], ["custom", "Custom"]]
+ [["project", "Project default"], ["timeline", "Timeline trim"], ["custom", "Custom"]]
.forEach(([v, l]) => { const o = el("option", null, l); o.value = v; if (v === mode) o.selected = true; sel.append(o); });
sel.onchange = () => {
const patch = { [kind + "_mode"]: sel.value };
diff --git a/movie_editor/frontend/onboarding.js b/movie_editor/frontend/onboarding.js
index 0a5eaafc..2e166543 100644
--- a/movie_editor/frontend/onboarding.js
+++ b/movie_editor/frontend/onboarding.js
@@ -15,7 +15,6 @@
const API = () => window.MovieEditorAPI;
const LS_DONE = "funpack_onboarded";
const LS_RUN = "funpack_wizard"; // an unfinished run (see save())
- const SS_SKIP = "funpack_wizard_skip"; // this tab session declined it
let root = null; // the full-screen overlay
let stage = null; // the animated content column
@@ -139,11 +138,76 @@
sub: "Markers that cut one written prompt into separate scenes.",
mount: (body) => ({ cleanup: window.Composer.mountPane("splits", body) }),
},
+ projects: {
+ title: "Your projects",
+ sub: "Open one, or bring in a project file from somewhere else.",
+ mount: (body, setActions) => mountProjects(body, setActions),
+ },
};
+ // The project picker. This screen replaced a separate welcome card — two different
+ // front doors for the same decision was one too many.
+ function mountProjects(body, setActions) {
+ const importInput = el("input");
+ importInput.type = "file";
+ importInput.accept = ".json,.funpack_project.json,application/json";
+ importInput.style.display = "none";
+ importInput.onchange = async () => {
+ const file = importInput.files?.[0];
+ importInput.value = "";
+ if (!file) return;
+ await S().importProject(file);
+ close();
+ };
+ body.append(importInput);
+
+ const imp = el("button", "btn ghost tiny", "Import project file…");
+ imp.type = "button";
+ imp.onclick = () => importInput.click();
+ setActions([imp]);
+
+ const list = el("div", "oo-projects");
+ body.append(list);
+
+ const paintList = async () => {
+ clear(list);
+ let projects = [];
+ try { projects = (await API().listProjects()).projects || []; } catch (_) {}
+ projects.sort((a, b) => (b.updated_at || 0) - (a.updated_at || 0));
+ if (!projects.length) {
+ list.append(el("p", "oo-body", "No projects yet. Go back and press Begin."));
+ return;
+ }
+ projects.forEach((p) => {
+ const row = el("button", "oo-project");
+ row.type = "button";
+ const main = el("div", "oo-project-main");
+ main.append(el("div", "oo-project-name", p.name));
+ const n = p.scene_count || 0;
+ main.append(el("div", "oo-project-meta",
+ `${n} ${n === 1 ? "scene" : "scenes"} · ${when(p.updated_at)}`));
+ row.append(main);
+ row.onclick = () => { close(); S().loadProject(p.id); };
+ list.append(row);
+ });
+ };
+ paintList();
+ return { cleanup: () => {} };
+ }
+
+ function when(ts) {
+ if (!ts) return "never opened";
+ const days = Math.floor((Date.now() / 1000 - ts) / 86400);
+ if (days <= 0) return "today";
+ if (days === 1) return "yesterday";
+ if (days < 30) return days + " days ago";
+ return new Date(ts * 1000).toLocaleDateString();
+ }
+
function openPane(kind) {
const spec = PANES[kind];
if (!spec) return;
+ const fromSplash = !panel && !!root?.querySelector(".oo-splash");
closePane();
const box = el("div", "oo-pane");
const head = el("div", "oo-pane-head");
@@ -164,17 +228,20 @@
body.append(el("p", "oo-body", "This part of the setup isn't available in this build."));
}
- panel = { kind, cleanup: mounted?.cleanup || null };
+ panel = { kind, fromSplash, cleanup: mounted?.cleanup || null };
paint(box, "pane:" + kind, { pane: true });
- root.append(actions({ primary: "Done", onPrimary: closePane }));
+ root.append(actions({ primary: fromSplash ? "Back" : "Done", onPrimary: closePane }));
root.classList.add("oo-has-actions");
}
function closePane() {
if (!panel) return;
+ const backToSplash = panel.fromSplash;
try { panel.cleanup?.(); } catch (_) {}
panel = null;
- if (root) renderStep();
+ if (!root) return;
+ if (backToSplash) paint(splash(), null);
+ else renderStep();
}
// ── step registry ─────────────────────────────────────────────────────────
@@ -508,7 +575,6 @@
resumeBtn.textContent = "Continue with “" + p.name + "”";
resumeBtn.hidden = false;
resumeBtn.onclick = () => {
- suppressSession();
close();
(S().loadProject(p.id) || Promise.resolve()).catch(() => {});
};
@@ -568,15 +634,7 @@
next();
}
- // Loading a project IS an answer to the wizard's question, so it stops asking for the
- // rest of this session — including the tour, which would only get in the way of work
- // that is already under way.
- function openExisting() {
- suppressSession();
- close();
- if (isEasyGen()) window.ProjectMenu.open({ dismissable: true });
- else window.WelcomePage.open();
- }
+ function openExisting() { openPane("projects"); }
// ── navigation ────────────────────────────────────────────────────────────
@@ -657,16 +715,6 @@
try { localStorage.removeItem(LS_RUN); } catch (_) {}
}
- // Suppressed for the rest of this tab's session: the user answered the wizard's own
- // question by loading a project instead. sessionStorage, not localStorage — next time
- // they open the app fresh, an unfinished setup is worth offering again.
- function suppressSession() {
- try { sessionStorage.setItem(SS_SKIP, "1"); } catch (_) {}
- }
- function sessionSuppressed() {
- try { return sessionStorage.getItem(SS_SKIP) === "1"; } catch (_) { return false; }
- }
-
// The tour lives in its own page mode against a sandbox project, so it cannot start
// over the editor — finishing navigates there.
function startTour() {
@@ -681,7 +729,6 @@
function finish() {
try { localStorage.setItem(LS_DONE, "1"); } catch (_) {}
clearSaved();
- suppressSession();
// The wizard IS the pipeline setup — without this its modal reopens over the editor
// and asks for the family and the node packs all over again.
window.PipelineSetup?.markHandled?.();
@@ -747,25 +794,21 @@
try { return localStorage.getItem(LS_DONE) === "1"; } catch (_) { return false; }
}
- // Called by each host's boot instead of its old welcome screen.
+ // The start screen for both frontends: shown whenever nothing is open, not just on a
+ // first run. Its splash carries Begin, the last project, the project list, and the
+ // maintenance actions, so there is one front door instead of two.
function maybeOpen() {
if (window.__FUNPACK_TOUR__) return false;
- if (sessionSuppressed()) return false;
- // Something is already open — a resumed generation, a restored project. Setup is not
- // the screen anyone wants in front of work that is already there.
- if (S()?.get?.().project) return false;
+ if (S()?.get?.().project) return false; // work already open — don't cover it
const saved = restore();
if (saved?.resumable) { open({ resume: true }); return true; }
// An unfinished run that did NOT end in a deliberate restart: start over.
- if (saved) { open({ carry: true }); return true; }
- if (done()) return false;
- open();
+ open(saved ? { carry: true } : undefined);
return true;
}
// Menu entry: run setup again on demand, whatever state the app is in.
function reopen() {
- try { sessionStorage.removeItem(SS_SKIP); } catch (_) {}
clearSaved();
open();
}
diff --git a/movie_editor/frontend/store.js b/movie_editor/frontend/store.js
index e8eb9bc7..a900479e 100644
--- a/movie_editor/frontend/store.js
+++ b/movie_editor/frontend/store.js
@@ -824,7 +824,6 @@
refreshPreview();
await loadModels();
_validateSceneRenders();
- window.WelcomePage?.close?.();
window.PipelineSetup?.maybePrompt?.();
}
@@ -849,7 +848,7 @@
if (state.projects[0]) await loadProject(state.projects[0].id);
else {
notify();
- if (window.WelcomePage) window.WelcomePage.open();
+ window.Onboarding?.open?.();
}
}
@@ -4341,11 +4340,9 @@
}
} catch (_) { /* queue unreachable — boot normally */ }
notify();
- // First run (or a wizard interrupted by an install restart) gets the setup flow;
- // everyone else gets the project picker they already know.
+ // One front door: the full-screen welcome, unless a running generation was resumed.
if (resumed) return;
- if (window.Onboarding?.maybeOpen()) return;
- if (window.WelcomePage) window.WelcomePage.open();
+ window.Onboarding?.maybeOpen();
}
// Backstop for a suspended save the user walks away from. Unconditional is safe:
diff --git a/movie_editor/frontend/styles.css b/movie_editor/frontend/styles.css
index bc9063cc..21ce4ba2 100644
--- a/movie_editor/frontend/styles.css
+++ b/movie_editor/frontend/styles.css
@@ -316,7 +316,7 @@ button, input, select, textarea { font-family: inherit; color: var(--text); }
}
.brand { font-family: var(--font-display); font-weight: 800; letter-spacing: .2px; font-size: var(--fs-title-3); display: flex; align-items: center; gap: 8px; }
.brand-mark { color: var(--accent); font-size: 12px; transform: translateY(-1px); filter: drop-shadow(0 0 6px var(--accent-soft)); }
-.brand-sub { color: var(--faint); font-weight: 500; font-size: 12px; letter-spacing: 2px; text-transform: uppercase; }
+.brand-sub { color: var(--faint); font-weight: 500; font-size: var(--fs-callout); }
/* The brand is a link back to the hub in both frontends. */
a.brand { text-decoration: none; color: inherit; }
a.brand:hover .brand-sub { color: var(--accent-hi); }
@@ -342,16 +342,17 @@ a.brand:hover .brand-sub { color: var(--accent-hi); }
}
.menu-item:hover { background: var(--accent-soft); color: var(--accent-hi); }
.menu-item.disabled { color: var(--faint); pointer-events: none; }
-.menu-item .hint { color: var(--faint); font-family: var(--font-mono); font-size: 11px; }
+.menu-item .hint { color: var(--faint); font-size: var(--fs-subhead); }
.menu-item:hover .hint { color: var(--accent); }
.menu-sep { height: 1px; background: var(--line); margin: 5px 6px; }
-.menu-label { padding: 4px 10px 3px; font-size: 10px; letter-spacing: 1.4px; text-transform: uppercase; color: var(--faint); }
+.menu-label { padding: 4px 10px 3px; font-size: 11px; color: var(--faint); font-weight: 600;}
.menu-veil { position: fixed; inset: 0; z-index: 150; }
.menubar-right { margin-left: auto; display: flex; align-items: center; gap: 12px; }
.health-chip, .save-chip {
- font-family: var(--font-mono); font-size: 11px; color: var(--muted);
- display: flex; align-items: center; gap: 6px; padding: 3px 9px; border: 1px solid var(--line); border-radius: 20px;
+ font-size: var(--fs-subhead); line-height: var(--lh-subhead); color: var(--muted);
+ display: flex; align-items: center; gap: 6px; padding: 3px var(--sp-2);
+ border: 1px solid var(--line); border-radius: 20px;
}
.health-chip .led { width: 7px; height: 7px; border-radius: 50%; background: var(--faint); }
.health-chip.ok .led { background: var(--good); box-shadow: 0 0 8px var(--good); }
@@ -361,7 +362,7 @@ a.brand:hover .brand-sub { color: var(--accent-hi); }
/* backend log viewer */
.log-btn { background: var(--ink-2); border: 1px solid var(--line); border-radius: 20px; color: var(--muted);
- font-family: var(--font-mono); font-size: 11px; letter-spacing: .5px; padding: 3px 11px; cursor: pointer; }
+ font-size: 11px; padding: 3px 11px; cursor: pointer; }
.log-btn:hover { color: var(--accent-hi); background: var(--ink-3); }
.log-btn.active { color: var(--accent-hi); border-color: color-mix(in srgb, var(--accent) 45%, var(--line)); }
.log-panel { position: fixed; z-index: 9000; right: 14px; top: 48px; width: min(720px, 88vw); height: min(420px, 60vh);
@@ -402,9 +403,8 @@ a.brand:hover .brand-sub { color: var(--accent-hi); }
/* panel toggle tabs (timeline header, top-right) */
.dock-tabs { display: flex; gap: 4px; margin-left: 12px; flex: 0 0 auto; }
.dock-tab {
- background: var(--ink-2); border: 1px solid var(--line); border-radius: 6px;
- color: var(--faint); font-family: var(--font-mono); font-size: 10px; letter-spacing: .8px;
- text-transform: uppercase; padding: 4px 10px; cursor: pointer; transition: background .12s, color .12s, border-color .12s;
+ background: var(--ink-2); border: 1px solid var(--line); border-radius: 6px;
+ color: var(--faint); font-size: 11px; padding: 4px 10px; cursor: pointer; transition: background .12s, color .12s, border-color .12s;
}
.dock-tab:hover { color: var(--muted); border-color: var(--muted); }
.dock-tab.on { background: var(--accent-soft); color: var(--accent-hi); border-color: color-mix(in srgb, var(--accent) 45%, var(--line)); }
@@ -419,11 +419,11 @@ a.brand:hover .brand-sub { color: var(--accent-hi); }
border-bottom: 1px solid var(--line);
}
.zone-title {
- font-family: var(--font-mono); font-size: 11px; letter-spacing: 2px; text-transform: uppercase; color: var(--muted);
+ font-size: var(--fs-callout); line-height: var(--lh-callout); color: var(--text); font-weight: 600;
}
-.zone-title::before { content: ""; display: inline-block; width: 6px; height: 6px; margin-right: 9px; border-radius: 1px; background: var(--accent); transform: translateY(-1px); box-shadow: 0 0 7px var(--accent-soft); }
+.zone-title::before { content: ""; display: inline-block; width: 5px; height: 5px; margin-right: var(--sp-2); border-radius: 50%; background: var(--accent); transform: translateY(-1px); }
.transport-actions { display: flex; align-items: center; gap: 6px; flex: 0 0 auto; }
-.transport-actions .btn.compact { min-height: var(--ctl-h-sm); padding: 0 var(--sp-2); font-size: var(--fs-subhead); font-family: var(--font-mono); }
+.transport-actions .btn.compact { min-height: var(--ctl-h-sm); padding: 0 var(--sp-2); font-size: var(--fs-subhead); }
.transport-actions .btn:disabled { opacity: 0.42; pointer-events: none; cursor: default; }
.transport-actions .btn.render {
background: linear-gradient(var(--teal-2), var(--teal-3)); color: var(--on-teal); border-color: var(--teal-2);
@@ -433,7 +433,7 @@ a.brand:hover .brand-sub { color: var(--accent-hi); }
.zone-head-right { margin-left: auto; font-family: var(--font-mono); font-size: var(--fs-subhead);
color: var(--faint); display: flex; gap: var(--sp-1) var(--sp-2); align-items: center;
flex-wrap: wrap; justify-content: flex-end; min-width: 0; }
-.zone-body { flex: 1; overflow: auto; padding: 12px; min-height: 0; }
+.zone-body { flex: 1; overflow: auto; padding: var(--sp-4); min-height: 0; }
/* splitters */
.splitter { background: transparent; position: relative; flex: 0 0 auto; }
@@ -468,11 +468,12 @@ a.brand:hover .brand-sub { color: var(--accent-hi); }
.btn.tiny { min-height: var(--ctl-h-sm); padding: 0 var(--sp-2); font-size: var(--fs-subhead); }
label.field { display: block; margin-bottom: 12px; }
-label.field > span { display: block; font-family: var(--font-mono); font-size: 10px; letter-spacing: 1px; text-transform: uppercase; color: var(--faint); margin-bottom: 5px; }
+label.field > span { display: block; font-size: var(--fs-subhead); line-height: var(--lh-subhead);
+ color: var(--muted); margin-bottom: var(--sp-1); font-weight: 500; }
/* Field label with an inline enable toggle (e.g. Postfix). Matches the field-label look. */
.field-toggle { display: flex; align-items: center; gap: 6px; margin-bottom: 5px; cursor: pointer; }
.field-toggle > input { width: auto; margin: 0; }
-.field-toggle > span { font-family: var(--font-mono); font-size: 10px; letter-spacing: 1px; text-transform: uppercase; color: var(--faint); }
+.field-toggle > span { font-size: 11px; color: var(--faint); font-weight: 600;}
.field > textarea:disabled { opacity: .45; cursor: not-allowed; }
input, select, textarea {
width: 100%; background: var(--ink-0); border: 1px solid var(--line); border-radius: var(--ctl-r);
@@ -489,7 +490,9 @@ input[type=number] { font-family: var(--font-mono); }
/* ───────────────────────── MEDIA BROWSER ───────────────────────── */
.mb-section { margin-bottom: 18px; }
-.mb-section-title { display: flex; align-items: center; justify-content: space-between; font-family: var(--font-mono); font-size: 10px; letter-spacing: 1.5px; text-transform: uppercase; color: var(--faint); margin-bottom: 8px; }
+.mb-section-title { display: flex; align-items: center; justify-content: space-between;
+ font-size: var(--fs-subhead); line-height: var(--lh-subhead); color: var(--muted);
+ margin-bottom: var(--sp-2); font-weight: 600; }
.project-row { display: flex; align-items: center; gap: var(--sp-2); padding: 6px var(--sp-2); border-radius: var(--ctl-r); cursor: pointer; border: 1px solid transparent; }
.project-row:hover { background: var(--ink-2); }
.project-row.active { background: var(--accent-soft); border-color: color-mix(in srgb, var(--accent) 27%, transparent); }
@@ -616,7 +619,7 @@ input[type=number] { font-family: var(--font-mono); }
.media-bin-cols { display: flex; align-items: center; gap: 8px; }
.media-bin-cols-switch { flex: 1; min-width: 0; }
.media-bin-cols-switch .insp-seg { flex: 1; min-width: 0; padding-left: 4px; padding-right: 4px; }
-.media-bin-sort-lbl { flex: 0 0 auto; font-family: var(--font-mono); font-size: 9px; letter-spacing: .8px; text-transform: uppercase; color: var(--faint); }
+.media-bin-sort-lbl { flex: 0 0 auto; font-size: 11px; color: var(--faint); font-weight: 600;}
.media-bin-sort-sel { flex: 1; min-width: 0; padding: 5px 7px; font-size: 12px; }
.media-bin-count { margin: 0; }
.media-grid-empty { grid-column: 1 / -1; padding: 4px 2px; }
@@ -653,9 +656,9 @@ input[type=number] { font-family: var(--font-mono); }
/* library add/edit form */
.lib-form { border: 1px solid var(--accent); border-radius: 8px; background: var(--ink-2); padding: 10px; margin: 8px 0; display: flex; flex-direction: column; gap: 8px; }
.lib-form.lib-form-modal { border: none; background: transparent; padding: 0; margin: 0; }
-.lib-form-title { font-family: var(--font-mono); font-size: 10px; letter-spacing: 1px; text-transform: uppercase; color: var(--accent); }
+.lib-form-title { font-size: 11px; color: var(--accent); font-weight: 600;}
.lib-field { display: flex; flex-direction: column; gap: 3px; }
-.lib-field > span { font-family: var(--font-mono); font-size: 9px; letter-spacing: .8px; text-transform: uppercase; color: var(--faint); }
+.lib-field > span { font-size: 11px; color: var(--faint); font-weight: 600;}
.lib-in { width: 100%; background: var(--ink-0); border: 1px solid var(--line); border-radius: 6px; color: var(--text); font-size: 12px; padding: 5px 7px; font-family: var(--font-mono); resize: vertical; }
.lib-form-actions { display: flex; gap: 6px; }
@@ -772,14 +775,14 @@ input[type=number] { font-family: var(--font-mono); }
.pm-anchor-btn { font-size: 12px; white-space: nowrap; }
/* ───────────────────────── INSPECTOR ───────────────────────── */
-.insp-tag { font-family: var(--font-mono); font-size: 10px; letter-spacing: 1px; color: var(--accent); text-transform: uppercase; }
+.insp-tag { font-size: var(--fs-subhead); line-height: var(--lh-subhead); color: var(--muted); font-weight: 600; }
.insp-hint { font-size: 11px; color: var(--faint); margin: -4px 0 12px; }
.insp-hint.warn { color: var(--accent-hi, var(--accent)); }
.insp-block { margin-bottom: 16px; }
/* global prompt — first entry in the inspector list (scrolls with content) */
.insp-global { margin: 0 0 14px; padding: 10px 10px 8px;
background: var(--ink-2); border: 1px solid var(--line); border-radius: 9px; }
-.insp-global-title { font-family: var(--font-mono); font-size: 10px; letter-spacing: 1px; text-transform: uppercase; color: var(--accent); margin-bottom: 6px; }
+.insp-global-title { font-size: 11px; color: var(--accent); margin-bottom: 6px; font-weight: 600;}
.insp-global-ta { width: 100%; background: var(--ink-0); border: 1px solid var(--line); border-radius: 7px;
color: var(--text); font-size: 12px; padding: 7px 8px; resize: vertical; font-family: var(--font-mono); line-height: 1.45; }
.insp-global .insp-hint { margin: 6px 0 0; }
@@ -787,7 +790,7 @@ input[type=number] { font-family: var(--font-mono); }
/* project / scene switch */
.insp-switch { display: flex; gap: 0; margin-bottom: 14px; border: 1px solid var(--line); border-radius: 8px; overflow: hidden; }
.insp-seg { flex: 1; background: var(--ink-1); border: none; color: var(--muted); padding: 7px 0; cursor: pointer;
- font-family: var(--font-mono); font-size: 11px; letter-spacing: 1px; text-transform: uppercase; transition: background .12s, color .12s; }
+ font-size: 11px; transition: background .12s, color .12s; }
.insp-seg + .insp-seg { border-left: 1px solid var(--line); }
.insp-seg:hover:not(:disabled) { background: var(--ink-3); color: var(--accent-hi); }
.insp-seg.active { background: var(--accent-soft); color: var(--accent-hi); }
@@ -863,8 +866,8 @@ input[type=number] { font-family: var(--font-mono); }
.tl-toolbar .btn.ghost:disabled:hover { color: var(--muted); background: transparent; border-color: transparent; }
.tl-toolbar .tl-spacer { flex: 1; }
.tl-tc { font-family: var(--font-mono); font-size: 11px; color: var(--muted); }
-.tl-zlabel { font-family: var(--font-mono); font-size: 10px; letter-spacing: 1px; text-transform: uppercase; color: var(--faint); }
-.tl-keys { font-family: var(--font-mono); font-size: 10px; letter-spacing: .5px; color: var(--faint); margin-right: 10px; opacity: .8; }
+.tl-zlabel { font-size: 11px; color: var(--faint); font-weight: 600;}
+.tl-keys { font-size: 11px; color: var(--faint); margin-right: 10px; opacity: .8; }
.tl-notice { margin-left: 12px; font-size: 11px; color: var(--accent-hi); display: inline-flex; align-items: center; gap: 6px; }
.tl-notice-dismiss { border: none; background: transparent; color: var(--faint); cursor: pointer; font-size: 11px; padding: 0 2px; }
.tl-notice-dismiss:hover { color: var(--text); }
@@ -930,9 +933,8 @@ input[type=number] { font-family: var(--font-mono); }
position: sticky; top: 0; z-index: 9; background: var(--ink-1); }
.tl-gutter-tracks { flex: 0 0 auto; display: flex; flex-direction: column; }
.tl-gutter-lane { display: flex; align-items: center; justify-content: center; text-align: center;
- padding: 0 4px; font-family: var(--font-mono); font-size: 9px; letter-spacing: .5px; text-transform: uppercase;
- color: var(--faint); border-bottom: 1px solid var(--line-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
- box-sizing: border-box; flex: 0 0 auto; }
+ padding: 0 4px; font-size: 11px; color: var(--faint); border-bottom: 1px solid var(--line-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
+ box-sizing: border-box; flex: 0 0 auto; }
.tl-gutter-lane.video { color: var(--faint); }
.tl-gutter-aud { flex: 0 0 auto; border-top: 1px solid var(--line); }
.tl-gutter-aud .tl-gutter-lane:last-child { border-bottom: none; }
@@ -1056,7 +1058,7 @@ input[type=number] { font-family: var(--font-mono); }
.ov-form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.ov-field { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.ov-field.full { grid-column: 1 / -1; }
-.ov-label { font-size: 11px; font-family: var(--font-mono); letter-spacing: .06em; text-transform: uppercase; color: var(--muted); }
+.ov-label { font-size: 11px; color: var(--muted); font-weight: 600;}
.ov-input, .ov-textarea { width: 100%; box-sizing: border-box; border: 1px solid var(--line); border-radius: 10px; background: var(--ink-2); color: var(--text); padding: 10px 12px; font: inherit; }
.ov-textarea { min-height: 72px; resize: vertical; line-height: 1.35; }
.ov-input:focus, .ov-textarea:focus, .ov-color:focus { outline: none; border-color: color-mix(in srgb, var(--violet) 45%, var(--line)); box-shadow: 0 0 0 3px color-mix(in srgb, var(--violet) 12%, transparent); }
@@ -1142,7 +1144,7 @@ body.tl-spacing { cursor: grabbing; user-select: none; }
background: var(--surface-raised-7); border: 1px solid var(--line); border-radius: 8px; padding: 8px;
display: flex; flex-direction: column; gap: 7px; box-shadow: 0 8px 22px color-mix(in srgb, var(--shadow-ink) 64.7%, transparent); }
.tl-dd-panel[hidden] { display: none; }
-.tl-dd-head { font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: .04em; }
+.tl-dd-head { font-size: 11px; color: var(--muted); font-weight: 600;}
.tl-dd-sel { width: 100%; }
.tl-dd-val { display: flex; align-items: center; justify-content: space-between; gap: 8px; font-size: 12px; }
.tl-dd-val[hidden] { display: none; }
@@ -1212,10 +1214,10 @@ body.tl-spacing { cursor: grabbing; user-select: none; }
.clip.selected.focus.clip-video::before,
.clip.selected.focus.src-v2v::before { background: var(--teal); opacity: 1; }
.tl-sel-badge {
- font-family: var(--font-mono); font-size: 10px; font-weight: 600; color: var(--teal);
- background: color-mix(in srgb, var(--teal) 12%, transparent);
- border: 1px solid color-mix(in srgb, var(--teal-soft) 55%, var(--line));
- border-radius: 10px; padding: 2px 8px; white-space: nowrap; flex-shrink: 0;
+ font-size: 11px; font-weight: 600; color: var(--teal);
+ background: color-mix(in srgb, var(--teal) 12%, transparent);
+ border: 1px solid color-mix(in srgb, var(--teal-soft) 55%, var(--line));
+ border-radius: 10px; padding: 2px 8px; white-space: nowrap; flex-shrink: 0;
}
.clip-src-stack { display: inline-flex; align-items: center; gap: 1px; line-height: 1; }
.clip-anchor-mark { font-size: 11px; color: var(--accent); }
@@ -1336,59 +1338,10 @@ body.tl-spacing { 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%, color-mix(in srgb, var(--accent) 8%, transparent) 0%, transparent 55%),
- radial-gradient(700px 400px at 80% 80%, color-mix(in srgb, var(--teal) 6%, transparent) 0%, transparent 50%),
- var(--backdrop-2);
- 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, var(--surface-raised-6) 0%, var(--surface-deep-3) 100%);
- box-shadow: var(--shadow), 0 0 0 1px var(--overlay-1) inset;
- animation: pop .18s ease;
-}
-.welcome-brand {
- display: flex;
- align-items: baseline;
- gap: 10px;
- flex-wrap: wrap;
- margin-bottom: 12px;
-}
-.welcome-mark { color: var(--accent); font-size: 13px; filter: drop-shadow(0 0 8px var(--accent-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: 13px;
- line-height: 1.55;
- max-width: 42em;
-}
+/* ── project-menu buttons (Easy Gen) ──────────────────────────────────────
+ The Editor's welcome card is gone — the full-screen wizard splash is the only
+ start screen now. Easy Gen's project menu still uses these button styles. */
.welcome-actions { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
-.welcome-maint { margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--line); }
-.welcome-maint .welcome-btn { padding-top: 8px; padding-bottom: 8px; }
.welcome-btn {
display: flex;
flex-direction: column;
@@ -1425,7 +1378,6 @@ body.tl-spacing { cursor: grabbing; user-select: none; }
.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) ───────────────────────── */
@@ -1455,7 +1407,7 @@ body.tl-spacing { cursor: grabbing; user-select: none; }
.composer { background: var(--ink-0); border: 1px solid var(--line); border-radius: 10px; padding: 14px; margin-bottom: 18px; }
.composer.flash { animation: composer-flash .9s ease; }
@keyframes composer-flash { 0%, 100% { box-shadow: none; border-color: var(--line); } 25% { box-shadow: 0 0 0 2px var(--accent); border-color: var(--accent); } }
-.composer-title { font-family: var(--font-mono); font-size: 10px; letter-spacing: 1.5px; text-transform: uppercase; color: var(--accent); margin-bottom: 10px; }
+.composer-title { font-size: 11px; color: var(--accent); margin-bottom: 10px; font-weight: 600;}
.composer-row { display: flex; gap: 10px; align-items: center; }
.composer-row select { flex: 1; }
.composer-row .composer-search { flex: 1.2; min-width: 0; }
@@ -1487,8 +1439,8 @@ body.tl-spacing { cursor: grabbing; user-select: none; }
/* config validation banner */
/* Pipeline requirements panel */
.req-panel { margin-bottom: 18px; }
-.req-title { font-family: var(--font-mono); font-size: 10px; letter-spacing: 1.5px; text-transform: uppercase; color: var(--teal); margin-bottom: 8px; }
-.req-opt-head { font-size: 10px; color: var(--faint); margin: 8px 0 4px; text-transform: uppercase; letter-spacing: 1px; }
+.req-title { font-size: 11px; color: var(--teal); margin-bottom: 8px; font-weight: 600;}
+.req-opt-head { font-size: 11px; color: var(--faint); margin: 8px 0 4px; font-weight: 600;}
.req-row { display: flex; align-items: flex-start; gap: 8px; padding: 6px 10px; border-radius: 6px; margin-bottom: 4px; font-size: 12px; flex-wrap: wrap; }
.req-row.ok { background: color-mix(in srgb, var(--teal) 8%, transparent); }
.req-row.missing { background: color-mix(in srgb, var(--danger) 8%, transparent); border: 1px solid color-mix(in srgb, var(--danger) 30%, transparent); }
@@ -1581,7 +1533,7 @@ label.field.linked input, label.field.linked select { opacity: .6; }
.chip-x:hover { color: var(--danger); }
.wire-box { margin-top: 12px; padding-top: 10px; border-top: 1px dashed var(--line); }
-.wire-title { font-family: var(--font-mono); font-size: 10px; letter-spacing: 1.5px; text-transform: uppercase; color: var(--teal); margin-bottom: 8px; }
+.wire-title { font-size: 11px; color: var(--teal); margin-bottom: 8px; font-weight: 600;}
.wire-row { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; min-width: 0; }
.wire-out { font-family: var(--font-mono); font-size: 11px; color: var(--muted); flex: 0 0 auto; min-width: 130px; }
.wire-arrow { color: var(--teal); }
@@ -1659,7 +1611,7 @@ label.field.linked input, label.field.linked select { opacity: .6; }
.insp-fold-sum { cursor: pointer; font-size: 12px; color: var(--muted); padding: 8px 0; list-style: none; }
.insp-fold-sum::-webkit-details-marker { display: none; }
.engine-preset-bar { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-bottom: 12px; }
-.engine-preset-label { font-family: var(--font-mono); font-size: 10px; text-transform: uppercase; color: var(--faint); }
+.engine-preset-label { font-size: 11px; color: var(--faint); font-weight: 600;}
.slot-picker-overlay { z-index: 12000; }
.slot-picker-modal { max-width: 420px; }
.slot-picker-list { display: flex; flex-direction: column; gap: 6px; margin: 12px 0; max-height: 50vh; overflow-y: auto; }
@@ -1779,8 +1731,8 @@ body.tour-mode { overflow: hidden; }
.composer-btn {
display: inline-flex; align-items: center; gap: 6px;
background: var(--ink-2); border: 1px solid var(--line); border-radius: 6px;
- color: var(--faint); font-family: var(--font-mono); font-size: 10px; letter-spacing: .8px;
- text-transform: uppercase; padding: 4px 10px; cursor: pointer; transition: background .12s, color .12s, border-color .12s;
+ color: var(--faint); font-size: var(--fs-subhead); line-height: var(--lh-subhead);
+ padding: 4px var(--sp-2); cursor: pointer; transition: background .12s, color .12s, border-color .12s;
}
.composer-btn:hover { color: var(--muted); border-color: var(--muted); }
.composer-btn .composer-dot { font-size: 9px; color: var(--teal); transform: translateY(-.5px); }
@@ -1811,7 +1763,7 @@ body.tour-mode { overflow: hidden; }
.fw-title { display: flex; align-items: baseline; gap: 9px; min-width: 0; }
.fw-dot { color: var(--teal); font-size: 9px; }
.fw-title-text { font-family: var(--font-display); font-weight: 700; font-size: 13px; letter-spacing: .2px; }
-.fw-sub { font-family: var(--font-mono); font-size: 10px; letter-spacing: 1px; text-transform: uppercase; color: var(--faint); }
+.fw-sub { font-size: 11px; color: var(--faint); font-weight: 600;}
.fw-ctrls { margin-left: auto; display: flex; gap: 4px; }
.fw-btn {
width: 24px; height: 24px; display: grid; place-items: center; padding: 0;
@@ -2397,6 +2349,24 @@ body.tour-mode { overflow: hidden; }
.oo-pane-body { flex: 1; min-height: 0; display: flex; flex-direction: column; overflow: auto; }
.oo-pane-body > * { min-height: 0; }
+.oo-projects {
+ display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
+ gap: var(--sp-2); padding: var(--sp-3) 0; align-content: start;
+}
+.oo-project {
+ display: flex; align-items: center; gap: var(--sp-3); text-align: left;
+ padding: var(--sp-3); border: 1px solid var(--line); border-radius: 10px;
+ background: var(--ink-1); color: var(--text); font-family: inherit; cursor: pointer;
+ transition: border-color .12s, background .12s;
+}
+.oo-project:hover { border-color: color-mix(in srgb, var(--accent) 50%, var(--line)); background: var(--ink-2); }
+.oo-project-main { min-width: 0; }
+.oo-project-name {
+ font-size: var(--fs-body); line-height: var(--lh-body); font-weight: 600;
+ overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
+}
+.oo-project-meta { font-size: var(--fs-subhead); line-height: var(--lh-subhead); color: var(--faint); margin-top: 2px; }
+
@media (max-width: 700px) {
.oo-root { padding: 60px 20px 128px; }
.oo-splash-title { font-size: 40px; line-height: 46px; letter-spacing: -.8px; }
diff --git a/movie_editor/frontend/welcome.js b/movie_editor/frontend/welcome.js
deleted file mode 100644
index 05215483..00000000
--- a/movie_editor/frontend/welcome.js
+++ /dev/null
@@ -1,109 +0,0 @@
-// 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() {
- close();
- const u = window.TourSandbox?.tourUrl?.() || (window.location.pathname + "?mode=tour");
- window.location.href = u;
- }
-
- 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", "Interactive walkthrough (demo project)", false, welcomeTour),
- );
- card.append(actions);
-
- // Update / Switch branch / Restart, reachable without first loading a project — a
- // branch swap shouldn't mean opening a montage you didn't want just to reach the menu.
- card.append(window.FunPackGit.maintenanceRow("welcome-maint"));
-
- overlay.append(card);
- document.body.append(overlay);
- }
-
- window.WelcomePage = { open, close, isOpen: () => !!overlay, openTour: welcomeTour };
-})();