Inspiration
Every student has a to-do list full of tasks that died weeks ago. Mine had "do laundry" on it for three weeks straight. Normal to-do apps just let tasks sit there quietly, and it's way too easy to ignore a checkbox. So I thought: what if ignored tasks didn't stay quiet? What if they came back to haunt you? It's spooky season, so it felt right.
What it does
Procrastinghost is a to-do list where every task has a timer. If the timer runs out before you finish, the task dies and comes back as a ghost that floats around your screen and nags you in speech bubbles.
The longer you ignore it, the angrier it gets:
| Level | Name | Behavior |
|---|---|---|
| 1 | Mildly spooky | Small ghost, polite reminders |
| 2 | Per my last haunting | Bigger, talks like a work email |
| 3 | Not mad, just disappointed | Angry eyebrows, guilt trips |
| 4 | Full poltergeist | Huge, red, shaking, yells in all caps, tints the whole screen |
When you click a ghost you get three choices:
- Exorcise it by actually doing the task. The ghost bursts into sparkles and rests in the Graveyard.
- Make an excuse. The ghost rebuts it ("Mercury is fine. It grows.") and levels up.
- Snooze. It leaves for a while but comes back one level angrier.
The only way to win is to do your tasks. That's the joke, and also the point.
How I built it
Plain HTML, CSS and vanilla JavaScript, no frameworks.
- The ghosts are SVGs. Their eyes and mouth change shape at each level, going from round eyes to angry eyebrows to glowing red pupils.
- Movement uses
requestAnimationFrame. Each ghost has a position and direction and bounces off the screen edges, and higher-level ghosts move faster. - Tasks are saved in
localStorage, so your ghosts are still waiting when you come back.
The haunt level is calculated from how overdue a task is, plus a penalty for every excuse or snooze:
$$ \text{level} = \min\left(4,\ 1 + \left\lfloor \frac{t_{\text{now}} - t_{\text{haunt}}}{\Delta t} \right\rfloor + E\right) $$
where \( \Delta t \) is the level-up interval and \( E \) is the number of excuses you've made.
Challenges I ran into
- Clicks breaking: the task list re-renders every second to update timers, which kept destroying my button event listeners. I fixed it with event delegation, using one listener on the document that checks
data-attributes. - Ghosts teleporting: if you switched tabs and came back, the animation frame gap was huge and ghosts jumped across the screen. Capping the frame time fixed it.
- Clicking through ghosts: the ghost layer covers the whole screen, so at first nothing underneath was clickable. Setting
pointer-events: noneon the layer andautoon just the ghosts solved it. - Writing the messages: making the ghosts sound passive-aggressive without being actually mean took more rewrites than the code did.
What I learned
- How to build a simple physics/animation loop from scratch
- Drawing and changing SVGs dynamically with JavaScript
- Event delegation, and why it matters when the DOM keeps changing
- That comedy is surprisingly hard to get right
What's next
- Spooky sound effects (chains rattling at level 4)
- Ghosts that physically block your other buttons
- Browser notifications when a new ghost spawns
- A leaderboard for most exorcisms
Built With
- css3
- html5
- javascript
- localstorage
- svg
Log in or sign up for Devpost to join the conversation.