Lipi Malayalam Learning Game
A highly visual, completely serverless client-side application for mastering Malayalam script, vocabulary, and conversation flows database-free. Lipi uses native browser APIs to handle stroke-by-stroke character tracing, audio synthesis, text-to-speech, and local persistence.
đ Submodule Project Structure
Located at apps/lipi:
apps/lipi/
âââ vite.config.js # Vite build & port configuration (Port 3006)
âââ index.html # Main entry point & SEO definitions
âââ package.json
âââ public/
â âââ appu.png # Appu the elephant mascot image asset
âââ src/
âââ main.js # App shell coordinator, router & toast system
âââ style.css # Dark mode glassmorphic CSS design system
âââ data.js # static letters (stroke arrays), vocabs, and phrases
âââ storage.js # Decoupled Storage Provider (Bridge pattern)
âââ gamification.js # XP, level calculations, heart counts, streaks, and league simulation
âââ audio.js # Web Audio synthesizer SFX & Web Speech API wrappers
âââ components/
âââ login.js # Entry gateway authentication gate & avatar picker
âââ profile.js # Personalization panel (themes, speed dials, muting)
âââ dashboard.js # Progress map, achievements & AI dictionary widget
âââ tracing.js # Responsive canvas-based character tracer
âââ matchgrid.js # Match cards grid & 30s Time Attack mode
âââ assembler.js # Word chip sentence builder puzzle
âââ flashcards.js # Leitner Spaced Repetition (SRS) flippable deck
âââ cultural.js # Locked/unlocked Kerala cultural discovery facts
âī¸ Stroke-by-Stroke Coordinate Tracing
The character tracing canvas does not draw simple pixel paths. Instead, it tracks the user's compliance with standard Malayalam glyph stroke paths:
* Guide Coordinate Representation: Characters in src/data.js are stored as arrays of strokes, where each stroke is a sequence of {x, y} coordinates normalized to a 0-100 bounding box. The database contains the complete Malayalam Aksharamala catalog of 56 alphabets (15 vowels, 36 consonants, and 5 chillu letters) with validated tracing-ready coordinates.
* Proximity Matching: When drawing, the app calculates the Euclidean distance to the next expected node in the active guide path:
$$\text{dist} = \sqrt{(x_{\text{user}} - x_{\text{guide}})^2 + (y_{\text{user}} - y_{\text{guide}})^2}$$
- If $\text{dist} \leq 25\text{px}$, the user is snapped to that node and the path advances.
- If the user strays too far ($\text{dist} > 45\text{px}$ from all segments), the canvas border flashes red, the stroke resets, and a heart is deducted.
* Responsive Scaling: Normalized coordinates are mapped to the responsive viewport bounds on-the-fly, allowing smooth finger tracing on mobile devices and cursor clicks on desktop.
đ§ Decoupled Storage Bridge
To run securely within any standard WebView sandbox (e.g. mobile apps, iframe playgrounds) without throwing security exceptions if cookie/storage permissions are restricted, Lipi implements the Bridge Pattern:
* StorageProvider Interface: Code layers query StorageProvider.get() and StorageProvider.set() instead of accessing browser stores directly.
* In-Memory Fallback: The bridge automatically verifies if localStorage is writeable. If blocked, it redirects all writes to an ephemeral JavaScript memory store (MemoryStorage) seamlessly.
class LocalStorageBridge {
getItem(key) {
try { return localStorage.getItem(key); }
catch { return null; }
}
setItem(key, value) {
try { localStorage.setItem(key, String(value)); }
catch {}
}
}
đ Session-Persistent Login Gate
Lipi enforces a visually immersive login screen gate on initial application load:
* Session Handshake: Intercepts routing calls in src/main.js and blocks entry to the main dashboard if a valid user profile (name and avatar) is not present.
* Persisted Caching: State is committed dynamically through GamificationEngine.saveState, writing to localStorage or falling back to in-memory caching.
* Form Validation Cues: Invalid inputs trigger tone effects (AudioEngine.playFailure()) and visual shake animations.
đ¨ Multi-Theme Swap & Personalization
The application integrates a real-time configuration panel inside src/components/profile.js to modify the learning environment:
* Dynamic CSS Variable Swapping: Swaps the class name of document.body between theme-forest, theme-gold, and theme-indigo. CSS variables then remap colors, backgrounds, borders, and glow values instantly.
* Audio Controls: Mutes auditory arpeggios and buzzer feedback by writing to module-level parameters in src/audio.js.
* Speech Playback Dials: Modifies the Text-to-Speech playback speed for Malayalam definitions, selecting between slow (0.75x), normal (1.0x), and fast (1.25x).
đ Web Audio Synth & Web Speech API
Lipi avoids heavy MP3 asset payloads by utilizing native browser synthesizers and voice synthesis:
* Sound FX Synthesizer: Triggers success chimes (pleasant arpeggio C5 -> E5 -> G5 -> C6) and error buzzers by scheduling frequency changes directly on a Web Audio OscillatorNode:
const osc = audioCtx.createOscillator();
osc.frequency.setValueAtTime(523.25, now); // C5
osc.frequency.setValueAtTime(659.25, now + 0.08); // E5
SpeechSynthesisUtterance. It dynamically searches for a native ml-IN voice pack. If the voice pack is missing, it automatically translates the speech text to the phonetic English spelling (e.g. pronouncing "Namaskaram! Sukhamano?" instead of reading "ā´¨ā´Žā´¸āĩā´ā´žā´°ā´! ā´¸āĩā´ā´Žā´žā´Ŗāĩ?") using the Indian English voice to prevent "question mark" readout bugs. It uses a 50ms queue buffer to avoid WebSpeech cancel race conditions on Chrome/Safari.
* Speech to Text (STT): Checks speaking response pronunciation using webkitSpeechRecognition configured for ml-IN. It matches spoken text dynamically against target Malayalam string outputs.
đ Local Development Commands
- Install Dependencies:
- Start Dev Server:
(Running on
http://localhost:3006) - Compile Client Build Bundle:
(Outputs relative-pathed static site assets to
dist/)
đ Public Malayalam Dictionary API
Lipi exposes its high-performance dictionary search engine as a public, CORS-compliant REST API. Any third-party application can fetch Malayalam definitions from this endpoint without facing domain routing restrictions.
Endpoint
GET /api/lipi/dictionary?word=<word>
Request Parameters
word(required, string): The English word to translate (e.g.schoolorfriend).
Response Schema (JSON)
{
"malayalam": "ā´ĩā´ŋā´Ļāĩā´¯ā´žā´˛ā´¯ā´",
"phonetic": "Vidyaalayam",
"english": "School",
"category": "Noun",
"difficulty": "Easy",
"exampleMalayalam": "ā´ā´žāĩģ ā´ĩā´ŋā´Ļāĩā´¯ā´žā´˛ā´¯ā´ ā´ā´žā´Ŗāĩā´¨āĩā´¨āĩ.",
"exampleEnglish": "I see the school.",
"synonyms": ["ā´Ēā´ŗāĩā´ŗā´ŋā´āĩā´āĩā´ā´", "ā´Ēā´žā´ ā´ļā´žā´˛"]
}
Architecture Features
- Partitioned Local Lookup: Performs instant local searches on the 58,800+ word Olam corpus with zero network overhead.
- Dynamic AI Cascading: Connects automatically to OpenRouter LLM APIs as a fallback to translate modern or complex slang words not in the core database.
- CORS Support: Implements preflight
OPTIONSrequests and replies withAccess-Control-Allow-Origin: *headers for full cross-site compatibility.