047

Creative Agency Template

£10

One-page agency site for any studio that sells campaigns, film or brand work. A pinned, scroll-scrubbed reel of sixty photographs is the hero, a capabilities marquee skews with scroll speed, and six case studies with results sit under a CSS-only discipline filter. Shown as an ad agency.

ScrollTriggerSplitTextObserver Lenis intermediate
17 more details
CustomEase image-sequencecanvaspinningscrubmask-revealtext-animationscroll-revealstaggerskewvelocity-trackingmagneticcount-upfiltersticky-stackprogressive-enhancementkeyboard-navigation
Creative Agency Template - website template preview

This template reads better at your own screen size than in the frame below:

About this template

A production-ready one-page site for a full-service advertising agency. It ships as a finished site for a fictional six-person shop, VOLUME, with six real campaign case studies, results stats, plain discipline tags and a written process, so what you open reads as a working agency rather than a grid of placeholder headings.

Read the full template overview

The hero's background IS the reel: sixty real photographs from a night shoot, pinned and scrubbed frame by frame as you scroll, with a deliberately minimal “MAKE NOISE.” statement and current-campaign rail held above it. Straight after it, the capabilities marquee does not just scroll: it reads live scroll velocity with the GSAP Observer plugin and skews the whole track in proportion to how fast you are moving right now, clamped to a sane maximum and snapped back to level with a CustomEase the instant you stop. Flick the page hard and the type leans into the flick; let it settle and the type settles with it.

Around that sit six full-width campaign boards, each fronted by a graded photograph and a real result stat. They stack at slightly offset sticky positions like boards building up in a pitch room, then return to ordinary document flow on mobile. A CSS-only discipline filter keeps working with JavaScript switched off, while a calmer constant-speed client-logo marquee and a practical enquiry form complete the page.

Every photograph is a bundled, optimised, art-directed derivative: source stills and the hero's frame sequence are graded into VOLUME's own duotone before shipping, never redistributed untouched. Every section is found by a data attribute and separately guarded, so delete the marquee, the work grid, the process or the enquiry block and the rest keeps running.

What's included

11 items
  • A minimal MAKE NOISE hero over a pinned, scroll-scrubbed reel of sixty graded photographs, with scroll position as the actual playhead
  • A capabilities marquee that skews proportionally to live scroll velocity via the GSAP Observer plugin, clamped to a sane maximum and eased back to level on a CustomEase the instant scrolling settles
  • A second, calmer marquee of client wordmarks at constant speed running the opposite direction, for contrast against the loud capabilities strip
  • Six campaign case studies, each with a discipline tag and a count-up results stat, in a CSS-only filter built on :has() and radio inputs that keeps filtering with JavaScript disabled
  • Six full-width campaign boards that stack at offset sticky positions on desktop, switch to ordinary flow on mobile, and alternate image direction and campaign colour
  • A two-line SplitText statement, compact capabilities label and current-campaign rail that keep the advertising work, not explanatory copy, as the focus
  • A five-step process rail, scroll-revealed rather than pinned, explaining how the agency actually works
  • An enquiry form matched to what an agency needs to qualify a lead: organisation, budget band, project type and a message
  • Bundled, optimised WebP photography for the reel and every case study card, each one an art-directed derivative graded into VOLUME's duotone, never a redistributed original
  • Every measured contrast ratio written into the stylesheet header, including why the accent is barred from small body text and where it is allowed as large display type
  • Accessible throughout: a complete static document with no JavaScript, a full page under reduced motion with the hero unpinned on a single frame and both marquees frozen, and a blocked CDN that degrades to the plain document

Perfect for

6 use cases
  • Advertising and campaign agencies that need a site for their own business
  • Small full-service shops selling brand strategy, film, social, OOH and experiential work
  • Freelance creative directors or strategists presenting a portfolio of campaign results
  • Any agency that wants to lead with results stats rather than a mood-board wall
  • Consultancies that want a confident, brash tone rather than a neutral corporate one
  • A starting point for any results-driven service business, since every section is independent

How it works

6 sections

Structure

One script initialises every section with a single setup and teardown path, and one motion branch whose conditions cover coarse pointers as well as desktop, so exactly one branch runs on every device. Each block is found by a data attribute and returns early if it is absent, so deleting any section leaves the others working. The page is a complete static document before any script runs.

The reel

