All case studies
02Client · Craft e-commerce

Mikono Creations

A full store for a Nairobi craft business that supports 25+ women: 30 animals, a custom-order studio with 29 order types, gift and size finders, a budgeted animal layer, and a checkout that writes a referenced WhatsApp order sized to fit the link.

Role
Strategy, design and full build, run as a multi-agent pipeline with audit gates
When
2026
Stack
Next.js 16 (App Router), React 19, TypeScript, Tailwind CSS 4
  • 51
    route templates
  • 29
    custom order types
  • 88/88
    supplied photos and video used
  • 294
    unit tests passing
01

The problem

  • The brief arrived as 5 WhatsApp zips: 87 photos, 1 video, a logo, seven stockists and a founder story. Prices, delivery fees and centimetre sizes were not supplied.
  • The client ruled out bright colours in the interface for a children's brand, then asked for a site that felt more alive.
  • Kenyan customers buy through WhatsApp, not card checkout, and much of the work is custom: mascots, corporate gifts, pet lookalikes, classroom sets.
02

Architecture

Media intelligenceEvery file got a stable id. 8 analyst agents labelled them, two blind passes re-labelled 60, and 5 independent deciders voted on the 44 unclear ones.
StrategySix strategy reports, then an audit that failed the set and listed 19 contradictions between them, then one binding decisions file of 40 rulings that overrides them all.
Storefront30 animals in 5 categories with colour and size variants, a cart, and an order wizard that ends in a prefilled WhatsApp message with an MK-YYMMDD-XXXX reference.
StudioA 3-step quick brief or a full brief of up to 11 steps. Each of the 29 order types carries flags that decide which questions appear. Multi-piece orders, plus a gift finder, a size finder and a safari family builder.
Living layer59 SVG animal sprites, a scroll thread and a 12-find herd game. The server renders every animal as still HTML; a standalone engine only sets data attributes, so hydration never disagrees.
ImagesA build step turns 280 source images into 2,391 WebP variants at up to 11 widths. A custom next/image loader picks the nearest one, so no runtime optimiser is billed.
Content and trust47 journal posts from four sources normalised into one model, 16 draft legal documents, and analytics tags that load only after consent.
03

Key decisions

01

Two real prototypes before choosing a look

A dark glass direction and a daylight clay direction were both built as static pages with the real photos and screenshotted at phone and desktop sizes.

TradeoffDaylight clay won: heavy backdrop blur is the costliest effect on low-end Android, and dark was the wrong first impression for a children's brand.
02

No invented prices

One pricesConfirmed flag drives four price states, so no total is shown or estimated until every line and the delivery fee are known. The catalogue throws at build time if the flag is on and any product lacks a price.

TradeoffEvery product reads 'Ask for price' until the client supplies prices, which costs some conversion.
03

Motion with a budget

An inline head script picks the animal tier before first paint from the visitor's switch, reduced motion, Save-Data and device memory. The engine then lets at most 4 animals move on a phone, 6 on desktop, 2 on weak devices.

TradeoffFewer flourishes. After three slow frames the engine cuts the budget by 2 for the session, and an Animals switch in every footer turns motion off.
04

Pre-built images instead of a runtime optimiser

The hosted image optimiser hit its plan limit, so every photo is now resized once at build time and served as static WebP.

TradeoffBuilds take longer as the photo set grows, so variants are incremental and AVIF stays opt-in.
04

Real code

lib/whatsapp.tsexcerpt
export const URL_BUDGET = 2000;
// …
/** Picks the longest level whose encoded URL fits the budget. fullText is always the full message for copying. */
export function planOrderSend(number: string | undefined, o: OrderMsg): SendPlan {
  const fullText = buildOrderMessage(o, "full");
  const levels: Level[] = ["full", "compact", "short"];
  let last: SendPlan | null = null;
  for (const level of levels) {
    const text = level === "full" ? fullText : buildOrderMessage(o, level);
    const url = buildWaUrl(number, text);
    last = { level, text, fullText, url, tooLong: false, pasteRest: level !== "full" };
    if (!url || url.length <= URL_BUDGET) return last;
  }
  return { ...(last as SendPlan), tooLong: true };
}
A wa.me link fails when it gets too long, so the order message steps down from full to compact to short until the encoded URL fits, and the full text is kept for the customer to paste.
components/fx/engine/index.tsexcerpt
function setBudget() {
  const phone = innerWidth < 900;
  budget = tier === "lite" ? 2 : phone ? 4 : 6;
  try { if (sessionStorage.getItem("mk-fx-slow")) budget = Math.max(1, budget - 2); } catch { /* storage blocked */ }
}
// …
  if (!force && lastT && dt > 100 && motionOK()) {
    slowFrames = slowFrames.filter((t) => now - t < 2000);
    slowFrames.push(now);
    if (slowFrames.length >= 3) { try { sessionStorage.setItem("mk-fx-slow", "1"); } catch { /* storage blocked */ } setBudget(); soon(0); slowFrames = []; }
  }
