Advertising Agency Template
A one-page site for a full-service ad agency, opening on a hero whose background is a pinned, scroll-scrubbed reel of location photography, before a capabilities marquee that skews in proportion to how fast the visitor is scrolling right now, tracked live with the GSAP Observer plugin. Six real campaign case studies with results stats, a CSS-only discipline filter, and an honest process rail sit either side of it.
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, your scroll position as the actual playhead, while the headline and stat strip hold in place on top of 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 campaign cards, each fronted by a graded photograph and a real result stat, a CSS-only discipline filter that keeps working with JavaScript switched off, a mouse-following tilt on the cards for fine pointers, a calmer constant-speed client-logo marquee running the other direction for contrast, and an enquiry form that asks what an agency actually needs to know before the first call.
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
- A hero whose background is a pinned, scroll-scrubbed reel of sixty graded photographs, scroll position as the actual playhead, the headline and stat strip holding in place on top of it
- A capabilities marquee that skews proportionally to live scroll velocity via the GSAP Observer plugin, clamped to a sane maximum and snapped back to level with a quickTo driven by 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
- A mouse-following tilt on every case study card for fine pointers, with a static press state as the touch fallback and a focus-visible equivalent for keyboard users
- A stacked, multi-line SplitText hero headline that reveals on load inside overflow masks, with a positioning statement and a four-figure stat strip underneath
- 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
- 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
Structure
One script initialises every section inside a single gsap.context, with one gsap.matchMedia whose condition sets 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 skewX value applied to the marquee track with a quickTo, 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 second quickTo built on a CustomEase eases 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 tilt cards
On a fine pointer, each case study card tracks the cursor and quickTo's rotateX and rotateY toward it, releasing back to flat on pointerleave. A coarse pointer gets a static pressed state on pointerdown instead of a continuous tilt, and keyboard focus gets its own lifted state through :focus-visible, so nothing about the card's information depends on being able to hover it.
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.