# Chrono Rail Timeline

> A pinned horizontal timeline where vertical scroll drives a chronology of full dated entries past a fixed playhead, with velocity smear, ruler era marks and an elastic settle.

Canonical: https://gsapvault.com/effects/chrono-rail-timeline
Live demo: https://gsapvault.com/demos/chrono-rail-timeline/index.html

| Property | Value |
|----------|-------|
| Type | effect |
| Tier | paid |
| Price | £5 |
| Difficulty | advanced |
| Plugins | ScrollTrigger |
| Techniques | horizontal-scroll, pinning, scrub, velocity, snap, timeline, 3d-transforms |
| Uses Lenis | Yes |

## Overview

A horizontal timeline built around an edit-suite playhead. The section pins, vertical scroll carries a rail of dated entries sideways, and a fixed head at the centre of the viewport decides what is current: the entry under the head is a full chronology record (year, title, a lead paragraph and a block of labelled facts), while the entries either side compress, tilt and blur into shapes.

Every channel is driven by the same scroll input. Distance from the playhead sets each entry's scale, depth and legibility, with the lead copy and the detail block fading faster than the card itself so the rail never reads as a wall of small print. Scroll velocity shears the whole rail, blurs the ruler and splits the era name into a blue and orange pair; the ruler runs at a third of the rail's speed so the strip reads as depth rather than a flat band. When the wheel stops the rail snaps to the nearest entry and settles with an elastic overshoot instead of a flat stop.

There is no tab bar: era boundaries are printed onto the ruler at the scroll position of their first entry, so jumping to an era is a mark on the timeline rather than a widget above it. The left and right arrow keys tween the same scroll position the moment the rail is on screen, without a click or a tab into it first; Home and End do the same once focus is inside the rail.

## Features

- Pinned rail: vertical scroll drives a horizontal chronology past a fixed centre playhead
- Full entries, not captions: year, title, a lead paragraph and a structured detail block (labelled facts or a short what-changed list) per stop
- Distance-from-head response: entries scale, rotate in 3D, fade and blur by how far they sit from the playhead, with a steeper falloff on the body copy and detail block than on the card
- Velocity smear: fast scroll shears the rail, blurs the ruler and splits the era title into a blue/orange pair, decaying on its own clock after the input stops
- Elastic settle: snap to the nearest entry, an overshoot on the rail and a bloom on the entry that landed
- Era marks printed onto the ruler at their first entry's scroll position, placed from the same snap points the rail lands on, with Left/Right tweening the real scroll position through Lenis as soon as the rail is on screen (no click or tab first), and Home/End doing the same once focus is inside the rail
- Parallax ruler running at a third of the rail's speed, plus an era wash that crossfades hue as the head enters a new era
- Geometry re-measured on every ScrollTrigger refresh, with a guard that keeps the last good measurement through a mid-pin refresh
- Accessible: reduced motion renders the whole chronology as a static grid, a polite live region announces the entry under the head, focus inside the rail brings its entry under the head, and the render loop is gated on an IntersectionObserver

## Use Cases

- Studio, brand and company history sections that need more than a stacked list
- Product roadmaps and release chronologies on a landing page
- Case study or process walkthroughs where the order of steps is the story
- Festival, exhibition and programme line-ups arranged by date
- Editorial retrospectives and anniversary features

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

ScrollTrigger pins the section and reports progress and velocity through onUpdate; nothing is rendered there. A single gsap.ticker callback reads those two values and writes the frame: gsap.utils.interpolate maps progress onto the rail's x, each entry's distance from the viewport centre drives its transform through one gsap.set, and quickSetters push the velocity-derived fringe, flare and blur into CSS custom properties. The same distance is published as a --chrono-t custom property, which is what fades the lead paragraph and the detail block ahead of the card.

Entry centres come from offsetLeft, never from transforms, so scaling an entry can never feed back into the maths. Snap points are those same centres normalised to 0-1 and resolved through gsap.utils.snap; the era marks are positioned from those snap points, and the settle runs on a separate inner wrapper so its elastic overshoot never fights the scrubbed position.

Era jumps tween the document scroll (through Lenis when it is running) rather than the rail, which keeps the pinned trigger authoritative.

