# Scroll Timeline Chronicle

> A scroll-scrubbed vertical chronology: entries magnetically snap into a reading band and overshoot at the handoff while a drawn spine, an odometer year and a depth-blur falloff all react to the same scroll.

Canonical: https://gsapvault.com/effects/scroll-timeline-chronicle
Live demo: https://gsapvault.com/demos/scroll-timeline-chronicle/index.html

| Property | Value |
|----------|-------|
| Type | effect |
| Tier | paid |
| Price | £5 |
| Difficulty | advanced |
| Plugins | ScrollTrigger |
| Techniques | scrub, pinning, velocity, depth-falloff, odometer-counter, viewport-aware-ticker |
| Uses Lenis | Yes |

## Overview

An editorial chronology effect that turns a company history, product roadmap or milestone list into one scroll-scrubbed instrument, art-directed as a monochrome fashion-editorial sheet: white, true black and hairline rules, with records filed behind a large didone folio number and a ruled margin, and a giant year bleeding off the corner in the lightest tint on the page. There is no accent colour: the active record is carried by weight, scale and one solid black mark. A single pinned stage holds every entry stacked in 3D; scroll position drives a continuous index, and a back.inOut curve on the fraction between entries makes each one hang at rest, then undershoot and overshoot as the next takes over.

Everything else in the composition is driven from that same number: a black spine draws itself down the ruled margin, a giant year rolls digit by digit like an odometer, and entries either side of the active one fall back in depth with a blur and desaturation falloff. Scroll velocity leans the whole column and stretches the spine head into a liquid streak.

## Features

- One pinned ScrollTrigger drives every channel from a single scrubbed index, so nothing can drift out of sync
- Magnetic snap with overshoot: a back.inOut curve on the between-entry fraction gives each handoff a hang, an undershoot and a settle
- Drawn accent spine with a travelling head, per-entry nodes that light as they are passed, and a tick ray fired across at each handoff
- Odometer year counter built from digit reels, with a per-digit lag so the year rolls rather than flicking
- Depth falloff on inactive entries: translateZ, rotationX, opacity, blur and desaturation, all quantised so the filter is only rewritten when it changes
- Velocity lean on a frame-rate independent ticker that decays back to rest, gated to the viewport by a second ScrollTrigger
- Optional external jump controls (data-chron-jump) that scroll to any entry, Lenis-aware, with aria-current kept in step
- Complete without JavaScript: the chronicle is authored as a plain readable list and only becomes the pinned stage once the effect runs
- Clickable ruled margin: every tick on the spine seeks to its entry, with the year revealed beside it on hover

## Use Cases

- Company history or about pages where the founding story is the sell
- Product roadmaps and changelogs that need to feel like a release, not a table
- Case-study process sections walking a project from brief to launch
- Annual reports and impact pages where each year deserves its own beat
- Conference or festival programmes counted down year by year

## Vibe-Code Ready Setup

This effect 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

A single pinned ScrollTrigger scrubs the whole section and converts progress into a floating index across the entries. The fraction between two entries is passed through a back.inOut ease, which is what produces the magnetic hang at each stop and the overshoot through the handoff; every other channel reads that same eased index.

Each frame the script sets y, x, translateZ, rotationX, opacity and a blur/saturate filter per entry from its distance to the index, scales the spine draw, positions the travelling head and sets each digit reel's yPercent to an interpolated digit value. Blur is quantised to half a pixel so the filter string is only rewritten when it actually changes.

Scroll velocity is read in onUpdate and fed to a gsap.ticker loop that decays it back to zero with a frame-rate independent exponential approach, driving a skewY on the entry column and a scaleY/scaleX stretch on the spine head. The ticker is gated to the viewport by a second ScrollTrigger, and everything is torn down through gsap.context and gsap.matchMedia.

## Integration Preview

How this effect integrates into a page. The full documentation (examples, events, programmatic API, customization guide) ships with the download.

### Quick Start

**1. Add to your HTML `<head>`:**

```html
<link rel="stylesheet" href="path/to/style.css">
```

**2. Add the chronicle to your `<body>`:**

```html
<div class="chron" data-chronicle>
  <div class="chron__stage" data-chron-stage>
    <p class="chron__year" data-chron-year aria-hidden="true">2014</p>

    <div class="chron__spine" aria-hidden="true">
      <div class="chron__rail" data-chron-rail>
        <div class="chron__draw" data-chron-draw></div>
      </div>
      <div class="chron__head" data-chron-head>
        <div class="chron__stretch" data-chron-stretch>
          <div class="chron__pop" data-chron-pop></div>
        </div>
      </div>
      <div class="chron__ray" data-chron-ray></div>
    </div>

    <ol class="chron__list" data-chron-list>
      <li class="chron__entry" data-chron-entry data-year="2014">
        <div class="chron__folio">
          <span class="chron__no">01</span>
          <span class="chron__rule" aria-hidden="true"></span>
          <span class="chron__yr">2014</span>
        </div>
        <div class="chron__text">
          <h3 class="chron__title">Two desks above a bike shop</h3>
          <p class="chron__body">Your entry copy.</p>
        </div>
      </li>
      <!-- one <li data-chron-entry data-year="..."> per milestone -->
    </ol>

    <p class="sr-only" role="status" aria-live="polite" data-chron-status>2014</p>
  </div>
</div>
```

