A mobile web game inspired by liquid sort puzzle games (like Magic Sort). Built with React, TypeScript, Vite, and Tailwind CSS.
- Core Gameplay: Sort colored liquids into bottles until each bottle contains a single color
- Drag & Drop: Intuitive drag and drop controls for mobile and desktop
- Progressive Difficulty: Levels scale from 4 colors (easy) to 8 colors (challenging)
- Undo System: Unlimited undo with full move history
- Move Counter: Track your efficiency
- Dark Mode: Toggle between light and dark themes
- Persistence: Game state saved to localStorage automatically
- Responsive Design: Optimized for mobile (9:16 vertical) with desktop support
- Smooth Animations: CSS-based 60fps animations
- Node.js 18+
- npm or yarn
# Clone or navigate to the project
cd liquid-sort-game
# Install dependencies
npm install
# Start development server
npm run devOpen http://localhost:3000 in your browser.
npm run build
npm run preview- Tap a bottle to select it, then tap another to pour
- Drag a bottle directly onto another to pour (mobile & desktop)
- Valid pours: Target must have space AND (be empty OR have matching top color)
- Win condition: Each bottle contains only one color or is empty
| Action | Description |
|---|---|
| Tap bottle | Select/pour |
| Drag bottle | Drag onto another bottle to pour |
| Undo button | Reverse last move |
| Restart button | Reset current level |
| Sun/Moon icon | Toggle dark mode |
liquid-sort-game/
├── src/
│ ├── components/
│ │ ├── Bottle.tsx # Bottle with liquid layers and animations
│ │ ├── GameBoard.tsx # Responsive grid layout
│ │ ├── TopBar.tsx # Level info and control buttons
│ │ ├── WinModal.tsx # Victory screen with star rating
│ │ └── index.ts
│ ├── engine/
│ │ └── GameEngine.ts # Pure game logic (framework-agnostic)
│ ├── hooks/
│ │ └── useGame.ts # React state management and persistence
│ ├── types/
│ │ └── index.ts # TypeScript type definitions
│ ├── App.tsx # Main application component
│ ├── main.tsx # Entry point
│ └── index.css # Global styles and animations
├── index.html
├── package.json
├── vite.config.ts
├── tailwind.config.js
├── tsconfig.json
└── postcss.config.js
| Layer | Responsibility |
|---|---|
| GameEngine.ts | Pure functions for game logic. No React, no side effects. Handles move validation, pour execution, win detection, and level generation. |
| useGame.ts | React hook that wraps GameEngine. Manages UI state, animations, and localStorage persistence. |
| Components | Memoized presentational components. No business logic. |
-
Immutable State: All game operations return new state objects, enabling reliable undo and preventing mutation bugs.
-
CSS Animations: Pour animations use CSS
@keyframeswith GPU-accelerated transforms for smooth 60fps performance. -
Responsive Sizing: Bottle dimensions use CSS custom properties (
--bottle-width,--bottle-height) that scale based on viewport. -
Level Generation: Starts from shuffled layers distributed across bottles, guaranteeing solvability by construction.
- Each bottle holds 4 layers maximum
- Only the top contiguous color segment can be poured
- Pour is valid if:
- Source bottle is not empty
- Target bottle has space (< 4 layers)
- Target is empty OR top colors match
- Levels include 1-2 empty bottles for maneuvering room
React.memo()on all components to prevent unnecessary re-rendersuseMemo()for computed values like liquid layer renderinguseCallback()for stable event handler references- CSS-only animations (no JavaScript animation loops)
- No blocking synchronous operations
| Levels | Colors | Bottles |
|---|---|---|
| 1-3 | 4 | 6 |
| 4-6 | 5 | 7 |
| 7-10 | 6 | 8 |
| 11-15 | 7 | 9 |
| 16+ | 8 | 10 |
Edit src/engine/GameEngine.ts:
export const COLORS: LiquidColor[] = [
'red', 'orange', 'yellow', 'green',
'blue', 'purple', 'pink', 'cyan',
// Add more here
];
export const COLOR_MAP: Record<LiquidColor, string> = {
// Add hex values for new colors
};Modify getLevelConfig() in src/engine/GameEngine.ts to change color/bottle ratios per level.
| Strategy | Implementation |
|---|---|
| Rewarded Ads | Watch video for hints or extra undos |
| Hint System | Highlight the best move (IAP for hint packs) |
| Level Packs | Premium themed levels |
| Remove Ads | One-time purchase |
| Daily Challenges | Time-limited puzzles with leaderboards |
- Add PWA support with service worker for offline play
- Implement cloud save (Firebase/Supabase)
- Add analytics for level difficulty tuning
- Haptic feedback for mobile (
navigator.vibrate) - Sound effects with Web Audio API
- Leaderboards and social sharing
- A/B testing for UI variations
- React 18 - UI framework
- TypeScript - Type safety
- Vite - Build tool and dev server
- Tailwind CSS - Utility-first styling
- No external game engines - Pure React + CSS
- No animation libraries - CSS keyframes + transitions
MIT
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request