The hero and the reel are the same section. Sixty photographs preload as Image objects, and a pinned ScrollTrigger maps its scrub progress to a frame index, drawing the nearest loaded frame to a canvas on every update: your scroll position is the actual playhead, not a simulated one. The headline and CTAs are revealed separately on load, then simply hold in place, pinned, while the footage plays behind them. The canvas stays transparent until every frame has decoded, so the plain <img> fallback underneath is what a slow connection or JS-off visitor sees, behind ordinary, unpinned text.

The velocity marquee

An Observer instance watches wheel, touch and pointer scrolling and reports velocity on every change. That velocity is clamped and mapped to a skew on the marquee track, so the type visibly leans harder the faster you flick the page. A short idle timer watches for the scrolling to stop; once it does, a CustomEase brings the skew back to level. The horizontal scroll itself is a separate, constant-speed loop, so the marquee never stops moving, only leans.

The discipline filter

Filtering is a CSS :has() selector reading the checked radio input and hiding case studies whose data-tags do not match, so it keeps working with this file deleted entirely. JavaScript only adds a stagger fade on the cards left visible after a change, which is why nothing about the filtering itself depends on script.js running.

The campaign stack

Each case study is a full-width campaign board with its own colour role, image direction and oversized result. CSS sticky positioning layers the boards at slightly offset top values as the reader moves through them, recreating the feeling of a pitch-room wall being assembled. Below 900px every board returns to ordinary document flow, so mobile visitors never lose viewport space to a trapped sticky composition.

Without JavaScript

The gating class goes on in the head before first paint, so reveal start states apply to the first frame and nothing is painted visible and then hidden; the script removes that class when GSAP is missing, so a blocked CDN degrades to the plain document. With scripting off the hero's canvas never receives a frame, so its plain photograph shows behind the headline in normal flow, nothing pinned; both marquees render as a single static row of the same terms, the filter still filters, and every stat shows its final value.

Plugins ScrollTrigger, SplitText, Observer, CustomEase
Difficulty Intermediate
Smooth scroll Lenis integration
Includes HTML + JS + CSS source, documentation, AI setup prompt, lifetime updates

Lighthouse, as measured

Google Lighthouse on this template's demo, 9 September 2026. A measurement of the demo as shipped, not a promise for your page.

Accessibility
100
Best practices
100

No performance score, on purpose. That figure depends on how you deploy: your server's compression and caching, your CDN, the connection and the device doing the test, none of which the code controls. The same page can score very differently on two consecutive runs, so measure it where it will live.

Questions about this template

What is the scroll-scrubbed reel in the hero?

Sixty graded photographs played with scroll position as the actual playhead, pinned behind a minimal MAKE NOISE hero. It is an image sequence rather than a video, which is why it scrubs precisely in both directions rather than seeking.

Does the marquee really react to how fast I scroll?

Yes, live. It skews in proportion to scroll velocity tracked with the GSAP Observer plugin, clamped to a sane maximum and eased back to level the instant scrolling settles. A second, calmer wordmark marquee runs the opposite way at constant speed for contrast.

Can I use my own campaign photography?

Yes, and you should. Everything bundled is an art-directed WebP derivative graded into the demo brand's duotone rather than a redistributed original, so you are replacing the grade as well as the pictures. Sixty frames is a real commitment for the reel; a shorter sequence works if you retune the scrub length.

Is the tone adjustable, or is it committed to being loud?

It is committed. Brash confidence is the whole art direction, from the hero statement to the velocity marquee to the results stats leading every case study. If you want neutral and corporate this is the wrong template; the web design agency one in the catalogue is the calmer choice.

Web Design Agency Template

Does the discipline filter work without JavaScript?

Yes. It is CSS-only, built on :has() and radio inputs, so the six case studies still filter with scripting disabled. Under reduced motion the hero unpins on a single frame and both marquees freeze, leaving a complete readable page.

Does the brief form send anything?

No. It validates in the browser, shows its success state and clears, but there is no backend, so nothing reaches an inbox. Point the action at Formspree, Basin or Netlify Forms before launch. The budget band and project-type fields are the part worth keeping wherever you wire it, because they qualify an enquiry before you reply to it.

Paid template

Purchase to unlock the code.

Buying Creative Agency Template opens the HTML, CSS and JavaScript source, the full documentation, an AI setup prompt for your editor, and every update we ship to it. Standard license: unlimited personal and commercial projects.

£10 Standard license, unlimited projects

Browse free effects

Documentation

