/ 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  ```