JSFiddle - React, Tailwind, and code Playground
HTML
<main id="leafCatcherApp" class="game-shell">
<header class="game-header">
<span class="eyebrow">GREEN WEEK CHALLENGE</span>
<h1>Catch the Green Leaves</h1>
<p>Move the basket, catch the good items, and build your best score!</p>
</header>
<section class="game-card" aria-label="Catch the Green Leaves game">
<div class="hud">
<div class="hud-box">
<span class="hud-label">TIME</span>
<strong><span id="timeValue">30</span>s</strong>
</div>
<div class="hud-box score-box">
<span class="hud-label">SCORE</span>
<strong id="scoreValue">0</strong>
</div>
<div id="effectBadge" class="effect-badge" aria-live="polite">
Normal speed
</div>
</div>
<div class="time-track" aria-hidden="true">
<div id="timeFill" class="time-fill"></div>
</div>
<div id="playfield" class="playfield">
<div class="cloud cloud-one" aria-hidden="true"></div>
<div class="cloud cloud-two" aria-hidden="true"></div>
<div class="hill hill-back" aria-hidden="true"></div>
<div class="hill hill-front" aria-hidden="true"></div>
<section id="startOverlay" class="overlay">
<div class="overlay-card">
<div class="big-icon" aria-hidden="true">🍃</div>
<h2>Ready to Catch?</h2>
<p>Use the buttons or your keyboard to move the basket.</p>
<button id="startButton" class="primary-button" type="button">
Start Game
</button>
</div>
</section>
<section id="resultOverlay" class="overlay hidden" aria-live="polite">
<div class="overlay-card result-card">
<span class="eyebrow">CHALLENGE COMPLETE</span>
<h2 id="resultRank">Leaf Collector</h2>
<p class="final-score">
<strong id="finalScore">0</strong>
<span> points</span>
</p>
<p...
CSS
:root {
--forest: #164b35;
--deep-green: #0f3727;
--leaf: #39a96b;
--lime: #94d65c;
--cream: #fffdf2;
--sun: #ffd45a;
--orange: #ff9d42;
--ink: #173429;
--muted: #60776d;
--card: rgba(255, 255, 255, 0.94);
}
* {
box-sizing: border-box;
}
body {
margin: 0;
min-width: 300px;
color: var(--ink);
background:
radial-gradient(
circle at 15% 10%,
rgba(255, 255, 255, 0.8),
transparent 26%
),
linear-gradient(145deg, #e8f7d9 0%, #c9efcf 48%, #b7e6cc 100%);
font-family:
Inter,
ui-rounded,
"Trebuchet MS",
system-ui,
-apple-system,
BlinkMacSystemFont,
"Segoe UI",
sans-serif;
}
button {
font: inherit;
}
.game-shell {
width: min(760px, 100%);
margin: 0 auto;
padding: 22px 14px 28px;
}
.game-header {
margin-bottom: 14px;
text-align: center;
}
.eyebrow {
display: inline-block;
margin-bottom: 7px;
color: #2f7f4f;
font-size: 0.75rem;
font-weight: 900;
letter-spacing: 0.15em;
}
.game-header h1 {
margin: 0;
color: var(--forest);
font-size: clamp(1.75rem, 5vw, 2.65rem);
line-height: 1.05;
}
.game-header p {
max-width: 540px;
margin: 9px auto 0;
color: var(--muted);
font-size: 0.98rem;
}
.game-card {
overflow: hidden;
border: 1px solid rgba(22, 75, 53, 0.13);
border-radius: 24px;
background: var(--card);
box-shadow: 0 18px 50px rgba(31, 93, 62, 0.17);
}
.hud {
display: grid;
grid-template-columns: 1fr 1fr minmax(125px, 1.4fr);
gap: 8px;
align-items: stretch;
padding: 12px 12px 9px;
background: #fffef8;
}
.hud-box,
.effect-badge {
display: flex;
min-height: 52px;
align-items: center;
justify-content: center;
border: 1px solid #dcebdc;
border-radius: 14px;
background: #f6fbf2;
}
.hud-box {
flex-direction: column;
}
.hud-label {
color: #73907e;
font-size: 0.65rem;
font-weight: 900;
...
JavaScript
(() => {
const GAME_SECONDS = 30;
const BASKET_WIDTH = 98;
const BASKET_HEIGHT = 62;
const NORMAL_SPEED = 330;
const SLOW_SPEED = 185;
const BOOST_SPEED = 500;
const EFFECT_SECONDS = 5;
const ITEM_TYPES = [
{
key: "leaf",
emoji: "🍃",
label: "Green Leaf",
points: 1,
weight: 47
},
{
key: "apple",
emoji: "🍏",
label: "Green Apple",
points: 3,
weight: 16
},
{
key: "dry",
emoji: "🍂",
label: "Dry Leaf",
points: -2,
weight: 17
},
{
key: "twig",
emoji: "🐛",
label: "Twig",
effect: "slow",
weight: 10
},
{
key: "caterpillar",
emoji: "☘️",
label: "Caterpillar",
effect: "boost",
weight: 10
}
];
const app = document.querySelector("#leafCatcherApp");
if (!app) return;
const playfield = app.querySelector("#playfield");
const basket = app.querySelector("#basket");
const startOverlay = app.querySelector("#startOverlay");
const resultOverlay = app.querySelector("#resultOverlay");
const startButton = app.querySelector("#startButton");
const playAgainButton = app.querySelector("#playAgainButton");
const copyButton = app.querySelector("#copyButton");
const copyStatus = app.querySelector("#copyStatus");
const leftButton = app.querySelector("#leftButton");
const rightButton = app.querySelector("#rightButton");
const timeValue = app.querySelector("#timeValue");
const scoreValue = app.querySelector("#scoreValue");
const timeFill = app.querySelector("#timeFill");
const effectBadge = app.querySelector("#effectBadge");
const finalScore = app.querySelector("#finalScore");
const resultRank = app.querySelector("#resultRank");
const resultMessage = app.querySelector("#resultMessage");
const scorePopups = app.querySelector("#scorePopups");
let score = 0;
let timeLeft...