DEV Community

Cover image for I Built a ₹15 Landing Page About Mumbai's Soul Food
Sarvar Nadaf
Sarvar Nadaf

Posted on

I Built a ₹15 Landing Page About Mumbai's Soul Food

Frontend Challenge Perfect Landing Submission 🍲🥧

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

What I Built

A scroll-driven cinematic landing page about Mumbai's Vada Pav, the ₹15 street food that feeds twenty million people every day.

Ten or fifteen of us would walk out of RMD Sinhgad College of Engineering, bags still heavy with books nobody opened, and head straight to the Warje Vada Pav Center. We were always broke. Not the romantic kind. The real kind, where you count coins for the bus and walk if you're short. But ₹15 could fill you up. Every single evening.

I earn in millions now. None of it hits the same. This page is for the Warje Vada Pav Center. And for every boy who stood in that circle.


Demo

🔗 Live Demo

📦 Source Code

Hero Screenshot

What's in the page:

Section What it does
Hero Full-viewport ₹15 typography over a Mumbai street stall at dusk
Origin (1966) The story of Ashok Vaidya's first cart at Dadar station
Personal Story Why this page exists. A college memory.
Build Your Own Interactive 5-step assembler (tab UI, image swap, progress bar)
Recipe Premium card layout with servings scaler (quantities update live)
Culture Six "Unwritten Rules" of eating vada pav
Closer "खाल्ला का?" - Marathi for "have you eaten?" but really means "I love you"

Journey

The Approach

No framework. On purpose. Single index.html + styles.css. No React. No Tailwind. No build step. Open the file and it works. The constraint forces better design decisions.

Dark and cinematic. The entire page lives in one mood: Mumbai at night. Tungsten bulb glow. Saffron accents on a deep navy background. No jarring color shifts between sections.

Typography does the heavy lifting. The first thing you see is "₹15" in massive Playfair Display. No hero image gallery. No sliders. Just the price, because that's the whole point of vada pav.


Technical Highlights

Nav fade-in: ₹15 always visible top-right. Links (Origin, Build, Recipe, Culture) start invisible, fade in proportionally as you scroll using requestAnimationFrame with eased interpolation.

Interactive assembler: ARIA tablist pattern with keyboard navigation. Each step swaps the image and updates the description. Progress bar fills. Five steps: Pav, Green Chutney, Vada, Garlic Chutney, Fried Chili.

Recipe scaler: Change servings from 1 to 8, all ingredient quantities recalculate instantly via data-base attributes.

Scroll reveal: IntersectionObserver triggers .reveal animations as sections enter viewport.


Accessibility

Built in from the start, not bolted on after:

  • Semantic HTML5 (<section>, <nav>, <article>)
  • role="tablist" + role="tab" for assembler
  • Keyboard navigation (arrow keys between steps)
  • aria-live="polite" for dynamic content
  • lang="mr" for Marathi text
  • prefers-reduced-motion disables all animations
  • WCAG AA color contrast verified

What I'm Proud Of

The emotional contrast. Going from "counting coins for the bus" straight to "I earn in millions now." That gap IS the story. The design exists to serve that one moment.

Also: the entire page is 32KB of HTML + 27KB of CSS + 2.8MB of images. No dependencies. No node_modules. No build step. Just files.


Stack

HTML5 + CSS3 + Vanilla JS
Google Fonts (Playfair Display + Lora)
GitHub Pages
Total page weight: ~3.2MB
Enter fullscreen mode Exit fullscreen mode

License: MIT


What's your ₹15? The food that got you through the hard years? Drop it in the comments.


Follow me for more on cloud architecture, frontend experiments, and building in public:
sarvarnadaf.com | LinkedIn | Dev.to | YouTube | AWS Builder Center

Top comments (24)

Collapse
 
debashish_ghosal profile image
Debashish Ghosal

You had me at vada pav! Nice

Collapse
 
sarvar_04 profile image
Sarvar Nadaf

Thanks Bebashish!

Collapse
 
salman_khan_c31307505285e profile image
Salmankhan

Mahn this is outstanding

Collapse
 
sarvar_04 profile image
Sarvar Nadaf

Thank You So Much Salman Bhai.

Collapse
 
publiflow profile image
PubliFlow

Good coverage of the fundamentals. For teams working at scale, I'd also recommend setting up strict ESLint configuration — catching these anti-patterns at lint time saves hours of runtime debugging.

Collapse
 
sarvar_04 profile image
Sarvar Nadaf

Absolutely agree!! Strict ESLint rules can catch these antipatterns early and help us maintain code quality and consistency as the team grow.

Collapse
 
publiflow profile image
PubliFlow

Spot on. Pairing strict ESLint configs with automated CI checks ensures that consistency becomes a hard requirement before code even hits the main branch. Have you found any specific custom rules particularly useful for catching scaling-related antipatterns in your own projects?

Thread Thread
 
sarvar_04 profile image
Sarvar Nadaf

honestly for this project there wasnt much to lint its literally vanilla html css and js with zero dependencies. but on my actual work projects yeah a fewcustom rules have saved me. no-restricted syntax to block nested ternaries that people sneak into jsx. a maxlines per function rule set to 50 because anything bigger is usually doing too much. and import no cycle which catches circular dependencies before they become that one bug nobody can trace. for scaling specifically i like no awaitinloop because sequential awaits in a loop is the number one silent perf killer ive seen in production code. people dont realize theyre making 200 api calls one at a time instead of batching. what about you any rules youve found that caught something nasty early?

