diff --git a/public/css/workflow.css b/public/css/workflow.css index a1dd47cd3..b5b4d7705 100644 --- a/public/css/workflow.css +++ b/public/css/workflow.css @@ -28,16 +28,35 @@ .command-manual { display: flex; flex-direction: column; - gap: var(--spacing-lg); + gap: var(--spacing-sm); padding-bottom: 20vh; } +.command-category-header { + font-family: var(--font-display); + font-size: 0.875rem; + font-weight: 600; + color: var(--color-accent); + text-transform: uppercase; + letter-spacing: 0.1em; + padding: var(--spacing-lg) var(--spacing-lg) var(--spacing-sm); + margin-top: var(--spacing-md); + border-bottom: 1px solid var(--color-mist); +} + +.command-category-header:first-child { + margin-top: 0; +} + .manual-entry { + position: relative; padding: var(--spacing-lg); + padding-left: calc(var(--spacing-lg) + 16px); /* Reserve space for transform */ border-left: 2px solid var(--color-mist); - transition: all 0.4s var(--ease-out); + transition: border-color 0.4s var(--ease-out), opacity 0.4s var(--ease-out), background 0.4s var(--ease-out), transform 0.4s var(--ease-out); opacity: 0.4; cursor: pointer; + transform: translateX(-16px); } .manual-entry:hover { @@ -47,7 +66,7 @@ .manual-entry.active { border-left-color: var(--color-accent); opacity: 1; - padding-left: var(--spacing-xl); + transform: translateX(0); background: linear-gradient(to right, var(--color-bg), transparent); } @@ -66,11 +85,38 @@ margin: 0; } +.manual-cmd-rel { + font-size: 0.8125rem; + color: var(--color-ash); + margin-top: var(--spacing-sm); + display: flex; + align-items: center; + gap: 0.5ch; + flex-wrap: wrap; +} + +.manual-cmd-rel .rel-icon { + color: var(--color-accent); + font-weight: 600; +} + +.manual-cmd-rel code { + font-family: var(--font-mono); + font-size: 0.75rem; + background: var(--color-mist); + padding: 2px 6px; + border-radius: 3px; + color: var(--color-ink); +} + /* Right: The Terminal (Sticky) */ .glass-terminal-wrapper { position: sticky; top: var(--spacing-lg); - height: 600px; + bottom: var(--spacing-lg); + height: calc(100vh - var(--spacing-lg) * 2); + max-height: 800px; + min-height: 500px; } @media (max-width: 900px) { @@ -123,13 +169,14 @@ .terminal-body { flex: 1; - padding: var(--spacing-lg); + padding: var(--spacing-md); font-family: var(--font-mono); font-size: 0.9375rem; color: var(--color-ink); overflow-y: auto; display: flex; flex-direction: column; + min-height: 0; /* Allow flex shrinking */ } .terminal-line { @@ -160,14 +207,125 @@ white-space: pre-wrap; } +/* Hide steps when viewport is short */ +@media (max-height: 800px) { + .terminal-output { + display: none; + } +} + +.terminal-cmd { + color: var(--color-accent); + font-weight: 600; +} + +.terminal-step { + color: var(--color-charcoal); +} + +.terminal-done { + color: var(--color-success, #22c55e); + font-weight: 500; +} + .terminal-preview { border: 1px solid var(--color-mist); border-radius: 4px; background: var(--color-paper); - padding: var(--spacing-md); - margin: var(--spacing-md) 0; + margin: var(--spacing-sm) 0; flex: 1; + min-height: 0; /* Allow shrinking */ overflow: auto; } +.terminal-cursor-line { + flex-shrink: 0; + margin-top: var(--spacing-sm) !important; +} + +.terminal-preview .demo-container { + display: flex; + flex-direction: column; + height: 100%; +} + +.terminal-preview .demo-header { + flex-shrink: 0; + padding: 0; +} + +.terminal-preview .demo-viewport { + flex: 1; + display: flex; + align-items: flex-start; + justify-content: center; + padding: var(--spacing-md); + min-height: 0; + max-height: 320px; + overflow: auto; + overscroll-behavior: contain; +} + +.terminal-preview .demo-viewport > * { + margin-block: auto; /* Center vertically when content is shorter than container */ +} + +.terminal-preview .demo-caption { + flex-shrink: 0; + font-size: 0.75rem; + color: var(--color-ash); + text-align: center; + padding: var(--spacing-sm) var(--spacing-md); +} + +/* Demo toggle in terminal */ +.terminal-preview .demo-toggle { + display: flex; + align-items: center; + justify-content: center; + gap: var(--spacing-sm); +} + +.terminal-preview .demo-toggle-switch { + width: 36px; + height: 20px; + background: var(--color-mist); + border-radius: 10px; + position: relative; + cursor: pointer; + transition: background 0.2s ease; +} + +.terminal-preview .demo-toggle-switch::after { + content: ''; + position: absolute; + top: 1px; + left: 1px; + width: 16px; + height: 16px; + background: white; + border-radius: 50%; + box-shadow: 0 1px 3px rgba(0,0,0,0.2); + transition: transform 0.2s ease; +} + +.terminal-preview .demo-toggle-switch.active { + background: var(--color-accent); +} + +.terminal-preview .demo-toggle-switch.active::after { + transform: translateX(16px); +} + +.terminal-preview .demo-toggle-label { + font-size: 0.75rem; + color: var(--color-ash); + transition: color 0.2s ease; +} + +.terminal-preview .demo-toggle-label.active { + color: var(--color-ink); + font-weight: 500; +} + @keyframes blink { 50% { opacity: 0; } } diff --git a/public/js/components/glass-terminal.js b/public/js/components/glass-terminal.js index 687d1c932..07f1d29f2 100644 --- a/public/js/components/glass-terminal.js +++ b/public/js/components/glass-terminal.js @@ -1,5 +1,6 @@ import { renderCommandDemo } from "../demo-renderer.js"; import { setupCommandDemoToggles } from "../demo-toggles.js"; +import { commandProcessSteps, commandCategories, commandRelationships } from "../data.js"; export function initGlassTerminal() { // Initial setup if needed @@ -8,11 +9,38 @@ export function initGlassTerminal() { export function renderTerminalLayout(commands) { const container = document.querySelector('.commands-gallery'); if (!container) return; - + + // Group commands by category + const categoryOrder = ['diagnostic', 'quality', 'intensity', 'adaptation', 'enhancement', 'system']; + const categoryLabels = { + 'diagnostic': 'Diagnose', + 'quality': 'Quality', + 'intensity': 'Intensity', + 'adaptation': 'Adaptation', + 'enhancement': 'Enhancement', + 'system': 'System' + }; + + const grouped = {}; + commands.forEach(cmd => { + const cat = commandCategories[cmd.id] || 'other'; + if (!grouped[cat]) grouped[cat] = []; + grouped[cat].push(cmd); + }); + + // Build manual with category headers + let manualHTML = ''; + categoryOrder.forEach(cat => { + if (grouped[cat] && grouped[cat].length > 0) { + manualHTML += `
${categoryLabels[cat] || cat}
`; + manualHTML += grouped[cat].map(cmd => renderManualEntry(cmd)).join(''); + } + }); + container.innerHTML = `
- ${commands.map(cmd => renderManualEntry(cmd)).join('')} + ${manualHTML}
@@ -35,7 +63,7 @@ export function renderTerminalLayout(commands) { `; setupScrollSpy(commands); - + // Select first command initially if (commands.length > 0) { updateTerminal(commands[0], document.getElementById('terminal-content'), commands); @@ -45,10 +73,24 @@ export function renderTerminalLayout(commands) { } function renderManualEntry(cmd) { + const relationship = commandRelationships[cmd.id]; + let relationshipHTML = ''; + + if (relationship) { + if (relationship.pairs) { + relationshipHTML = `
pairs with /${relationship.pairs}
`; + } else if (relationship.leadsTo && relationship.leadsTo.length > 0) { + relationshipHTML = `
leads to ${relationship.leadsTo.map(c => `/${c}`).join(', ')}
`; + } else if (relationship.combinesWith && relationship.combinesWith.length > 0) { + relationshipHTML = `
+ combines with ${relationship.combinesWith.map(c => `/${c}`).join(', ')}
`; + } + } + return `

/${cmd.id}

${cmd.description}

+ ${relationshipHTML}
`; } @@ -56,15 +98,15 @@ function renderManualEntry(cmd) { function setupScrollSpy(commands) { const entries = document.querySelectorAll('.manual-entry'); const terminalContent = document.getElementById('terminal-content'); - - // Intersection Observer for Scroll Spy - const observer = new IntersectionObserver((entries) => { - entries.forEach(entry => { + + // Intersection Observer for Scroll Spy - trigger around vertical center + const observer = new IntersectionObserver((observerEntries) => { + observerEntries.forEach(entry => { if (entry.isIntersecting) { // Update active state document.querySelectorAll('.manual-entry').forEach(e => e.classList.remove('active')); entry.target.classList.add('active'); - + // Update terminal const cmdId = entry.target.dataset.id; const cmd = commands.find(c => c.id === cmdId); @@ -74,15 +116,26 @@ function setupScrollSpy(commands) { } }); }, { - threshold: 0.5, - rootMargin: "-20% 0px -60% 0px" + threshold: 0.1, + rootMargin: "-35% 0px -55% 0px" // Trigger zone biased toward upper-middle of viewport }); entries.forEach(e => observer.observe(e)); - - // Click interaction + + // Click interaction - immediate selection + smooth scroll entries.forEach(e => { e.addEventListener('click', () => { + // Immediately update state + document.querySelectorAll('.manual-entry').forEach(el => el.classList.remove('active')); + e.classList.add('active'); + + const cmdId = e.dataset.id; + const cmd = commands.find(c => c.id === cmdId); + if (cmd) { + updateTerminal(cmd, terminalContent, commands); + } + + // Then scroll into view e.scrollIntoView({ behavior: 'smooth', block: 'center' }); }); }); @@ -90,30 +143,19 @@ function setupScrollSpy(commands) { function updateTerminal(cmd, container, allCommands) { if (!cmd || !container) return; - - // Content update - const content = ` -
- - vibe run ${cmd.id} -
-
- > Analyzing design context...
- > Applying ${cmd.id} transformation...
- > Done. -
-
- ${renderCommandDemo(cmd.id)} -
-
- - -
- `; - - container.innerHTML = content; - + + // Get process steps for this command (or use generic fallback) + const steps = commandProcessSteps[cmd.id] || ['Analyze', 'Transform', 'Verify']; + const stepsOutput = steps.map((step, i) => + `${i + 1}. ${step}...` + ).join('
'); + + // Content update - no extra indentation to avoid whitespace + container.innerHTML = `
/${cmd.id}
+
${stepsOutput}
✓ Complete
+
${renderCommandDemo(cmd.id)}
+
`; + // Re-bind toggles - // We pass a no-op for the callback because scrolling handles selection now - setupCommandDemoToggles(allCommands, () => {}); + setupCommandDemoToggles(allCommands, () => {}); } diff --git a/public/js/demos/commands/adapt.js b/public/js/demos/commands/adapt.js new file mode 100644 index 000000000..6ce4f66a8 --- /dev/null +++ b/public/js/demos/commands/adapt.js @@ -0,0 +1,80 @@ +// Adapt command demo - shows desktop-only design becoming responsive +export default { + id: 'adapt', + caption: 'Fixed layout → Responsive across devices', + + before: ` +
+
+
+
+
+
+
Text too small...
+
+
+
Mobile
+
Broken ✗
+
+
+
+
+
+
+
+
+
+
+
+
Desktop
+
Works ✓
+
+
+ `, + + after: ` +
+
+
+
+
+
+
+
+
+
Mobile
+
Stacked ✓
+
+
+
+
+
+
+
+
+
+
+
+
Tablet
+
2-col ✓
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Desktop
+
Sidebar ✓
+
+
+ ` +}; diff --git a/public/js/demos/commands/animate.js b/public/js/demos/commands/animate.js index fc355c805..bbcdebe6d 100644 --- a/public/js/demos/commands/animate.js +++ b/public/js/demos/commands/animate.js @@ -1,36 +1,41 @@ -// Animate command demo - shows static page becoming choreographed +// Animate command demo - shows static elements becoming choreographed export default { id: 'animate', - caption: 'Static page → Choreographed page load', - - // This demo uses CSS animations triggered by data-state attribute - // The before/after states are the same HTML, animation is CSS-driven + caption: 'Static layout → Choreographed entrance', + before: ` -
-
Hero
-
-
-
-
-
+
+
+
+
+
+
+
+
`, - - after: null, // Uses CSS animation, no HTML change needed - - // Custom toggle handler for animation re-triggering - onToggle: (viewport, isAfter) => { - viewport.dataset.state = isAfter ? 'after' : 'before'; - if (isAfter) { - // Re-trigger animation by cloning the element - const page = viewport.querySelector('.motion-demo-page'); - if (page) { - const clone = page.cloneNode(true); - page.parentNode.replaceChild(clone, page); + + after: ` +
+
+
+
+
+
+
+
+
+
+ + ` }; diff --git a/public/js/demos/commands/audit.js b/public/js/demos/commands/audit.js new file mode 100644 index 000000000..32ef0d4e7 --- /dev/null +++ b/public/js/demos/commands/audit.js @@ -0,0 +1,40 @@ +// Audit command demo - shows scattered issues being identified and flagged +export default { + id: 'audit', + caption: 'Hidden issues → Identified problems with recommendations', + + before: ` +
+
+
Welcome message
+ +
+
+
Featured Item
+
+
+ + +
+
+ `, + + after: ` +
+
+
CONTRAST
+
Welcome message
+ +
+
+
READABILITY
+
Featured Item
+
+
+
TOUCH TARGET
+ + +
+
+ ` +}; diff --git a/public/js/demos/commands/clarify.js b/public/js/demos/commands/clarify.js new file mode 100644 index 000000000..f56bbacfa --- /dev/null +++ b/public/js/demos/commands/clarify.js @@ -0,0 +1,36 @@ +// Clarify command demo - shows confusing UX copy becoming clear +export default { + id: 'clarify', + caption: 'Confusing copy → Clear, actionable language', + + before: ` +
+
+
Processing Status
+
Your request is being processed. Please wait while we complete the operation. This may take some time depending on various factors.
+
+
+
⚠️ Warning
+
Proceeding with this action may result in irreversible consequences to your data and settings configuration.
+
+ +
+ `, + + after: ` +
+
+
Saving changes...
+
About 10 seconds remaining
+
+
+
+
+
+
Delete this project?
+
This will permanently delete 23 files. You can't undo this.
+
+ +
+ ` +}; diff --git a/public/js/demos/commands/colorize.js b/public/js/demos/commands/colorize.js new file mode 100644 index 000000000..39081e08c --- /dev/null +++ b/public/js/demos/commands/colorize.js @@ -0,0 +1,56 @@ +// Colorize command demo - shows monochrome becoming strategically colored +export default { + id: 'colorize', + caption: 'Monochrome UI → Strategic, harmonious color', + + before: ` +
+
TASK OVERVIEW
+
+
+
+ Design review +
+
+
+ Update copy +
+
+
+ Final QA +
+
+
+
Progress: 1 of 3
+
+
+ `, + + after: ` +
+
TASK OVERVIEW
+
+
+
+ Design review +
+
+
+ Update copy +
+
+
+ Final QA +
+
+
+
+
+
+
+ 1/3 +
+
+
+ ` +}; diff --git a/public/js/demos/commands/delight.js b/public/js/demos/commands/delight.js new file mode 100644 index 000000000..c69d6f94c --- /dev/null +++ b/public/js/demos/commands/delight.js @@ -0,0 +1,53 @@ +// Delight command demo - shows functional UI gaining moments of joy +export default { + id: 'delight', + caption: 'Functional feedback → Moment of joy', + + before: ` +
+
+
Milestone reached
+
100
+
tasks completed
+
+ +
+ `, + + after: ` +
+
+ 🎊 + + 🎉 + + 🎊 +
+
+
Milestone unlocked!
+
100
+
tasks completed
+
+
+ 🏆 + Centurion Badge Earned +
+
+ + ` +}; diff --git a/public/js/demos/commands/extract.js b/public/js/demos/commands/extract.js new file mode 100644 index 000000000..7ff712276 --- /dev/null +++ b/public/js/demos/commands/extract.js @@ -0,0 +1,62 @@ +// Extract command demo - shows patterns being identified and documented +export default { + id: 'extract', + caption: 'Scattered styles → Documented design tokens', + + before: ` +
+
+ padding: 12px 16px; +
+
+ padding: 18px 24px; +
+
+ color: #3b82f6; +
+
+ color: #3a80f5; +
+
+ font-size: 14px; +
+
+ `, + + after: ` +
+
Design Tokens
+
+
SPACING
+
+
+
+ sm +
+
+
+ md +
+
+
+ lg +
+
+
+
+
COLORS
+
+
+
+
+
+
+
+
+
TYPOGRAPHY
+
Display
+
Body text
+
+
+ ` +}; diff --git a/public/js/demos/commands/harden.js b/public/js/demos/commands/harden.js new file mode 100644 index 000000000..a21c2811b --- /dev/null +++ b/public/js/demos/commands/harden.js @@ -0,0 +1,42 @@ +// Harden command demo - shows error handling and edge cases +export default { + id: 'harden', + caption: 'Fragile UI → Robust error handling', + + before: ` +
+
+
⚠️
+
Error
+
Something went wrong
+ +
+
+
Form Input
+ +
+
+ `, + + after: ` +
+
+
+
+ ! +
+
+
Connection failed
+
Unable to reach the server. Check your internet connection and try again.
+ +
+
+
+
+
Email Address
+ +
Please enter a complete email address
+
+
+ ` +}; diff --git a/public/js/demos/commands/index.js b/public/js/demos/commands/index.js index 7fef11e41..131c459ee 100644 --- a/public/js/demos/commands/index.js +++ b/public/js/demos/commands/index.js @@ -3,11 +3,35 @@ import animate from "./animate.js"; import bolder from "./bolder.js"; import normalize from "./normalize.js"; +import audit from "./audit.js"; +import polish from "./polish.js"; +import optimize from "./optimize.js"; +import harden from "./harden.js"; +import clarify from "./clarify.js"; +import quieter from "./quieter.js"; +import simplify from "./simplify.js"; +import colorize from "./colorize.js"; +import delight from "./delight.js"; +import extract from "./extract.js"; +import adapt from "./adapt.js"; +import onboard from "./onboard.js"; export const commandDemos = { normalize, bolder, animate, + audit, + polish, + optimize, + harden, + clarify, + quieter, + simplify, + colorize, + delight, + extract, + adapt, + onboard, }; export function getCommandDemo(commandId) { diff --git a/public/js/demos/commands/onboard.js b/public/js/demos/commands/onboard.js new file mode 100644 index 000000000..de4be1952 --- /dev/null +++ b/public/js/demos/commands/onboard.js @@ -0,0 +1,31 @@ +// Onboard command demo - shows empty state becoming helpful onboarding +export default { + id: 'onboard', + caption: 'Empty state → Guided onboarding experience', + + before: ` +
+
📁
+
No items found
+
+ `, + + after: ` +
+
+ +
+
Create your first project
+
Projects help you organize your work. Start with a template or blank canvas.
+
+ + +
+
+
Need help? Watch a quick tutorial →
+
+
+ ` +}; diff --git a/public/js/demos/commands/optimize.js b/public/js/demos/commands/optimize.js new file mode 100644 index 000000000..937368c3c --- /dev/null +++ b/public/js/demos/commands/optimize.js @@ -0,0 +1,57 @@ +// Optimize command demo - shows performance improvements +export default { + id: 'optimize', + caption: 'Heavy, slow UI → Lightweight, performant', + + before: ` +
+
+
BUNDLE SIZE
+
+
+
+
847 KB
+
+
+
RENDER TIME
+
+
+
+
2.4s
+
+
+
LAYOUT SHIFTS
+
+
+
+
CLS: 0.18
+
+
+ `, + + after: ` +
+
+
BUNDLE SIZE
+
+
+
+
124 KB (-85%)
+
+
+
RENDER TIME
+
+
+
+
0.3s (-88%)
+
+
+
LAYOUT SHIFTS
+
+
+
+
CLS: 0.02 (excellent)
+
+
+ ` +}; diff --git a/public/js/demos/commands/polish.js b/public/js/demos/commands/polish.js new file mode 100644 index 000000000..9fc970317 --- /dev/null +++ b/public/js/demos/commands/polish.js @@ -0,0 +1,33 @@ +// Polish command demo - shows rough UI becoming refined +export default { + id: 'polish', + caption: 'Rough edges → Refined, pixel-perfect details', + + before: ` +
+
User Profile
+
+
+
+
John Doe
+
Developer
+
+
+ +
+ `, + + after: ` +
+
User Profile
+
+
JD
+
+
John Doe
+
Developer
+
+
+ +
+ ` +}; diff --git a/public/js/demos/commands/quieter.js b/public/js/demos/commands/quieter.js new file mode 100644 index 000000000..0e6250d2c --- /dev/null +++ b/public/js/demos/commands/quieter.js @@ -0,0 +1,28 @@ +// Quieter command demo - shows loud design becoming calm +export default { + id: 'quieter', + caption: 'Overwhelming design → Calm, focused interface', + + before: ` +
+
🎉✨🚀
+
AMAZING DEAL!
+
Don't miss out on this INCREDIBLE opportunity!!!
+
+ + +
+
+ `, + + after: ` +
+
Limited Time Offer
+
Save 20% on annual plans. Offer ends Friday.
+
+ + +
+
+ ` +}; diff --git a/public/js/demos/commands/simplify.js b/public/js/demos/commands/simplify.js new file mode 100644 index 000000000..27c026c16 --- /dev/null +++ b/public/js/demos/commands/simplify.js @@ -0,0 +1,47 @@ +// Simplify command demo - shows cluttered UI becoming minimal +export default { + id: 'simplify', + caption: 'Cluttered interface → Essential elements only', + + before: ` +
+
+ Dashboard +
+ + + + +
+
+
+
Revenue
$12,345
+
Users
1,234
+
Growth
+12%
+
Bounce
32%
+
+
📊 View detailed analytics →
+
🎯 Set up goals →
+
+ `, + + after: ` +
+
This Month
+
+
$12,345
+
↑ 12% from last month
+
+
+
+
1,234
+
Active users
+
+
+
68%
+
Retention
+
+
+
+ ` +}; diff --git a/source/patterns.md b/source/patterns.md index 54844f5e0..c8d017138 100644 --- a/source/patterns.md +++ b/source/patterns.md @@ -68,7 +68,7 @@ antipatterns: items: - Don't animate without purpose - Don't use durations over 500ms for UI feedback - - Don't animate layout properties (use transform) + - Don't animate layout properties (width, height, padding, margin) - they trigger reflows and can cause text wrapping mid-animation; use transform instead - Don't ignore prefers-reduced-motion - Don't use animation to hide slow loading - name: Interaction