Mission Control
Access Required
Wrong passphrase
Total Cards
710+
Series 1 + Series 2
Features Built
20+
This session alone
Bugs Fixed
22
Security + UX + logic
Dead Code Removed
740
Lines (14% of file)
What's Done (v1.11)
Full Card Catalog / Pokédex (OWNED / ALL / MISSING)
Visual Battle UI for daily PvP
Daily Login Calendar (7-day streak)
Welcome Back Bonus (3+ days absence)
Share Card Image (canvas PNG + watermark)
Pull History Log (last 100 pulls)
Card Lore links (Wikipedia / NASA)
Starter Guarantee (first pack = SR+)
Series 2 Type Advantages
Rarity sounds wired during reveal
Security hardening (XSS, trades, auth, codes)
SEO meta tags + JSON-LD
iOS scroll fix
Dead code cleanup (740 lines)
What's Next
Duplicate Recycling / Stardust Forge
Category Completion Rewards & Badges
PWA / Add to Home Screen
Invite / Referral System
Ranked Seasons
Procedural Ambient Music
Reskin: Cryptid / Dino / Myth / Deep Sea / Pixel Gacha
Battle Pass monetization
List on itch.io, post to Reddit
Enable Cloudflare Web Analytics
v1.11 — The Big Update LIVE
Features: Card Catalog, Visual Battle UI, Login Calendar, Welcome Back Bonus, Share Card, Pull History, Card Lore, Starter Guarantee, S2 Type Advantages, Rarity Sounds
Security: XSS protection, trade validation, server-side codes, caller verification, syncState caps, rate limiting
Fixes: DEF sort, starter guarantee, trade button, toast spam, event leaks, iOS scroll, dead code cleanup (740 lines)
SEO: JSON-LD, canonical URL, noscript, og fixes, aria-labels, reduced-motion
v1.3 — UI Polish
Pack selector fixes, series buttons, spacing, cut canvas, trade sorting
v1.2 — Nebula Chronicles
210 new S2 cards, 10 SECRETs, 8 categories, 5 new pack types
v1.1 — Social Update
Server battles, friends, chat, trading, lobby, 9 ranks, raid boss
v1.0 — Launch
500+ cards, pack opening, PvP, daily missions, Netlify deploy
XSS Protection
Vulnerability: Any user-controlled string rendered via innerHTML could inject arbitrary HTML/JS. Attacker could steal sessions, redirect users, or deface the UI.

Fix: Created esc() utility that replaces &, <, >, ", ' with HTML entities. Applied to all 27 innerHTML insertion points across player names, chat messages, trade descriptions, and card display text. Zero raw user strings touch innerHTML now.
Vulnerability: Unlimited name length could break UI layouts. HTML chars in names enabled stored XSS via trade/friend/chat displays.

Fix: Added maxlength=20 on input element. Client-side strips <>&"' before submission. Server-side Convex mutation also strips and truncates, so even direct API calls are sanitized.
Vulnerability: Unlimited chat messages enabled XSS injection and chat flooding/spam that could DoS other players' browsers.

Fix: Input maxlength=500. Server strips all < and > characters. 2-second cooldown per player enforced server-side via timestamp check. Messages exceeding limits are silently rejected.
Trade System
Vulnerability: Player could offer cards they didn't own by crafting a direct API call to trades:create with arbitrary card IDs.

Fix: Server-side validation checks sender's collection for every card in the offer array. If any card is missing or quantity insufficient, the mutation throws and the trade is not created.
Vulnerability: Race condition — player could trade away cards then accept another trade for the same cards. Or accept trade with insufficient stardust.

Fix: At accept time, server re-validates both players' collections and stardust balances. If either side can't fulfill, trade is cancelled and both players are notified.
Vulnerability: When trading away last copy of a card, the removal logic set count to 1 instead of 0/deleting. Players kept phantom copies of traded cards.

