section-027

Starting Point Testimonials

Vault only: not sold separatelyGet the Vault

Testimonials chosen by where each person started, told as a story you scroll through, with the same change of colour, headline type and photograph played by tabs or drag.

SplitTextScrollTrigger advanced
10 more details
scrubbed-transitionscroll-storytellingdrag-to-scrubcolour-interpolationclip-path-wipeinterruptible-animationprogressive-enhancementreduced-motioncontainer-queriesresponsive-section

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

Updated

About this section

Tidewell Lido is a fictional outdoor pool that teaches adults to swim. Its testimonials are organised by starting point rather than by name: six first-person lines such as "I was afraid of deep water" and "My son was learning, so I did too" sit beside the question "Where did you start?", so a visitor can find the person who began where they are. The chosen swimmer's starting point is set huge in yellow capitals on their own colour, running over the edge of their portrait, with their quote, what they took, what they do now and a link to that course.

Read the full section overview

Scrolling tells the stories in turn: the story is held in view while the page moves, and each stretch of scrolling scrubs the change to the next swimmer. Every change between two stories is one scrubbable transition. The whole section blends to the next swimmer's colour, the big lines swim out one way and in from the other with a stagger, the quote and details follow, and the new portrait wipes over the old one behind a yellow seam. Scroll scrubs it; clicking a starting point or an arrow plays it and moves the scroll to match; dragging or swiping the story scrubs it directly, and letting go finishes or falls back depending on distance and speed. Any input can interrupt any other and continues from what is on screen.

Each story is an ordinary figure with a blockquote and figcaption, so stories are added, removed or reordered in HTML and the rail, count and arrows follow. Without JavaScript every story is shown as its own colour block. The swimmers, figures and generated photographs are sample content to replace with real testimonials.

Starting Point Testimonials - website section preview

What's included

9 items
  • Scroll-driven story: the section holds in view and scrolling scrubs each change, with a rest on every story
  • Starting-point rail built from the testimonial markup
  • One transition scrubbed by drag or swipe, or played by click and keys
  • Section colours blended through HSL so saturated grounds stay saturated
  • Headline lines split with SplitText and staggered in and out
  • Photograph wipe with a leading seam and parallax
  • Velocity-aware release, reversal across the start point, interruption from the drawn state
  • Per-story ground, display and text colours set on each item
  • No-JavaScript, missing-GSAP and reduced-motion fallbacks

Perfect for

4 use cases
  • Swimming, fitness and coaching testimonials
  • Course and class providers matching new students to past ones
  • Clinics and services where customers start from different situations
  • Driving, language and music schools

How it works

2 sections

Each story is an li containing a figure: the photo, a blockquote whose first paragraph is the starting point, and a figcaption with the name, two facts and a course link. The script builds the rail from the starting points, stacks every story in one grid cell so the section keeps the height of the longest, and splits each starting point into lines.

A change is planned as a list of tracks: each line, the quote, the caption, the photo reveal, the three section colours and the rail marker, each with its own start value (read from what is drawn), end value, time window and ease. One progress value from 0 to 1 renders every track, so a GSAP tween, a drag distance, a release tween or the scroll position can all drive it, and a reversal is the same tracks run back. Photos reveal with clip-path inset from the side they enter, led by a seam element. Reduced motion or missing GSAP makes changes instant. Each root mounts independently, and destroy() reverts the split text and removes only its own nodes, styles and listeners. When the story fits the viewport, the script wraps it in a track about 0.9 viewport heights taller per story and makes it sticky; a ScrollTrigger over the track maps scroll to a position in stories, eased by a short follow tween, and holds each story at rest for part of every step. Clicks and drags pause that mapping while they play and then move the page to the chosen story's scroll position.

Plugins SplitText, ScrollTrigger
Difficulty Advanced
Includes HTML + JS + CSS source, documentation, AI setup prompt, lifetime updates

Vault only

Get the Vault to use this section.

The Vault includes Starting Point Testimonials, 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. Every paid product is tested for desktop and mobile layouts, reduced-motion handling, animation cleanup and keyboard controls wherever there is something to operate. Sections are available only in the Vault for now while the collection is small.

Documentation

Quick Start

Files you need:

Code snippet omitted: it ships with the download.

  1. Load the fonts, the stylesheet and the scripts:

Code snippet omitted: it ships with the download.

  1. Copy everything from <section class="spt" data-starting-points ...> to its closing </section> into your page. That element is the whole product; the white <body> in the demo is page context only.

GSAP, SplitText and ScrollTrigger are optional. Without GSAP every change is instant; without ScrollTrigger the stories change only by the rail, arrows and drag; without SplitText the big line moves as one piece and does not overlap the photo; without JavaScript every story is shown one after another, each on its own colour.

Make it yours

