DEV Community

Cover image for The 2 A.M. Fridge: A Landing Page With Exactly One Light Source
Prince Panchani
Prince Panchani

Posted on

The 2 A.M. Fridge: A Landing Page With Exactly One Light Source

Frontend Challenge Perfect Landing Submission 🍲🥧

This is a submission for Frontend Challenge - Comfort Food Edition, Perfect Landing

🍕 What I Built

Most "comfort food" landing pages are going to show you a hero shot of steam rising off a bowl of ramen, a warm colour palette, maybe a parallax scroll. I wanted to build the opposite of that: a page with almost no light in it, where the only thing you can see is whatever you choose to open.

The 2 A.M. Fridge is a single page. It's pitch black. There's a fridge door in the middle of the screen, closed. You pull the tab, the door swings open, and a rectangle of cold light spills into the kitchen — and that's the entire lighting model for the rest of the experience. Nothing else on the page is lit except what the fridge illuminates.

The concept came from a very specific, very unglamorous kind of comfort: not the food itself, but the ritual of standing in front of an open fridge at 2 a.m., not really hungry, just looking. So instead of a menu or a hero pitch, the page is structured as five "acts" — top shelf, middle shelf, the drawer, the freezer, and a hidden jar pushed to the back — and every item you find has a short piece of fiction attached to it. Someone else's mustard, three-day-old curry, a jar of stock made in a good week for a bad one. Nothing dramatic. Just the small, specific stories that live in everyone's fridge.

The technical decision that everything else hangs off: the whole scene is driven by one CSS custom property.

@property --light-intensity {
  syntax: "<number>";
  inherits: true;
  initial-value: 0;
}
Enter fullscreen mode Exit fullscreen mode

As you drag the pull-tab (or press Enter/Space on it — more on that below), fridge-door.ts updates --light-intensity from 0 to 1. That single number feeds the lightwell's radial-gradient glow, the door outline's filter: brightness(), every shelf item's cast shadow, and the condensation overlay's opacity. One value, read in one place, drives the entire visual state of the page. No shadow maps, no lighting engine — just a number and a handful of CSS consumers that all agree on what it means.