Fix: Changed removal logic to properly decrement and delete the card entry when count reaches 0. Tested with single-copy and multi-copy scenarios.
Vulnerability: Any player could cancel or decline any other player's trades by calling the mutation with an arbitrary trade ID.

Fix: Server checks that callerId matches either the sender (for cancel) or receiver (for decline). Unauthorized calls are rejected.
Server-Side Hardening
Vulnerability: Mutations could be called by anyone with another player's visitorId, enabling impersonation of any action.

Fix: All 12 mutations now verify callerId matches the authenticated session's visitorId. Mismatched calls are rejected with an auth error.
Vulnerability: syncState accepted any values — attacker could set stardust to max int, battleWins to millions, etc. Could be spammed every frame.

Fix: 10-second cooldown between syncState calls. Stardust capped at 9,900,000. battleWins can only increase by max 5 per call. Values outside bounds are clamped, not rejected (prevents desync).
Vulnerability: All redeem codes were hardcoded in client-side JavaScript. Anyone could view source to find every code.

Fix: Codes moved to a Convex database table with fields for code string, reward type, reward amount, max uses, and used-by array. Client sends code to server, server validates and returns reward. Codes no longer visible in source.
Vulnerability: setDonator mutation was callable by anyone, allowing any player to grant themselves donator perks (badge, extra packs, etc.).

Fix: Mutation now requires an admin key parameter that's checked server-side. Only calls with the correct key are processed.
Vulnerability: Donation code tracking checked suffix, so DONATE-ABC and XDONATE-ABC were treated as the same code. Attackers could generate unlimited valid codes by changing the prefix.

Fix: Changed to exact string match. Each code is a unique entry in the database. No prefix/suffix matching.
Vulnerability: Import save accepted any JSON. Attacker could import a save with max stardust, all SECRET cards, impossible stats.

Fix: Import now validates: stardust capped at 9.9M, card IDs must exist in the actual card database, quantities capped at 99, stats validated against reasonable bounds. Invalid fields are stripped or clamped.
Vulnerability: Lobby names were rendered in other players' browsers without sanitization, enabling XSS via lobby creation.

Fix: Server strips HTML characters from lobby names on creation. Maxlength enforced at 30 characters. Display uses esc() on the client as well for defense-in-depth.
Issue: HEROOO_ADMIN_2026 is hardcoded in codes.ts and players.ts. Anyone with Convex dashboard access or source code access can see it.

Impact: Full admin access — can set any player as donator, create redeem codes, modify any player state.

Fix Required: Move to Convex environment variable via npx convex env set ADMIN_KEY [new-random-key]. Reference in mutations via process.env.ADMIN_KEY. Rotate the key immediately since the current one is in git history.

Effort: ~10 minutes. Change 2 files, set 1 env var.
Issue: All game state (stardust, cards, stats) lives in localStorage. S.stardust = 9999999; save() in the console gives max currency instantly. syncState only runs periodically and trusts client values.

Impact: Complete economy bypass. Any player with basic JS knowledge can give themselves unlimited resources.

Fix Required: Server-authoritative economy — all stardust changes happen via Convex mutations (earnStardust, spendStardust). Client only displays server values. Major architectural change.

Effort: 2-3 days. Requires rewriting pack opening, battle rewards, mission rewards, trading to all be server-side.
Issue: Accept a trade (cards transfer server-side), then immediately call syncState with pre-trade collection data. Server overwrites with old state, restoring traded-away cards while the other player keeps their copies too.

Impact: Unlimited card duplication. Destroys economy integrity for competitive/trading players.

Fix Required: syncState should never overwrite server-side trade results. Add a version counter or timestamp — server rejects syncState if a trade happened after the client's last sync. Or make collection fully server-authoritative.

Effort: 1-2 hours for version counter approach. 2-3 days for full server authority.
Issue: Battle outcome is determined client-side. The client simply calls a "reward" function after claiming victory. Attacker can call the reward function directly without battling, or always claim wins.

Impact: Unlimited battle rewards (stardust, rank points). Leaderboard corruption.

