October 2, 2026
How we built this site
No clients to show yet, so we built the studio's own site as the first full-stack demo — dark-first, GSAP-driven, and honest about what's still pending.
Problem
We're a new studio with no client projects to show. Every agency site leans on the same trust signals — client logos, testimonials, case-study metrics — and we had none of them to use honestly. The brief we gave ourselves was blunt: build a site that earns trust without a single invented number, fake logo or borrowed quote, and make the build itself the proof.
Research
We looked at what makes agency sites feel generic: stock photography, gradient-blob hero sections, and performance that doesn't match the "we build fast sites" pitch. We also looked at our own constraint — no case studies — and decided the honest move was to say so plainly on the homepage rather than paper over it, and to treat this build log as the first entry in that proof section instead of hiding the gap.
Design
We settled on "Signal" as the creative concept: most agency sites are
noise, ours should read as a clean signal cutting through it. That gave us
a dark-only visual system (#0A0A0B background, a single lime accent used
sparingly), a fluid type scale built with clamp() instead of fixed
breakpoints, and one signature moment — a particle field that resolves out
of noise into a waveform — reused as a motif rather than a one-off hero
animation.
Architecture
The site is Next.js App Router, static-first: Server Components by
default, with small "use client" islands only where real interactivity
is needed (the custom cursor, the smooth-scroll provider, individual motion
components). Brand tokens live in Tailwind v4's @theme block as CSS
variables, so color, type and easing are defined once and consumed
everywhere — including inside GSAP, via a small lib/motion.ts that
mirrors the CSS easing curves as GSAP CustomEase instances.
Content is data, not markup: service, process and FAQ copy live in typed
TypeScript content files, while case studies and blog posts (this page
included) are MDX, compiled at build time with next-mdx-remote/rsc and
rendered through a shared component map — so every future case study
follows the same seven-section shape this one does.
Development
The hardest part wasn't the 3D scene — it was making it optional. The
signature particle field is react-three-fiber, lazy-loaded behind
dynamic(..., { ssr: false }), and only mounted after we've checked for
prefers-reduced-motion, Save-Data, a coarse pointer, and WebGL support
itself; everyone else gets a static SVG waveform that's also the real
Largest Contentful Paint element. The scroll-driven sections (the
problem-to-approach pin, the stacked service cards, the process timeline)
use GSAP's ScrollTrigger scrubbed to scroll position, layered on top of
Lenis for smooth scroll — synced to GSAP's own ticker so there's a single
render loop instead of two competing ones.
One genuine surprise: this project uses React Compiler, and its stricter
lint rules actively reject the standard react-three-fiber pattern of
mutating a material's uniforms inside useFrame — by design, since that's
imperative code outside React's render cycle. We scoped those specific
rules off for the Three.js files rather than fighting the framework, and
left a comment explaining why, so a future reader doesn't "fix" it back
into a 60-renders-per-second bug.
Performance
The budget we set before writing any code: Lighthouse 90+ on mobile, LCP under 2.5s, layout shift under 0.1, and a 3D chunk that never sits on the critical path. Here is what we measured, and how.
How we measured. Lighthouse 13 against a local production build, headless Chrome, mobile profile with real DevTools throttling (4x CPU, slow-4G network) and the desktop preset. These are lab numbers on one machine, not field data from real visitors, and not yet the deployed site. Treat them as a build-quality check, not a guarantee.
- Performance, mobile (home, median of 8)
- 95
- Performance, mobile (services, work, contact)
- 91–98
- Performance, desktop (all four)
- 99
- Accessibility / Best Practices / SEO
- 100 / 100 / 100
- LCP, mobile
- 1.5–1.6 s
- Layout shift (CLS)
- 0
- Total blocking time, mobile (home)
- ~100 ms
- Shared JavaScript, every page
- ~225 KB gzip
- 3D chunk (home only)
- ~238 KB gzip, lazy
What the 3D costs. The particle-field chunk (three.js plus react-three-fiber) is the heaviest thing on the site. It is code-split, loads only on the homepage, only after the intro loader starts lifting, and not at all for visitors with reduced motion, Save-Data, or no WebGL. Going through react-three-fiber costs roughly 105 KB more than raw three.js would (we measured a tree-shaken raw build at about 129 KB gzipped); we kept react-three-fiber for maintainability and say so openly.
Other checks. Interaction latency, measured with a phone profile (4x CPU throttling) and the 3D scene running: typing in the form about 40 ms, accordion and menu taps 48–72 ms. A desktop navigation click typically took 88–96 ms, though the very first interaction in a cold browser measured 296 ms once, so that one is a known soft spot. (It was a steady 216 ms until we removed a backdrop blur that was re-rendering over the live 3D canvas.) An automated axe-core sweep of every page on desktop, phone and reduced-motion profiles flagged a handful of real issues, which we fixed. Twelve home-to-services-to-work-and-back round trips used to leak about 25 event listeners each (a GSAP quirk we worked around) and now leak none, and the WebGL context is released on every route change. The site was exercised in Chromium, Firefox and WebKit.
Results
Too early to say, and we won't pretend otherwise. This site hasn't gone live publicly yet, so there's no traffic, no conversion rate, and no qualified-brief count to report — the numbers we actually track once it is live. We'll update this page with the real figures once we have them, for exactly the same reason we don't publish placeholder ones now.