Thread Thread
 
publiflow profile image
PubliFlow

It makes perfect sense to skip heavy linting for a zero-dependency vanilla project where the scope is so contained. Those custom rules for your day job are excellent, especially capping functions at 50 lines to prevent god-functions from creeping in. I usually pair import/no-cycle with strict TypeScript module boundaries to keep the architecture clean as the codebase scales. Have you found any other specific rules particularly useful for keeping JSX maintainable?

Thread Thread
 
sarvar_04 profile image
Sarvar Nadaf

Thanks!

Thread Thread
 
publiflow profile image
PubliFlow

Glad it made sense. Keeping the stack minimal is a great move for performance, but did you run into any friction handling the food photography without a heavy image optimization library? I would love to hear how you balanced the visual storytelling of Mumbai's soul food with such a lightweight setup.

Thread Thread
 
sarvar_04 profile image
Sarvar Nadaf

honestly no friction at all. i only had 6-7 images so i just ran them through squoosh manually, converted to webp, kept quality around 75-80%. the hero image i kept slightly higher because thats what loads first. browser native lazy loading handles the rest. for this scale you really dont need a library, 10minutes of manual work and done. if it was 50 images different story but for a single landing page its overkill to add dependencies just for image optimization.

Thread Thread
 
publiflow profile image
PubliFlow

That manual workflow with Squoosh is exactly the right call when you only have a handful of assets. Pulling in an image optimization library for six files just adds unnecessary JavaScript bloat to the critical rendering path. Since you are relying on native lazy loading, did you also add explicit width and height attributes to prevent layout shifts on the smaller food shots?

Thread Thread
 
sarvar_04 profile image
Sarvar Nadaf

yeah i did actually. every img tag has explicit width and height set so the browser reserves the space before the image loads. without that you get those annoying jumps as images pop in especially on slower connections. cls was basically zero on lighthouse which felt good for something hand coded. the trick is just matching the aspect ratio in the attributes even if css overrides the actual rendered size, browser still knows the slot dimensions upfront.

Thread Thread
 
publiflow profile image
PubliFlow

Setting explicit dimensions and matching the aspect ratio in the HTML attributes is an underrated habit that saves so much layout thrashing before the CSS even parses. It is incredibly satisfying to see that zero CLS score on Lighthouse when you have hand-coded every detail. Did you end up using native lazy loading for the images further down the page, or just kept everything eager to keep the code simple?

Thread Thread
 
publiflow profile image
PubliFlow

Nailing that zero CLS score purely through native HTML attributes is a huge win for a hand-coded site. It's wild how many developers still rely on CSS hacks to fix layout shifts when the browser handles it perfectly with intrinsic sizing. Did you find it tedious to calculate those exact aspect ratios manually for all the food photos, or did you write a quick script to batch-process them?

Thread Thread
 
sarvar_04 profile image
Sarvar Nadaf

i didn't calculate them manually. used a quick bash one-liner with imagemagick to extract width/height from all photos and generate the html attributes. something like identify -format "%w %h" *.jpg then piped it into the img tags. took about 30 seconds for all images. the browser does the rest with those intrinsic dimensions. no layout shift, no javascript, no cls issues. sometimes the simplest solution is the right one.

Thread Thread
 
publiflow profile image
PubliFlow

That ImageMagick bash one-liner is an absolute lifesaver for static sites where adding a build step just for image optimization feels like overkill. Piping the dimensions directly into the HTML keeps the stack incredibly lean while completely solving the layout shift problem. Have you considered wrapping that command in a simple Makefile so it runs automatically before your deployment?

Collapse
 
indiesolovibe profile image
Muhammad Yusuf Abubakar

Excellent brother did you build using Ai?

Collapse
 
sarvar_04 profile image
Sarvar Nadaf

Yes ive generated all the images using Chatgpt and bit of code polish.

Collapse
 
mattewens profile image
mattewens

The scroll-story format hits so much harder than the usual hero-features-cta grid we all copy from each other. I'm currently rebuilding my own landing after realising I'd shipped the same tired SaaS template as everyone else, and this is genuinely making me reconsider going narrative-first.

Also: no build step, no framework. Chef's kiss. I keep forgetting you can just... write HTML.

Collapse
 
sarvar_04 profile image
Sarvar Nadaf

man thank you. this is exactly what i was going for. vada pav doesnt need a features section or a pricing table lol. it needed a story. and yeah i think we all forgot that html just works. no bundler no hydration no 200kb runtime for a page that literally just scrolls. good luck with your rebuild, go full narrative youll love how much simpler the design decisions become when every section either moves the story or gets deleted.

Collapse
 
mattewens profile image
mattewens

Ha, exactly what I keep re-learning. Bundler-brain is a real disease. Every time I ship something in plain HTML I remember pages used to load in 200ms and nobody had to configure anything.

Rooting for the rebuild. Post it when it's live, I want to see the story version.

Thread Thread
 
sarvar_04 profile image
Sarvar Nadaf

bundler-brain is such a perfect name for it lol. we've all been there, spending 2 hours configuring webpack for a page that could've been done in 20 minutes with a single html file. will definitely share it when its live, drop me a follow so you dont miss it. and honestly just start with the story first, write it out like youre telling a friend, then wrap html around it. the design figures itself out when the narrative is strong.