Fix Required: Server-side battle resolution. Client sends team composition, server runs battle logic, server awards rewards. Or at minimum, server tracks daily battle count and caps rewards.

Effort: 1-2 days for full server battles. 2-3 hours for server-side reward caps.
Issue: A player can spam unlimited trade offers, friend requests, and battle challenges. No server-side throttling on these mutations.

Impact: Grief vector — flood a target player's inbox with hundreds of requests. Could also be used to DoS the Convex backend with rapid mutation calls.

Fix Required: Add per-player timestamp rate limits on each social mutation. E.g., max 1 trade offer per 10 seconds, max 5 friend requests per minute, max 1 challenge per 30 seconds.

Effort: 1-2 hours. Add lastAction timestamps to player document, check in each mutation.
3 FIXED — Fully server-side
3 MITIGATED — Server validation + caps
3 PENDING — Architecture change needed
#IssueStatusImpactFix
What was done: syncState now has a 10-second cooldown, caps stardust increase to +5000/call, limits battleWins to +5/call, and merges collection via Math.max (never overwrites with lower values). Console manipulation is still possible but the damage per exploit attempt is bounded.

Remaining risk: Repeated calls over time still accumulate. Full fix requires server-authoritative game logic.
What was done: Added server-side daily tracking: consumePack, consumeBattle, consumeRaid, consumePaidRaid, checkDaily mutations. Schema tracks freePacks, lastDailyReset, battlesToday, raidAttacksToday, paidRaidsToday per player. Server resets daily counters on first action of the day.
Status: Still uses client-side 30-second timer. AdSense integration is pending approval — once approved, Server-Side Verification (SSV) can be implemented.
What was done: Friend battles already use server-side battle:simulate mutation. Daily AI battles still run client-side, but syncState limits battleWins increase to +5 per call with 10s cooldown. Server also tracks wins via consumeBattle mutation.

Remaining: Daily AI battle could be moved to server-side mutation for full fix.
Decision: Deliberately keeping visitorId-based identity. No login requirement reduces friction for new players. visitorId is now verified on all 12 Convex mutations. Export save no longer exposes visitorId in shareable format. Trade with Space Gacha and Whale Gacha share the same identity.
Status: Pack opening RNG still runs in browser. Server-side consumePack deducts packs but doesn't generate cards. Full fix requires server to run RNG and return card results.
What was done: All 12 Convex mutations now require visitorId parameter and verify the caller matches the player record. Unauthenticated or mismatched calls are rejected. Rate limits (5s cooldown) added to trades, friends, and challenges.
What was done: Codes moved to server-side Convex table (codes.ts). No patterns in source. DONATE- prefix tracks by prefix not suffix (1 redemption per prefix per player). Admin-only addCode mutation requires admin key via environment variable.
What was done: validateSaveData() caps all stats on import (stardust ≤9.9M, battleWins ≤10000, freePacks ≤100). Validates card IDs exist in ALL_CARDS. syncState applies same caps on server sync. Crafted save files can still inject within the cap limits.
#FeatureStatusEffortPersonas
What: Browse ALL cards in the game with OWNED / ALL / MISSING filter tabs. Search by name, filter by rarity and category. Shows completion percentage per category.

Why: Collectors need to see what they're missing. Drives engagement by showing progress toward completion. Creates "gotta catch 'em all" motivation loop.

Personas: The Completionist (primary), The Collector, The Explorer, The Social Player (shows off collection)

Implementation: ~50 lines. Filters existing card database, renders grid with owned/missing indicators, category progress bars. Uses existing card rendering components.
What: Animated battle screen showing both player's cards facing off. HP bars, attack animations, type advantage indicators, turn-by-turn combat log.

Why: Battles were previously just "you won/lost" text. Visual feedback makes battles exciting, gives meaning to card stats, and makes type advantages visible.

Personas: The Battler (primary), The Competitive Player, The Social Player