The interactions I'm most proud of aren't the obvious ones:

  • The fridge door has real spring physics, not a CSS transition easing curve. There's a tiny critically-damped spring integrator (spring.ts, no dependencies) that also powers a "nudge" effect — hover over a shelf item and its neighbours flinch slightly, like you bumped the shelf.
  • Shelf items are a real 2D grid widget. Arrow keys move focus row-to-column, Home/End jump to the ends, and only the focused item sits in the tab sequence (roving tabindex). It's not a <div> soup pretending to be interactive.
  • The jar pushed to the back of the freezer is a two-step micro-puzzle — move the ice tray, move the bag of peas, then the jar becomes reachable. It holds the emotional peak of the whole piece (a broth someone's grandmother made "for a day she must have known she might not be there for"), and it's built from three ordinary buttons. No drag, no timing window, no pointer-only trap.
  • The closing line is emergent, not fixed. There are about ten hand-written closing sentences, and which one you see depends on what you actually did — did you find the jar, did you close the door and reopen it, did you never touch a mouse, did you linger longest at the freezer. It's a pure decision tree over signals tracked in memory for that visit only. Nothing is sent anywhere, nothing persists.

None of this needed a framework to do the heavy lifting. It's Astro, TypeScript, and vanilla DOM/CSS — the LCP element (the fridge door) is server-rendered HTML/CSS with zero JS required to paint it, and everything animated only ever touches transform/opacity/filter. backdrop-filter never gets driven by a scroll or state variable on purpose — it's one of the most expensive properties you can animate, and this page didn't need to prove a point by using it.

🚀 Demo

Live Demo: https://the-2-am-fridge.netlify.app/

Please actually open the fridge door instead of just scrolling past it — drag the pull-tab, or tab to it and hit Enter. Go through all five acts. Hover over a shelf item to see the neighbour-nudge. Find the jar at the back of the freezer (you'll need to move two things first). Try the "Turn on the kitchen light" toggle in the header, which flips the entire page to a fully lit, 7:1-contrast layout using the exact same design tokens, not a separate theme bolted on. Then close the door and see what the closing line says about your visit.

GitHub: https://github.com/PrinceXDev/perfect-landing-comfort-food

If you want to see how it's actually wired together, src/scripts/fridge-door.ts and src/scripts/spring.ts are the two files that do the most with the least code.

🧑‍💻 Journey

I didn't start with "let's build a dark UI." I started with the idea that the most honest comfort-food story isn't a recipe, it's the fridge itself — specifically what's still in it that nobody's dealt with. Once I had that, the constraint fell out naturally: if the fridge is the only story, the fridge should be the only light.

That constraint turned out to be the best thing that happened to the build, because it forced every decision through one question — does this need light, or can it stay dark? The hero heading is visually hidden (there's nothing to read in pitch black, just a floating pull-tab and a timestamp). The condensation effect on the shelf glass runs through an SVG feTurbulence filter rather than a canvas or WebGL trick, because it only needed to look right inside a small lit rectangle, not do anything clever at scale.

The hardest part wasn't the visuals — it was making sure the darkness didn't become an accessibility liability. It's easy to build a moody dark UI that's a nightmare for anyone using a screen reader, reduced motion, or keyboard-only navigation. I didn't want the "experience" to be a tax paid only by sighted mouse users, so I wrote a whole /accessibility page documenting the actual decisions, not a boilerplate statement:

  • The drag-to-open door has a non-dragging equivalent (WCAG 2.5.7) — Enter/Space fully opens or closes it.
  • Motion respects prefers-reduced-motion and a persisted on/off toggle, implemented as a real CSS state (html[data-motion]), not a one-time sweep of running animations — a sweep misses requestAnimationFrame loops and SVG SMIL entirely. The condensation filter's SMIL <animate> specifically needed SVGSVGElement.pauseAnimations() because CSS media queries can't reach into an SVG filter.
  • forced-colours: active (Windows Contrast Themes) is handled deliberately: since the OS overrides colours regardless of what I author, the decorative light/shadow/condensation layers are hidden outright instead of rendering unforced on top of a forced palette.
  • Every interactive target clears the 24×24px minimum (WCAG 2.5.8), verified on an iPhone 13 viewport in Playwright.

Writing that page after the fact, rather than bolting accessibility on as an afterthought, changed how I thought about the "emergent epilogue" feature too. The keyboard-only branch of the closing line exists because a keyboard-only visitor does something measurably true — no pointerdown ever fires, but real input clearly did — so the accessibility work became part of the story itself instead of a separate feature to demo.

The other genuinely hard problem was performance discipline against my own worst instincts. It would have been very easy to reach for backdrop-filter for the frosted-glass condensation look, or to run the shelf-nudge spring loop continuously "just in case." Instead, the spring loop starts on requestAnimationFrame only when nudged and kills itself (rafId = null) the instant every item has settled, and I kept the animated set strictly to properties the compositor can handle cheaply.

I write the shelf and freezer stories almost like flash fiction — a few sentences, an interior monologue, no exposition. That took longer than any of the code. Getting a hidden jar of a grandmother's broth to land emotionally in two sentences, gated behind an actual puzzle instead of just appearing, was the part I rewrote the most.

What I'd improve next: the README is upfront about this — there's no art direction or photography pass yet (it's a deliberately text-only build), fonts are still the system stack rather than self-hosted and subset, and the mobile pass covers touch/viewport correctness but hasn't had the same interaction polish as desktop pointer events. If I kept going, next would be a proper mobile drag gesture for the door, and maybe extending the "one custom property drives everything" idea to a subtle day/night cycle based on the visitor's actual local time.

What I learned, more than anything: constraints are a gift. "The fridge is the only light source" sounds like it limits what you can build. In practice, it made almost every design decision obvious, because there was always a clear test — does this need the light, or does it live in the dark.


If you try it, I'd genuinely like to know which shelf item got to you, and whether you found the jar. Open the fridge, poke around, and if something breaks or the accessibility claims above don't hold up — file an issue, that page says as much itself.

Tags: #webdev #javascript #astro #accessibility #showdev

Top comments (0)