A stacker. Offline-first. No email.
Browser-Arcade mit Cloud-Sync, Events und globalem Leaderboard — Vanilla JS, LocalStorage als Source-of-Truth, Supabase Free-Tier als additive Ebene. Nickname + PIN statt DSGVO-Schleppe.
Why this exists.
Die Ketchapp-Schule des Stack-Towers hat das Genre definiert und dann in Werbe-Interstitials vergraben. Wer heute "Stack" spielen will, bekommt proprietäre App-Store-Titel mit intrusiver Monetarisierung, keinem Gerätewechsel-Support und Leaderboards, die kaum mehr als Dekoration sind.
Stack Surge bringt die Mechanik zurück in den Browser — instant spielbar via URL, als PWA installierbar, ohne Email und ohne App-Store-Zwang. Fortschritt synct optional, Achievements, Daily Challenges und Quest Chains laufen lokal, Leaderboard global. Das Design-Ziel: zwei Tap entfernt von der ersten Runde, der Rest passiert im Hintergrund.
The box it had to fit in.
How it runs.
Four deliberate choices.
Vanilla JS + Canvas statt React / Three.js / Phaser.
GameLoop mit Delta-Time, modulare Systems/EntitiesSupabase + RLS statt eigenes Node-Backend.
supabase/security.sqlLocalStorage als Source-of-Truth, Cloud nur additiv.
storage als primärer State, SyncService reconciled später gegen Supabase — Records via Math.max, Collections als UnionNickname + PIN statt Email-Auth.
Things that were not obvious.
GradientCache gegen GC-Stottern
BackgroundSystem pro Frame dutzende createLinearGradient-Aufrufe — auf alten Androids mördert das den GC, FPS kollabieren.Lösung: ein
GradientCache mit Key aus Farben + Dimensionen. Invalidierung nur bei Theme-Wechsel. Zusammen mit einem ParticleSystem-Object-Pool (Swap-and-Pop statt Array.splice: O(1) statt O(n)) bleibt der Fever-Mode auf Low-End-Devices bei konstanten 60 FPS.Anti-Cheat ohne autoritativen Server
(s, b, p, d, t) + VITE_SIGNATURE_SALT aus der Env. Die Supabase-RPC submit_score speichert die Signatur und führt eigene Plausibilitätschecks: Score-Cap pro Block, min. Zeit pro Block, perfects ≤ blocks, streak ≤ perfects, Rate-Limit 5 Submissions / 5 min pro Nickname.Keine perfekte Lösung — der Salt liegt im Bundle, Reverse-Engineering ist möglich. Aber: genug, um den Fire-and-Forget-Cheat vom öffentlichen Board fernzuhalten, ohne Game-State auf einen Server zu zwingen. Pragmatisch kalibriert gegen die Angreifer, die tatsächlich auftauchen.
Seasonal-Events als Daten, nicht Code
src/data/themes/{standard,premium,seasonal}/ — pro Theme ein ESM-Modul mit Color-Phases, Effect-Flags und -Counts. Der SeasonalEventManager detektiert das Datum, aktiviert Themes + ThemeReactions automatisch — fliegende Fledermäuse zu Halloween, Schneeflocken zu Weihnachten.Neuer Event = neue Theme-Datei + ein Eintrag in
SeasonalEvents.js mit Datumsfenster. Kein Code-Change am Renderer, keine Deploy-Koordination. Die Grenze zwischen Content-Change und Code-Change bewusst gezogen und bewahrt: was Autoren und was Entwickler anfassen, ist seit Tag 1 getrennt.Reconcile mit max() und Union
SyncService zieht beim Login den Cloud-State, mergt gegen LocalStorage und schreibt das Ergebnis zurück. Strategie pro Feld: bestScore und Statistik- Counter via Math.max, inventory und achievements als Union, equipped und settings beim ersten Sync aus der Cloud, danach local-first. coins beim Restore-Sync via Math.max, im aktiven Spiel local — sonst würde Cloud bereits ausgegebene Coins zurückspielen.Konsequenz: wer auf drei Geräten spielt, verliert keinen Fortschritt — Hochwasser-Marken bleiben konsistent, Käufe und freigeschaltete Inhalte addieren sich, die zuletzt aktive Session bestimmt den Loadout-State. Schmal genug, dass die Konflikt-Cases im Code direkt sichtbar bleiben — keine versteckte Last-Write-Wins-Magie, die später jemand versehentlich "optimiert".
What's running.
What I learned.
Offline-first ist Feature, nicht Fallback.
LocalStorage als Source-of-Truth hat den ganzen UX-Flow entkoppelt — kein Loading-Spinner, keine "Verbindung wird hergestellt"-Blocker, kein Geräusch, wenn das Netz wackelt. Der Sync-Pfad ist eng, getestet und hat klare Fehler-Grenzen. Das nehme ich in jede App mit, die irgendwo zwischen "rein lokal" und "rein vernetzt" lebt.
Backend-as-a-Service bis es weh tut.
Free-Tier-Supabase hat mich komplett von Ops befreit — keine VMs, kein nginx, kein pg_dump-Cronjob. RLS ersetzt einen API-Server, den ich sonst selbst hätte bauen müssen. Die Grenze ist klar: sobald ich custom Realtime-Game-State brauche, geht das nicht mehr. Aber bis dahin: jeder Solo-Dev sollte das als Default evaluieren, bevor er wieder einen Node-Server containerisiert.