Sabotage
- Role
- Front-end development
- Design
- CCT
- Stack
- Next.js, Sanity, Stripe
- Built
- August 2026, 24 days
CCT designed a new website for Sabotage, a London creative studio that works with Adidas, Nothing, Nike, Stone Island and Moncler, and brought me in to build it. I was the only developer, from CCT's Figma file to the last commit in 24 days.
The design is loud on purpose. My job was to make it hold exactly as drawn at every screen size, and to put every word and picture in a Sanity Studio inside the site, so the studio never needs a developer to change anything.
Impact
~3 seconds from Publish to the live site, 100+ fields the studio edits itself, 93 with help text written for them, 24 days from the first commit to the last, as the only developer.
The homepage strip
The strip is CCT's own Figma outlines, packed to within half a unit of the drawing.


let x = 0;for (let i = 0; i < n; i++) { const key = shapes[i]; const s = SHAPES[key]; blocks.push({ shape: key, left: x, top: s.top, width: s.width, height: s.height }); // Advance to the next block, wrapping so // the loop seam is gapped correctly too. x += ADVANCE[`${key}>${shapes[(i + 1) % n]}`] ?? s.width;}const total = x;
Project pages and the Smoking Area
Every size on the site is a unit of a Figma frame, with one breakpoint.


/* homepage frame 1728 × 1117 */--u: calc(100dvh / 1117);/* project frames */--pu: calc(100dvh / 1126);/* phone store, scrolls down */--su: calc(100vw / 402);/* legal page, scrolls down */--lu: calc(100vw / 1728);/* phone nav = desktop HUD × 343/449 */--mu: calc(0.76391 * var(--su));The Studio
Press Publish and the live site changes in about three seconds.


const { isValidSignature, body } = await parseBody(request, secret); if (isValidSignature === false) { return Response.json( { revalidated: false }, { status: 401 });} // expire: 0, so the first visitor after a// publish waits for a fresh render, not the// old page. That visitor is almost always// the client.revalidateTag(SANITY_TAG, { expire: 0 });
The store
The browser says what, never how much.


const products = await client.fetch( productsForCheckoutQuery, { ids }, { cache: "no-store" },);const byId = new Map( products.map((p) => [p._id, p])); for (const item of wanted) { const product = byId.get(item.productId); lineItems.push({ quantity: item.qty, price_data: { currency: "gbp", unit_amount: toPence(product.price), }, });}
Search and sharing
Every page draws its own share card from the same data as the page.

const filled = template.replace( /\{\{\s*([\w.-]+)\s*\}\}/g, (_, token: string) => lookup.get(token.toLowerCase()) ?? "\u0000",); const tidied = filled // 1. The sentence a trailing hole // was the end of. .replace(/[^.!?]*\u0000[ \t]*[.!?]+/g, "")
Looking back
The design was CCT's. The part of the build I'm proudest of is the part nobody sees: a studio that changes its own site without asking anyone.
Connections
What I owned
The whole front end, from CCT's Figma file: the homepage strip, project pages, store, Smoking Area, legal page and 404.
The Sanity Studio: every schema, field and line of help text, four custom inputs, and Presentation for editing on the page.
Publishing: the signed webhook that puts a change live in about three seconds.
The Stripe store and the newsletter.
Search and sharing: the sitemap, structured data, and a share card drawn for every page.
The handover: a guide to the Studio, field by field, and the system documented as built.
What I contributed
Responsive behaviour between CCT's desktop and phone frames, proposed to CCT, who owned those calls.
What I learned
Measure, don't eyeball. Every size on the site is a unit of a Figma frame, and that is why it holds at every screen.
Write the CMS for the people using it. Field names and help text in the page's own words do more than a training call.
A client site is finished when the client stops needing you.
Figma to handover
Kickoff: A call with Ray at CCT on 3 August: the scope, a week-by-week plan, and who owns what.
Figma: CCT's file measured to the unit: the frames, the block outlines and the colours taken straight from it.
Build: Next.js on Vercel, with the Sanity Studio inside the site and Stripe behind the store.
Rounds: Each round went to CCT as collages, screen recordings and a short changelog.
Handover: A guide to the Studio, field by field, and the site handed to Sabotage.
The homepage strip, Problem
CCT's homepage is a band of project blocks turned 45°, their corners touching the top and bottom of the screen and locked together with 10-unit gaps. Scaled naively, the corners clip or float as soon as the window changes shape.
The studio picks the projects and their covers, so the strip has to stay composed for any number of them.
The homepage strip, Decision
I read the five outlines straight from the Figma file's vector data and sized everything in units of screen height, so a block's corners sit on the screen's edges at every size with no JavaScript doing the layout.
Blocks are packed by a no-fit test along each scanline for exactly 10 units of clearance, from a table that covers every pair of shapes, and the loop's seam is gapped the same way.
By default project n takes the nth shape of the design's own sequence, so a fresh strip reproduces the Figma layout. The studio can switch a project to Best fit, which picks the outline closest to its cover by comparing aspect ratios in log space.
It loops forever, mounts only the blocks near the screen, and drags with the same feel on a phone: the gearing scales so a swipe covers the same share of the composition as on a desktop.
The homepage strip, Results
The packed strip lands within 0.55 units of the Figma layout, on a frame 1117 units tall.
Whatever the studio publishes lands in the strip in the right shape, with no layout shift as it loads.
The homepage strip, Tradeoffs
One clip path per block. Chrome failed to repaint an SVG clip under transforms, and clipping each piece on its own tripled the image work.
Project pages and the Smoking Area, Problem
CCT drew every page at a desktop size and a phone size. Media queries for every width in between would drift away from the drawings.
Project pages and the Smoking Area, Decision
Each kind of page has its own unit: the homepage is screen height over 1117, project pages over 1126, the phone store screen width over 402. Every size is written in those units.
The phone project frame matches the desktop one to four figures, so project pages need no breakpoint at all. The whole site has one, at 767px.
The phone nav is the desktop one scaled by exactly 343/449.
Project photographs sit at one height and take the width their own shape asks for, so nothing is cropped. Video plays silently among them while it is on screen.
The Smoking Area's photographs come from the Studio or from an are.na channel, whichever the studio picks.
Project pages and the Smoking Area, Results
Desktop and phone match CCT's frames with six units and one breakpoint for the whole site.
The Studio, Problem
A studio site goes stale the moment every edit needs its developer, and the people editing this one are designers.
Before the webhook, a change took up to a minute to show. The client refreshed, saw yesterday's strip, and fairly concluded publishing hadn't worked.
The Studio, Decision
The Studio lives at the site's own address: one deploy, one domain. Presentation opens the real page beside the form, and clicking anything on the page jumps to its field.
Eleven schema types and more than a hundred fields, 93 with help text in the words the page uses. Soft limits are warnings, not errors: a fifth tag gets "Four is the drawing."
Four custom inputs show the choices instead of naming them, like the mark picker, and the share preview draws the page's card under its empty field.
Pages that exist once are locked: they can't be duplicated, deleted or made again from the + menu.
Publish fires a signed webhook that clears the site's cache. Visitors read a CDN query under one site-wide tag, and every page refreshes within 60 seconds anyway if the webhook ever fails.
Editing previews hide invisible markers in text. I kept them out of four lookup fields, where they quietly swapped the mark the client chose for the default one.
The Studio, Results
Publish to live in about three seconds, and never more than a minute.
The site writes nothing to Sanity, and without its keys it runs on sample data with a plain message instead of an error.
The Studio, Tradeoffs
One tag for the whole site means a publish re-renders every page on its next visit. For a studio's site that is cheap, and no corner of it can go stale.
Vision, Sanity's query tool, is gone from the client's Studio: a tab that could only confuse.
The store, Problem
The studio sells a handful of things and changes them in the Studio. A second copy of the catalogue in Stripe would drift, and a basket that sends its own prices can be edited.
The store, Decision
Products and prices live only in the Studio. The browser sends ids, sizes and quantities; the server reads each price, name, image and stock back from Sanity, uncached, and builds the Stripe Checkout session from that.
Pounds typed in the Studio become pence in exactly one place.
The basket lives in the browser and is read through one store, so the first render and a second tab always agree with it.
CCT's store is a composition of six placed products, so I measured six slots off the Figma frames and gave each product a Position field. A seventh starts a second composition and the page drags sideways.
The store, Results
Turning payments on took one key. There are no Stripe products to create and no sync to maintain.
The store, Tradeoffs
No accounts and no order database: Stripe holds the orders. Right for a studio selling a few things, not for a big shop.
Search and sharing, Problem
Before launch the site had no sitemap, robots file, canonical links, structured data, 404 or descriptions, and a link posted anywhere showed one image for every page.
Search and sharing, Decision
Every route draws its own 1200×630 card with next/og, rebuilt from the site's parts to match CCT's card in Figma and read from the same Sanity data as the page, so a new cover changes the card.
Titles and descriptions come from templates in Settings, like {{title}} and {{year}}. A sentence whose value is empty drops out rather than printing braces.
The sitemap is built from the same queries as the pages, with structured data for the studio, each project and its credits, and each product and its price. One switch in Settings takes the whole site out of search.
About 37 KB came off the store, Smoking Area and project pages by keeping Sanity's browser client out of them, and the pattern artwork was redrawn at screen precision, from 238 KB to 149 KB.
Search and sharing, Results
All 19 projects and both products went out with search titles and descriptions, and seven pages with cards of their own.









