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:
co-authored by
Cursor
parent
09e951cf81
commit
160cb733f9
@@ -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>
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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; } }
|
||||
|
||||
@@ -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 };
|
||||
})();
|
||||
Reference in New Issue
Block a user