This is a submission for Frontend Challenge - Comfort Food Edition, Perfect Landing.
What I Built
The only image file in this project is the favicon.
No <img> tag anywhere. No inline SVG. No url() in any stylesheet. The pot, the broth, the steam, the fire, the rain running down the glass, the fog on the mountains, the moon, the roots floating in the bowl — every one of them is HTML and CSS. No photography, no icon set, no framework, no UI library, nothing loaded from another server. Check the source; that is the whole point.
Niebla is a one-page concept kitchen in the mountains of Puerto Rico, and the hook is weather instead of a menu: when the hills disappear into fog, the sancocho comes out.
It is also not a poster. You cook in it:
- Take a root out of the pot — calabaza, yuca, yautĂa, plátano verde, maĂz. The piece leaves the CSS bowl and the live description rewrites itself for whatever is left.
- Set the simmer — low, steady, rolling. Two CSS custom properties drive the steam speed and the hue of the broth. No new markup, no re-render.
-
Take a seat — six of them, through a native
<dialog>, saved to that one device and sent nowhere. -
Read the whole thing in Spanish or English — copy, controls, and the live text a screen reader announces, with
langswitching so the pronunciation follows.
Six seats. Rain as chrome. One dish.
Demo
Live: https://niebla-comfort-food.vercel.app
Open it, kill an ingredient, slam simmer to rolling, set a seat. That is the whole demo.
Journey
Constraint first: if this is a frontend challenge, the food can’t be a photograph I didn’t make. The hero pot is borders, gradients, shadows, and timed steam layers. The weather layer is fixed rain that respects prefers-reduced-motion.
JS stays small on purpose:
- ingredient buttons with
aria-pressed - simmer buttons that write
--steam-speedand--soup-hue - a readout that renames the bowl (
La Completa,La Tuya,Lo Esencial,El Caldo) from how many roots are left - place card open / save / clear, focus restored when the dialog closes
- an ES/EN toggle that swaps
document.documentElement.langalong with the copy, so thearia-livebowl description announces in the language the reader is actually in
No analytics. No network after load.
What broke: first full-page QA captures came back with empty sections. Not a layout bug. An IntersectionObserver “reveal” kept content at opacity 0 until a real scroll. Headless capture never scrolls — and neither does a screen reader working down the document, or a browser with JS off. Same class of failure as a page that only becomes readable after JS animation. I deleted the reveal so the document paints complete on first load.
Measured: W3C HTML — zero messages. Lighthouse — 100 accessibility, 100 best practices, 100 SEO (perf ~94 last run). Overflow checked at 320 / 390 / 768 / 1024 / 1440 — zero at every width. Zero console errors, zero failed requests, zero external runtime assets.
Puerto Rico is in the product: sancocho, named roots, mountain weather. Not a real restaurant. Not a published family recipe. A built object.
The page that only becomes readable after a scroll animation is not finished. First paint has to carry the whole document.
MIT.


Top comments (0)