Case Study
MORA Specialty Coffee
A complete storefront for an Egyptian coffee roaster — from a supplied brand identity to a working checkout, an order that can be tracked, and an admin panel that actually runs the shop.

- Role
- Design & build, end to end
- Scope
- Storefront + admin, 25 routes
- Stack
- Next.js 16 · TypeScript · Tailwind 4
- Supplied
- Brand identity, photography, motion spec
The brief
The client had a finished brand — logo, palette, typography, product photography — and an unusually detailed motion specification running to 25 numbered behaviours with exact durations, easings and stagger values. What they did not have was a website.
The ask was a storefront that read as a real premium coffee brand rather than a template, with the whole purchase path intact: browse, choose a grind and a weight, add to cart, check out, pay, and find out where the order got to.
One constraint shaped everything else — no real backend. That is not a gap to apologise for, it is a scoping decision, and the interesting question becomes which parts of a shop you can make genuinely work without one.
What shipped
- Home, shop, product, cart, checkout, payment, order confirmation, tracking, wishlist, brewing guides, about, 404
- Variants with live pricing, stock awareness and sold-out states
- Cart drawer and full cart, shipping estimated by governorate, discount codes
- Validated checkout with Egyptian mobile formats, card or cash on delivery
- Admin: overview, product CRUD, order statuses with audit history, per-SKU inventory
- Placing an order decrements stock; changing a status updates the customer's tracking page
- routes
- 25routes
- components
- 32components
- lines of TypeScript
- 8,500+lines of TypeScript
- supplied assets, none recreated
- 17supplied assets, none recreated
Decisions, and why
Anyone can list a stack. The useful part of a case study is the calls that had a real alternative — so each of these says what the tradeoff was, not just what was chosen.
- 01
No customer accounts
Guest checkout, plus order lookup by number.
Accounts are the largest build in commerce and the least differentiating. For a roaster this size, entering an order number covers the behaviour people actually have — they check one order, once, a day after buying it.
Tradeoff · No saved addresses and no history across devices. The tracking page lists every order placed from that browser, which covers the realistic case.
- 02
The wordmark is typeset, not the supplied bitmap
The lockup is set in live type rather than placed as an image.
The logo arrived as a JPG carrying its own dark background, which reads as a visible box against the site's near-black. Cormorant Garamond is the brand's own display face, so setting it in type reproduces the mark exactly and stays sharp at any size.
Tradeoff · It is a reproduction, so it needs signing off against the brand guide. In exchange it is resolution-independent and can be recoloured per context.
- 03
Two conflicting palettes, resolved by measurement
#C9A96E took every interactive and text role. #AC8760 stayed decorative.
The brand board specified Antique Gold #AC8760; the motion spec specified #C9A96E. Rather than choose on taste, both were measured against the near-black ground — 8.6:1 versus 5.9:1. The brighter gold clears AAA for body text; the antique gold does not.
Tradeoff · Neither source document was followed literally. Both golds survive as tokens, so reversing the decision is a one-line change.
- 04
Stock lives on weight, not on grind
A 500g bag is one SKU whether it is ground for espresso or filter.
Roasters stock by weight and grind to order. Treating grind as a stock dimension would have tripled the SKU count and modelled an operation that does not exist.
Tradeoff · None in this domain. It is simply the correct model, and it keeps the inventory screen honest.
- 05
The payment gateway fails, on purpose
Sandbox cards that decline, a transaction reference, and a working retry.
Most demos show a success screen and stop. The interesting engineering is the unhappy path: a declined card moves the order to failed, keeps the reference, and the retry restores it to payment-pending and re-enters the flow. That path is where real checkouts break.
Tradeoff · Nothing is transmitted and no card is charged. Swapping in Paymob or Stripe means replacing one component, not rewriting the flow.
- 06
Motion built from the client's tokens, not a library
25 specified behaviours implemented in CSS with one shared observer.
The brief arrived with exact durations, easings, stagger values and a list of forbidden effects. Those became custom properties, so the spec and the implementation use the same numbers. A library would have meant translating the spec into someone else's API.
Tradeoff · More code than an import. In exchange it matches the spec literally and ships far less JavaScript — the one animation dependency is scoped to a single component and code-splits away from every page that does not use it.
- 07
Content that starts invisible survives JavaScript failing
Scroll-revealed sections begin at opacity 0 — with two escape hatches.
A `<noscript>` rule neutralises the reveal entirely, and a sweep on scroll and visibility change catches anything the observer missed. A background tab suspends IntersectionObserver, so without that sweep an element scrolled past while hidden would stay invisible permanently.
Tradeoff · A little redundancy for a failure mode most sites never handle — and the reason no visitor can land on a blank page.
- 08
Server and client render identically, by construction
Persisted state hydrates after first paint, never during it.
Cart, wishlist and orders live in localStorage, which the server cannot know about. Rehydrating explicitly after mount means the server HTML and the first client render always match, instead of producing a mismatch React silently recovers from.
Tradeoff · Counts appear one frame late. That is invisible, and the alternative is React discarding a subtree and the state that came with it.
- 09
The admin is unlocked, and that is safe
Anyone can edit products, move orders and change stock. It never leaves their browser.
The admin is the part that separates building a website from building a system, so it has to be openable. Every writable value lives in that visitor's own localStorage — verified against the live deployment by tampering with a catalogue in one browser and confirming the server still served the original to everyone else.
Tradeoff · Admin edits do not persist across devices. Correct for a demo, wrong for production, and a deliberate line rather than an accident.
The motion specification
The most demanding part of the brief was a document listing 25 motion behaviours: hero load sequence, navbar transition, product card hover, scroll reveal, image reveal, parallax at 0.85, button states, the add-to-cart sequence, the cart drawer, quantity controls, variant selection, wishlist, badges, links, headings, footer, loading, page transitions, mobile rules, accessibility and performance constraints — with a closing rule that every animation must serve comprehension, feedback, or perceived quality, and anything else should be cut.
All 25 are implemented. The tokens in the spec became the tokens in the stylesheet, so the durations and easings in the brief are literally the ones running in the browser. The add-to-cart sequence was verified against the spec by instrumenting it: pressed, showing “adding” at 150ms, “added” at 600ms after the specified 500ms minimum, drawer open at 900ms, back at rest by 2s.
prefers-reduced-motion is honoured properly rather than blanket-disabled: parallax, stagger, page transitions and large transforms are removed, colour and border transitions stay, marquees stop and become manually scrollable, and spinners keep spinning so they still read as “working”.
What was deliberately left out
Scope discipline is part of the work. Each of these was considered and cut for a stated reason.
- Real accounts
- Guest checkout covers the behaviour without the backend.
- Loyalty points
- Needs a backend to mean anything.
- An AI assistant
- Nothing here is a question a chatbot answers better than the page.
- Real subscription billing
- Recurring payments need a live gateway and a real merchant.
The customer reviews on the demo are written placeholder content, not real purchases, and the file that holds them says so. Publishing invented reviews on a shop that takes real orders is deceptive advertising, so they exist to demonstrate the component and are meant to be replaced.
Need something built to this standard?
Codeino builds storefronts, booking systems and product sites — designed and engineered by the same hands, so the decisions hold together.