Implementation: ~150 lines. Card-vs-card layout, CSS animations for attacks, HP bar transitions, type advantage glow effects. Uses Web Audio for hit/win/lose sounds.
What: 7-day streak calendar with escalating rewards. Day 1: 500 stardust, Day 7: guaranteed SR pack. Visual calendar grid shows claimed/upcoming days.

Why: Daily login rewards are the #1 retention mechanic in gacha games. Creates habit formation. Streak anxiety prevents churn. 5 of 8 personas specifically requested this.

Personas: The Daily Player (primary), The F2P Grinder, The Collector, The Casual, The Completionist

Implementation: ~80 lines. localStorage tracks streak count and last claim date. Calendar UI with 7 slots, CSS animations for claiming. Streak resets if a day is missed.
What: Special bonus popup for players returning after 3+ days of inactivity. Grants stardust and bonus packs based on days away (up to 7 days of rewards).

Why: Re-engages lapsed players instead of punishing them for leaving. Reduces permanent churn. Makes returning feel rewarding rather than overwhelming.

Personas: The Returning Player (primary), The Casual, The Collector, The Explorer

Implementation: ~30 lines. Check lastLoginDate on load, calculate days absent, show modal with accumulated rewards. One-time claim per return.
What: "Stardust Forge" — sacrifice duplicate cards for stardust or craft materials. Higher rarity = more stardust. Bulk recycling option for mass duplicates.

Why: Duplicates currently have zero value, making pulls feel wasted. Recycling gives every pull meaning. Creates a stardust sink that balances the economy.

Personas: The F2P Grinder (primary), The Collector, The Completionist, The Battler

Implementation: ~60 lines. Select duplicates (keep at least 1), confirm sacrifice, animate destruction, award stardust. Rates: N=10, R=50, SR=200, SSR=500, UR=1000.
What: Generate a shareable PNG image of any card with the Space Gacha watermark. One-tap save/share to social media. Includes card stats, rarity border, and player name.

Why: Social sharing is the #1 organic growth driver for gacha games. "Look what I pulled!" drives FOMO and virality. Zero-cost marketing from every rare pull.

Personas: The Social Player (primary), The Collector, The Competitive Player, The Explorer, The Content Creator

Implementation: ~100 lines. Canvas API renders card at 1080x1920 with rarity-colored border, card image, stats overlay, and spacegacha.pages.dev watermark. Uses toBlob() for PNG export.
What: Badges and bonus rewards for completing entire card categories (e.g., all Nebulae, all Exoplanets). Visual badge display in profile. Special frame for 100% completion.

Why: Gives collectors concrete goals beyond "get everything." Category focus makes the collection feel structured. Badges provide social status and bragging rights.

Personas: The Completionist (primary), The Collector, The Explorer, The Social Player

Implementation: ~80 lines. Check owned vs total per category, award badge at 100%, display in profile grid. Reward: category-specific card back + stardust bonus.
What: Scrollable log of last 100 pulls with timestamp, pack type, and cards received. Color-coded by rarity. Filterable by rarity tier.

Why: Players want to track their luck. Transparency about pull rates builds trust. Data-driven players use this to analyze their actual rates vs published rates.

Personas: The Analyst (primary), The F2P Grinder, The Competitive Player

Implementation: ~40 lines. Array in localStorage, push on each pull, render as timestamped list in Stats tab. Auto-prunes at 100 entries.
What: First pack opened by any new player guarantees at least one SR or higher card. Ensures an exciting first experience.

Why: First impression is everything. A first pack of all N/R cards is devastating for retention. Guaranteeing a shiny card hooks the player immediately.

Personas: The New Player (primary), The Casual

Implementation: ~5 lines. Check if totalPulls === 0, if so force one card in the pack to SR rarity minimum. Simple conditional in pack opening function.
What: Removed 740 lines of unused code — unreachable functions, commented-out features, duplicate logic, unused CSS, dead event listeners.

Why: 14% of the file was dead weight. Slows page load, increases parse time, makes maintenance harder, and increases attack surface (dead code can still have vulnerabilities).

