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.
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.
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.
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.
Thanks for reading — and happy solstice, whichever hemisphere you're in. 🌅 Feedback and high scores very welcome in the comments.