Add Welcome screen on Cutting Room first load.

Show a four-action landing page on boot instead of auto-opening the latest project, with New, Open Latest, Load, and a placeholder Welcome Tour.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
digital-garbage
2026-06-12 14:20:22 +03:00
co-authored by Cursor
parent 09e951cf81
commit 160cb733f9
4 changed files with 200 additions and 3 deletions
+1
View File
@@ -118,6 +118,7 @@
<script src="sampler_panel.js"></script>
<script src="engine_settings.js"></script>
<script src="slot_picker.js"></script>
<script src="welcome.js"></script>
<script src="inspector.js"></script>
<script src="rating_picker.js"></script>
<script src="waveform.js"></script>
+6 -3
View File
@@ -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 = {
+90
View File
@@ -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; } }
+103
View File
@@ -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 };
})();