## 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 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>
<!-- Optional. The effect works without it; era jumps use it when present. -->
<script src="https://unpkg.com/lenis@1.3.17/dist/lenis.min.js"></script>
<script src="path/to/script.js"></script>
```

**3. Add the effect HTML anywhere in your `<body>`:**

```html
<section class="chrono" data-chrono aria-label="Company chronology">
  <div class="chrono__band" data-chrono-band aria-hidden="true"></div>

  <div class="chrono__deck">
    <div class="chrono__playhead" aria-hidden="true">
      <span class="chrono__playhead-cap"></span>
      <span class="chrono__playhead-line"></span>
      <span class="chrono__playhead-foot"></span>
    </div>

    <div class="chrono__viewport" data-chrono-track tabindex="0" role="group"
         aria-label="Chronology. Use left and right arrow keys to move between entries.">
      <div class="chrono__inner" data-chrono-inner>

        <article class="chrono__entry" data-chrono-entry
                 data-chrono-era="early" data-chrono-era-name="Early"
                 data-chrono-hue="232" data-chrono-stamp="1998">
          <div class="chrono__card chrono__card--cream" data-chrono-card>
            <div class="chrono__masthead" aria-hidden="true">
              <span>Early</span>
              <span>01 / 10</span>
            </div>
            <span class="chrono__mark" aria-hidden="true">
              <svg viewBox="0 0 100 100" preserveAspectRatio="xMidYMid meet" focusable="false">
                <circle cx="50" cy="50" r="32" fill="none" stroke="currentColor" stroke-width="21"/>
              </svg>
            </span>
            <div class="chrono__head">
              <span class="chrono__year">1998</span>
              <h3 class="chrono__title">Two desks, one modem</h3>
            </div>
            <p class="chrono__note">Two or three sentences of real copy. This is the
              entry, not a caption for it.</p>
            <dl class="chrono__facts">
              <div><dt>Team</dt><dd>2</dd></div>
              <div><dt>Clients</dt><dd>9</dd></div>
              <div><dt>First invoice</dt><dd>&pound;480</dd></div>
            </dl>
          </div>
        </article>

        <!-- ...more entries, in chronological order... -->

      </div>
    </div>
  </div>

  <footer class="chrono__base">
    <div class="chrono__scale" data-chrono-scale>
      <div class="chrono__ruler-wrap" aria-hidden="true">
        <div class="chrono__ruler" data-chrono-ruler></div>
      </div>
      <div class="chrono__marks" aria-label="Jump to era">
        <button type="button" class="chrono__mark" data-chrono-jump="early">
          <span class="chrono__mark-tick" aria-hidden="true"></span>
          <span class="chrono__mark-label">Early</span>
        </button>
        <!-- one button per era -->
      </div>
    </div>
    <div class="chrono__meta">
      <span class="chrono__readout" data-chrono-readout>1998</span>
      <span class="chrono__era-title" data-chrono-era-title data-text="Early">Early</span>
    </div>
    <p class="sr-only" aria-live="polite" data-chrono-live></p>
  </footer>
</section>
```

Swap `chrono__facts` for `chrono__changes` (a plain `<ul>`) on entries where a
short "what changed" list reads better than labelled facts. Both carry the same
distance-from-head fade.

### Options

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

| Attribute | Values | Default | Description |
|-----------|--------|---------|-------------|
| `data-chrono-pace` | Any number (px) | `500` | Scroll distance per entry. Lower is a faster chronology; with full-length entries, below ~350 they pass faster than they can be read. |
| `data-chrono-focus` | Any number | `1.02` | How far either side of the playhead an entry still reads as in focus, as a multiple of the entry pitch. Below ~0.9 neighbours pop rather than compress. |
| `data-chrono-smear` | `0` to `~2` | `1` | Peak shear, ruler blur and colour fringe on a hard flick. `0` disables the smear entirely. |
| `data-chrono-snap` | `true`, `false` | `true` | Snap to the nearest entry, and with it the elastic settle. |

### Accessibility

- **Reduced motion**: `prefers-reduced-motion: reduce` skips the pin entirely. The rail becomes a static grid of entries, all readable, in both CSS and the JS branch.
- **Keyboard**: Left/Right move one entry and work as soon as the rail is on screen, with no click or tab first — the listener sits on the document and only claims the key while the rail owns the middle of the viewport, so the rest of the page still scrolls normally. Home and End jump to the ends but stay scoped to focus inside the rail, because taking those from a visitor who wants the top or bottom of the document costs more than it buys. The rail is still focusable, and the era marks on the ruler are real `<button>` elements with `aria-current` and a padded press target around the hairline tick. Anything focusable inside an entry brings that entry under the head, so tabbing never lands on something off screen.
- **Announcement**: `[data-chrono-live]` is a polite live region carrying the year and title of the entry under the head. Scrolling moves focus nowhere, so without it the rail is silent to a screen reader.
- **No JS**: nothing is pre-hidden. Without the script the rail is a plain horizontal row of readable cards.
- **Touch**: the mechanic is unchanged on a phone — vertical scroll still drives the rail. One entry fits beside the head, so the distance-from-head compression does more of the work, and the entry type steps down rather than the content being cut.

### Dependencies

- GSAP 3.15.0
- ScrollTrigger
- Lenis (optional; era jumps route through it when it is present)

## 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 Chrono Rail Timeline](https://gsapvault.com/effects/chrono-rail-timeline)
- [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
