From 34fe8183b67af00deaa3d26a2b170efae7b98c3b Mon Sep 17 00:00:00 2001 From: Iisyourdad Date: Wed, 10 Jun 2026 20:03:18 -0500 Subject: [PATCH] welcome screen --- app/renderer/app.js | 55 ++++++++++++++++++++++++++- app/renderer/style.css | 85 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 137 insertions(+), 3 deletions(-) diff --git a/app/renderer/app.js b/app/renderer/app.js index 07b56ec..f8d781c 100644 --- a/app/renderer/app.js +++ b/app/renderer/app.js @@ -6,13 +6,14 @@ const dialogs = window.StepForgeDialogs || {}; class StepForgeApp { constructor() { this.view = document.getElementById('view'); + this.topbar = document.getElementById('topbar'); this.topbarContext = document.getElementById('topbar-context'); this.searchInput = document.getElementById('global-search'); this.captureStatus = document.getElementById('capture-status'); this.homeBtn = document.getElementById('btn-home'); this.state = { - view: 'library', + view: 'welcome', query: '', folderFilter: 'all', library: { guides: [], folders: [], guideFolders: {} }, @@ -24,9 +25,11 @@ class StepForgeApp { this.libraryRenderToken = 0; this.view.innerHTML = ` +
`; + this.welcomeHost = document.getElementById('welcome-host'); this.libraryHost = document.getElementById('library-host'); this.editorHost = document.getElementById('editor-host'); @@ -87,7 +90,7 @@ class StepForgeApp { async init() { await this.refreshData(); this.updateCaptureState(await api.capture.state()); - this.renderLibrary(); + this.renderWelcome(); } async refreshData() { @@ -123,12 +126,44 @@ class StepForgeApp { setView(view) { this.state.view = view; + this.topbar.classList.toggle('hidden', view === 'welcome'); + this.welcomeHost.classList.toggle('hidden', view !== 'welcome'); this.libraryHost.classList.toggle('hidden', view !== 'library'); this.editorHost.classList.toggle('hidden', view !== 'editor'); this.searchInput.classList.toggle('hidden', view !== 'library'); this.renderTopbar(); } + renderWelcome() { + this.setView('welcome'); + clearNode(this.welcomeHost); + this.welcomeHost.append( + el('section.welcome-screen', {}, + el('div.welcome-copy', {}, + el('div.welcome-kicker', {}, 'Offline capture workspace'), + el('h1', {}, 'StepForge'), + el('p', {}, + 'Start a new capture, open an existing workspace, or adjust settings before you begin.'), + ), + el('div.welcome-spacer', {}), + el('div.welcome-actions', {}, + el('button.primary', { + type: 'button', + onClick: () => this.startNewCapture(), + }, 'New Capture'), + el('button', { + type: 'button', + onClick: () => this.showLibrary(), + }, 'Existing Workspace'), + el('button', { + type: 'button', + onClick: () => this.openSettings(), + }, 'settings'), + ), + ), + ); + } + async showLibrary(reason = null) { this.editor.setActive(false); this.setView('library'); @@ -182,6 +217,7 @@ class StepForgeApp { renderTopbar() { clearNode(this.topbarContext); + if (this.state.view === 'welcome') return; if (this.state.view === 'library') { this.topbarContext.append( el('button', { type: 'button', onClick: () => this.createGuide() }, 'New'), @@ -448,6 +484,21 @@ class StepForgeApp { await this.openGuide(guide.guideId); } + async startNewCapture() { + const title = await dialogs.promptText({ + title: 'New Capture', + label: 'Title', + value: 'Untitled guide', + placeholder: 'Untitled guide', + }); + if (title == null) return; + const guide = await api.library.create({ title: title.trim() || 'Untitled guide' }); + await this.refreshLibrary(); + await this.openGuide(guide.guideId); + await api.capture.session({ action: 'start', guideId: guide.guideId }); + this.updateCaptureState(await api.capture.state()); + } + async createFolder() { const name = await dialogs.promptText({ title: 'New folder', label: 'Folder name', value: '' }); if (name == null || !name.trim()) return; diff --git a/app/renderer/style.css b/app/renderer/style.css index 7ffddca..2824750 100644 --- a/app/renderer/style.css +++ b/app/renderer/style.css @@ -53,6 +53,7 @@ body { #app { display: flex; flex-direction: column; height: 100vh; } #view { flex: 1; min-height: 0; display: flex; } +#view > div { flex: 1; min-width: 0; min-height: 0; } .hidden { display: none !important; } .muted { color: var(--muted); font-size: 12px; } @@ -153,6 +154,89 @@ kbd { color: #fff; } +#welcome-host { + position: relative; + display: flex; + overflow: hidden; + background: + radial-gradient(circle at top left, rgba(0, 104, 255, 0.14), transparent 32%), + radial-gradient(circle at bottom right, rgba(0, 104, 255, 0.09), transparent 28%), + linear-gradient(180deg, color-mix(in srgb, var(--panel-solid) 84%, transparent), color-mix(in srgb, var(--bg) 92%, var(--panel-solid) 8%)); +} +.welcome-screen { + position: relative; + width: 100%; + min-height: 100%; + display: flex; + flex-direction: column; + justify-content: space-between; + padding: 40px clamp(24px, 4vw, 56px) 30px; + isolation: isolate; +} +.welcome-screen::before { + content: ''; + position: absolute; + inset: 18px 18px auto auto; + width: min(36vw, 340px); + height: min(36vw, 340px); + border-radius: 50%; + background: radial-gradient(circle, rgba(0, 104, 255, 0.16), transparent 68%); + filter: blur(8px); + pointer-events: none; + z-index: 0; +} +.welcome-copy { + position: relative; + z-index: 1; + max-width: 760px; +} +.welcome-kicker { + margin-bottom: 18px; + font-size: 11px; + font-weight: 700; + letter-spacing: 0.22em; + text-transform: uppercase; + color: var(--accent-strong); +} +.welcome-copy h1 { + margin: 0; + max-width: 13ch; + font-size: clamp(40px, 6vw, 72px); + line-height: 0.94; + letter-spacing: -0.05em; +} +.welcome-copy p { + margin: 16px 0 0; + max-width: 46rem; + color: var(--muted); + font-size: 16px; + line-height: 1.6; +} +.welcome-spacer { + flex: 1; + min-height: 24px; +} +.welcome-actions { + position: relative; + z-index: 1; + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 12px; + width: min(100%, 980px); + align-self: stretch; +} +.welcome-actions button { + min-height: 64px; + border-radius: 16px; + padding: 14px 18px; + font-size: 15px; + font-weight: 650; + box-shadow: var(--shadow); +} +.welcome-actions button.primary { + box-shadow: 0 18px 38px rgba(0, 104, 255, 0.22); +} + .library, .editor { flex: 1; min-height: 0; display: flex; } .lib-side { @@ -670,4 +754,3 @@ fieldset legend { border: 0; border-top: 1px solid var(--border); } -