Skip to content
Knight-FrostPublic

About

An interactive engineering portfolio built as a navigable record of systems, decisions, and evidence.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Elton Sakyi — Portfolio

A single-page, scroll-scrubbed journey through four systems: Camp Burnt Gin, Wyncrest, Pathfinder-X and FutureYou. Each project scene pins for several viewport heights while a multi-plane camera moves through a still image and short text beats cross-fade in sequence.

Every scene also carries an interactive module that demonstrates the system's logic. Those modules are the point of the site, not decoration: a role explorer where no single role holds every power, an append-only ledger that refuses a duplicate at the boundary, a query path that shows its sources and no invented figures, and a deterministic financial projection whose dial also moves the light in the scene behind it.

Live: https://elton-sakyi-portfolio.vercel.app

Stack

Concern Choice
Framework Next.js 15 (App Router), React 19
Language TypeScript, strict + noUncheckedIndexedAccess
Styling Hand-written CSS in app/globals.css. No utility framework
Motion One hand-written requestAnimationFrame scrubber. No animation library
Images next/image, five 16:9 stills reused across scenes
Fonts next/font — Bricolage Grotesque, Manrope, IBM Plex Mono
Hosting Vercel (static; every route is prerendered)

Dark only, by design: the scenes are lit for a black ground and there is no light variant of the artwork. No CMS, no database, no analytics, no chatbot.

Commands

pnpm install
pnpm dev        # http://localhost:3000
pnpm build      # production build
pnpm start      # serve the production build
pnpm typecheck  # tsc --noEmit
pnpm lint       # eslint

No environment variables are required, locally or for deployment.

How the camera works

components/camera/cinematic.tsx is the only place in the app that touches the DOM directly, and it does so deliberately. Plane transforms and beat opacities change on every scroll frame; routing those through React state would re-render the page tree sixty times a second to produce styles React would then have to diff. Instead the tree renders once — mostly as server components — and a single coalesced rAF loop writes styles onto nodes it finds by data attribute.

The maths is pure and lives apart, in lib/camera.ts: one SHOTS entry per scene mapping scroll progress to scale, translation and rotation, plus planeTransform for one depth layer. Those curves are the design's authored values; they are what makes each scene read as a specific camera move rather than generic parallax.

The markup contract is entirely data attributes — data-scene, data-origin, data-perspective, data-cut, data-plane, data-beat/data-in/data-out/data-y, data-reveal. Adding a scene means adding markup with those attributes and a SHOTS entry; nothing else has to know.

Two non-obvious constraints are load-bearing and documented at their call sites:

  • The depth planes are not in a shared preserve-3d context. Inside one, the browser sorts children by 3D geometry rather than DOM order, and a few degrees of Y rotation is enough for the blurred atmosphere plane to punch through the sharp subject plane. Each plane carries its own perspective(...) transform function instead.
  • The match-cut clip-path is removed once the cut finishes, rather than left at its fully-open value. Clipping that group forces the whole set of blurred, transformed planes to be rasterised into one buffer, and holding that for the remaining 90% of a six-viewport scene locks up the compositor.

Structure

app/            layout, the single page, and metadata routes (icon, OG image, sitemap)
components/
  camera/       the rAF scrubber
  scenes/       one file per scene, plus the shared Scene/Stage/Plane/Beat primitives
  modules/      the four interactive panels
  shell/        fixed header and project navigator
content/        site constants and all module data
lib/            camera maths, tokens, the shared camera store
public/media/   the five scene stills

Accessibility and motion

  • prefers-reduced-motion: reduce zeroes camera strength and depth, holds the planes on a static scale, disables the match cuts, leaves beats to fade only, starts reveals visible, and makes programmatic scrolls jump rather than animate.
  • A beat that has faded out drops pointer-events and sets aria-hidden, so only the legible beat is reachable by pointer or screen reader.
  • The reveal animation's hidden state is applied in JavaScript, not CSS, so content stays visible if the script never runs.
  • Chamber hover on the work map is wired to focus as well, so keyboard users get the same lighting cue and caption.

Content

All copy lives in the scene components and content/. It is the client's own wording and is intentionally verbatim, including the curly apostrophes in the hero. There are no em dashes anywhere in the copy, by request.

The interactive modules are models, not production dumps, and each panel says so in its header. The role matrix and ledger entries are simplified from the real policy layer; the Pathfinder-X panel shows no figures at all, because inventing numbers for a data product would undercut the argument the panel exists to make. If those disclaimers are ever removed, the demos stop being honest.

About

An interactive engineering portfolio built as a navigable record of systems, decisions, and evidence.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages