DEV Community

Cover image for ๐ŸŒ Earth Day Waste Sorting Challenge - ๐Ÿ† Gamified Learning
Nizzad
Nizzad Subscriber

Posted on

๐ŸŒ Earth Day Waste Sorting Challenge - ๐Ÿ† Gamified Learning

DEV Weekend Challenge: Earth Day

This is a submission for the DEV Weekend Challenge: Earth Day Edition: Build for the Planet

Why I Built This

Every April 22nd, Earth Day reminds us that the planet needs more than good intentions โ€” it needs informed action. One of the most impactful things any individual can do is sort their waste correctly. Yet most people genuinely don't know whether a greasy pizza box goes in paper recycling, whether broken glass belongs with regular glass, or whether a CFL light bulb is hazardous waste.

I wanted to build something that makes learning those answers feel like play, not homework. So I built the Earth Day Waste Sorting Challenge โ€” a drag-and-drop educational game that teaches real recycling science through fast-paced gameplay, environmental fact cards, and a live impact tracker that shows you exactly how much waste you're diverting from landfill.


Table of Contents


What I Built

The Earth Day Waste Sorting Challenge is a React 18 / TypeScript web game where players drag waste items into six color-coded bins: โ™ป๏ธ Plastic, ๐Ÿ“„ Paper, ๐ŸŽ Organic, ๐Ÿบ Glass, ๐Ÿ”‹ E-Waste, and โš ๏ธ Hazardous.

What makes it different from a simple quiz:

  • 60+ real waste items with factually accurate environmental facts (decomposition times, COโ‚‚ figures, recycling science)
  • Live Impact Tracker showing estimated kilograms of waste diverted from landfill and COโ‚‚ equivalent saved โ€” in real time, as you sort
  • Earth Day Mode that activates automatically on April 22nd each year, with a curated 30-item challenge and a countdown to midnight
  • Fact Cards after every sort โ€” correct or wrong โ€” so every interaction is a learning moment
  • Five game modes from casual Zen sorting to competitive Survival with lives
  • Full accessibility support including keyboard navigation, ARIA labels, and a high-contrast mode

Live Demo

๐Ÿš€ Play the Earth Day Waste Sorting Challenge โ†’

GitHub logo mohamednizzad / Waste-Sorting-Challenge

An educational drag-and-drop waste sorting game built to celebrate Earth Day (April 22nd) and deepen players' understanding of real-world recycling, environmental impact, and sustainable habits.

๐ŸŒ Earth Day Waste Sorting Challenge

Sort Smarter. Save the Planet.

An educational drag-and-drop waste sorting game built to celebrate Earth Day (April 22nd) and deepen players' understanding of real-world recycling, environmental impact, and sustainable habits.

React TypeScript Tailwind CSS Vite


๐ŸŽฎ What Is This?

The Earth Day Waste Sorting Challenge is a fast-paced, educational web game where players drag waste items into the correct recycling, organic, or hazardous bins. Every correct sort teaches a real environmental fact and tracks your estimated real-world impact โ€” kilograms of waste diverted from landfill and COโ‚‚ equivalent saved.

The game features a special Earth Day Mode that activates on April 22nd each year, a curated library of 60+ waste items, five distinct game modes, and a full impact tracker that makes every sort feel meaningful.


โœจ Features

๐ŸŒฑ Earth Day Theme

  • Earth-green color palette (#2d6a4f, #52b788) with parallax scrolling background
  • Animated ๐ŸŒ globe and "Sortโ€ฆ

The Earth Day Experience

The game is designed to feel like an Earth Day celebration from the moment you open it.

The home screen greets you with an animated ๐ŸŒ globe, the tagline "Sort Smarter. Save the Planet.", and a rotating "Did You Know?" panel cycling through 10 curated Earth Day facts every 5 seconds. The color palette โ€” deep greens (#2d6a4f, #52b788), ocean blues (#0077b6, #90e0ef), and earth tones โ€” sets the mood immediately.

On April 22nd, the experience transforms. Animated falling leaves drift across the screen, a "๐ŸŒ Happy Earth Day!" banner appears, and a special Earth Day Mode button becomes available. Earth Day Mode presents a curated set of 30 waste items chosen specifically for their real-world environmental impact โ€” the items that matter most to get right. A countdown timer shows how many hours and minutes remain until midnight, creating a gentle sense of occasion.

On every other day, the home screen shows a "Next Earth Day" countdown โ€” a small but persistent reminder that April 22nd is coming, and that the planet is always worth thinking about.

The Earth Day detection is isolated behind a single injectable getNow() function:

function useEarthDay(getNow?: () => Date): UseEarthDayReturn {
  const now = (getNow ?? (() => new Date()))();
  const isEarthDay = now.getMonth() === 3 && now.getDate() === 22;
  // ...
}
Enter fullscreen mode Exit fullscreen mode

This makes the hook trivially testable โ€” tests inject a fixed date, production uses new Date().


How the Game Works

The core loop is simple and satisfying:

  1. A waste item card appears in the Action Area โ€” for example, "โ˜• Coffee Grounds"
  2. You drag it to one of the six bins
  3. The game validates your choice and shows a Fact Card
  4. The next item spawns

The Fact Card is where the real learning happens. For a correct sort, it confirms your choice and shares an environmental fact: "Coffee grounds add nitrogen to compost and can even deter pests from gardens." For a wrong sort, it explains the correct category and why โ€” and for counterintuitive "trick" items, it leads with "Did you know?" to signal that the answer is surprising.

// FactCard auto-dismiss timing
const dismissDelay = isZenMode ? null : (isCorrect ? 1500 : 2500);
Enter fullscreen mode Exit fullscreen mode

Correct sorts dismiss after 1.5 seconds so the game stays fast. Wrong sorts stay for 2.5 seconds โ€” long enough to actually read the fact. In Zen mode, the card stays until you tap "Got it โ†’".


The Impact Tracker โ€” Making It Feel Real

The most important design decision in this project was the Impact Tracker.

Points and combos are fun, but they're abstract. What makes the game feel genuinely meaningful is watching real numbers change: "You've diverted 2.3 kg of waste from landfill and saved 1.1 kg COโ‚‚e."

Every waste item in the game data carries two values:

interface WasteItem {
  // ...
  impactKg: number;    // kg of waste diverted per correct sort
  co2eSaved: number;   // kg COโ‚‚e saved per correct sort
}
Enter fullscreen mode Exit fullscreen mode

These are based on real-world recycling impact data. A correctly sorted aluminum can saves roughly 0.17 kg COโ‚‚e. A recycled glass bottle diverts about 0.4 kg from landfill. The numbers are estimates, but they're grounded in published environmental research.

The accumulation logic is a pure function:

function accumulateImpact(
  items: WasteItem[],
  correctSortIds: string[]
): { totalImpactKg: number; totalCo2eSaved: number } {
  return correctSortIds.reduce((acc, id) => {
    const item = items.find(i => i.id === id);
    if (!item) return acc;
    return {
      totalImpactKg: acc.totalImpactKg + item.impactKg,
      totalCo2eSaved: acc.totalCo2eSaved + item.co2eSaved,
    };
  }, { totalImpactKg: 0, totalCo2eSaved: 0 });
}
Enter fullscreen mode Exit fullscreen mode

The Planet Health Bar fills from 0% to 100% as your correct sorts accumulate. When it hits 100%, the screen erupts in green confetti and displays: "You've made a real difference today! ๐ŸŒ"

The results screen closes the loop with a concrete sentence: "You properly sorted 23 items, diverting an estimated 4.6 kg of waste from landfills and saving 2.1 kg COโ‚‚e!"


Trick Items โ€” Challenging What You Think You Know

The most educational moments in the game come from the "trick" items โ€” waste items whose correct category is counterintuitive.

Item What Most People Think Correct Answer Why
๐Ÿ• Greasy Pizza Box Paper recycling Organic / Trash Grease contaminates paper recycling; the greasy portion must be composted or trashed
๐Ÿ’ฅ Broken Glass Glass recycling Hazardous Broken glass is dangerous for sanitation workers and can't be processed with whole glass
๐Ÿ’ก CFL Light Bulb General trash Hazardous Contains mercury โ€” must be processed at a hazardous waste facility
๐Ÿต Tea Bag Organic Organic (check first!) Most are compostable, but some contain plastic mesh โ€” always check
๐Ÿงด Bleach Bottle (partial) Plastic recycling Hazardous Chemical residues make partial containers hazardous

When a trick item is sorted incorrectly, the Fact Card leads with "Did you know?" โ€” a signal that the answer is surprising and worth paying attention to.

const factText = item.isTrick && !isCorrect
  ? `Did you know? ${item.fact}`
  : item.fact;
Enter fullscreen mode Exit fullscreen mode

These items are the ones players remember. They're the moments where the game changes someone's actual behavior.


The Eco_Score System

The scoring system is designed to reward both accuracy and environmental awareness:

Eco_Score = floor(base_points ร— combo_multiplier) + speed_bonus + impact_bonus
Enter fullscreen mode Exit fullscreen mode
  • Base points: 10 (difficulty 1) / 15 (difficulty 2) / 25 (difficulty 3)
  • Combo multiplier: ร—1.0 โ†’ ร—1.2 โ†’ ร—1.5 โ†’ ร—2.0 for streaks of 1โ€“2, 3โ€“4, 5โ€“9, 10+
  • Speed bonus: +5 pts for sorts under 2 seconds (not in Zen mode)
  • Impact bonus: floor(impactKg ร— 10) โ€” items with higher real-world impact are worth more points
  • Wrong sort: โˆ’5 pts (minimum 0), combo resets

The impact bonus is the key design choice here. It means that correctly sorting a lithium battery (high environmental impact) scores more than sorting a newspaper (lower impact). The scoring system teaches players which items matter most.

All scoring logic lives in pure functions with no side effects:

export function calculateEcoScore(params: {
  difficulty: 1 | 2 | 3;
  combo: number;
  responseTimeSeconds: number;
  impactKg: number;
  gameMode: GameMode;
}): number {
  const base = getBasePoints(params.difficulty);
  const mult = getComboMultiplier(params.combo);
  const speed = getSpeedBonus(params.responseTimeSeconds, params.gameMode);
  const impact = getImpactBonus(params.impactKg);
  return Math.floor(base * mult) + speed + impact;
}
Enter fullscreen mode Exit fullscreen mode

Five Game Modes for Every Player

Mode Best For Mechanic
Classic Competitive players 60-second timer, maximize score
Time Attack Speed runners 20 items; correct sorts +3s, wrong sorts โˆ’5s
Survival Challenge seekers 3 lives; mistakes and slow sorts cost a life
Zen Learners, beginners No timer, no penalties, facts stay until dismissed
Earth Day April 22nd Curated 30-item challenge with midnight countdown

Zen mode is particularly important for the educational mission. Without time pressure, players actually read the Fact Cards. They learn. The game becomes a reference tool rather than a reflex test.


Building It: Tech Stack and Architecture

The game is built on React 18 / TypeScript / Tailwind CSS / @dnd-kit / framer-motion โ€” the same stack as the original prototype, extended without breaking any existing contracts.

Architecture Principles

Additive, not destructive. Every change extends existing types and components rather than replacing them. The original WasteGame, DraggableItem, and DroppableBin components remain intact; new capabilities are layered on top.

Pure-function core. Scoring, impact calculation, level gating, and serialization are all pure functions. No side effects, no React state โ€” just inputs and outputs. This makes them trivially testable and reusable.

src/
โ”œโ”€โ”€ logic/          # Pure functions (scoring, levelGating, impactCalc, serializer)
โ”œโ”€โ”€ hooks/          # React hooks (useGameEngine, useEarthDay, useAccessibility)
โ”œโ”€โ”€ components/     # UI components
โ””โ”€โ”€ data/           # Game data and Earth Day static content
Enter fullscreen mode Exit fullscreen mode

Date-aware but mockable. Earth Day detection is isolated behind a single injectable getNow() function. Tests inject a fixed date; production uses new Date(). No global mocking required.

Serialization-safe. All session state serializes to/from JSON with full round-trip fidelity. The deserializer never throws โ€” it always returns either a valid state or a descriptive error object.

type DeserializeResult =
  | { ok: true; state: GameSessionState }
  | { ok: false; error: string };

function deserializeSession(json: string): DeserializeResult {
  try {
    const parsed = JSON.parse(json);
    // validate required fields...
    return { ok: true, state: parsed };
  } catch (e) {
    return { ok: false, error: String(e) };
  }
}
Enter fullscreen mode Exit fullscreen mode

Drag and Drop

@dnd-kit handles all drag-and-drop interactions with full touch support. The MouseSensor and TouchSensor are configured with activation constraints to prevent accidental drags:

const sensors = useSensors(
  useSensor(MouseSensor, { activationConstraint: { distance: 10 } }),
  useSensor(TouchSensor, { activationConstraint: { delay: 100, tolerance: 5 } })
);
Enter fullscreen mode Exit fullscreen mode

Draggable items use touch-none and select-none to prevent mobile browser scroll interference during drag operations.

Responsive Layout

The bins grid uses CSS auto-fit to collapse to 2 columns on narrow viewports and expand to 6 on wide ones:

grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
Enter fullscreen mode Exit fullscreen mode

All font sizes and spacing use clamp() for fluid scaling without fixed breakpoints.


The Spec-Driven Approach

This project was built using a spec-driven development workflow. Before writing a single line of implementation code, I created three documents:

  1. Requirements โ€” 13 requirements with user stories and acceptance criteria in EARS format
  2. Design โ€” Architecture diagrams, component interfaces, data models, and 18 formal correctness properties
  3. Tasks โ€” 21 implementation tasks with requirement traceability

The correctness properties in the design document are the bridge between human-readable requirements and machine-verifiable guarantees. For example:

Property 3: Session serialization round-trip
For any valid GameSessionState object, deserializeSession(serializeSession(state).json) SHALL return { ok: true, state: s } where s is deeply equal to the original state.

Having these properties written down before implementation meant I knew exactly what "correct" looked like before I started coding. It also meant the property-based tests wrote themselves.


Property-Based Testing for Correctness

The project uses fast-check for property-based testing alongside Vitest. Instead of writing specific test cases, property tests generate hundreds of random inputs and verify that invariants hold for all of them.

Property 1: Eco_Score formula correctness

fc.assert(fc.property(
  fc.integer({ min: 1, max: 3 }),   // difficulty
  fc.nat(50),                        // combo
  fc.float({ min: 0, max: 30 }),     // responseTime
  fc.float({ min: 0, max: 5 }),      // impactKg
  fc.constantFrom('classic', 'survival', 'timeattack', 'zen', 'earthday'),
  (difficulty, combo, responseTime, impactKg, gameMode) => {
    const result = calculateEcoScore({ difficulty, combo, responseTime, impactKg, gameMode });
    const expected =
      Math.floor(getBasePoints(difficulty) * getComboMultiplier(combo))
      + getSpeedBonus(responseTime, gameMode)
      + getImpactBonus(impactKg);
    return result === expected;
  }
), { numRuns: 200 });
Enter fullscreen mode Exit fullscreen mode

Property 2: Score never goes negative

fc.assert(fc.property(
  fc.array(fc.boolean(), { minLength: 1, maxLength: 100 }),
  (correctness) => {
    let score = 0;
    let combo = 0;
    for (const isCorrect of correctness) {
      if (isCorrect) {
        score += calculateEcoScore({ difficulty: 1, combo, responseTime: 5, impactKg: 0.1, gameMode: 'classic' });
        combo++;
      } else {
        ({ score, combo } = applyIncorrectSort(score));
      }
    }
    return score >= 0;
  }
), { numRuns: 200 });
Enter fullscreen mode Exit fullscreen mode

18 properties in total cover scoring, serialization, impact accumulation, level gating, planet health bar clamping, performance ratings, Earth Day countdown ranges, and ARIA label formats.

Property-based testing found two bugs during development that example-based tests would have missed โ€” both edge cases in the combo multiplier boundary conditions.


Accessibility First

Accessibility wasn't an afterthought โ€” it's a first-class feature.

Accessibility Mode (toggled from settings) applies:

  • Font sizes increased by 25%
  • Touch targets enlarged to 64ร—64px minimum
  • High-contrast color palette
  • All parallax and non-essential animations disabled, replaced with simple fades
[data-accessibility] * {
  font-size: calc(1em * 1.25) !important;
  min-width: 64px;
  min-height: 64px;
  animation: none !important;
  transition: opacity 0.2s ease !important;
}
Enter fullscreen mode Exit fullscreen mode

Keyboard navigation lets players sort without a mouse or touch screen: Tab/arrow keys cycle through bins, Enter or Space confirms a sort.

ARIA labels on every interactive element:

  • Bins: aria-label="Plastic recycling bin"
  • Items: aria-label="Water Bottle โ€” drag to sort"

These are verified by property tests (Properties 17 and 18) that render every bin and item and assert the label format.


What I Learned

1. Spec-driven development pays off. Writing requirements and a design document before coding felt slow at first. But when I started implementing, I never had to stop and ask "what should this do?" The spec answered every question. The property tests wrote themselves from the correctness properties.

2. Pure functions are a superpower. Isolating all game logic into pure functions (scoring.ts, levelGating.ts, impactCalc.ts) made testing trivial and debugging fast. When a scoring bug appeared, I knew exactly which file to look in.

3. Property-based testing finds bugs example tests miss. I wrote 18 property tests and they found 2 bugs that my hand-written examples didn't cover. The combo multiplier boundary at exactly combo = 5 behaved differently than I expected. fast-check found it in seconds.

4. The educational content is the hard part. Writing 60+ unique waste item facts, each at least 20 words and factually accurate, took longer than any single component. Getting the trick items right โ€” making sure the "Did you know?" moments are genuinely surprising and correct โ€” required real research.

5. Making impact feel real changes behavior. The most common feedback from playtesters was about the Impact Tracker. Seeing "2.3 kg diverted from landfill" made people want to keep playing. Abstract points don't do that. Real numbers do.


Conclusion

The Earth Day Waste Sorting Challenge started as a simple drag-and-drop game and became something I'm genuinely proud of โ€” a tool that teaches real recycling science through play, makes environmental impact feel tangible, and celebrates Earth Day in a way that lasts beyond April 22nd.

Every time someone learns that a greasy pizza box doesn't go in paper recycling, or that broken glass is hazardous waste, or that a correctly sorted aluminum can saves measurable COโ‚‚ โ€” that's a small win for the planet.

The game is open source. If you want to add more waste items, improve the educational facts, or translate it for your community, pull requests are very welcome.

Sort Smarter. Save the Planet. ๐ŸŒ


Navigation

Built with โค๏ธ for the Earth Day Challenge 2026

Top comments (1)

Collapse
 
yousufmohamed profile image
Yousuf Mohamed

Appreciate your way of writing.