Text and links

  • The heading (h2#spt-heading, referenced by the section's aria-labelledby) and lead are in .spt__head. Change the heading to h1 or h3 freely; styles use classes. Give each instance on a page its own heading id.
  • Each story is one <li class="spt__item">:
    • .spt__start: the starting point, in the person's voice. It is also the rail label (a trailing full stop is dropped). Keep it under about 40 characters so it stays three or four lines at full size.
    • .spt__words: the quote. Quotation marks are added by CSS.
    • .spt__name and the text after it: who they are.
    • .spt__facts: two dt/dd pairs, "Took" (what they bought) and "Now" (the outcome). Rename the dt text for your business; keep two pairs for the two-column layout.
    • .spt__cta: the link to what they took. The demo links (#adult-beginners and so on) are placeholders; point them at your real pages.
  • .spt__note at the bottom is the sample-content notice. Delete it once the stories are real.

Photos

Each story has one photo in .spt__photo:

Code snippet omitted: it ships with the download.

  • Recommended source: portrait, 2:3 or 4:5, at least 800 px wide. The frame is 4:5 on wide screens and square on narrow ones, cropped with object-fit: cover.
  • --spt-focus is the object-position for that photo: keep the face inside it on both crops (the square crop is the tighter one). Put the person's head in the upper third.
  • Set width and height to your file's real pixel size so space is reserved while it loads. Any aspect ratio works; nothing in the animation depends on the image.
  • The first story's image is loading="eager"; the rest are lazy and the script starts them loading about a second after the section mounts.
  • Write alt text that describes the person and setting.

Colours per story

Each <li> sets its own three colours:

Code snippet omitted: it ships with the download.

  • --spt-ground: the section background while this story is shown.
  • --spt-display: the big starting-point line, the rail marker, the seam and the button.
  • --spt-ink: body text. Muted text is mixed from ink and ground automatically.

Check contrast for every pair you choose: body text needs 4.5:1 against the ground (the demo pairs are 5.8:1 or more), and the big line needs at least 3:1 (the demo is 4.6:1 or more). Muted text is 84% ink, so it lands a little below the ink ratio. The section blends between stories in HSL, so two saturated colours stay saturated in between.

Root variables

On .spt in assets/style.css:

Variable Default Controls
--spt-ground, --spt-display, --spt-ink teal, yellow, white Fallback colours for a story that sets none
--spt-font-display Unbounded Heading, starting points, labels, count
--spt-font-body Hanken Grotesk Quote and body text
--spt-width 84rem Maximum content width
--spt-gutter clamp(1.25rem, 4vw, 3.5rem) Side padding
--spt-space clamp(3rem, 7vw, 6.5rem) Top padding
--spt-radius 0.375rem Photo corners
--spt-photo-ratio 4 / 5 Photo frame on wide containers

Rebrand example, a climbing wall with its own faces and a darker palette:

Code snippet omitted: it ships with the download.

Code snippet omitted: it ships with the download.

Adding, removing and reordering stories

Add, delete or move <li class="spt__item"> elements. The rail buttons, their numbers, the count and the arrows are built from the list, and the section keeps the height of its longest story so the page does not jump. There is no hard limit; the rail reads best with two to nine starting points. With a single story the rail and arrows are not shown.

Behaviour and options

  • Scroll through the stories: when the story fits the browser window, it is held in view (position: sticky) while the page scrolls past it, and each stretch of scrolling plays the change to the next story, scrubbed to the scroll position with a short rest on each story. Scrolling back plays the change backwards. When the window is too short for the story (most phones held upright, or a short laptop window), the section is an ordinary block of the height of one story and changes by the rail, arrows and swipe only. Resizing switches between the two.
  • Click or tap a starting point to change story. Arrow keys, Home and End move along the rail. The arrows under the story step one at a time.
  • Drag (mouse) or swipe (touch) the story sideways: left for the next story, right for the previous. The change follows your finger; let go past halfway, or with a flick, to finish, otherwise it falls back. Crossing back over where you started switches direction. Vertical swipes scroll the page as normal.
  • Any input can interrupt a change in progress; everything continues from where it is drawn and settles on the last request. In scroll mode, a click, key or drag plays the change directly, then moves the page to that story's place in the scroll so the two agree; while the story is held in view the move is invisible, and from above the section the page scrolls down to it.

Options can be set on the section as data attributes:

Code snippet omitted: it ships with the download.

  • data-spt-duration: seconds for a played change (default 1.15).
  • data-spt-drag-distance: share of the story width one full drag covers (default 0.45).
  • data-spt-line-travel: share of the story width the big lines travel (default 0.55).
  • data-spt-scroll-step: window heights of scrolling per story in scroll mode (default 0.9).
  • data-spt-scroll="off": no scroll mode; the section keeps the height of one story at every size.

The script mounts every [data-starting-points] element on load. For content added later, or to tear down:

Code snippet omitted: it ships with the download.

destroy() reverts the split lines, removes the scroll track and its ScrollTrigger, the rail buttons, arrows, seams and live region, and clears the inline styles and classes it added, leaving the original markup.

Accessibility and integration

  • Stories are <figure> elements with a <blockquote> and <figcaption>. Hidden stories are visibility: hidden and aria-hidden, so screen readers get only the current one; a polite live region announces each change ("Story 3 of 6: Aisha Rahman. Nobody ever taught me.").
  • Rail buttons are toggle buttons (aria-pressed) with aria-controls pointing at their story. All controls have visible focus and 44 px or larger targets.
  • Reduced motion (including a change while the page is open): no scroll mode, changes are instant; swipes still switch story.
  • The section is a full-width colour block and is designed to span the page width. In a column narrower than 44rem it switches to a single-column layout with a square photo. In scroll mode it adds scroll length of its own (about 0.9 window heights per story) and holds the story with position: sticky; it does not take over the wheel, change the page's scrolling or install smooth scrolling. It reads the window's scroll position, so it expects the page itself to scroll rather than an inner scrolling container. Keep ancestors free of overflow: hidden or auto, which stop sticky elements from sticking (overflow: clip is fine). With Lenis on the host page, feed Lenis into ScrollTrigger as usual.
  • The component uses CSS container queries and color-mix() (current Chrome, Edge, Firefox and Safari).

Dependencies and credits

  • GSAP 3.15 core, SplitText and ScrollTrigger, from jsDelivr. Both are free under the GSAP standard licence.
  • Fonts: Unbounded and Hanken Grotesk, SIL Open Font License, from Google Fonts.
  • Photographs: AI-generated concept images made for this demo. They show no real people or place and must not be presented as customers.

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