The problems were structural. The blog section existed, technically, but it was buried - no pagination, no tags, no archives, no way for anyone to actually discover content organically. Mobile experience was an afterthought. The CMS structure had grown organically to the point where the marketing team needed a developer (me) for every content change. With nine language versions and a game approaching new milestones, it was time to rebuild from scratch.
And I do mean scratch.
The Plan
I knew going in that this wasn't a reskin. The client wanted a modern gaming website that would appeal to both football fans and gamers - dark, immersive, with beautiful gradients and real atmosphere. They were also developing new key visuals, so the design language needed room to evolve.
The tech stack decision was straightforward:
Next.js 15 with React 19 - App Router, Server Components, Turbopack for dev builds
TailwindCSS v4 - the latest, with PostCSS plugin integration
Storyblok - headless CMS, but restructured properly this time
next-intl - for routing and translations across all nine languages from day one
Framer Motion - for scroll-triggered animations (though I'd later learn to be selective about where)
Vercel - deployed to Frankfurt for EU-optimized performance
The workflow was iterative by design: build a section, test it with Playwright, get client feedback, refine. Claude Code served as a capable pair-programming partner throughout - handling boilerplate, catching edge cases, and accelerating the structural work that would have taken days manually. But creative direction, architecture decisions, and design iterations were mine.
Nine languages from the ground up - English, German, Spanish, French, Polish, Portuguese (BR), Chinese, Russian, and Turkish. Not bolted on afterward. Baked into routing, CMS structure, and every component from the first commit.
Phase 1: The Home Page
The home page is fifteen sections deep. Each one maps to a Storyblok blok - hero, wishlist CTA, platform covers, trailer, stadiums, clubs, features, game modes, and more. In total, I built 48 components following Atomic Design: 11 atoms, 8 molecules, 14 organisms, and 15 Storyblok bloks that tie everything to the CMS.
The Color Journey
This is where design iteration really mattered. The first version leaned into Copa City's bright blue brand palette - vivid, energetic, unapologetically colorful.
The client liked the structure but wanted something darker, more atmospheric. So we pivoted. V2 introduced deeper tones. V3 went full dark navy with blue accents - and that's when it clicked.
Three rounds of color iteration to get there. Lesson learned: don't fall in love with your first palette.
The Atmosphere
A gaming website needs to feel alive. Two canvas-based effects carry the visual weight:
Confetti Canvas - a custom WebGL implementation with vertex and fragment shaders. Forty to one hundred particles depending on viewport, each with 3D tumble physics, multi-layer sway, and a flutter effect that mimics paper catching air. Brand colors only - teal, blue, deep blue, white. Motion trails on desktop, disabled on mobile.
Smoke Background - multi-layer radial gradients composited in "lighter" blend mode. Three overlapping gradients per particle for irregular, organic shapes. Lazy-loaded via IntersectionObserver so it doesn't touch performance until it's near the viewport.
Phase 2: Blog, Contact & CMS
The Blog System
The blog was the biggest gap in the original site. I built it with four distinct routes:
/blog - paginated listing with five posts per page
/blog/[slug] - individual posts with rich text, related articles, and social sharing
/blog/tag/[tag] - tag-filtered views
/blog/archive/[year]/[month] - monthly archives for SEO crawlability
Each post lives entirely in Storyblok: title, rich text body, tags, featured image, author, publication date, related posts, and full SEO metadata. The sidebar surfaces tags and archive months with post counts. Reading time is calculated at 200 words per minute from the rich text content.
The Contact Form
Three contact categories routing to three different email addresses - general inquiries, business development, and press - each with full Zod validation:
Name: 2–100 characters
Email: validated format
Subject: 3–200 characters
Message: 10–5,000 characters
Honeypot field for bot protection
Rate limiting: 3 requests per 15 minutes per IP
The backend uses Resend for delivery, with branded HTML templates matching Copa City's color scheme. React Hook Form handles the frontend with Framer Motion for the modal. Success auto-closes after 2.5 seconds.
Cookie Consent
I replaced the third-party Cookiebot dependency with a custom implementation. Three categories - necessary (always on), analytics, and marketing - persisted to localStorage. Context-based state management, a settings modal for granular control, and conditional script loading based on consent. Lighter, faster, and no external dependency.
CMS Architecture
This is the part I'm proudest of. Every section of the site maps to a Storyblok blok. The marketing team can rearrange sections, update copy in any of nine languages, swap images, toggle visibility - all without touching code. Dynamic imports keep the JavaScript lean: only the hero and page wrapper load statically. The other eleven bloks are lazy-loaded.
Storyblok's webhook triggers a revalidation endpoint, so content changes go live within sixty seconds via ISR.
The Performance Story
This is where things got humbling.
The first Lighthouse audit after adding all the visual effects came back around 30 on mobile. Confetti, smoke, Framer Motion animations - they made the site beautiful and the performance score ugly.
Getting from 30 to 95+ was a multi-week effort:
Deferred WebGL canvas - moved ConfettiCanvas initialization to requestIdleCallback() so it doesn't block Largest Contentful Paint. Device pixel ratio capped at 2x.
CSS animations over Framer Motion for the hero - Framer Motion is powerful but heavy. The hero section's entrance animations were rewritten as pure CSS @keyframes. Framer Motion was kept for scroll-triggered reveals deeper in the page, dynamically imported to keep it out of the initial bundle.
Typekit font loading - switched to media="print" with an onload swap to "all". Prevents the font stylesheet from blocking First Contentful Paint while still loading the brand typeface (Avant Garde) quickly.
AVIF images - Next.js image optimization configured to serve AVIF with WebP fallback. Image cache TTL set to one year on Vercel's edge.
Lazy YouTube iframes - trailer section only loads the embed when it enters the viewport.
Code splitting - eleven Storyblok bloks loaded via next/dynamic, reducing initial JavaScript by roughly 40%.
Smoke lazy start - IntersectionObserver with 200px root margin. The smoke canvas doesn't start rendering until the user scrolls near it.
The accessibility score hit 100. Performance stabilized at 95+ on desktop, 88+ on mobile (the WebGL effects still cost a few points on slower mobile devices, but the tradeoff is worth it for the atmosphere).
Lessons Learned
Design iteration is non-negotiable
Three color rounds. Three. The first version was technically sound but visually wrong for the brand's direction. If I'd committed to V1 and moved on, we'd have shipped something "fine." The dark base in V3 transformed the whole feel.
Performance and animation are constantly in tension
Every visual effect has a cost. The confetti canvas is gorgeous on a 2024 MacBook Pro. On a budget Android phone, it needs to be lighter, or deferred, or both. Building the toggle early - disable trails on mobile, reduce particle count, defer initialization - saved me from a painful retrofit later.
CMS architecture deserves as much planning as code architecture
The old site's CMS was a mess of loosely connected fields. The new structure - fourteen clearly defined bloks, each with explicit props and rich text support - means I haven't gotten a single "can you change this text for me" request since launch. That's the real measure of success.
Internationalization is a first-class concern or it's a nightmare
Nine languages can't be an afterthought. Every component, every CMS field, every route, every meta tag, every sitemap entry needs to know about locales from day one. The next-intl library handles this beautifully, but only if you structure around it from the start.
The Result
The Copa City website went from a single-page marketing site to a full-featured, multilingual gaming platform:
48 components following Atomic Design principles
9 languages with SEO-optimized hreflang tags and locale-prefixed routes
ISR with 60-second revalidation - content changes go live in under a minute
Lighthouse 95+ desktop / 88+ mobile performance with full WebGL effects
Blog with four route types - paginated, tagged, archived, individual posts
Custom cookie consent - no third-party dependencies
Contact form with category routing, validation, rate limiting, and honeypot protection
All deployed on Vercel's Frankfurt edge, all managed through Storyblok by a marketing team that hasn't needed a developer for content changes.
The tools are there. The frameworks are mature. And yes, AI-assisted development accelerates the process considerably. But the architecture decisions, the design direction, the performance tradeoffs - those still need a human with opinions. The code gets written faster. The thinking doesn't.