// Quill Keyboard — reusable iPhone mockup with the in-keyboard AI toolbar. // Props: w (screen width px), kind 'ai'|'standard', popup bool, draft text, label. const KB_ROWS_EN = [ 'q w e r t y u i o p'.split(' '), 'a s d f g h j k l'.split(' '), 'z x c v b n m'.split(' '), ]; // the action chips shown on the AI bar (subset of the library) const BAR_ACTIONS = ['Summarize', 'Fix grammar', 'Make formal', 'Shorten', 'Translate', 'Explain']; const BAR_TABS = [ { name: 'Quick', icon: 'sparkles' }, { name: 'Writing', icon: 'pen-line' }, { name: 'Translate', icon: 'languages' }, { name: 'Clipboard', icon: 'clipboard' }, ]; function StatusBar({ dark }) { const c = dark ? '#fff' : '#1D1D1F'; return (
9:41
); } function Bubble({ side, children, tint }) { const me = side === 'me'; return (
{children}
); } // the floating result popup ("lives where the text is") function ResultPopup({ bottom = 330 }) { return (
Summarize Claude Sonnet

Following up on the proposal — checking the timing still works for your team.

); } function Key({ children, grow = 1, wide, dark, accent, icon }) { return (
{icon ? : children}
); } // the AI toolbar that sits above the keys — the differentiator function AIBar() { return (
{/* tab strip */}
{BAR_TABS.map((t, i) => (
{(i === 0 || i === 3) ? t.name : null}
))}
{/* action chips */}
{BAR_ACTIONS.map(n => { const a = ACTIONS.find(x => x.name === n) || ACTIONS[0]; return (
{n}
); })}
); } // the predictive bar a normal iOS keyboard shows function PredictiveBar() { const sugg = ['the', 'I', 'we']; return (
{sugg.map((s, i) => (
{s}
))}
); } function Keyboard({ kind }) { const isAI = kind === 'ai'; return (
{isAI ?
: }
{/* row 1 */}
{KB_ROWS_EN[0].map(k => {k})}
{/* row 2 (inset) */}
{KB_ROWS_EN[1].map(k => {k})}
{/* row 3 */}
{KB_ROWS_EN[2].map(k => {k})}
{/* row 4 */}
123
space
{/* home indicator */}
); } function MessagesApp({ draft, kind }) { return (
{/* nav bar */}
Alice
{/* thread */}
Hey! Are we still on for the proposal recap? yes — putting the follow-up together now Amazing, thank you 🙏 of course How did the call with their team go? really well actually they liked the phased rollout idea love that No rush at all. We just need the timing confirmed for the team before Friday. totally, makes sense Did you ever send that recap over? almost done sending in a sec
{/* compose bar */}
{draft}|
); } function AppHeader({ title, right = 'Done' }) { return (
Settings {title} {right}
); } function QuickSlotsScreen() { const slots = ['Summarize', 'Fix grammar', 'Translate', 'Make formal', 'Shorten', 'Command', 'Explain', null]; return (

Pin up to eight favorite actions to the Quick tab for one-tap access.

{slots.map((n, i) => { if (!n) return (
Add
); const a = ACTIONS.find(x => x.name === n) || ACTIONS[0]; return (
{n}
); })}
); } function Toggle({ on }) { return (
); } function Row({ icon, tint, deepIcon, title, sub, trailing, last }) { return (
{icon && }
{title}
{sub &&
{sub}
}
{trailing}
); } function Group({ title, children }) { return (
{title}
{children}
); } function SettingsScreen() { return (
} />
Pulled live from Anthropic, so new models appear automatically.
Connected} last /> } /> } last /> } /> } last />
); } function Phone({ w = 348, kind = 'ai', screen = 'messages', popup = false, draft = 'hey, following up on the proposal we talked about — wanted to check the timing still works for you and the team', label }) { const h = Math.round(w * 2.12); const wrapRef = useRef(null); const [zoom, setZoom] = useState(1); useEffect(() => { const fit = () => { const parent = wrapRef.current && wrapRef.current.parentElement; if (!parent) return; const avail = parent.clientWidth; const devW = w + 24; setZoom(avail < devW ? Math.max(0.5, avail / devW) : 1); }; fit(); window.addEventListener('resize', fit); return () => window.removeEventListener('resize', fit); }, [w]); return (
{/* screen */}
{/* dynamic island */}
{screen === 'messages' ? ( <> {popup && } ) : screen === 'quickslots' ? : }
); } Object.assign(window, { Phone, Keyboard, MessagesApp, StatusBar, QuickSlotsScreen, SettingsScreen });