Only the best few animals may move at once. The budget depends on screen width and device tier, and drops for the rest of the session after three slow frames.
lib/studio/flow.tsexcerpt
export const wantsBusiness = (b: Brief) => isBusinessCustomer(b) || hasType(b, (t) => !!t.business);
// …
export const isBulk = (b: Brief) => hasType(b, (t) => !!t.bulk) || b.pieces.some((p) => !!qtyBands.find((x) => x.id === p.qtyBand)?.bulk) || totalCount(b) >= BULK_FROM;
// …
export const mayHaveLogo = (b: Brief) => hasLogoFinish(b) || hasType(b, (t) => !!t.logo) || b.pieces.some((p) => p.baseId === "character");
// …
export function activeSteps(path: PathMode, b: Brief): StepId[] {
  if (path === "quick") return quickSteps;
  return fullSteps.filter((s) => s !== "business" || wantsBusiness(b));
}
The studio asks only what the brief makes relevant. Order types are data with flags, and small pure predicates decide which steps and sections appear.
scripts/test-whatsapp.mjsexcerpt
t("the KRA PIN is in the message but never in a draft", () => {
  const form = { ...F.emptyForm, customerTypes: ["shop"], name: "Amina", phone: "0712345678", businessName: "Savanna Gifts", kraPin: "A123456789B", paymentNote: "x" };
  const ticks = { ...F.noTicks, terms_acknowledged: true };
  const msg = F.toOrderMsg(form, [{ sku: "a-b-s", name: "A", colourLabel: "B", size: "S", qty: 1 }], "MK-261001-AAAA", ticks, "", "", C.CONSENT_VERSION);
  assert.ok(W.buildOrderMessage(msg, "full").includes("KRA PIN: A123456789B"));
  const full = W.buildOrderMessage(msg, "full");
  F.writeDraft({ step: "review", skipGift: false, skipAbout: false, form, ref: "MK-261001-AAAA", sent: { message: full, text: full, level: "full", at: 1 } });
  const raw = mem.get(F.DRAFT_KEY);
  assert.ok(raw && !raw.includes("A123456789B") && !raw.includes("kraPin"), "draft has no PIN");
  const back = F.readDraft();
  assert.equal(back.form.kraPin, "");
});
Privacy rules are tests: a business's KRA PIN goes into the WhatsApp message but must never reach the draft saved on the device.
05

How it was verified

  • Three independent verifiers (visual, content and media, function and accessibility) per rendering phase; their reports for the shop, trade and content phases are committed to the repo.
  • 294 unit tests across checkout, WhatsApp messages, enquiries, the studio and helpers, including privacy rules: no child data fields, and the KRA PIN never stored on the device.
  • Build gates: a copy scan for dashes and banned phrases, a choreography check for the animal layer, and an OKLCH chroma cap of 0.13 with WCAG AA contrast pairs.
  • Playwright mobile scripts for layout, interactions, width and performance across 11 viewports; the width gate passes all 366 page and width runs.
06

Results

  • All 88 supplied files (87 photos, 1 video) appear on the site; the 44 unclear species were settled by vote for the client to confirm at review.
  • Live with 51 route templates, 30 animals, 47 journal posts and a custom studio that turns any brief into one WhatsApp message.
  • The pipeline was written up as the Foundation Build Playbook for future client sites.
07

What I'd do next

  • Switch on prices and product offer markup once the client confirms them.
  • Clear the open critical and high findings from the latest mobile layout gate.
  • Run the SEO crawler against the live site and fix what it finds.
  • Have a Kenyan advocate review the 16 draft legal documents before launch.
$ ./hire --re mikono-creations

Want a build like Mikono Creations?

Senior remote roles and contract builds. Replies within a day, EAT (UTC+3).