Personas: All personas benefit from faster load times and reduced bundle size.

Implementation: Systematic audit of every function, CSS rule, and event listener. Verified no callers before removing. 740 lines removed, zero regressions.
Tier 2 Ideas
Interactive Tutorial PWA / Home Screen Mobile Bottom Nav Friend Activity Feed Invite / Referral Ranked Seasons SECRET Pity Timer Offline Pack Bank
Journey traced: Land on page → see changelog modal (confusing for new players) → dismiss → see pack opening area → no explanation of what to do → eventually find "Cut to Open" text → open first pack → see cards but don't understand rarity system → find missions tab (this is where engagement begins).

Pain points:
  • Changelog modal appears on first ever visit — should only show for returning players
  • No tutorial or guided first-time experience
  • Rarity colors (N/R/SR/SSR/UR/SECRET) never explained
  • Battle system discoverable only by clicking through tabs
  • Card stats (ATK/DEF/HP) meaning unclear without context
  • Stardust purpose not communicated upfront
Recommendations: Add 3-step tooltip tutorial on first visit. Hide changelog for firstTimePlayers. Add "What is this?" help icon on key UI elements. Show a "Your first SR!" celebration on starter guarantee pull.
Journey traced: Return after 3+ days → see Welcome Back modal with accumulated rewards → claim bonus → open packs → check what's new → browse collection.

Pain points:
  • Welcome Back bonus was missing before this update (now fixed)
  • No "what's new" summary for returning players — just the full changelog
  • raidBossHP initializes to 5000 but server overwrites to 100000 on first sync — confusing flash of wrong HP
  • If returning after a long time, daily streak is reset with no explanation
  • Pull history only shows last 100 — long absence means gap in data
Recommendations: Show condensed "New since you left" summary. Fix raidBossHP initialization. Add streak reset explanation. Consider "catch-up" packs for players who missed many days.
Journey traced: Open on iPhone 13 Mini (375px) → layout mostly works but cramped → try trading → trade panel nearly unusable → open chat → chat fills entire screen → try to sort collection → sort buttons too small to tap.

Pain points:
  • Only 2 CSS breakpoints — needs at least 3 (375px, 768px, 1024px)
  • Trade panel uses 2-column layout at 375px — cards overlap and text truncates
  • Chat panel is 300px fixed width — fills entire viewport on small phones
  • Multiple chat windows go offscreen with no scroll
  • Sort/filter buttons at 0.45rem font-size — below Apple's minimum tap target
  • Pack cutting gesture works but visual hint is tiny on mobile
  • iOS elastic scroll bounce was causing layout jumps (now fixed with overflow fix)
Recommendations: Add 375px breakpoint. Make trade panel single-column on mobile. Chat panel should be full-screen overlay on mobile. Increase button sizes to 44px minimum tap target per Apple HIG. Test on iPhone SE (320px).
Journey traced: Open friend list → click trade → select cards to offer → add requested cards → send offer → wait for response → accept/decline incoming trades.

Pain points:
  • _requestedCards array is built in the UI but never sent to the Convex mutation — the other player has no idea what you want in return. Trade offers are effectively blind.
  • Both players pay 100 stardust per trade with no clear explanation why — feels like a tax
  • No list of outgoing/pending trades you've sent — only incoming trades are visible
  • Trade button was broken (click handler on wrong element) — now fixed
  • No trade history or log of completed trades
  • Can't cancel a sent trade without the other player declining
Recommendations: Wire _requestedCards to the server mutation. Show stardust cost upfront before confirming. Add "My Offers" tab showing outgoing trades with cancel option. Add trade history log.
Journey traced: Open card detail → click "Learn More" → link opens to Wikipedia (if mapped) or NASA search (fallback).

