- Document Number
016
- Subject of Record
- BUILDING A PORTFOLIO ON ONE CLOCK
- Prepared By
- TULIO CUNHA
- Date Filed
- OCT 03, 2026
- Classification
- INTERNAL TECHNICAL MEMORANDUM
Building a Portfolio on One Clock
SUBJECT BRIEF
Inside an Apple II portfolio: React, WebGPU, Swiss typography, render caching, GPU budgets, and a Cloudflare editorial backend.

Opening
An Apple II sits in a bright photographic room. Behind it, an outlined greeting occupies the frame. Scrolling brings the visitor toward the monitor and into an archive of computing artifacts. The interaction moves between a photograph, a collection of objects, and an editorial publication.
Making those parts agree is the central engineering problem of my portfolio. A camera, a line of type, and a scroll position can each look correct in isolation and still disagree when they move together. The architecture gives them a shared time domain, explicit ownership, and a rendering budget.
The visual premise is equally specific: Swiss composition, computing artifacts, and CRT material. That premise reaches into the code. Typography has named roles. Routes have distinct substrates. The machine persists while its contents change.
The Stack Follows the Experience
The application uses React 19 and TypeScript, with TanStack Start and TanStack Router for server rendering and route structure. Vite+ provides the development and verification workflow. Tailwind CSS v4 holds the shared design tokens, while semantic typography utilities give components a consistent editorial vocabulary.
The scene uses Three.js through React Three Fiber and Drei. Its renderer is WebGPURenderer; materials and post-processing are built with Three Shading Language, or TSL. TypeGPU bridges into the renderer's existing device for the GPU systems, and vgpu handles the WGSL route-glitch module.
GSAP owns authored timelines. Lenis provides the scroll runtime. Both participate in the same frame contract as the scene.
| Layer | Tools | Responsibility |
|---|---|---|
| Application | React, TypeScript, TanStack Start/Router | Documents, routes, server rendering |
| Design system | Tailwind v4, local licensed fonts, SVG artwork | Tokens, typography, identity |
| Scene | Three.js, React Three Fiber, Drei | Camera, lighting, models, scene composition |
| GPU effects | TSL, TypeGPU, vgpu/WGSL | Post-processing, CRT systems, route interference |
| Motion | GSAP, Lenis, shared heartbeat | Timelines, scroll, ordered presentation |
| Content | SonicJS, Hono, Drizzle, Zod | Editorial backend and content contracts |
| Infrastructure | Cloudflare Workers, D1, R2, KV, Images | Runtime, records, media, cache, image delivery |
| Film playback | Native HLS, lazy hls.js light | Adaptive video player implementation |
| Verification | Vitest, coverage, source invariants, diagnostic instruments | CPU behavior and architectural contracts |
These are tools present in the inspected application and CMS. The film player is implemented, but its Stream asset configuration remains pending in this source snapshot.
Give Every Frame an Order
The frame clock lives in src/motion/heartbeat.ts. It has four phases:
Acquire scroll and input.
Derive the frame's presentation signals.
Present GSAP timelines and DOM updates.
Advance the React Three Fiber scene.
Two application listeners on GSAP's ticker establish that order. The prioritized listener acquires input and derives signals. The normal listener presents the remaining DOM work and advances the scene after GSAP's own timeline update.
This excerpt is the acquisition phase from the current source:
const acquire: gsap.TickerCallback = (_time, deltaMs, frame) => {
heartbeat.frame = frame
lastIntervalMs = deltaMs
const paced = paceFrame(pacing, deltaMs / 1000)
pacedWallMs += paced * 1000
heartbeat.dt = Math.min(MAX_STEP_SECONDS, paced)
heartbeat.time += heartbeat.dt
steps.scroll?.()
steps.derive?.()
heartbeat.sync.derive = heartbeat.frame
}The maximum simulation step is 1 / 20 second. A long pause therefore produces a bounded step rather than handing every integrator several seconds at once. Lenis reads the paced wall clock, while simulation time accumulates the clamped step. Those clocks have explicit purposes inside one scheduler.
The R3F canvas uses frameloop="never". A frame driver advances the root manually. Scene time accumulates only when the scene advances, so returning from an occluded or hidden state does not charge the scene for the whole interval it skipped.
The heartbeat also records the frame last completed by derivation, presentation, and the scene. That makes synchronization inspectable. A writer slipping outside its phase leaves a measurable disagreement.
Keep the Machine Resident
The main scene canvas persists through navigation. An archive world renders into a texture sampled by the physical monitor. Route specimens and editorial documents reuse the surrounding system.
That persistence needs restraint. An opaque reading surface can cover the photograph completely. In that state, rendering the full scene buys the reader nothing.
resolveSceneDemand() accounts for the visible route, specimen aperture, boot, warming, and transfers. The heartbeat also checks document visibility. These gates let the renderer stop work when it has no visible reader, while DOM presentation can continue to settle.
Warming is a temporary reason to render. A newly mounted object may owe pipeline compilation and a texture upload before it appears. The warm queue prepares that work, and the demand gate remains open until the scene is warm. Otherwise, a well-intended visibility optimization can move the entire loading bill into the visitor's first interaction.
A Cached Texture Needs a Wake-Up Rule
The archive's offscreen target is gated by a signature of the state it draws. If the signature stays unchanged, the target can keep its picture. Real movement changes the signature and triggers a repaint.
The subtle case is returning to the page. A cached render target is GPU state; unchanged inputs alone cannot prove its contents remain usable. The shared gate observes a wake counter and invalidates its remembered paint after a wake.
The following excerpt from src/experience/render-gate.ts separates asking whether a draw is owed from recording that it happened:
return {
stale(signature) {
checkWake()
return signature !== painted
},
drawn(signature) {
checkWake()
painted = signature
},
invalidate() {
painted = null
},
}That separation matters when another condition prevents the draw. Asking about staleness should never certify a picture that was not painted.
There is a second cache to keep honest: the mip chain. The CRT can sample the archive at a much smaller size than its offscreen target. Updating only the base level lets filtered reads blend the new image with old lower-resolution levels. In motion, those stale levels can leave old silhouettes behind an artifact.
The visible archive path enables mipmap generation before repainting, then publishes its paint revision afterward. The temporal CRT system reads that revision to identify fresh content. When persistence has been paused, its history is invalidated before resuming.
Those mechanisms address different kinds of stale imagery. Treating every trail as one generic blur problem would miss the distinction.
Spend Pixels Where They Carry Information
Quality profiles are selected by input characteristics rather than window width alone. A narrow desktop window still uses the desktop profile; a touch tablet retains the mobile profile.
Both profiles cap authored DPR at 2. The mobile drawing extent also has a ceiling derived from the tablet and phone sizes priced by the application. The archive keeps a 2048 base resolution because its labels must remain readable through the monitor crop.
The current settings make the tradeoffs concrete:
| Setting | Desktop | Mobile |
|---|---|---|
| DPR range | 1–2 | 1–2 |
| Bloom resolution scale | 1 | 0.375 |
| Shadow-map edge | 2048 | 2048 |
| GTAO resolution scale | 0.5 | 0.5 |
| GTAO samples | 16 | 12 |
| Depth of field | Enabled | Disabled |
| Scene MSAA samples | 4 | 4 |
Bloom's scale is a useful example. Reducing a linear resolution factor from 0.75 to 0.375 quarters the nominal pixel area of a correspondingly scaled target. That is budget arithmetic; it does not promise a fourfold improvement in the whole frame.
The post chain owns scene multisampling. The default framebuffer disables antialiasing because it receives a fullscreen composite. Paying for another multisampled final buffer would duplicate work on the wrong part of the image.
Multisampled attachments are discarded after resolve where the next pass only reads the resolved image. When a specimen owns the monitor for a settled stretch, the unused archive target shrinks to one pixel. Returning to the archive restores its size and invalidates its gates before it is sampled again.
On high-refresh displays, the scene can move to every-second-tick GPU submission under sustained missed refreshes. Simulation and DOM updates continue on each tick. A motion hold prevents the adaptive mode from changing during an authored move. This is a cadence policy, not an unconditional frame-rate guarantee.
Loading Includes GPU Work
The current Apple II file is 6,937,784 bytes and declares meshopt compression, mesh quantization, and Basis/KTX2 textures. Its loader shares a KTX2 transcoder and waits for renderer capability detection before decoding textures. The transcoder initializes alongside the model fetch, uses a two-worker limit, and releases idle workers after the hero maps are ready.
Model URLs carry a content hash, so immutable caching identifies the actual bytes. A build plugin prepares a Brotli sidecar for the hero; the Worker serves it when the client accepts Brotli. Transport compression and GPU texture compression solve separate problems: one reduces transferred bytes, the other changes how texture data reaches and resides on the GPU.
The chunk rules also protect server rendering. React is assigned independently of the graphics libraries. Browser-only inspector and KTX2 loader modules retain dynamic imports, keeping their module-level browser assumptions out of Worker evaluation.
Typography Is Part of the Contract
Ruder Plakat names routes. GT Pressura carries the thesis and reading text. PP Watch classifies fields; Ioskeley reports dates, counts, and coordinates. The Home greeting uses outlined SVG artwork.
The utilities in src/typography.css preserve those roles. This machine voice excerpt uses tabular numbers so changing readings retain their register:
@utility type-machine {
font-family: var(--font-mono);
font-weight: 500;
font-variant-numeric: tabular-nums lining-nums;
letter-spacing: var(--tracking-register);
line-height: var(--leading-machine);
}The seven-layer screen optic mounts once at the document root. It combines bloom, grille, sweep, grain, falloff, photographic glass, and front-plate noise. GPU rendering handles the physical monitor's signal; the shared DOM optic carries the screen material across editorial routes. Text remains real document content.
Make the System Inspectable
Vitest exercises the CPU laws, including quality budgets, frame timing, archive motion, caching, and content contracts. Source invariants protect rendering choices that cannot be meaningfully proved in a DOM test. The development instrument can freeze and step the shared clock, making a particular presentation frame repeatable.
These checks establish behavior within their scope. Device rendering, memory pressure, and perceived motion still need browser and hardware evidence. A test count cannot stand in for watching the archive move on an iPhone.
The most reusable decision in this portfolio is the ownership model. Each presentation channel has a writer. Each expensive pass has a reason to run. Each cache has a way to become stale. That is what lets an expressive interface remain understandable as a system.
Further Reading
The editorial approach follows the connection between design decisions and code demonstrated in Codrops' Oscar Pico portfolio case study and Bruno Arizio portfolio case study. The implementation described here is this portfolio's own.
Three's WebGPURenderer documentation explains its WebGPU and WebGL2 backend selection. The application's TypeGPU bridge separately checks for a real WebGPU device before enabling its GPU path.
END OF MEMORANDUM