**3. Add before the closing `</body>` tag:**

```html
<script src="https://cdn.jsdelivr.net/npm/gsap@3.15.0/dist/gsap.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.15.0/dist/ScrollTrigger.min.js"></script>
<script src="path/to/script.js"></script>
```

The script finds every `[data-chronicle]`, builds the digit reels and rail nodes, adds `is-live`, pins the stage and scrubs the whole composition from one ScrollTrigger.

### Options

Set on the `[data-chronicle]` element.

| Attribute | Values | Default | Description |
|---|---:|---:|---|
| `data-chronicle` | marker | required | Identifies a chronicle |
| `data-step` | number | `85` | Scroll distance per entry, as a percentage of the stage height. Below about `55` the entries cross faster than the year can be read |
| `data-gap` | number | `0.3` | Vertical distance between neighbouring entries, as a fraction of stage height. **Scale it with your longest entry**, see below |
| `data-blur` | pixels | `7` | Peak blur applied to the furthest visible entry |
| `data-lean` | degrees | `9` | Maximum velocity lean of the entry column. `0` disables the lean and the head stretch |
| `data-scrub` | seconds | `0.6` | ScrollTrigger scrub smoothing. Higher trails further behind the scroll |

**`data-gap` is the one value your copy decides.** Entries are absolutely
positioned and offset by `gap x stage height`, so the gap has to clear the
tallest entry or a neighbour's last line lands on the next entry's title. The
default `0.3` suits a one-sentence entry. A paragraph of four or five lines
wants about `0.46`, which is what the demo runs: thirteen entries at 48 to 72
words each. Nothing warns you about this, because an overlapping entry is still
faded and blurred and reads as intentional depth until you look at the line
that has gone through a heading. Set your longest entry first, scroll to it, and
raise the gap until it clears.


Per-entry: `data-year` (four digits) on each `[data-chron-entry]`.

| Hook | Where | Purpose |
|---|---|---|
| `data-chron-stage` | inside the chronicle | The element that gets pinned |
| `data-chron-list` | inside the stage | Stacking parent for the entries |
| `data-chron-entry` | repeated | One milestone |
| `data-chron-rail` | optional | Nodes are injected into it, one per entry |
| `data-chron-draw` | optional | Scaled on the Y axis to draw the spine |
| `data-chron-head` / `-stretch` / `-pop` | optional | Travelling head, velocity stretch, handoff pop |
| `data-chron-ray` | optional | Tick ray fired across at each handoff |
| `data-chron-year` | optional | Replaced with digit reels; keep a readable year in it as the no-JS state |
| `data-chron-status` | optional | Visually hidden live region announcing the active year |
| `data-chron-jump` | anywhere on the page | Buttons that scroll to entry N, in document order |

The jump buttons may sit outside the chronicle (a toolbar, a sidebar). The script looks inside `[data-chronicle]` first and falls back to a document-wide lookup, and keeps `aria-current` on them in step with the active entry.

### Accessibility

- **Reduced motion**: `gsap.matchMedia()` never adds `is-live`, so the chronicle stays a plain, fully readable list in document order. Nothing is hidden and nothing is pinned.
- **Without JavaScript**: identical. The live layout is the enhancement, not the baseline.
- **Keyboard**: the jump controls are real `<button>` elements and carry `aria-current`. They scroll the page, so the reader's focus and scroll position stay in agreement.
- **Announcements**: `[data-chron-status]` is a visually hidden `role="status"` region updated with the active year at each handoff.
- **Motion budget**: the velocity lean is clamped by `data-lean` and decays on a frame-rate independent curve, so a fast flick cannot leave the column tilted.

### Dependencies

- GSAP 3.15.0
- Instrument Serif and Inter (Google Fonts; swap freely, see Theming)
- ScrollTrigger
- Lenis 1.3.17 (optional; smooth scroll is opt-out with `data-smooth="off"` on `<html>` or `?smooth=off` in the URL)

### Browser Support

Chrome, Firefox, Safari and Edge, current versions. The depth falloff uses `translateZ` with `perspective` and a `blur()` filter; both degrade to a plain fade in anything that does not support them.

## 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 is a premium effect. The standard license costs £5 one-time and covers unlimited personal and commercial projects with no attribution required. The only restrictions: no redistribution of the code itself and no competing effect libraries.

- [Buy Scroll Timeline Chronicle](https://gsapvault.com/effects/scroll-timeline-chronicle)
- [Effects & Templates Vault (£39 one-time, best value): every current and future effect and template](https://gsapvault.com/effects)

## Judge the Code Quality First

These related effects are free with complete source published, written to the same production standard (cleanup functions, reduced-motion support, framework-agnostic):

- [Scroll Progress Indicator](https://gsapvault.com/effects/scroll-progress.md): A precise GSAP reading-progress instrument with bar, ring, side rail, percentage, and active chapter feedback.
- [CSS Scroll Reveal](https://gsapvault.com/effects/css-scroll-reveal.md): Native CSS scroll-driven reveals for crisp fade, slide, and scale entrances with accessible static fallbacks and no animation JavaScript.
- [Parallax Hero](https://gsapvault.com/effects/parallax-hero.md): A pinned hero that separates its background photograph, copy, and foreground card into distinct scroll depths from a single scrubbed ScrollTrigger.

---

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