Pain points:
  • Only ~155 of 710+ cards have direct Wikipedia URL mappings (22%)
  • Remaining 78% use NASA search with the card name as query — works great for "Andromeda Galaxy" but fails for generated names like "NGC-4821-X" or "Void Entity Omega"
  • Some Wikipedia links are to disambiguation pages rather than the specific object
  • No in-app lore text — always redirects externally
  • Series 2 procedurally-named cards (e.g., "Nebula Fragment #47") have no meaningful search results anywhere
Recommendations: Add 2-3 sentence in-app lore text per card (can be generated). Improve Wikipedia mapping coverage to 50%+. For procedural names, link to the category page instead (e.g., link "Nebula Fragment" cards to the Nebula Wikipedia article). Consider adding a "Did you know?" random fact overlay.
Quick Wins (This Week)
Tiered Ko-fi rewards ($3/$10/$25)
Stripe: $2.99/month Stardust Pass
5 purchasable card backs ($0.99 each)
Amazon affiliate links in card modals
TikTok pack opening clips
Revenue Projections
Monetize existing game with cosmetics ($0.99-$4.99 card backs, avatar borders, pack effects), $4.99/season battle pass with 30 tiers, $2.99/month Stardust Pass subscription (50 daily packs, no ads). Already have Stripe via Ko-fi.
500+ real dinosaurs using Wikipedia public domain paleoart. 8 categories: Theropods, Sauropods, Armored, Sea Reptiles, Flying Reptiles, Ice Age, Arthropods, Living Fossils. Amber/brown earth tones. Fossil Dig pack opening mechanic. 77% of code reusable, only card data + images + theme colors need changing.
Zero-install Kahoot competitor. Host creates room, shares link, players join in browser. Real-time via Convex. Free for 10 players, $5/mo for 50, $15/mo for 200. Same tech stack as Space Gacha.
Music-themed gacha leveraging Chandler's audio engineering background. Cards are iconic albums, instruments, producers, studios. Each card has a Web Audio synthesized sound. Cross-promote with chandlerboster.com portfolio.
Sell custom gacha games to businesses, influencers, events. $500-2000 setup + $50-100/month. Framework is 77% reusable. Pitch to Phoenix local businesses first via Grand Ave Studio network.
Framework
77%
Reusable across themes
Theme Content
23%
~1,085 lines to change
Min Reskin
4-6h
With emoji cards
Full Reskin
1-2d
With real images
Top 5 Reskin Proposals
Categories:Forest Cryptids, Lake & Sea Monsters, Flying Cryptids, Urban Legends, Alien Encounters, Paranormal, Government Secrets, Anomalous Objects SECRETs:SCP-000, THE BACKROOMS, INTERNET ENTITY, THE MEN IN BLACK, REALITY GLITCH Palette:Dark green / static-gray — #060a06 bg, #33cc66 accent Images:Public domain illustrations, vintage woodcuts, AI-generated found-footage style Audience:Ages 14-30, horror/creepypasta/SCP fans Unique Twist:"Declassified" pack opening — cards appear as redacted government documents that unblur. VHS/found-footage aesthetic with scan lines and static noise.
Categories:Olympian, Norse, Egyptian, Japanese, Hindu, Celtic, Aztec/Mayan, Mythic Beasts SECRETs:CHAOS, THE TRICKSTER, THE FORGOTTEN GOD, RAGNAROK, THE PLAYER Palette:Deep gold / midnight blue — #0a0812 bg, #d4af37 accent Images:Metropolitan Museum, Wikimedia, British Museum (public domain classical art) Audience:Ages 12-25, mythology fans, Percy Jackson readers, God of War gamers Unique Twist:Type advantages based on actual mythological relationships. "Altar" sacrifice system for duplicates — offer cards to the gods for blessings.
Categories:Theropods, Sauropods, Armored Dinos, Sea Reptiles, Flying Reptiles, Ice Age Beasts, Ancient Arthropods, Living Fossils SECRETs:SPECIMEN ZERO, THE EXTINCTION EVENT, GLITCH FOSSIL, THE LIVING MOUNTAIN, LAST DINOSAUR Palette:Earthy amber / brown — #0a0806 bg, #cc8833 accent Images:Wikipedia Commons paleoart, Natural History Museum collections Audience:Kids 8-14, dinosaur enthusiasts, educational gaming Unique Twist:"Fossil Dig" pack opening — chip away at a rock slab to reveal cards. Timeline battles — same-era cards get stat bonuses when paired together.
Categories:Twilight Zone, Midnight Zone, Abyssal Zone, Hadal Zone, Hydrothermal Vents, Cold Seeps, Jellyfish & Ctenophores, Deep Coral & Sponges SECRETs:THE BLOOP, MARIANA UNKNOWN, LIVING FOSSIL X, THE HIVE MIND, DEEP INTELLIGENCE Palette:Bioluminescent blue-green — #010408 bg, #00ccaa accent Images:NOAA Ocean Explorer (all public domain US government) Audience:Ages 10-25, marine biology fans, nature documentary viewers Unique Twist:Depth meter instead of pack opening — "descend" through ocean zones to open packs. Deeper = rarer creatures. Pressure gauge visual builds tension.
Categories:Heroes, Monsters, Items, Arcade Cabinets, Chiptune Legends, Glitch World, Console Relics, NPCs SECRETs:PLAYER ONE, THE FINAL BOSS, SAVE CORRUPTION, CREDITS ROLL, NEW GAME+ Palette:CRT green-on-black — #0a0a14 bg, #44ff44 accent Images:All generated as 32x32 pixel art sprites — zero copyright concerns Audience:Ages 18-35, retro gaming, pixel art, nostalgia Unique Twist:Cards render as actual pixel sprites with image-rendering: pixelated. 8-bit treasure chest opening animation. CRT scanline overlay on entire UI. Chiptune sounds via Web Audio square/triangle waves.
Immediate (This Week)
Add canonical URL + JSON-LD schema
Fix Whale Gacha OG tags
Add noscript fallback
Enable Cloudflare Web Analytics (free)
Create 1200x630 OG banner image
Short-Term (This Month)
List on itch.io
Add Share button (card image generator)
Post to r/WebGames
Create @spacegacha Twitter
Key Marketing Angles
NASA hook: "I turned NASA's image library into a free gacha card game"
No P2W: Free game, no predatory monetization
Educational: Real astronomical facts on every card
Best subs: r/WebGames (600K), r/space (25M), r/gachagaming (400K)
Root cause: setInterval(pollServer, 5000) fires regardless of whether any data has changed. Each poll calls: getPlayer, getIncomingTrades (x2 — duplicate call), getFriends, getChat (per open chat window), getLobby, getRaidBoss. With 3 friends and 2 chat windows, that's 10+ requests every 5 seconds.

