# Starting Point Testimonials

> 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.

Canonical: https://gsapvault.com/sections/starting-point-testimonials-section
Live demo: https://gsapvault.com/demos/starting-point-testimonials-section/index.html

| Property | Value |
|----------|-------|
| Type | section |
| Tier | paid |
| Price | Included only in the Vault |
| Difficulty | advanced |
| Plugins | SplitText, ScrollTrigger |
| Techniques | scrubbed-transition, scroll-storytelling, drag-to-scrub, colour-interpolation, clip-path-wipe, interruptible-animation, progressive-enhancement, reduced-motion, container-queries, responsive-section |
| Uses Lenis | No |

## Overview

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.

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.

## Features

- 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

## 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

## Vibe-Code Ready Setup

This website section includes `START-HERE-AI.md`, a product-specific copy-paste setup prompt for Cursor, Claude Code, ChatGPT, GitHub Copilot, Windsurf, and other coding assistants. It tells the assistant to inspect the existing stack, integrate the supplied files, preserve the design, scope selectors, retain accessibility and responsive behaviour, add framework-appropriate GSAP cleanup, and report what it tested.

[How AI-assisted setup works](https://gsapvault.com/vibe-coding)

## How It Works

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.

## Documentation

How this website section works and how it goes into a page. The reference you use once you own the files (worked examples, events, the programmatic API, the class list) ships with the download.

### 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._

2. 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](https://gsap.com) core, SplitText and ScrollTrigger, from jsDelivr. Both are free under the GSAP standard licence.
- Fonts: [Unbounded](https://fonts.google.com/specimen/Unbounded) and [Hanken Grotesk](https://fonts.google.com/specimen/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.

## What You Get

- `index.html`: working demo page
- `assets/script.js`: commented, readable source
- `assets/style.css`: effect styles
- `README.md`: full documentation with examples and framework integration notes
- `START-HERE-AI.md`: product-specific copy-paste prompt for AI-assisted setup
- `LICENSE.txt`: standard license terms
- Lifetime updates: re-download anytime from your library

## Get the Code

This website section is included only in the Vault, for existing and new owners. It is not sold individually. The standard licence covers unlimited personal and commercial projects; bundled assets retain their own licence requirements.

- [Get the Vault](https://gsapvault.com/pricing)
- [The Vault (£99 one-time, best value): every collection in the Vault library, plus future items added to those collections](https://gsapvault.com/effects)

---

From [GSAP Vault](https://gsapvault.com): production-ready GSAP animation effects. Full catalog for agents: https://gsapvault.com/llms-full.txt
