diff --git a/apps/web/public/logo-lockup.svg b/apps/web/public/logo-lockup.svg
new file mode 100644
index 0000000..48ee162
--- /dev/null
+++ b/apps/web/public/logo-lockup.svg
@@ -0,0 +1,49 @@
+
diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx
index af2952b..71228de 100644
--- a/apps/web/src/App.tsx
+++ b/apps/web/src/App.tsx
@@ -1,6 +1,6 @@
import { useState, useEffect } from "react";
import { Routes, Route } from "react-router-dom";
-import { Sidebar, Topbar, CommandPalette } from "./components/index.js";
+import { Sidebar, Topbar, CommandPalette, SplashScreen } from "./components/index.js";
import { ExportDialog } from "./features/export/ExportDialog.js";
import { ToastViewport } from "@pubscript/ui";
import Write from "./routes/Write.js";
@@ -42,6 +42,7 @@ export default function App() {
return (
+
setPaletteOpen(true)} onOpenExport={() => setExportOpen(true)} />
diff --git a/apps/web/src/components/SplashScreen.tsx b/apps/web/src/components/SplashScreen.tsx
new file mode 100644
index 0000000..1db38df
--- /dev/null
+++ b/apps/web/src/components/SplashScreen.tsx
@@ -0,0 +1,38 @@
+import { useEffect, useState } from "react";
+import { useProjectStore } from "../store/project-store.js";
+
+/**
+ * Full-screen brand splash shown while the project store is loading
+ * (`status !== "ready"`). Uses the vertical logo lockup so the app opens
+ * with a polished brand moment before the writing UI appears. Fades out
+ * gracefully once the store is ready.
+ */
+export default function SplashScreen() {
+ const status = useProjectStore((s) => s.status);
+ const [exiting, setExiting] = useState(false);
+
+ useEffect(() => {
+ if (status === "ready" && !exiting) {
+ // Fade out, then unmount once the 300ms exit animation finishes.
+ const t = setTimeout(() => setExiting(true), 300);
+ return () => clearTimeout(t);
+ }
+ }, [status, exiting]);
+
+ if (status === "ready" && exiting) return null;
+
+ return (
+
+

+
+ {status === "loading" ? "Loading project…" : "Initializing…"}
+
+
+ );
+}
diff --git a/apps/web/src/components/index.ts b/apps/web/src/components/index.ts
index 074d47c..9cd91e6 100644
--- a/apps/web/src/components/index.ts
+++ b/apps/web/src/components/index.ts
@@ -5,3 +5,4 @@ export type { TopbarProps } from "./Topbar.js";
export { CommandPalette } from "./CommandPalette.js";
export type { CommandPaletteProps } from "./CommandPalette.js";
export { Settings } from "./Settings.js";
+export { default as SplashScreen } from "./SplashScreen.js";
diff --git a/apps/web/src/main.tsx b/apps/web/src/main.tsx
index 9762f96..af8f338 100644
--- a/apps/web/src/main.tsx
+++ b/apps/web/src/main.tsx
@@ -9,6 +9,7 @@ import "./styles/plot.css";
import "./styles/timeline.css";
import "./styles/whiteboard.css";
import "./styles/export.css";
+import "./styles/splash.css";
createRoot(document.getElementById("root")!).render(
diff --git a/apps/web/src/styles/splash.css b/apps/web/src/styles/splash.css
new file mode 100644
index 0000000..faf0b54
--- /dev/null
+++ b/apps/web/src/styles/splash.css
@@ -0,0 +1,63 @@
+/* ============================================================================
+ * SplashScreen — full-screen brand overlay shown while the project store loads.
+ * ============================================================================ */
+
+.ps-splash {
+ position: fixed;
+ inset: 0;
+ z-index: 1000;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: 28px;
+ background: var(--ps-bg);
+ color: var(--ps-text);
+ animation: ps-splash-fade-in 0.35s ease-out both;
+}
+
+.ps-splash-logo {
+ height: 30vh;
+ max-height: 360px;
+ width: auto;
+ animation: ps-splash-appear 0.6s cubic-bezier(0.22, 1, 0.36, 1) both;
+}
+
+.ps-splash-status {
+ font-family: var(--ps-font-ui);
+ font-size: 15px;
+ letter-spacing: 0.04em;
+ color: var(--ps-text-muted);
+ animation: ps-splash-pulse 1.4s ease-in-out infinite;
+}
+
+@keyframes ps-splash-appear {
+ from {
+ opacity: 0;
+ transform: translateY(12px) scale(0.96);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0) scale(1);
+ }
+}
+
+@keyframes ps-splash-fade-in {
+ from { opacity: 0; }
+ to { opacity: 1; }
+}
+
+@keyframes ps-splash-pulse {
+ 0%, 100% { opacity: 0.55; }
+ 50% { opacity: 1; }
+}
+
+/* Fade the overlay out once the app is ready to avoid a jarring cut. */
+.ps-splash.ps-splash-exit {
+ animation: ps-splash-disappear 0.3s ease-in forwards;
+}
+
+@keyframes ps-splash-disappear {
+ from { opacity: 1; }
+ to { opacity: 0; pointer-events: none; }
+}