Impact: ~120+ requests/minute per active player. Convex has generous limits but this burns through them fast with concurrent users. Also drains mobile battery and data.

Fix: Replace polling with Convex real-time subscriptions (useQuery). Data pushes to client only when it changes. Eliminates 95%+ of requests. Deduplicate the double cvxGetIncomingTrades call immediately (1-line fix).
Was: Every 5-second poll checked for pending trades. If any existed, it showed a toast notification. Result: toast appeared every 5 seconds for as long as a trade was pending — extremely annoying.

Fix: Added lastSeenTradeId tracking. Toast only fires when a NEW trade appears (ID differs from last seen). Persists across polls until a different trade arrives.
Was: initPackCut() added mousemove/touchmove/mouseup/touchend listeners to window every time the Packs tab was opened. After 10 tab switches, there were 40+ duplicate listeners all firing on every mouse move.

Fix: Store listener references. Remove previous listeners before adding new ones in initPackCut(). Verified with getEventListeners(window) in devtools — now stays constant regardless of tab switches.
Was: The decorative starfield background used requestAnimationFrame running at 60fps continuously, even when the browser tab was hidden/minimized. Wasted CPU cycles and battery on mobile.

Fix: Added document.hidden check — animation loop skips rendering when tab is not visible. requestAnimationFrame is already throttled by browsers when hidden, but the explicit check prevents all canvas operations.
Issue: Opening the Collection tab renders ALL cards as DOM nodes simultaneously. With 500+ cards, that's 500+ complex card elements with images, stats, borders, and event listeners. Causes visible jank on mid-range phones.