Quick Start

  1. Copy the whole advertising-agency-template folder to your site.
  2. Open index.html in a browser — it works immediately, no build step.
  3. Replace VOLUME with your own name throughout index.html (the wordmark, <title>, the footer legal line and the mailto:/tel: links).
  4. Rewrite the copy: the hero headline and lede, the six case studies, the process steps and the contact details are all real prose written for a fictional agency, not lorem ipsum. Swap in your own.
  5. Repoint assets/style.css's --accent and --signal (see Brand colours, below) if cobalt and orange are not your colours.
  6. Wire up the contact form's action attribute to your form handler (Formspree, Basin, Netlify Forms, or your own endpoint).

Everything is plain HTML, CSS and JavaScript. There is no bundler, no package manager and no build step — deploy the folder as static files anywhere.

Brand colours: how to make it yours

The page uses two deliberate brand colours in assets/style.css:

Code snippet omitted: it ships with the download.

--accent is the cobalt hero field, primary action and active filter colour. White on cobalt measures 6.77:1. --signal is the orange capabilities band and hero emphasis; dark ink on orange measures 5.80:1. Keeping those roles separate is what gives the page its hard editorial colour rhythm without turning every component into a different colour.

If you repaint either token, re-measure the text that sits on it. The complete contrast notes are in the header of assets/style.css.

Palette

The colour tokens all live in :root in assets/style.css. Every translucent colour on the page — hairlines, dimmed type, the marquee mask — is mixed from an -rgb triple rather than written as a literal rgba(...), which is what makes a reskin a short edit instead of a hunt.

Token Value Role
--ground #F1EDDF Warm editorial paper used through the main page
--card #FFFBF3 Raised case-study surfaces
--sunk #DDE3FF Pale cobalt wash for recessed sections
--paper #151713 Primary ink colour
--accent #234BDB Cobalt hero field and primary actions
--signal #FF5A2E Orange emphasis and capabilities band
--deep #11130F Contact and footer ground
--light #F5F0E6 Type on deep surfaces

Dimmed type variants are derived from the active ink token. The full measured table for every pair, at the size it is actually used, is in the header comment at the top of assets/style.css.

Typefaces

Three faces, three jobs, loaded from Google Fonts:

  • Archivo Black — display. The wide geometric hero, section titles, marquees, client names and process steps.
  • Manrope — body. Every word meant to be read at reading size.
  • IBM Plex Mono — labels only: eyebrows, discipline tags, stat units, form labels and legal copy.

Sections and options

Every section is found in assets/script.js by a data-* attribute on its <section> and returns early if that attribute is missing, so you can delete any section from index.html and the rest keeps working.

1. Hero + the reel — [data-hero] [data-reel] (the same section)

The hero is deliberately spare: a two-line “MAKE NOISE.” statement, one compact capabilities label, two actions and a current-campaign rail. Each display line gets its own masked SplitText reveal. Behind it, sixty photographs preload as Image objects and a pinned ScrollTrigger maps scrub progress to a canvas frame, so scroll position is the actual playhead rather than a simulated one.

The reel opens inside a large offset circular aperture against the cobalt field. As the visitor scrolls, the aperture expands to full bleed, the copy exits left and the frame sequence takes over the viewport before handing off to the orange capabilities band. The canvas stays fully transparent until every frame has decoded, so the plain <img class="reel-fallback"> sitting underneath it is what a slow connection sees in the meantime, and what a JS-off visitor sees permanently. Swap the frame count by changing FRAME_COUNT in script.js and the zero-padding in frameSrc() to match your own set; frames live in assets/frames/frame_001.jpg upward.

Case-study results below use data-count / data-count-to, with the final value written directly into the markup so it reads correctly with JavaScript disabled. The counter understands data-count-prefix, data-count-suffix and data-count-decimals, which is how values such as +9.4M render. The primary hero button carries data-magnetic, which pulls it gently toward the cursor on fine pointers; remove the attribute and it is an ordinary link.

2. Capabilities marquee — [data-marquee] (the signature)

An infinite horizontal strip of service names. script.js runs two things on the same track element and lets GSAP composite them into one transform:

  • a constant, linear xPercent loop (the marquee never stops moving)
  • a skewX value driven by an Observer instance reading live wheel, touch and pointer velocity at the window level, clamped to ±16°

A short idle timer watches for scrolling to stop; once it does, a second quickTo built on a CustomEase (volumeSettle, registered at the top of script.js) eases the skew back to level.

The track is written out twice, back to back — that duplication is what makes the loop from xPercent 0 to -50 invisible to wrap. If you add or remove a service name, change it in both copies in index.html or the loop will visibly jump.

