diff --git a/app/renderer/app.js b/app/renderer/app.js index 07b56ec..88555e0 100644 --- a/app/renderer/app.js +++ b/app/renderer/app.js @@ -12,7 +12,7 @@ class StepForgeApp { this.homeBtn = document.getElementById('btn-home'); this.state = { - view: 'library', + view: 'welcome', query: '', folderFilter: 'all', library: { guides: [], folders: [], guideFolders: {} }, @@ -24,9 +24,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'); @@ -61,7 +63,7 @@ class StepForgeApp { }); this.homeBtn.addEventListener('click', () => { - if (this.state.view === 'editor') this.showLibrary(); + if (this.state.view !== 'welcome') this.showWelcome(); }); document.addEventListener('keydown', (e) => { @@ -85,9 +87,18 @@ class StepForgeApp { } async init() { - await this.refreshData(); - this.updateCaptureState(await api.capture.state()); - this.renderLibrary(); + this.renderWelcome(); + try { + await this.refreshData(); + } catch (err) { + console.error(err); + } + try { + this.updateCaptureState(await api.capture.state()); + } catch (err) { + console.error(err); + } + if (this.state.view === 'welcome') this.renderWelcome(); } async refreshData() { @@ -123,12 +134,38 @@ class StepForgeApp { setView(view) { this.state.view = view; + 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(); } + async showWelcome() { + this.editor.setActive(false); + this.setView('welcome'); + this.renderWelcome(); + } + + async openExistingWorkspace() { + try { + await this.refreshData(); + } catch (err) { + console.error(err); + } + this.state.query = ''; + this.searchInput.value = ''; + this.state.folderFilter = 'all'; + this.setView('library'); + this.renderLibrary(); + } + + async startNewCapture() { + const guide = await api.library.create({ title: 'Untitled capture' }); + await this.openGuide(guide.guideId); + await api.capture.session({ action: 'start', guideId: guide.guideId }); + } + async showLibrary(reason = null) { this.editor.setActive(false); this.setView('library'); @@ -182,6 +219,9 @@ 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'), @@ -205,6 +245,30 @@ class StepForgeApp { ); } + renderWelcome() { + this.setView('welcome'); + clearNode(this.welcomeHost); + + const body = el('div.welcome', + {}, + el('div.welcome-hero', + {}, + el('div.welcome-kicker', {}, 'Local guide capture'), + el('h1', {}, 'StepForge'), + el('p', {}, 'Offline guide capture and export for local workspaces.'), + ), + el('div.welcome-actions', + {}, + el('button.primary.welcome-action', { type: 'button', onClick: () => this.startNewCapture() }, 'New Capture'), + el('button.welcome-action', { type: 'button', onClick: () => this.openExistingWorkspace() }, 'Existing Workspace'), + el('button.welcome-action', { type: 'button', onClick: () => this.openSettings() }, 'Settings'), + ), + ); + + this.welcomeHost.append(body); + this.renderTopbar(); + } + async renderLibrary() { this.setView('library'); this.editor.setActive(false); diff --git a/app/renderer/style.css b/app/renderer/style.css index 7ffddca..ae9b005 100644 --- a/app/renderer/style.css +++ b/app/renderer/style.css @@ -53,6 +53,14 @@ body { #app { display: flex; flex-direction: column; height: 100vh; } #view { flex: 1; min-height: 0; display: flex; } +#welcome-host, +#library-host, +#editor-host { + flex: 1; + min-width: 0; + min-height: 0; + display: flex; +} .hidden { display: none !important; } .muted { color: var(--muted); font-size: 12px; } @@ -155,6 +163,63 @@ kbd { .library, .editor { flex: 1; min-height: 0; display: flex; } +.welcome { + flex: 1; + min-height: 0; + display: flex; + flex-direction: column; + justify-content: space-between; + padding: clamp(28px, 7vh, 76px) clamp(28px, 6vw, 92px) clamp(24px, 8vh, 72px); +} +.welcome-hero { + width: min(760px, 100%); + margin: clamp(32px, 12vh, 132px) auto 0; + text-align: center; +} +.welcome-kicker { + margin-bottom: 12px; + color: var(--muted); + font-size: 12px; + font-weight: 650; + letter-spacing: 0.08em; + text-transform: uppercase; +} +.welcome-hero h1 { + margin: 0; + font-size: clamp(44px, 5vw, 72px); + line-height: 1.03; + letter-spacing: -0.04em; +} +.welcome-hero p { + margin: 16px auto 0; + max-width: 560px; + color: var(--muted); + font-size: 15px; +} +.welcome-actions { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 14px; + width: min(940px, 100%); + margin: 0 auto; +} +.welcome-action { + min-height: 60px; + padding: 15px 18px; + border-radius: 16px; + font-size: 15px; + box-shadow: var(--shadow); +} +.welcome-actions .primary { + background: var(--accent); + border-color: var(--accent); + color: var(--accent-fg); +} +.welcome-actions .primary:hover { + background: var(--accent-strong); + border-color: var(--accent-strong); +} + .lib-side { width: 248px; min-width: 248px; @@ -671,3 +736,15 @@ fieldset legend { border-top: 1px solid var(--border); } +@media (max-width: 820px) { + .welcome { + padding: 24px 20px 28px; + } + .welcome-hero { + margin-top: 72px; + } + .welcome-actions { + grid-template-columns: 1fr; + width: min(440px, 100%); + } +}