Skip to content
Codeino
Work2026 · Self-initiated concept build

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.

Browse, choose a grind and a weight, add to cart, check out, pay, and find out where the order got to. Plus the admin side that moves it through statuses and keeps stock honest.

The MORA brand lockup beside a bag of Ethiopia single origin
Role
Design & build, end to end
Scope
Storefront + admin, bilingual
Stack
Next.js 16 · TypeScript · Tailwind 4
Supplied
Brand identity, photography, motion spec
01

The brief

The starting point was 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 did not exist 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.

02

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
  • A complete Arabic version with real RTL — not a translation layer bolted on top
routes, in two languages
25
components
32
lines of TypeScript
8,500+
supplied assets, none recreated
17
MORA Ethiopia single origin, 250g
Product photography was supplied and used as delivered — each shot has a hand-tuned crop focus so nothing important is cut.
A pour over being brewed with MORA Ethiopia
The brew finder pairs a method to the coffees that suit it, folding two separate homepage ideas into one section.
03

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.

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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.

  6. 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.

  7. 07

    Arabic is a real localisation, not a translation layer

    Separate faces, logical properties, and letter-spacing switched off entirely.

    Cormorant and Manrope carry no Arabic glyphs, so Arabic would fall back to whatever the OS supplies. And the Latin design leans on tracking for its labels — but Arabic is cursive, and tracking pulls the letter joins apart. That single detail is the most common reason an Arabic site looks broken.

    Tradeoff · Two type systems to maintain. In exchange the Arabic side reads as though it were designed first, rather than passed through a translator.

  8. 08

    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.

  9. 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.

04

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”.

05

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.

Stack

  • Next.js 16
  • React 19
  • TypeScript
  • Tailwind CSS 4
  • Zustand
  • Vercel

Services

  • Product design
  • Design system
  • Frontend engineering
  • Motion design
  • Localisation (EN/AR)

Need something built to this standard?

We design and build complete systems — a purchase path that works, roles, and an admin panel that actually runs the business. And Arabic here is never a translation layer bolted on at the end.