01Professional WorkPrivate repository · described at architecture level only

WLE Website

A public website and self-hosted CMS for an independent wireline and well-testing company, built on a strict contract between the people who publish content and the code that renders it.

Public routes
22 approved
Content model
16 collections · 13 globals
Animation libraries
None
Pinned scroll sequences
Two
Context
Well Logging Energy · Professional work
Client
Independent wireline and well-testing services, Pakistan onshore fields
Period
June 2026 — Present
Role
Requirements, architecture, constraints, direction and review
Shape
One Next.js application carrying both the public site and its CMS
Status
In development · CMS connected, content migration in progress
Visibility
Private repository · no source, screens or client data shown

Covers

Systems Programming
Data
Databases
Interface
Product
People

Stack

  • Next.js
  • React
  • TypeScript
  • Payload CMS
  • PostgreSQL
  • Tailwind CSS
  • Railway
  • Cloudflare Turnstile
01

The problem

WLE is an independent wireline and well-testing contractor working Pakistan's onshore oil and gas fields. Its public presence had to do two unrelated jobs at once: present the company to operators evaluating a service provider, and give the people inside it a way to keep that presentation current.

Those two audiences pull in opposite directions. A visitor wants a site that reads as considered and specific — real photography, real evidence of how the company works safely, a clear account of what it actually does. An editor wants to correct a phone number without waiting on a deployment.

So the brief was never a set of pages. It was a system in which the public surface and the publishing surface could each move at their own speed without breaking the other.

Public website

People finding information

CMS

Structure and publishing

Content editors

People maintaining it

02

One contract, two sides

The decision the rest of the project rests on is that no component knows where its content comes from. Every section and card is presentational and receives its data as props. Pages are the composition layer, and the only place aware that an origin exists.

Between the two sits a single typed contract module — the declared shape of every piece of content the site can render. A mapping layer converts CMS documents into those contracts, and is the only runtime code permitted to import generated CMS types. Let components read CMS types directly and they are coupled to the CMS exactly as they were once coupled to placeholder files.

The payoff arrived when the CMS was connected: it was a change in the page files and nothing else. Before the boundary existed, roughly two dozen components imported placeholder content directly, and swapping in real data would have meant editing every one of them.

One rule keeps it from eroding. Contracts are declared explicitly, never inferred from sample data. A type derived from a fixture only describes that fixture, and silently changes shape the moment the fixture does — so typing a prop as "whatever this placeholder happens to be" is banned outright.

The same discipline covers imagery. The media reference type treats a photograph that does not exist yet as a first-class case: the source is optional, the alt text is not. A picture nobody has taken is still described, still laid out and still accessible — it renders as an intentional placeholder rather than a broken image or a silent gap.

    • Public visitors

      Read

    • Content editors

      Publish

  1. Next.js routes

    Composition layer

  2. Content contracts

    Declared types

  3. Mapping layer

    CMS → contracts

  4. Payload CMS

    Collections & globals

  5. PostgreSQL

    Migration-driven schema

  • Presentational components

    Props only

  • Private object storage

    Proxied media

03

The content model

Payload runs self-hosted inside the same Next application rather than as a separate service, so the site and its administration share one deployment, one type system and one build. The trade is that the admin brings its own document shell — which is why the application has no single root layout, and instead gives the public site and the CMS a root each.

The model splits along a line that matters. Reusable business entities are collections; page-specific editorial structure lives in globals. People, locations, departments, policies, standards, news, job openings and enquiries are things the organization has. A page's headings and section order are things a page has.

Nothing is stored as a catch-all block of page JSON. Repeating structures are real relational child tables, which is what makes them queryable, migratable and safe to change deliberately rather than by overwriting a blob.

Everything public carries a draft, published and archived lifecycle plus revision history. An anonymous reader sees published records; a signed-in editor sees the whole workflow. Media and documents are separate taxonomies with separate access rules, so a private CV never inherits the behaviour of a public photograph.

  1. MEDIA
  2. DOCUMENTS
  3. PEOPLE
  4. NEWS
  5. POLICIES
  6. ROLES
  7. JOBS
  8. STANDARDS
  9. DEPARTMENTS
  10. LOCATIONS
  11. ENQUIRIES
04

Motion without a library

There is no animation library on the public site, and adding one would be a regression. Motion is CSS transitions and keyframes triggered by a single IntersectionObserver wrapper with a forward or pop variant and a delay for stagger. That covers the entrance reveals carrying most of the site, at no dependency cost.

Two sequences go further and are built as pinned, scrubbed experiences: the company mark unwinding act by act over a field of formation strata with a depth-log readout, and a head-to-toe inspection of protective equipment annotated across eight acts.

Both follow the same shape. A pure function maps scroll progress, zero to one, onto opacity, transform and state. Because it is pure, the timeline can be verified numerically without a browser — and both were. The component that plays it writes results straight to the DOM through refs on each animation frame, so scrolling never triggers a React render.

Reduced motion is not a degraded version of that. When the preference is set, a static summary renders instead: the same information, arranged to be read rather than played. The preference is honoured live, not only at first paint.

  • Entrance reveals
  • Pinned scroll sequences
  • Pure-function timelines
  • Per-frame DOM writes
  • Reduced-motion alternates
  • Keyboard-accessible gallery
05

When to take the scroll

The two pinned sequences answer the same question in opposite ways, and the difference is the point. The homepage sequence captures the scroll. It is the first thing a visitor meets, it is the company's own mark being explained, and it earns a moment that advances act by act rather than by distance.

The safety-equipment sequence does not capture anything. It sits on a page someone may have reached looking for one specific fact, and an information page should not fight a reader who wants to leave it. The same engine drives both; only the decision about whose gesture wins is different.

Scroll captured — the homepage

  • The first thing a visitor meets
  • The company's own mark, explained
  • Advances by act, not by distance
  • A held moment is the whole point

Scroll left alone — the safety page

  • Reached by people after one fact
  • Pinned and scrubbed, never hijacked
  • Leaves at the reader's pace
  • Same engine, different authority
  • Attention
  • or
  • Autonomy
06

Invariants

A design system on a real project erodes through small exceptions, so a few things are held as invariants rather than conventions — enforced by removing the escape hatch, not by asking people not to use it.

Every public hero shares one typographic contract. A route may vary its photography, crop, height, motion and copy; it cannot quietly change a hero's font, size, weight, leading or tracking. The per-page typography overrides that once made that possible were deleted rather than deprecated.

Header height is a single derived token instead of a number repeated in a dozen places. It is computed from a viewport unit, bounded, and read by everything that depends on it — viewport-fitted heroes, pinned stage padding, section snap offsets. The rule is capability-based: nothing sniffs an operating system or a browser. Display scaling and extra browser chrome shrink the usable height, and the geometry follows instead of diverging from it.

The brand values the animations paint with — strand colours, stage background — stay in code and are explicitly not editable through the CMS. An editor changing a background would break the contrast the sequence is tuned against, and it would surface as a bug rather than as a content change.

  1. 01One typographic contract for every public hero
  2. 02Per-page typography overrides removed, not deprecated
  3. 03Header height derived once and consumed everywhere
  4. 04Capability-based sizing — no operating system or browser sniffing
  5. 05Animation brand values held in code, outside the CMS
  6. 06Schema reachable only through committed migrations

Known-data KPI test

  1. Viewport unit
  2. Bounded range
  3. Header token
  4. Hero height
  5. Stage padding
  6. Snap offsets

The chain is how one measurement propagates, named at stage level only. No values, breakpoints or thresholds are published here.

07

How it grew

The order was deliberate. The content boundary was extracted and every component moved onto props before any CMS existed, which meant the integration later had somewhere to land instead of somewhere to invade.

Work that is finished but not currently on the site is parked rather than deleted: kept restorable, excluded from the type-check, lint and build, and held outside the application source. A long-form leadership sequence lives there now — one crew photograph panned and zoomed person to person across roughly fifteen screens of pinned, scrubbed scroll.

Parking it paid for itself. Two bugs found while building it apply to the two live sequences, and are written down beside it where they will be found again: a viewport height reading as zero in a hidden tab, and a scrubbed timeline taking its duration from its last step rather than from its intended runway.

  1. 01

    Route hierarchy

    The approved sitemap built out against placeholder content, with responsive navigation.

  2. 02

    The boundary

    Content contracts declared and every component moved onto props — before any CMS existed.

  3. 03

    CMS foundation

    Payload self-hosted in the same application, with auth, lifecycle, revisions and migration-driven schema.

  4. 04

    Connection

    Page files switched to CMS reads through the mapping layer. No component changed.

  5. 05

    Real content

    Company photography, contact details and page copy migrated in. The remaining pages still carry placeholders.

08

Delivery discipline

Schema changes are committed migrations in every environment, with the CMS's development-mode auto-push disabled everywhere including locally. The build compiles without database access at all; migrations run afterwards as a separate pre-deploy step on the private network, and the release is promoted only if they succeed.

