SOLSTICE gameplay — the Sunbearer fights shadow creatures in a stone ring as Light and Dawn meters fill toward sunrise
June Solstice Game Jam · Submission

SOLSTICE

A browser-based 3D action-survival game where daylight is your health bar — hold back the dark through the longest night, and witness the solstice dawn.

⚙️ Three.js · WebGL ⌨️ Laptop-first · WASD 🎮 Plays in the browser 📦 One HTML file

On the June solstice the sun lingers longer than on any other day of the year — and then, inevitably, it sets. SOLSTICE is a game about the night that follows: the longest, darkest night, and one keeper of the light who refuses to let it end. I wanted to take the jam's theme literally — light, darkness, and the passage of time — and turn all three into a single mechanic you can feel in your hands.

What I Built

You play the Sunbearer , the last guardian of an ancient stone ring. As the solstice sun goes down, shadow creatures pour out of the dark to snuff out your flame. The twist is the resource system:

Your light is your life, your weapon, and the clock. When it runs out, the night wins.

A single golden meter — your Light — slowly drains as time passes and drops fast when enemies hit you. Slay the shadows and they shatter into light fragments you collect to keep your flame burning. Meanwhile a second meter, Dawn , fills as you survive. Reach 100% and the solstice sun crests the horizon: you win. It's a tug-of-war between the dark draining you and the dawn you're racing toward.

Demo

Watch a full run — sunset, the swarm, the Warden boss, and the dawn:

▶ Playable build (single HTML file, runs in any modern browser): longphanquangminh.github.io/sunbearer

The Theme: Why the Solstice

I didn't want the solstice to be set dressing — I wanted the whole game to be the solstice. Three connections drive every system:

☀ Light as a resource

The jam prompt literally suggested "a platformer where daylight is your resource." I ran with that idea in 3D: light isn't a number in a corner, it's the thing you're physically fighting to keep. Let it go out and the screen edges bleed red, your glow dims, and the night takes you.

🌗 The passage of time, made visible

The Dawn meter doesn't just count up — it repaints the entire world . The match begins at burning sunset, sinks through violet dusk into a deep starlit midnight, and finally breaks into golden dawn at the moment you win. The sky, fog, sunlight color and angle, ambient tone, and even the stars all interpolate in real time. You can see how far you are from sunrise just by looking up.

🗿 The longest night, in stone

The arena is a Stonehenge-inspired ring of standing stones and trilithons — the real monument is aligned to the solstice sunrise, so it felt like the perfect altar to defend. "Darkest before dawn" isn't just a phrase here: the toughest wave and the Warden of the Long Night boss arrive right before first light.

How to Play

Built keyboard-first for laptops — no gamepad or fancy mouse required.

W A S D / Arrows
Move the Sunbearer
Space / Left-click
Light Slash — arc melee
Shift
Dash — i-frames to dodge attacks
E
Solar Flare — charged AoE ultimate

The loop is slash → dodge → collect → survive : cut down shadows, dash through their dark bolts, vacuum up the light they drop, and chain kills for a score combo. Save your Solar Flare for when you're swarmed or for the boss.

Three trials

Difficulty is selectable on the start screen — and you can bail out to change it any time via the pause menu or the end screen.

Acolyte
A gentler night to learn the ropes.
Sunbearer
The true longest night. Faster, deadlier, denser.
Default
Eclipse
Brutal. Two bosses, relentless swarms.

How I Built It

SOLSTICE is a single self-contained HTML file — no build step, no bundler, no assets to download. That was a deliberate constraint: anyone can open it and play instantly, and it can be hosted literally anywhere.

3D Three.js r160 (WebGL) glow UnrealBloomPass audio Web Audio (procedural) loader ESM importmap 0 external assets

No bundler — ES modules straight from a CDN

Three.js loads via a native importmap , so the whole engine streams from a CDN with zero tooling:

<!-- map bare specifiers to a pinned CDN build -->
<script type="importmap">
{ "imports": {
    "three": "https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.module.js",
    "three/addons/": "https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/"
} }
</script>

The day → dawn pipeline

The heart of the game. One value — dawn (0 → 1) — drives a five-stop keyframe interpolation across the sky, fog, sun and stars. Survival literally paints the passage of the night:

// sunset → dusk → deep night → pre-dawn → dawn
const SKY = [
  // p,    sky,      fog,      sun,    sunInt, amb, hemi, stars
  [0.00, 0xE8743B, 0x6e3a2e, 0xffb060, 1.5, 0.55, 0.60, 0.0],
  [0.50, 0x0c1230, 0x0a1026, 0x6a78c0, 0.35, 0.32, 0.35, 1.0],
  [1.00, 0xFFC56B, 0x9a6a3e, 0xffe0a0, 1.7, 0.70, 0.85, 0.0],
];

function updateDay() {
  const p = dawn / dawnMax;              // how close to sunrise
  const [a, b, t] = segment(SKY, p);     // pick + blend two stops
  scene.background.lerpColors(a.sky, b.sky, t);
  scene.fog.color.lerpColors(a.fog, b.fog, t);
  sun.intensity = lerp(a.sunInt, b.sunInt, t);
  stars.opacity = lerp(a.stars,  b.stars,  t); // fade in at midnight, out at dawn
  // + sun arc position, ambient & hemisphere all interpolate too
}

Light that actually glows

Everything luminous — the light-blade, dropped fragments, the dawn itself — runs through UnrealBloomPass postprocessing. I load it with a dynamic import() wrapped in try/catch , so if postprocessing ever fails the game falls back to direct rendering instead of showing a black screen.

Sound from nothing

There are no audio files. Every effect — the slash swish, hit blips, the pickup chime, the Solar Flare roar, the boss horn — is synthesized at runtime with Web Audio oscillators and noise buffers. It keeps the single-file footprint tiny and the load instant.

The rest of the toolbox

  • Combat — arc hit-detection (range + facing angle), knockback, and a combo multiplier; dash grants brief invulnerability so dodging is a real skill.
  • Enemies — shades, fast wisps, ranged casters that force you to move, and a heavy Warden boss, all spawned on an escalating wave director.
  • Difficulty — a thin multiplier layer (HP, damage, speed, spawn rate, drain) applied at run start gives three distinct tiers from one codebase.
  • Performance — capped pixel ratio, a recycled sprite particle pool, fake blob shadows plus one shadow-casting sun, and an enemy cap keep it smooth on a laptop.

What I Learned

The best design decision was collapsing health and the timer into one "Light" bar . Early versions had separate HP and a survival clock and it felt noisy. Merging them made every choice tense — do I dash to safety and let my light tick down, or push in for the kill and the fragments that refill me? Tuning the drain-vs-reward balance so that aggression is survival took the most iteration, which is exactly why I added selectable difficulty rather than guessing one "right" number.

Hold the light until dawn.

Best on a laptop, with headphones on.

▶ Play SOLSTICE

Thanks for reading — and happy solstice, whichever hemisphere you're in. 🌅 Feedback and high scores very welcome in the comments.