AQUA FLEX
Water treatment engineering, built without inventing a single claim.
Corporate site for a Lahore water-treatment company running since 1995. Nine routes covering commercial and domestic RO systems, how treatment actually works, services, references, the team, contact, and an interactive plant selector — built around the constraint that almost nothing about the business had been documented.

Numbers about the client's business — useful context, not my engineering output.
Numbers I produced — measurable, attributable to the work I did.
My specific scope on this project — separate from anything the client team supplied.
- Nine-route Next.js 16 site — design system, every page, header, footer
- Tailwind v4 @theme token system with a contrast gate script that fails if any of 22 colour pairs drops below WCAG AA
- Interactive treatment-process explorers — 9 stages on How It Works, a 5-stage preview on the homepage — built as WAI-ARIA tab patterns
- Eight-step plant selector with a conservative rule-based recommendation engine that explains its own reasoning
- Contact form and selector both compose a structured WhatsApp enquiry — no backend was available, so none was faked
- Centralised pricing module: a price renders only once the business has verified it, otherwise the call to action renders instead
- Business-claims audit that found and removed nine unverifiable public claims
- Vectorised the supplied logo into a mark / wordmark / reversed system, all cut from one master by viewBox
- Namecheap to Vercel domain migration with canonical, sitemap, robots and schema moved on one constant
From brief to production system.
The client supplied a phone number, an address, seven client names with their sectors, ten staff names, two portraits and two hand-drawn process diagrams. That is it. No capacities, no project photography, no prices at the start, no certifications. Every gap on a site like this is an invitation to write plausible copy — a fake case study, a stock photo under a real client's name, an invented response time. Doing that would have handed the business claims it could not honour.
Built every surface so that missing information degrades honestly instead of being filled in. Prices live in one module and render their call to action until the business verifies a figure. Service intervals stay unpublished because they depend on the water. The plant selector recommends a starting category and says three times over that design comes from an analysis. Social icons render as decoration — not dead links — until a real URL exists. A written claims audit records the evidence behind every public statement, and the nine that were removed for having none.
Live on its own domain with nine routes, 100 on accessibility, best practices and SEO across every page measured, zero dead links, and zero horizontal overflow from 320px up. Every published claim traces to a supplied file or written confirmation, and the handoff document tells the client exactly what is still needed to fill the remaining gaps.
The boundaries that shaped the build.
- 01Seven client references with name and sector only — no capacity, year, value or photograph
- 02Two of ten staff portraits supplied; no generated faces permitted for the rest
- 03No backend, no mail service, no API key — the contact path had to work anyway
- 04Prices arrived late in the build and had to be publishable the moment they did
- 05Pakistani market: mostly mobile, WhatsApp-first, cost-sensitive hosting
What I chose, and what I gave up.
Typography-led client references instead of illustrated case-study cards
Visual richness on the strongest credibility page. A stock plant photo under a real client's name implies that equipment is theirs, which is not mine to imply — so the references are set in type until real site photography is cleared.
Structured WhatsApp messages instead of a form backend
A lead database and submission analytics. There was no mail service to post to, and a form that silently goes nowhere is worse than none — this way the visitor reads the exact message before it sends, on the channel the market already uses.
A stated experience label rather than one computed from the founding year
Self-updating copy. A site that quietly becomes '32 years' next January is a site nobody is checking; the figure is stated once and reviewed deliberately.
How it shipped, week by week.
Design system + homepage
Built the token system, the contrast gate, header, footer and homepage before any other page existed, so every later route inherited one visual language.
Product and process pages
Commercial, domestic, how-it-works and services. The treatment explorers were built as accessible tab patterns rather than bespoke widgets.
Proof, people and conversion
References, the team presentation, contact, and the plant selector with its rule engine. Every claim on these pages had to trace to something the client supplied.
Claims audit + production hardening
Audited every public statement against the evidence, removed nine unverifiable claims, added SEO, structured data, sitemap and robots, and fixed the accessibility defects Lighthouse surfaced.
Brand + domain
Vectorised the official logo into a derived variant system, then migrated the canonical origin from the Vercel alias to aquaflexpk.com — DNS, certificates, redirects and every absolute URL.
What it does. How it's built.
Features
- Interactive 9-stage treatment process explorer, keyboard-operable
- Eight-step plant selector with an explainable recommendation
- Domestic system pricing with cartridge and component price list
- Requirement form that composes a structured WhatsApp enquiry
- Client reference grid with sector filtering
- Team presented as departments, with initials where no portrait exists
- LocalBusiness schema with confirmed opening hours
- Branded 404, sitemap and robots
Architecture
- 01Next.js 16 App Router, Server Components by default
- 02Client JS only where something is genuinely interactive — header, two process explorers, reference filter, contact form, selector
- 03Tailwind v4 @theme design tokens, no component library
- 04Single-source data modules: company, clients, team, pricing, products
- 05Contrast gate reads the palette from globals.css so it cannot drift
- 06next/image with priority on the LCP hero, lazy below the fold
- 07Static generation for all nine routes
- 08Vercel edge hosting, custom domain via Namecheap A + CNAME records
Annotated excerpts.
export function formatPrice(
key: string,
fallbackLabel = "Request Current Price",
): string {
const p = pricing[key];
if (!p || !p.verified || p.type === "quote" || p.amount === undefined) {
return fallbackLabel;
}
if (p.type === "range" && p.maxAmount !== undefined) {
return formatRupees(p.amount) + " – " + formatRupees(p.maxAmount);
}
if (p.type === "starting-from") {
return "From " + formatRupees(p.amount);
}
return formatRupees(p.amount);
}export function recommend(a: SelectorAnswers): Recommendation {
const reasons: string[] = [];
const caveats: string[] = [];
const litres = a.demand ? toLitresPerDay(Number(a.demand), a.demandUnit) : null;
// An installed plant changes the question entirely.
if (a.hasExisting === "yes") {
reasons.push("There is already a treatment system installed");
caveats.push(
"Whether it is serviced, modified or replaced depends on what is " +
"installed and how far it is from what is now needed.",
);
return { category: categories["service-upgrade"], reasons, caveats };
}
const processUse = a.purpose === "process" || a.purpose === "boiler";
const isIndustrial =
a.application === "industrial" ||
processUse ||
(litres !== null && litres >= INDUSTRIAL_FROM);
// Nothing to go on: say so rather than guessing a category.
if (!isIndustrial && a.application !== "home" && a.source === "unknown") {
caveats.push("A test of the supply is the sensible first step.");
return { category: categories["analysis-first"], reasons, caveats };
}
// ...source and TDS qualify the answer; they never decide it alone.
}Continue browsing
Have a project like this in mind? Let's talk.
Send me a brief and I'll respond within 24 hours.