That arrangement is fail-closed without coupling compilation to a database. A bad migration stops a deployment instead of half-applying itself to a live schema, and a database that is briefly unreachable cannot break a build.

Uploads go to a private object store and reach the browser through server-side proxies that apply access checks and immutable caching, rather than being exposed directly. Public imagery and access-controlled documents travel separate paths on purpose. Retired routes are permanent redirects, so old bookmarks keep working without unapproved pages staying in the sitemap.

  1. Client

  2. Railway

    Build and runtime

  3. Build

    Compiles without database access

  4. Pre-deploy migrations

    Private network

  5. Next.js + Payload

    One deployment

  6. PostgreSQL

    09

    My responsibility

    I gathered requirements from the people who would use the site on both sides, shaped the architecture and the constraints it had to hold, directed AI-assisted implementation against them, tested the working system, reviewed changes and iterated on weak behaviour.

    The constraints are the part I would point at. That components may not import content. That contracts are declared rather than inferred. That schema moves only through committed migrations. That an information page does not take the reader's scroll. Those decisions are what the codebase is still holding, and they are why a section can be added now without renegotiating the whole system.

    1. 01Requirements
    2. 02Architecture
    3. 03Constraints
    4. 04AI-assisted Development
    5. 05Testing
    6. 06Review
    7. 07Iteration

    I do not claim that I manually wrote every line.

    10

    What I can show

    The repository is private and the site is a client's, so this page describes the system rather than displaying it. Everything above is architecture and reasoning: decisions, boundaries, the shape of the model and why each one is there.

    What is not here is the operational surface — no source, no credentials or secret names, no database or storage identifiers, no admin or API paths, no schema definitions, and no client records or contact details. Approved screenshots would extend the first list; they would not shorten the second.

    Can show

    • Client and project name
    • Technology stack
    • Architecture and its rationale
    • Content model at concept level
    • Motion and interaction techniques
    • Delivery and access discipline
    • Approved screenshots when available

    Private

    • Source code
    • Credentials and secret names
    • Database and storage identifiers
    • Admin and API paths
    • Schema definitions
    • Internal and client records
    11

    Current status

    The route hierarchy and responsive navigation are complete, and the content routes read through the mapping layer from a connected CMS. The committed migrations are applied, and the production asset library is in place behind its proxy.

    Real company content is partially migrated: the homepage and the company overview and vision pages carry supplied photography and copy, while the remaining pages still render approved placeholders. That is the honest reason this case study describes a system rather than a finished website.

    Route hierarchy
    Complete
    Responsive navigation
    Complete
    CMS connected to content routes
    Complete
    Content model & migrations
    Applied
    Production asset library
    In place
    Real company content
    Partially migrated
    Public launch
    Pending client sign-off

    Publishing and access

    • Anonymous visitors read published records only
    • Signed-in editors see draft, published and archived states
    • Writes require authentication; destructive administration is separated again
    • Public enquiries are never written straight to a collection — a validated handler stands in front
    • A honeypot field, a per-address rate window and optional bot verification guard that handler
    • Uploaded documents carry their own access checks, independent of public media

    Concept level only. No roles, permissions, routes, endpoints or field names are published here.

    Evidence boundary

    Supported

    I can account for the architecture in detail: the content and presentation contract and why it exists, the shape of the CMS model, the hand-written motion system and its two pinned sequences, and the migration, access and delivery discipline around them.

    Not overstated

    The repository is private and the site has not launched publicly, so none of this is independently verifiable from outside. I show no source, screens, internal content or client records, and I claim no traffic, business or commercial outcome.

    Technical notes

    The public site and its CMS ship as one Next.js application, backed by managed PostgreSQL and a private S3-compatible bucket whose objects are served through access-checked proxies. Schema moves only through committed migrations, applied in a pre-deploy step before a release is promoted.

    Core stack

    • Next.js (App Router)
    • React
    • TypeScript (strict)
    • Payload CMS
    • PostgreSQL

    Interface

    • Tailwind CSS
    • CSS keyframes + IntersectionObserver
    • No animation library

    Data & media

    • Migration-driven schema
    • Private S3-compatible object storage
    • Access-checked media proxies

    Deployment & services

    • Railway
    • Pre-deploy migration step
    • Permanent redirects for retired routes

    Security & integrations

    • Cloudflare Turnstile
    • Honeypot and rate-limited submissions
    • Server-side validation of every public form
    Repository
    Private
    Public artifacts
    Approved screenshots and diagrams will be added after redaction and review.