A single-file AI chatbot built specifically for iPad Mini 1st Gen (iOS 9.3.6). Most modern AI chat interfaces rely on APIs and JavaScript features that Safari on iOS 9 does not support. This project works around every one of those limitations.
Why This Exists · Visual Structure · Architecture Flow · Quick Start · Screenshots · Guides · License
iOS 9.3.6 Safari is a 2016-era WebKit browser. It lacks:
fetch()APIconst/let/ arrow functions (ES6+)async/await/ native Promises- Full CSS Grid and CSS custom properties
This chatbot is written entirely in ES5 JavaScript using standard XMLHttpRequest SSE-streaming, -webkit- prefixed CSS styles, and zero dependencies — no bundler, no npm, no build step.
LegacyChat is designed as a standalone static application. The project is organized as follows:
legacychat/
├── index.html # The main codebase containing all HTML structure, CSS layouts, and JS logic
├── LICENSE # Standard MIT legal terms
├── README.md # General project index and setup guidelines
├── CHANGELOG.md # Record of changes and feature releases
├── COMPONENTS.md # In-depth technical guide to index.html functions
├── API.md # Details of OpenRouter API schema & ES5 SSE-streaming
├── ONBOARDING.md # Installation and developer workflows
├── CONTRIBUTING.md # Guidelines for suggestions and PR edits
└── Screenshots/ # Visual assets depicting different screens of the app
├── Chat.png
├── SetupScreen.png
├── ChatHistory.png
├── Models.png
├── Persona.png
└── LightTheme.png
The diagrams below demonstrate how UI components interact with local states, browser canvas, and OpenRouter completion APIs:
flowchart TD
UI[HTML Form / Chat UI] -->|User Input| Actions[Message Actions: Edit / Regen / Delete]
UI -->|Image Selection| Canvas[Hidden Canvas compression]
Canvas -->|Optimized Base64| State[Global JavaScript State Variables]
Actions -->|Update Thread| State
State -->|XMLHttpRequest Streaming| OR[OpenRouter completion endpoint]
OR -->|SSE Raw Chunks| XHR[XHR state change listener]
XHR -->|formatMarkdown & sanitizeHTML| Bubble[Dynamic Chat Bubble Render]
Bubble -->|Update View| UI
State -->|Sync with cleanup triggers| Storage[(Browser Local Storage)]
Unlike modern applications, LegacyChat has no backend and does not use .env files because all requests are sent directly from Safari. Storing keys on a server is not needed, and secrets are managed directly in the client browser.
- API Key (
api_keyin localStorage): The credentials used to authorize completions with OpenRouter. Entered securely in the Setup screen. - Model Selector (
modelin localStorage): Slugs pointing to active models (e.g.anthropic/claude-3.5-sonnet). - Temperature (
tempin localStorage): Controls response variance (0.0 for deterministic factual codes, 2.0 for highly creative answers). - Max Tokens (
tokensin localStorage): Controls the hard limit for output tokens per generation call.
Safari imposes a strict 5MB boundary on localStorage. LegacyChat works around this by:
- Compressing Uploaded Photos: Images are resized on a hidden canvas down to maximum 800x800 resolution and compiled as a 60% quality JPEG before saving to Base64.
- Dynamic Cleanup: When saving conversations to storage, historical images from previous threads are cleared and replaced with a tiny placeholder SVG to ensure settings and texts never exceed the 5MB quota.
- Go to openrouter.ai and create a free account
- Navigate to Keys → Create Key and copy the key (starts with
sk-or-v1-...)
- Create a new GitHub repository (can be private)
- Upload
index.htmlto the root of the repo - Go to vercel.com → Add New Project → import your repo
- Click Deploy (Vercel automatically detects the static
index.html)
- Open Safari on your device and navigate to your Vercel URL.
- (Optional) Tap the share icon → Add to Home Screen for a standalone app-like experience.
| Chat History | AI Models |
|---|---|
![]() |
![]() |
| Persona Selection | Theme (Light Mode) |
|---|---|
![]() |
![]() |
To drill down into specific development and architectural features, check out these guides:
- 🛠️ Developer Onboarding Guide: Step-by-step instructions on setting up tunnels and testing Safari 9 configurations.
- 🧩 Code Component Deep-Dive: Complete list of globals, styles, and functions like
formatMarkdown(). - 🔌 API & Streaming Manual: Explanation of standard
XMLHttpRequestSSE-streaming algorithms. - 🤝 Contribution Guidelines: Details on suggesting features, coding styles, and submitting PRs.
- 📜 Changelog: History of feature updates, XSS fixes, and release milestones.
This project is licensed under the MIT License (see LICENSE).
- Allows: Commercial use, modifications, distribution, private use, and sublicensing.
- Requires: Including the copyright notice and license text in any distributions.
- Prevents: Holding the authors liable for any damages or code warranties.