Fix approach: Implement virtual scrolling with IntersectionObserver. Only render cards visible in the viewport + a small buffer. As user scrolls, recycle off-screen DOM nodes. Alternative: paginate with "Load More" button (simpler, ~20 lines).

Expected improvement: Initial render from ~500 DOM nodes to ~20. Scroll performance from janky to smooth on all devices.
Was: localStorage.setItem() in save() had no error handling. When localStorage quota was exceeded (5MB limit), the call threw an uncaught exception that crashed the entire game loop. Players lost all progress from that session.

Fix: Wrapped save() in try-catch. On quota error, shows a warning toast ("Storage full — some data may not be saved") and continues. Also added a size check before save — if approaching 4.5MB, warns player to export their save.
Critical
user-scalable=no prevents zoom (WCAG 1.4.4)
Nav tabs are div not button — no keyboard access (WCAG 2.1.1)
33+ clickable divs not keyboard-accessible
Zero ARIA attributes on modals, tabs, progress bars
Most img tags missing alt text
Fixed
prefers-reduced-motion CSS added
aria-labels on 6 form inputs
Toast: role="alert" aria-live="assertive"
FIXED Rarity Sounds Never Played
N/R/SR/SSR/UR/SECRET SFX were defined but never called during card reveal. Now wired up.
Synthesis Improvements
Replace playTone() with synthVoice() — ADSR, filtering, vibrato, detune
Algorithmic reverb (feedback delay network)
Stereo width via Haas effect on UR/SECRET
Global 300Hz highpass (except tear boom)
Bitcrusher for SECRET cards (WaveShaperNode)
Procedural Ambient Music
3-4 detuned oscillators with slow LFO
Contextual moods: space / calm / tense / warm
2-second crossfade on tab switch
Battle music intensification (layers as HP drops)
Separate SFX / Music volume sliders
Interactive Timing Slider
Total Pack Open Time
1.0s
0.3s (Instant)1.0s (Balanced)3.0s (Dramatic)
Split Animation
0.60s
60% of total
Card Delay
0.40s
40% of total
CSS Transition
0.60s
transform + opacity
Dblclick Cut
150ms
proportional scale
// Pack opening timing (1.0 seconds)
topHalf transition: transform 0.60s, opacity 0.60s
bottomHalf transition: transform 0.60s, opacity 0.60s
setTimeout delay: 400ms
dblclick cut step: 150ms
A/B Comparison Timeline
VersionTotal TimeSplit AnimCard DelayFeel
Original (v1.0)~1.8s0.8s0.7s + 0.3s bufferDramatic, slow
Current (v1.11)~1.0s0.6s0.5sBalanced
Fast~0.5s0.4s0.3sSnappy, arcade
v1.0 (1.8s)
v1.11 (1.0s)
Fast (0.5s)
Industry Comparison
0s1s2s3s4s
Pokemon TCG Pocket
3.0s
Slow tension, 1 card at a time
Genshin Impact
2.5s
Shooting star, dramatic color reveal
Overwatch Loot Box
2.0s
Dramatic lid pop, all items at once
Apex Legends
1.5s
Fast spin per item
Space Gacha (current)
1.0s
Cut + split + instant cards
Space Gacha (fast)
0.5s
Snappiest possible
Visual Timeline Animation
Play animation to preview feel
CUT
SPLIT ANIM
DELAY
CARDS
0.05s
0.60s
0.40s
0.05s
0.05s
0.60s
0.40s
0.05s
Phase 1: Cut/swipe (user action)
Phase 2: Pack split animation
Phase 3: Transition delay
Phase 4: Cards appear