A zero-dependency, single-file AI chatbot crafted for vintage WebKit browsers and legacy hardware.
Tested and optimized for iPad Mini 1st Gen (iOS 9.3.6), older mobile Safari, and retro web engines.
Overview • The Engineering Challenge • Key Features • Quick Start • Architecture • Screenshots • Troubleshooting & FAQ • Documentation Hub • License
Modern web applications increasingly demand cutting-edge JavaScript features (fetch(), async/await, ES6 modules, CSS Grid, and custom properties) that fail completely on pre-2017 mobile browsers. Consequently, millions of capable vintage tablets—such as the iPad Mini 1st Generation (Apple A5, iOS 9.3.6)—are excluded from experiencing modern Large Language Model interfaces.
LegacyChat solves this problem entirely.
Contained within a single standalone file (index.html), LegacyChat delivers a state-of-the-art AI chat experience with glassmorphism aesthetics, live token-by-token streaming, Markdown parsing, multimodal camera attachments, and exportable chat history—without requiring Node.js, Webpack, Babel, npm dependencies, or intermediate proxy servers.
Important
Single-File Zero-Dependency Guarantee
Everything needed to run LegacyChat lives inside index.html. Open the file locally in any web browser or drop it onto any static web host to begin immediately.
Safari on iOS 9.3.6 runs a 2016-era WebKit engine with hard architectural limits: dual-core 1.0 GHz Apple A5 processor, 512MB RAM, no fetch() API, no Promises, and no ES6 grammar.
LegacyChat bridges every modern web API to strict legacy-compatible standards:
| Modern Web Expectation | Safari 9.3.6 Status | LegacyChat ES5 Solution |
|---|---|---|
fetch() & ReadableStream |
Unsupported | Native XMLHttpRequest polling buffer on readyState === 3 |
const, let, () => {} |
Unsupported | Strict ES5 var, explicit function closures, prototype methods |
async / await & Promises |
Unsupported | Asynchronous callback chains with discrete error propagation |
| CSS Grid | Unsupported | Flexible -webkit-box and -webkit-flex box layout fallback systems |
CSS gap property |
Unsupported | Adjacent sibling combinators (.item + .item { margin-left: X }) |
CSS Custom Variables (var(--)) |
Unsupported | Dynamic DOM stylesheet rules and explicit theme body classes |
Modern Event Constructors (new Event()) |
Unsupported | Backward-compatible document.createEvent('Event') dispatching |
Element.prototype.remove() |
Unsupported | Traditional node.parentNode.removeChild(node) DOM removal |
| High-Frequency DOM Repaints | Layout Thrashing | 60ms throttled streaming renderer (~16fps) conserving A5 CPU cycles |
5MB localStorage Limits |
Quota Crashes | Client-side Canvas JPEG downsampling and SVG image placeholder caching |
- Zero ES6 syntax: verified using strict ECMAScript 5 abstract syntax tree validation.
- Compatible with Safari 9.3.6, iOS 8+, older Android WebViews, and desktop legacy browsers.
- Requires zero transpilation steps or build systems.
- Streams token deltas from OpenRouter Server-Sent Events (SSE).
- Throttles DOM markdown updates to 60ms intervals (
~16fps), eliminating UI freeze and battery drain on low-spec mobile chips. - Native autoscroll engine tracks streaming output without layout stutter.
- Full parser for headers, bold, italics, inline code, and strikethrough text.
- Fenced code blocks with language headers and one-tap Copy Code functionality.
- GitHub Flavored Markdown (GFM) table support with alternating row formatting.
- Ordered and unordered nested list generation.
- Attach photos directly from device camera roll or file inputs.
- Automatic off-screen
<canvas>image downsampler scales photos to a maximum boundary of 800x800 pixels at 0.6 JPEG quality. - Reduces multi-megabyte camera files to lightweight Base64 payloads (~50KB) to prevent browser memory exhaustion.
- Full export and import of chat histories in structured JSON format.
- Edit prior prompts with automatic response regeneration.
- Delete individual messages or clear entire conversation threads.
- Automatic startup routine prunes orphaned draft keys to preserve the 5MB browser storage quota.
- Preserves storage quota by converting older chat images to compact SVG placeholders.
- Connects directly to the public OpenRouter API to fetch and sort available AI models.
- Includes presets for top industry models (Claude Sonnet 4.5, GPT-4o, Gemini 3.5 Flash, DeepSeek V4.1 Flash).
- Features verified 100% free models by default (Google Gemma 4 31B Free, Gemma 4 26B Free, Qwen 3.8 27B Free, Cohere North Mini Code Free).
- Customizable system persona templates (Coding Assistant, Translator, Academic, Creative, or Custom).
Clone the repository and open index.html in your browser:
git clone https://github.com/gininaba/legacychat.git
cd legacychat
open index.htmlIf testing across devices on your local network (e.g., streaming over Wi-Fi to your iPad Mini):
# Using Python 3
python3 -m http.server 8080
# Using Node.js (npx)
npx serve .
# Using PHP
php -S 0.0.0.0:8080Access the server on your iPad Mini by entering your computer's local IP address (e.g., http://192.168.1.50:8080).
- Push this repository to your GitHub account.
- Log in to Vercel and click Add New Project.
- Select your
legacychatrepository and click Deploy. - Your application will be live at
https://your-project.vercel.appwith full HTTPS support.
- Open your deployed URL in Safari on your iPad Mini.
- Tap the Share button in Safari's toolbar.
- Tap Add to Home Screen.
- Launch LegacyChat from the home screen icon to enjoy a distraction-free, full-screen app view without browser address bars.
LegacyChat follows a modular, single-file architecture. All communication and data transforms happen client-side in the browser:
flowchart TD
subgraph Client [Browser Runtime: index.html]
UI[User Interface & Inputs]
Canvas[Hidden Canvas Resizer]
State[Global ES5 State Store]
Parser[formatMarkdown & sanitizeHTML]
Storage[(Local Storage Quota Guard)]
Timer[60ms Throttle Interval]
end
subgraph Remote [OpenRouter Cloud]
OR_Models[GET /api/v1/models]
OR_Chat[POST /api/v1/chat/completions]
end
UI -->|Photo Upload| Canvas
Canvas -->|Base64 JPEG <= 800px| State
UI -->|Submit Message| State
State -->|XMLHttpRequest SSE Streaming| OR_Chat
OR_Chat -->|Raw Chunk Stream| State
State -->|Accumulated Deltas| Timer
Timer -->|Render Event| Parser
Parser -->|Safe HTML Injection| UI
State -->|Debounced Serialization| Storage
UI -->|Refresh Models Button| OR_Models
OR_Models -->|Populate Dropdown| State
LegacyChat communicates directly with OpenRouter. All configuration options are saved locally on your device in localStorage:
| Setting | Storage Key | Description | Default |
|---|---|---|---|
| API Key | legacychat_api_key |
OpenRouter authorization token (sk-or-v1-...) |
Empty |
| Model | legacychat_model |
Selected AI model identifier slug | google/gemma-4-31b-it:free |
| Persona | legacychat_persona_select |
Active system persona prompt preset | General Assistant |
| Custom Persona | legacychat_custom_persona |
User-defined system instructions | Empty |
| Temperature | legacychat_temp |
Output randomness (0.0 deterministic to 2.0 creative) | 0.7 |
| Max Tokens | legacychat_tokens |
Maximum generation token limit per response | 2048 |
| Theme | legacychat_theme |
Visual appearance (dark or light) |
dark |
| Primary Chat View | Setup & Configuration |
|---|---|
![]() |
![]() |
| Chat History Drawer | Dynamic Model Catalog |
|---|---|
![]() |
![]() |
| System Persona Selection | Clean Light Mode |
|---|---|
![]() |
![]() |
1. Error: "This request requires more credits, or fewer max_tokens..."
This occurs when your OpenRouter account balance or token credit limit is lower than the configured Max Tokens limit (2048 tokens).
Resolution:
- Open Settings in LegacyChat.
- Under the Advanced section, adjust Max Tokens to a lower value (e.g.,
80or150). - Alternatively, select one of the free models from the model dropdown (e.g.,
google/gemma-4-31b-it:freeorqwen/qwen3.8-27b:free). - Or add credits to your balance in your OpenRouter Dashboard.
2. Why does my API key disappear after closing Safari?
If Safari is running in Private Browsing Mode, WebKit isolates all localStorage writes in temporary volatile memory and clears them when tabs are closed.
Resolution:
- Exit Private Browsing mode in Safari to allow persistent storage of your preferences and API key.
3. What should I do if I see a "Storage Limit Exceeded" alert?
Safari enforces a strict 5MB quota on local browser storage. Storing many extended conversations with high-resolution image uploads can approach this limit.
Resolution:
- Open the History Drawer (top-left menu button).
- Click Export to save an offline JSON backup of all your conversations.
- Click Clear All to purge old stored threads and free device memory.
4. Does LegacyChat transmit my API key through any intermediary server?
No. LegacyChat has zero backend code. Every network request is initiated directly from your device browser to https://openrouter.ai/api/v1/chat/completions. Your API key never leaves your local device storage except in standard HTTPS headers sent directly to OpenRouter.
Comprehensive engineering guides are available for contributors and developers:
- Developer Onboarding Guide: Environment setup, ES5 verification commands, and USB remote debugging with Safari Web Inspector.
- Component Architecture: Complete index of DOM element structures, CSS class tokens, and JavaScript helper functions.
- API & Streaming Manual: Detailed explanation of OpenRouter payloads, base64 vision serialization, and SSE buffer parsing.
- Contribution Guidelines: Coding standards, ES5 testing checklist, and pull request workflows.
- Changelog: Chronological history of releases, optimizations, and bug fixes.
legacychat/
├── index.html # Unified single-file web app (HTML markup, CSS design system, and ES5 engine)
├── logo.png # High-resolution application brand logo (1024x1024)
├── apple-touch-icon.png # Optimized iOS home screen icon (180x180)
├── LICENSE # Standard MIT License terms
├── README.md # Master project documentation
├── CHANGELOG.md # Detailed version releases and patch notes
├── COMPONENTS.md # DOM elements, state variables, and function references
├── API.md # OpenRouter endpoints, payload models, and SSE streaming specifications
├── ONBOARDING.md # Contributor quickstart, ES5 syntax enforcement, and hardware test guidelines
├── CONTRIBUTING.md # Guidelines for issues, code reviews, and pull requests
└── Screenshots/ # Visual application previews
├── Chat.png
├── SetupScreen.png
├── ChatHistory.png
├── Models.png
├── Persona.png
└── LightTheme.png
This project is licensed under the MIT License. See LICENSE for the full license text.
Copyright (c) 2026 gininaba




