Loading

All projects

Design + Dev · 2023

Vertex Agency Site

A brochure site that burned paid traffic. Rebuilt as an award-style scroll experience: a frame-sequence hero, GSAP section reveals and a CMS so the team publishes case studies without touching code.

TanStack StartGSAPSanity

Client

Vertex Agency

Industry

Creative Agency

Timeline

2 weeks

My role

Designer & Front-End Developer

The problem

A forgettable brochure site that generated almost no inbound leads despite strong paid traffic.

The solution

An animated, scroll-driven marketing site with a CMS so the team can publish case studies themselves.

+3.1x

Inbound leads

+180%

Time on page

2 wks

Delivery time

What I built

Scroll-driven hero

300-frame sequence mapped to scroll, decoded to ImageBitmap for zero jank.

CMS-driven work

Sanity studio for case studies, team and testimonials with live preview.

Section reveals

GSAP ScrollTrigger timelines that respect prefers-reduced-motion.

Perfect Lighthouse

98+ performance on mobile despite the heavy hero.

Architecture

  • TanStack Start with SSR and file-based routing
  • GSAP ScrollTrigger for timelines, canvas for the frame sequence
  • Sanity CMS with GROQ queries cached at the edge
  • Cloudflare Pages deploy, sitemap and per-page OG images

Hard problems

Heavy frame sequence

Bounded-concurrency preloading (6 desktop / 3 mobile) plus a nearest-frame fallback keeps scroll smooth while loading.

SEO vs animation

All copy is server-rendered; animation only enhances what is already in the HTML.

Code from the build

A few real excerpts from the repository.

src/components/scroll-sequence.tsx
const draw = (index: number) => {
  const bmp = frames[index] ?? nearestLoaded(index);
  if (!bmp) return false;
  const scale = Math.max(canvas.width / bmp.width, canvas.height / bmp.height);
  const w = bmp.width * scale;
  const h = bmp.height * scale;
  ctx.drawImage(bmp, (canvas.width - w) / 2, (canvas.height - h) / 2, w, h);
  return true;
};

const tick = () => {
  current += (target - current) * 0.12;   // easing toward scroll position
  draw(Math.round(current));
  raf = requestAnimationFrame(tick);
};
src/lib/loadFrames.ts
const limit = navigator.hardwareConcurrency > 4 ? 6 : 3;

export async function loadFrames(urls: string[], onProgress: (n: number) => void) {
  let i = 0, done = 0;
  const workers = Array.from({ length: limit }, async () => {
    while (i < urls.length) {
      const index = i++;
      const res = await fetch(urls[index]);
      frames[index] = await createImageBitmap(await res.blob());
      onProgress(++done / urls.length);
    }
  });
  await Promise.all(workers);
}
"The site became our best sales asset — clients bring it up on every intro call."
Sara M. · Marketing Head, Vertex

How it was built

  • Discovery call, scope and fixed timeline agreed up front
  • Custom design system built in Figma before development
  • Typed, reviewed codebase with CI checks on every commit
  • Deployed with monitoring, backups and post-launch support

Want something like this?

Start a Project