Loading...
Loading...
A mobile-first clothing store with curated collections, rich product pages, a wishlist, a cart and a smooth 4-step checkout, all installable as an offline-ready PWA.
Lookbook-style home, category and collection screens with filters, sorting, sale toggles and grid/list views across 77 products in 5 categories.
Image gallery, colour and size pickers, ratings, quantity control and quick add-to-cart with a flying-thumbnail animation into the cart icon.
Heart any product, move everything to the cart in one tap, and swipe cart rows left to delete or right to save for later.
Address, delivery (standard, express or scheduled), payment (UPI, card, net banking, wallets, COD) and review, finished with a slide-to-place-order control.
Order history with status filters and tracking, saved addresses, payment methods and an editable profile.
A web app manifest and custom service worker make Vyntra installable on the home screen and usable offline.
Most clothing store templates are desktop sites squeezed onto a phone. Vyntra was designed the other way round: a mobile-first shopping app that feels like flipping through a fashion lookbook, with editorial typography, a warm forest-and-amber palette and big product photography.
Built with Next.js and React, Vyntra covers the whole shopping journey. Users go from an onboarding splash through sign-in, browsing categories and collections, and product details to the wishlist, cart, a 4-step checkout and order tracking. Everything runs in the browser and can be installed to the home screen as a Progressive Web App, so it works both as a polished client demo and as a ready starting point for a real store.
Vyntra is a frontend-only application built on the Next.js App Router. Each screen is a route (/home, /categories, /collection/[slug], /product/[id], /cart, /checkout/*, /orders, /profile and more) backed by a dedicated screen component. App state such as the cart, wishlist, checkout selections, addresses, profile and placed orders lives in small localStorage-backed stores, so the app is fully interactive without a backend and can later be wired to a real API.
The UI uses React 19 and Tailwind CSS 4, with Gloock, DM Sans and Caveat loaded through next/font. Every screen is laid out inside a phone-style frame that defines a fluid design unit, so the layout scales exactly from the original design at any phone width.
// Phone-style column. `--u` = 1 design pixel of the 941px-wide reference.
export function PhoneFrame({ children }: { children: ReactNode }) {
return (
<div className="flex min-h-dvh justify-center bg-page">
<div
className="relative min-h-dvh w-full max-w-[430px] bg-page [container-type:inline-size]"
style={{ ["--u" as string]: "calc(100cqw / 941)" }}
>
{children}
</div>
</div>
);
}
State is shared through a tiny store factory built on useSyncExternalStore, which keeps every screen in sync, including across browser tabs:
/** Tiny localStorage-backed store, usable with a hook or imperatively. */
export function createStore<T>(key: string, initial: T) {
let cache: T | null = null;
const listeners = new Set<() => void>();
const get = (): T => {
if (cache !== null) return cache;
try {
const raw = localStorage.getItem(key);
cache = raw ? (JSON.parse(raw) as T) : initial;
} catch {
cache = initial;
}
return cache;
};
const set = (next: T) => {
cache = next;
localStorage.setItem(key, JSON.stringify(next));
listeners.forEach((l) => l());
};
// ...subscribe + `use` hook via useSyncExternalStore
}
export const checkoutStore = createStore<CheckoutState>("checkout-state", DEFAULT_CHECKOUT);
Key Technologies:
useSyncExternalStoreA web app manifest (name, icons, maskable icon and shortcuts to Categories, Cart and Wishlist) makes Vyntra installable. A custom service worker handles caching: cache-first for static assets and network-first for page navigations, falling back to the last cached copy and then to a dedicated offline page.
/* Vyntra service worker: makes the app installable and usable offline.
- Static assets (JS/CSS/fonts/images/icons): cache-first, filled as they are used.
- Page navigations: network-first, falling back to the last cached copy, then to the offline page. */
const VERSION = "v2";
const STATIC = `vyntra-static-${VERSION}`;
const PAGES = `vyntra-pages-${VERSION}`;
const PRECACHE = ["/home", "/categories", "/cart", "/wishlist", "/profile", "/offline"];
Infrastructure:
Problem: The design was delivered as fixed-width mockups. Hard-coded pixel values looked right on one device but broke on smaller or larger phones.
Solution: I introduced a single CSS design unit, --u, calculated with container query units as one design pixel of the 941px reference (100cqw / 941). Every spacing, radius and font size is written as calc(var(--u) * N), so the whole interface scales proportionally inside the phone frame while staying faithful to the original design.
Problem: Swipe-to-delete cart rows, a slide-to-place-order control and the add-to-cart "flying product" animation all stuttered when every pointer move triggered a React re-render.
Solution: These interactions move elements directly through the DOM, writing only transform once per animation frame, and hand control back to React only when a gesture commits. Swipe rows use open and commit thresholds plus flick-velocity detection, the add-to-cart flight uses the Web Animations API along a curved path and is skipped when the user prefers reduced motion.
Problem: The client needed to demo real flows like cart totals, coupons, delivery dates and placed orders before any backend existed.
Solution: I modelled each domain (cart, wishlist, addresses, checkout, orders, profile) as its own typed local store, with derived hooks for totals, discounts (for example the VYNTRA10 coupon) and delivery ETAs. Screens only talk to these hooks, so swapping localStorage for a real API later means changing the store layer, not the UI.

Mobile Application · Next.js · React

Web Application · Next.js · React Flow

Web Application · React · TypeScript