Kalite Men's Wear
A bilingual storefront and admin for a Cairo menswear factory selling direct for the first time — from a 266-pixel logo sent over WhatsApp to an Arabic-first shop, a written brand system, and a panel the owner uses to run the catalogue and stock.
Built for how Egypt actually buys: on a phone, in Arabic, paying cash on delivery — and with the owner able to add products and manage stock without a developer.

- Role
- Brand system, design & build — end to end
- Scope
- Bilingual storefront + admin, 11 routes
- Stack
- Next.js 16 · TypeScript · Tailwind 4 · Postgres
- Supplied
- A 266px logo. Nothing else.
The brief
Kalite is a factory in Cairo that makes everyday menswear — jeans, tees, gabardine trousers, jackets, shorts. It had a logo, a Facebook page of about 1,900 followers that had gone quiet, and nothing else: no website, no product photography, no written identity.
The ask was a shop that reads as an everyday brand rather than a factory outlet — affordable but considered — built for how Egypt actually buys: on a phone, in Arabic, paying cash on delivery, with the owner able to add products and manage stock without a developer.
Two constraints shaped the build. The only logo file was a 266-pixel JPEG, so every decision about the mark had to work within what that file could honestly deliver. And the client chose to leave checkout and courier integration for the final phase, which turned the question into which parts of a shop can be made genuinely useful before an order system exists.
What shipped
- Home, all products, seven category pages, product, cart and 404 — each in Arabic (right-to-left, the default) and English
- Product pages with colour and size pickers driven by real stock per variant, an “only 2 left” notice, sale and sold-out states, and a sticky add-to-cart bar on phones
- A cart that will not exceed what is in stock, and sends the order as a pre-filled WhatsApp message listing every line and the total
- Admin: login, a dashboard with a running-low list, product create, edit and delete, a stock grid per colour and size, photo upload with reordering, hide and publish
- Saving in the admin refreshes the static storefront pages — no rebuild, no waiting for a deploy
- A 614-line written brand system: tokens, a type scale, a measured contrast table, motion rules, photography rules and tone of voice
- routes
- 11
- components
- 24
- lines of TypeScript
- 5,100+
- supplied asset, never redrawn
- 1



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
The brand colour was measured, not taken from the brief
#F8C51C was sampled from the logo's own pixels and replaced the specified #D9A900.
The identity document named a darker gold than the one actually in the logo. Using both would have put two different yellows side by side in the header, which reads as a mistake rather than a choice. Contrast was calculated for each: the logo's gold reaches 12.3:1 on the near-black, better than the 9.8:1 of the specified one.
Tradeoff · On white it measures 1.6:1, so the rule became absolute — gold is never text and never a thin line on a white ground. Stating it that plainly is what keeps it true as the site grows.
- 02
The logo is never redrawn, so it has a size ceiling
The mark is used exactly as supplied, capped at 96 pixels tall.
The only source was a 266-pixel JPEG from WhatsApp; the artwork inside it is 208 pixels tall. Extracting it to a transparent file was acceptable. Retracing it was not — a retraced logo is a different logo. That sets a hard limit: above roughly 100 pixels it is being enlarged on a modern phone screen and goes soft.
Tradeoff · The client asked for a bigger logo and got 96 pixels. The limit lifts the day a vector file arrives, and the code needs no change when it does.
- 03
Stock is tracked per colour and size, in one row
Each product is a single database row; colours, sizes, stock and photos live in JSON columns.
A factory holds navy in 32 separately from beige in 32, so stock is a grid, not a number. Keeping that grid inside the product row means the admin saves a product as a whole in one write, and the storefront reads a product in one query.
Tradeoff · Variants cannot be queried relationally. That is the right shape for a catalogue of this size, and the wrong one at ten thousand SKUs — which is a decision to revisit at a scale this shop does not have.
- 04
Static pages, refreshed from the admin
The storefront is prerendered; saving a product invalidates it.
Every public page is static HTML served from the edge, so the shop is fast on a mobile connection. When the owner saves, the pages are marked stale and rebuilt on the next visit. Measured on a production build: the first request after a price change was a cache miss carrying the new price, the second a cache hit.
Tradeoff · The first visitor after an edit waits for one page to rebuild. In exchange every other visitor gets static HTML from the edge, which on an Egyptian mobile connection is the trade worth making.
- 05
The order goes out over WhatsApp, on purpose
The cart's primary button opens WhatsApp with the full order already written.
Checkout was scheduled for the last phase. A half-built checkout that takes details and loses them is worse than none, so the cart does the one useful thing it can: it composes a message with every line, colour, size, quantity and the total, addressed to the shop.
Tradeoff · It is a stopgap and the interface says so — the total is labelled “before shipping”, and no order record is created until checkout is built.
- 06
The admin refuses to run without a real database
On the hosting platform, with no database connected, login is blocked with a plain message.
Locally the project runs on an in-process Postgres so it needs no setup. On a serverless host that same fallback would give every instance its own throwaway copy: a save would appear to succeed and then vanish. So the admin checks for that exact situation and declines to open.
Tradeoff · For the days before the database was provisioned, the live admin was deliberately unusable. That is the cheaper failure by a wide margin.
- 07
Seven categories broke the header, so the header changed shape
On desktop the categories moved to their own centred row under the logo.
With five Arabic category names beside a centred logo everything fit. At seven, in English, the list pushed the logo between 51 and 159 pixels off centre and wrapped to two lines on a tablet. Rather than shrink the type, the layout was changed to one that holds any number of categories in either language.
Tradeoff · A taller header — 166 pixels, collapsing to 122 on scroll — bought in exchange for a layout the eighth category will not break.
- 08
Photos are shrunk twice — once by us, once in the owner's browser
Supplied images were re-encoded for the web, and admin uploads are resized before they leave the device.
The supplied photographs arrived at two to three megabytes each; re-encoded they went from 19.4 MB to 1.07 MB in total. The same rule is built into the admin: a phone photo is resized to 1,600 pixels and re-encoded in the browser before upload, so the owner cannot accidentally publish an 8 MB image.
Tradeoff · The store keeps the web copy rather than the original, so a different crop means uploading again — which is also what stops the catalogue filling with files nobody can download on mobile data.
- 09
Nothing on the page claims what has not been confirmed
Unconfirmed facts are stored as “unknown” and the interface hides them.
Free-shipping thresholds and delivery times had not been agreed, so those values are empty in the configuration and the matching lines simply do not render. The customer-photos section stays hidden until real photos exist. An Instagram handle that could not be verified was removed.
Tradeoff · A sparser page than a template would give. The brand's second stated value is honesty, and an invented delivery promise is a bug.
Arabic first
The shop is Arabic by default and English second, which is more than flipping the layout. Spacing and position rules are written against the reading direction rather than left and right, so one stylesheet serves both. Arrows mirror. Arabic text drops the letter-spacing and capitals that the English labels use, because neither exists in the script.
The details are where it usually goes wrong. A “-19%” sale badge rendered as “19%-” inside right-to-left text until it was isolated as left-to-right. Arabic has six plural forms, so the product count says “منتجين” for two and “٦ منتجات” for six rather than gluing a number to one word. Prices use Western digits — the platform default for Egyptian Arabic is Arabic-Indic numerals, which no Egyptian shop uses for prices.
Language survives a switch mid-purchase: the cart stores which product and which colour was chosen, not the words, so an item added in Arabic is named in English the moment the visitor changes language. Two typefaces are loaded, one per script — Cairo for Arabic and Montserrat for Latin — each subset and self-hosted rather than pulled from a font CDN. Inputs are set at 16 pixels on phones in both languages, because anything smaller makes iOS Safari zoom the page on focus.
The brand system
Before any interface was built, the identity was audited against the one artefact that existed — the logo. An open question about the name (“Kalite” or “K@lite”) was settled by enlarging the wordmark: the second letter is a plain geometric “a”. The gold was sampled from the file. Every colour pairing was given a calculated contrast ratio rather than a tick.
The result is a 614-line specification that the code follows literally: six colours and no others, two typefaces, a two-pixel corner radius, no gradients, no shadows, no emoji, one icon library. Motion is limited to a single 300-millisecond reveal per block and a 1.03 zoom on hover, all of it switched off for visitors who ask for reduced motion.
The contrast table is the part that earns its keep. Black on white measures 19.6:1 and the grey on stone 4.8:1, both passing. Gold on black, and black on gold, reach 12.3:1. Gold on white measures 1.6:1 and is written into the specification as forbidden — which is why warning text uses a separate darker amber at 5.1:1 rather than borrowing the brand colour.
The admin, in one paragraph
The admin is in Arabic, built for a phone, and assumes its user is a shop owner rather than a developer. Common colours and sizes are one tap. Stock is a grid — colours down, sizes across — with a “fill all” shortcut. Photos are reordered with two buttons and the first is the cover. A product can be hidden without being deleted. The dashboard answers the two questions an owner actually has in the morning: what has sold out, and what is about to.
Behind it there is no user table. The shop has one operator, so there is one owner password and an expiring signed session — HTTP-only, scoped to the admin path, verified before any admin page renders and verified again inside every write and every upload, so a routing mistake cannot expose a save. Credentials exist in production only; preview deployments cannot log in at all. Roles and an audit trail are the first things to add the day a second person starts using it.
What was deliberately left out
Scope discipline is part of the work. Each of these was considered and cut for a stated reason.
- Checkout and order management
- Scheduled as the final phase by the client. The WhatsApp order covers the behaviour until then.
- Courier integration
- There is no shipment without an order; it follows checkout.
- Customer accounts
- Nothing to show in an account until orders exist.
- Search
- A small catalogue with category filters does not need it; the icon was removed rather than left as a dead button.
- Dark mode
- A brand decision — one light look, stated in the identity.
- Card payment
- A cash-on-delivery market; optional once checkout exists.
The catalogue on the demo is placeholder content — six products with invented names, prices and fabric details — and the file that holds them says so. Product photography has not been shot, so product cards show a neutral placeholder at the final aspect ratio. The hero and category photographs are AI-generated with the client's approval; the identity recommends real photography for the products themselves, because a generated garment is not the garment the customer receives.
Stack
- Next.js 16
- React 19
- TypeScript
- Tailwind CSS 4
- Postgres
- Drizzle ORM
- Neon
- Vercel
Services
- Brand system
- Product design
- Frontend engineering
- Backend engineering
- Localisation (AR/EN)
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.