3. Work — [data-work]

Six full-width campaign boards (.work-card), each with a data-tags attribute (brand-strategy, film, social, ooh, experiential) and a data-count-driven result stat. On desktop they layer at slightly offset sticky positions, like boards being added to a pitch-room wall; below 900px they return to ordinary document flow.

The discipline filter is pure CSS. A <fieldset> of radio inputs uses :has() to hide cards whose data-tags do not match the checked value — this works with script.js deleted entirely. JavaScript only adds a stagger fade to the cards left visible after a change; it never controls which cards show.

4. Process — [data-process]

Five numbered steps (.step), scroll-revealed as a group via data-reveal-group. Deliberately not pinned or scroll-scrubbed — this is a plain, disciplined reveal.

5. Client trust strip — [data-trust]

A second marquee, calmer on purpose: constant speed, no Observer, no skew, running the opposite direction from the capabilities marquee above it (a fromTo(-50, 0) loop rather than to(-50)). Swap the eight client wordmarks in index.html for your own; they are plain styled text, not logo images.

6. Contact — [data-contact]

A plain HTML <form> (name, organisation, email, project type, budget band, message) plus contact details in a <dl>. The form posts nowhere until you set its action; it keeps working with JavaScript disabled either way.

Photography

Every photograph on the page — the six case-study card images and the sixty-frame reel — is a bundled, optimised WebP/JPEG derivative with VOLUME's duotone grade baked into the pixels, never a redistributed original. Nothing here is a live hotlink.

  • Case-study cards: assets/img-manifest.json lists the six source photographs (Unsplash) plus the crop, size and grade for each. Regenerate with bun scripts/build-template-assets.ts advertising-agency-template --force from the site repo, or bake your own replacements by editing the manifest and re-running it — see the script's own header comment for the full manifest shape. Output lands in assets/img/.
  • The reel: assets/frames/frame_001.jpg through frame_060.jpg, sixty frames pulled from a single Pexels video clip and downsampled. To use your own footage, encode a short clip and extract frames with:

Code snippet omitted: it ships with the download.

then update FRAME_COUNT in assets/script.js if your count differs from 60, and replace assets/frames/frame_030.jpg as the <img class="reel-fallback"> source in index.html with whichever frame best represents the clip.

Credits (source photographs, before grading):

All used under the Unsplash License and the Pexels License, both of which permit free commercial use without attribution; the credits above are kept for your own reference when swapping images, not because they are required.

Dependencies

Code snippet omitted: it ships with the download.

Lenis is optional. Smooth scroll is opt-out (data-smooth="off" on <html>, or ?smooth=off in the URL) and is also off automatically under prefers-reduced-motion. Delete the Lenis <script> tag and its block in script.js to drop it entirely; nothing else depends on it.

These CDN tags carry no integrity attributes on purpose — a template is deployed to a buyer's own domain, and a stale SRI hash would break their site the next time GSAP ships a patch release.

Accessibility

  • No JavaScript: the whole page is a complete, readable static document. The hero's canvas never receives a frame, so its plain <img> fallback shows behind the headline in normal flow, nothing pinned; both marquees render as a single static row of the same words (no duplicated copy, no motion), the discipline filter still filters (it is CSS, not JavaScript), and every stat shows its final value.
  • Reduced motion: the hero never pins or loads its frame sequence at all — the fallback photograph and the headline show immediately, both marquees freeze on one frame, the hero headline is fully visible with no mask animation, counters show their final values immediately, and nothing needed for the page to make sense is hidden behind a state only prefers-reduced-motion: no-preference reaches.
  • Keyboard: every interactive element is a real <button>, <a> with an href, or form control. Campaign boards require no pointer interaction, and the discipline filter is native radio inputs, fully operable by keyboard alone.
  • Blocked CDN: script.js removes the has-js class if gsap or ScrollTrigger fail to load, which un-hides everything the CSS had pre-hidden for the animated version. The page degrades to the same static document described above.

Browser support

Built on :has(), which is required for the discipline filter and has been supported in every major evergreen browser (Chrome, Edge, Firefox, Safari) since 2023. In a browser without it, the filter chips are visible but inert and every case study shows — a safe, readable fallback, not a broken one.

Worked examples, the events and programmatic API, and the class reference ship with the download, alongside the full source.

Your cart

Your cart is empty

The Vault £99

Everything in the catalogue, plus everything we release next.