/ references / interactive-prototype.md
interactive-prototype.md
1 # Interactive Prototype 2 3 > **Load when:** Building a multi-screen interactive prototype with navigation, state, or transitions 4 > **Skip when:** Single-screen static design, slide deck, or landing page 5 > **Why it matters:** Multi-screen prototypes need routing, state management, and transition patterns that single-screen templates don't provide 6 > **Typical failure it prevents:** Hardcoding all screens visible at once, no navigation, broken state on screen switches, no transition feel 7 8 ## Delivery Format Decision (Ask First) 9 10 Multi-screen app prototypes have two standard delivery formats. **Ask the user which one they want** before starting — don't default to one and build blindly. 11 12 | Format | When to use | Implementation | 13 |--------|-------------|----------------| 14 | **Overview Tiled** (design review default) | User wants to see full picture / compare layouts / review design consistency / multiple screens side-by-side | **All screens tiled side-by-side**, each in its own independent iPhone frame, content complete, no clickability needed | 15 | **Flow Demo Single-Device** | User wants to demonstrate a specific user flow (e.g., onboarding, purchase funnel) | Single iPhone, embedded `AppPhone` state manager, tab bar / buttons / annotation points all clickable | 16 17 ### Routing Keywords 18 19 - Task contains "tiled / show all pages / overview / take a look / compare / all screens" → **Overview** 20 - Task contains "demo flow / user path / walk through / clickable / interactive demo" → **Flow demo** 21 - Not sure? Ask. Don't default to flow demo (it's more work, not all tasks need it) 22 23 ### Overview Tiled Skeleton 24 25 Each screen is an independent IosFrame side-by-side: 26 27 ```jsx 28 <div style={{display: 'flex', gap: 32, flexWrap: 'wrap', padding: 48, alignItems: 'flex-start'}}> 29 {screens.map(s => ( 30 <div key={s.id}> 31 <div style={{fontSize: 13, color: '#666', marginBottom: 8, fontStyle: 'italic'}}>{s.label}</div> 32 <IosFrame> 33 <ScreenComponent data={s} /> 34 </IosFrame> 35 </div> 36 ))} 37 </div> 38 ``` 39 40 ### Flow Demo Skeleton 41 42 Single clickable state machine: 43 44 ```jsx 45 function AppPhone({ initial = 'today' }) { 46 const [screen, setScreen] = React.useState(initial); 47 const [modal, setModal] = React.useState(null); 48 // Render different ScreenComponent based on screen, pass onEnter/onClose/onTabChange/onOpen props 49 } 50 ``` 51 52 Screen components receive callback props (`onEnter`, `onClose`, `onTabChange`, `onOpen`, `onAnnotation`), don't hardcode state. TabBar, buttons, artwork cards get `cursor: pointer` + hover feedback. 53 54 ## Navigation Patterns 55 56 Choose based on the app type. Each pattern includes a React + Babel implementation snippet. 57 58 ### Tab Bar (mobile apps, 3-5 main sections) 59 60 ```jsx 61 function TabBar({ tabs, activeTab, onTabChange }) { 62 return React.createElement('nav', { 63 style: { display: 'flex', justifyContent: 'space-around', padding: '8px 0', borderTop: '1px solid var(--border)' } 64 }, tabs.map((tab, i) => 65 React.createElement('button', { 66 key: i, onClick: () => onTabChange(i), 67 style: { flex: 1, textAlign: 'center', opacity: activeTab === i ? 1 : 0.5, background: 'none', border: 'none', cursor: 'pointer' } 68 }, tab.label) 69 )); 70 } 71 72 // Usage: state-driven screen switching 73 const [activeTab, setActiveTab] = React.useState(0); 74 ``` 75 76 ### Sidebar (desktop apps, dashboards, 5+ sections) 77 78 ```jsx 79 function Sidebar({ items, active, onSelect }) { 80 return React.createElement('aside', { 81 style: { width: '240px', height: '100vh', padding: '16px', background: 'var(--surface)', borderRight: '1px solid var(--border)' } 82 }, items.map((item, i) => 83 React.createElement('button', { 84 key: i, onClick: () => onSelect(i), 85 style: { display: 'block', width: '100%', padding: '12px', textAlign: 'left', background: active === i ? 'var(--accent-light)' : 'none', border: 'none', cursor: 'pointer', borderRadius: '6px', marginBottom: '4px' } 86 }, item.label) 87 )); 88 } 89 ``` 90 91 ### Wizard / Step flow (forms, onboarding, checkout) 92 93 ```jsx 94 function Wizard({ steps, currentStep, onStepChange }) { 95 const total = steps.length; 96 return React.createElement('div', null, 97 React.createElement('div', { 98 style: { display: 'flex', gap: '8px', marginBottom: '24px' } 99 }, steps.map((s, i) => 100 React.createElement('div', { 101 key: i, 102 style: { flex: 1, height: '4px', borderRadius: '2px', background: i <= currentStep ? 'var(--accent)' : 'var(--border)' } 103 }) 104 )), 105 steps[currentStep], 106 React.createElement('div', { style: { display: 'flex', gap: '12px', marginTop: '24px' } }, 107 currentStep > 0 && React.createElement('button', { onClick: () => onStepChange(currentStep - 1), style: { padding: '8px 16px', borderRadius: '6px', border: '1px solid var(--border)', cursor: 'pointer' } }, 'Back'), 108 currentStep < total - 1 && React.createElement('button', { onClick: () => onStepChange(currentStep + 1), style: { padding: '8px 16px', borderRadius: '6px', background: 'var(--accent)', color: 'var(--surface)', border: 'none', cursor: 'pointer' } }, 'Next') 109 ) 110 ); 111 } 112 ``` 113 114 ## State Management 115 116 For prototypes, keep state simple. Use these patterns based on complexity: 117 118 | Complexity | Pattern | When to use | 119 |------------|---------|-------------| 120 | 2-3 screens | `useState` for active screen index | Tab bar, simple nav | 121 | 5+ screens | `useReducer` with screen + form state | Wizards, multi-step forms | 122 | URL sync needed | `hash routing` | When back button should work, or sharing specific screen URLs | 123 124 ### Hash routing snippet 125 126 ```js 127 const [screen, setScreen] = React.useState(location.hash.slice(1) || 'home'); 128 React.useEffect(() => { 129 const onHash = () => setScreen(location.hash.slice(1) || 'home'); 130 window.addEventListener('hashchange', onHash); 131 return () => window.removeEventListener('hashchange', onHash); 132 }, []); 133 // Navigate: location.hash = '#settings' 134 ``` 135 136 ## Transitions 137 138 ### Page transition (fade + slide) 139 140 ```jsx 141 function PageTransition({ children, direction = 'forward' }) { 142 return React.createElement('div', { 143 style: { 144 animation: direction === 'forward' ? 'slideIn 0.3s ease-out' : 'slideBack 0.3s ease-out', 145 } 146 }, children); 147 } 148 149 // Add to <style>: 150 // @keyframes slideIn { from { opacity: 0; transform: translateX(20px); } to { opacity: 1; transform: translateX(0); } } 151 // @keyframes slideBack { from { opacity: 0; transform: translateX(-20px); } to { opacity: 1; transform: translateX(0); } } 152 ``` 153 154 ### Micro-interaction (button press feedback) 155 156 ```css 157 .interactive:hover { transform: scale(1.02); transition: transform 0.15s ease; } 158 .interactive:active { transform: scale(0.98); } 159 ``` 160 161 ## Prototype Structure Template 162 163 A complete multi-screen prototype follows this pattern: 164 165 ```html 166 <!DOCTYPE html> 167 <html> 168 <head> 169 <script src="https://unpkg.com/react@18/umd/react.production.min.js"></script> 170 <script src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script> 171 <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script> 172 <style>/* CSS variables + animations */</style> 173 </head> 174 <body> 175 <div id="root"></div> 176 <script type="text/babel"> 177 // Navigation component (tab/sidebar/wizard) 178 // Screen components (ScreenA, ScreenB, ScreenC) 179 // App component with state management 180 ReactDOM.createRoot(document.getElementById('root')).render(<App />); 181 </script> 182 </body> 183 </html> 184 ```