This is a submission for Frontend Challenge - Halloween Edition, CSS Art, by Stefan Donosa.
Inspiration
The inspiration behind this piece was the desire to create a "maximalist" Halloween scene, a single artboard that captures the entire essence of the holiday in one diorama. I wanted to move beyond a simple animation and build a complete, immersive digital painting using only CSS.
The central theme is a haunted cabin perched precariously on a hill, set against a supernatural, malevolent sky. The color palette of the sky (blue-violet-orange-red) and the stark, white spectral moon were the starting points for setting a tense and magical atmosphere.
I wanted the scene to feel "alive," so I integrated classic horror elements: an ancient graveyard, a vigilant black cat, ethereal ghosts, a sinisterly glowing Jack-o'-lantern, and the composition's centerpiece, a full skeleton rising from its grave. The addition of the River Styx in the corner was meant to ground the piece in a deeper mythology, hinting at a gateway between worlds.
Demo
To see the painting in its full animated glory, please visit the live demo below:
Journey
This project was a monumental challenge in layering and detail management. The goal was to build a scene with incredible depth using exclusively HTML and CSS, with no JavaScript.
The process
It all started with a clear vision: a complete tableau. I built the scene layer by layer, starting from the background (z-index: 0) and working my way to the foreground (z-index: 30+). The primary challenge was ensuring every element was visible and interacted correctly. For example, the River Styx (.river-styx) had to flow "over" the grass field (.hill-bg), but the tombstones and skeleton had to sit "on" the grass, "next to" the river. This required meticulous z-index management.
What I learned
CSS texturing: To prevent elements from looking "empty" or "transparent" (an early problem), I used complex
repeating-linear-gradienttricks to create a detailed brick-like texture for the cabin and a lush, dark grass effect for the unified ground.Complex shape creation: The full skeleton is, by far, the element I am most proud of. It's built from dozens of
divs and pseudo-elements (::before,::after), each styled withborder-radiusandtransformto create an articulated skull, spine, ribcage, and arms.-
Atmosphere is everything: A static scene is boring. The real challenge was orchestrating the animations:
- Lightning (
.lightning) uses akeyframesanimation that triggers abruptly at long intervals. - Rain (
.rain-container-fg) is composed of 300 individualdivs, each with a uniqueanimation-delay, to create a dense and chaotic effect. - Ghosts (
.ghost) were intentionally scaled down (transform: scale(0.6)) and blurred (filter: blur(2px)) to make them feel more distant and ethereal. - The Pumpkin was simplified; I removed all complex
insetshadows in favor of a flat shape, where the flickering light of the carved face is the only light source.
- Lightning (
Future hopes
While I'm incredibly proud of the final result (over 1200 lines of code), the next step would be to add interactivity. Perhaps a hover effect on the tombstones that makes the skeleton jolt, or a lightning flash triggered by a click. For now, it stands as a piece of pure, animated digital art.
Top comments (3)
This is incredible CSS artistry the depth and atmosphere you've created with pure code is stunning. How did you manage the complexity of the z-index layering to keep everything perfectly positioned?
Thank you so much for the incredible compliment! That's so kind of you to say. 🙏
You've absolutely pinpointed the toughest part of the entire build! Managing the z-index complexity was a real journey, especially with over 1000 elements.
My main strategy was to avoid "magic numbers" (like 1, 2, 3...) and instead think in layer groups.
The background (sky, moon, distant stars) lived in the z-index: 0-10 block.
The mid-ground (the cabin, background trees) was in the z-index: 11-20 block.
The foreground (tombstones, skeleton, pumpkin, cat) got the z-index: 21-30 block.
Finally, all the atmospherics (fog, rain, lightning) were layered on top with z-index: 30+.
This "block" system made it much easier to slot new elements in without breaking everything. The most difficult negotiation was definitely the River Styx, the grass, and the skeleton. Getting the river (z-index: 21) to flow over the main grass field (z-index: 2), while ensuring the skeleton and tombstones (z-index: 22) sat on that grass, was a fun puzzle!
I'm so glad you like how the depth and atmosphere turned out. Thanks again for the support!
Some comments may only be visible to logged-in visitors. Sign in to view all comments.