Initial commit

This commit is contained in:
jackdoyle
2026-03-04 15:14:02 -06:00
parent 3e12cfb14d
commit 43a8bf0915
8 changed files with 679 additions and 0 deletions
+30
View File
@@ -0,0 +1,30 @@
# Dependencies
node_modules/
# IDE
.vscode/
.idea/
*.swp
*.swo
# OS
.DS_Store
Thumbs.db
# Logs
*.log
npm-debug.log*
# Environment
.env
.env.local
.env.*.local
# Build outputs
dist/
build/
# Temporary files
tmp/
temp/
*.tmp
+27
View File
@@ -0,0 +1,27 @@
# Guidance for AI Agents Working in This Repo
This repository contains **GSAP (GreenSock Animation Platform) skills** for AI coding agents. When editing or adding skills, follow these rules.
## Repo structure
- **skills/** — Each subdirectory is one skill. The CLI and agents discover skills by scanning `skills/` for directories that contain `SKILL.md`.
- **Skill directory name** must exactly match the `name` in that skill’s frontmatter (e.g. `skills/gsap-core/` ↔ `name: gsap-core`).
## SKILL.md requirements
- **Frontmatter (YAML):**
- `name` (required): lowercase, hyphens only, max 64 chars, must match parent directory name.
- `description` (required): what the skill does and when to use it; include trigger terms so agents know when to apply it. Max 1024 chars.
- `license` (optional): e.g. `MIT` if the skill is under the repo license.
- **Body:** Markdown instructions. Keep under ~500 lines; put long reference material in `references/` or `scripts/` and link from SKILL.md.
## Conventions
- Write descriptions in **third person** (e.g. "Use when…" not "You can use when…").
- Be concise; avoid restating general GSAP docs. Focus on correct API usage, pitfalls, and cleanup.
- When adding a new skill: create `skills/<skill-name>/SKILL.md`, then update README.md "Skills" table and "Structure" section.
## References
- [Agent Skills specification](https://agentskills.io/specification.md)
- [skills CLI (discovery, install)](https://github.com/vercel-labs/skills)
+54
View File
@@ -1,2 +1,56 @@
# gsap-skills
Official AI skills for GSAP. These skills teach AI coding agents how to correctly use the GSAP (GreenSock Animation Platform), including best practices, common animation patterns, and plugin usage.
Skills follow the [Agent Skills](https://agentskills.io) format and work with the [skills](https://github.com/vercel-labs/skills) CLI (Cursor, Claude Code, Codex, Windsurf, GitHub Copilot, and others).
## Installation
Install all GSAP skills with the skills CLI:
```bash
npx skills add <owner>/gsap-skills
```
Replace `<owner>` with the GitHub org or username (e.g. `greensock/gsap-skills`). To list skills without installing:
```bash
npx skills add <owner>/gsap-skills --list
```
To install a single skill by name:
```bash
npx skills add <owner>/gsap-skills --skill gsap-core
```
Agents use each skill when the task matches the skill’s `description` (e.g. “scroll animation” → gsap-scrolltrigger).
## Skills
| Skill | Description |
|-------|-------------|
| **gsap-core** | Core API: `gsap.to()` / `from()` / `fromTo()`, easing, duration, stagger, defaults |
| **gsap-timeline** | Timelines: sequencing, position parameter, labels, nesting, playback |
| **gsap-scrolltrigger** | ScrollTrigger: scroll-linked animations, pinning, scrub, triggers, refresh & cleanup |
| **gsap-react** | React: useGSAP hook, refs, `gsap.context()`, cleanup, SSR |
| **gsap-performance** | Performance: transforms over layout props, will-change, batching, ScrollTrigger tips |
## Structure
```
gsap-skills/
README.md
AGENTS.md # Guidance for agents editing this repo
skills/
gsap-core/ SKILL.md
gsap-timeline/ SKILL.md
gsap-scrolltrigger/ SKILL.md
gsap-react/ SKILL.md
gsap-performance/ SKILL.md
```
## Compatibility
- **[skills CLI](https://github.com/vercel-labs/skills)** — installs into Cursor, Claude Code, Codex, Windsurf, GitHub Copilot, and [40+ agents](https://github.com/vercel-labs/skills#supported-agents)
- **[Agent Skills spec](https://agentskills.io/specification.md)** — SKILL.md format and discovery
+146
View File
@@ -0,0 +1,146 @@
---
name: gsap-core
description: Use GSAP core API correctly — gsap.to(), gsap.from(), gsap.fromTo(), easing, duration, stagger, and defaults. Use when animating DOM elements, SVG, or objects with GSAP, or when the user asks about GSAP tweens, easing, or basic animation.
license: MIT
---
# GSAP Core
## When to Use This Skill
Apply when writing or reviewing GSAP animations that use the core engine: single tweens, eases, staggers, or when explaining how GSAP tweens work.
## Core Tween Methods
- **gsap.to(targets, vars)** — animate from current state to `vars`. Most common.
- **gsap.from(targets, vars)** — animate from `vars` to current state (good for entrances).
- **gsap.fromTo(targets, fromVars, toVars)** — explicit start and end; no reading of current values.
- **gsap.set(targets, vars)** — apply immediately (duration 0).
Always use **property names in camelCase** in the vars object (e.g. `backgroundColor`, `marginTop`, `rotationX`, `scaleY`).
## Common vars
- **duration** — seconds (default 0.5).
- **delay** — seconds before start.
- **ease** — string or function. Prefer built-in: `"power1.out"` (default), `"power3.inOut"`, `"back.out(1.7)"`, `"elastic.out(1, 0.3)"`, `"none"`.
- **stagger** — number (seconds between) like `0.1` or object: `{ amount: 0.3, from: "center" }`, `{ each: 0.1, from: "random" }`.
- **overwrite** — `false` (default), `true` (immediately kill all active tweens of the same targets), or `"auto"` (when the tween renders for the first time, only kill individual overlapping properties in other **active** tweens of the same targets).
- **repeat** — number or `-1` for infinite.
- **yoyo** — boolean; with repeat, alternates direction.
- **onComplete**, **onStart**, **onUpdate** — callbacks; scoped to the Animation instance itself (Tween or Timeline).
## Targets
- **Single or Multiple**: CSS selector string, element reference, array or NodeList. GSAP handles arrays; use stagger for offset.
## Stagger
Offset the animation of each item by 0.1 second like this:
```javascript
gsap.to(".item", {
y: -20,
stagger: 0.1
});
```
Or use the object syntax for advanced options like how each successive stagger amount is applied to the targets array (`from: "random" | "start" | "center" | "end" | "edges" | (index)`)
### Learn More
https://gsap.com/resources/getting-started/Staggers
## Easing
Use string eases unless a custom curve is needed:
```javascript
ease: "power1.out" // default feel
ease: "power3.inOut"
ease: "back.out(1.7)" // overshoot
ease: "elastic.out(1, 0.3)"
ease: "none" // linear
```
Built-in eases: base (same as `.out`), `.in`, `.out`, `.inOut` where "power" refers to the strength of the curve (1 is more gradual, 4 is steepest):
```
base (out) .in .out .inOut
"none"
"power1" "power1.in" "power1.out" "power1.inOut"
"power2" "power2.in" "power2.out" "power2.inOut"
"power3" "power3.in" "power3.out" "power3.inOut"
"power4" "power4.in" "power4.out" "power4.inOut"
"back" "back.in" "back.out" "back.inOut"
"bounce" "bounce.in" "bounce.out" "bounce.inOut"
"circ" "circ.in" "circ.out" "circ.inOut"
"elastic" "elastic.in" "elastic.out" "elastic.inOut"
"expo" "expo.in" "expo.out" "expo.inOut"
"sine" "sine.in" "sine.out" "sine.inOut"
```
### Custom: use CustomEase (plugin)
Simple cubic-bezier values (as used in CSS `cubic-bezier()`):
```javascript
const myEase = CustomEase.create("my-ease", ".17,.67,.83,.67");
gsap.to(".item", {x: 100, ease: myEase, duration: 1});
```
Complex curve with any number of control points, described as normalized SVG path data:
```javascript
const myEase = CustomEase.create("hop", "M0,0 C0,0 0.056,0.442 0.175,0.442 0.294,0.442 0.332,0 0.332,0 0.332,0 0.414,1 0.671,1 0.991,1 1,0 1,0");
gsap.to(".item", {x: 100, ease: myEase, duration: 1});
```
## Returning and Controlling Tweens
All tween methods return a **Tween** instance. Store the return value when controlling playback is needed:
```javascript
const tween = gsap.to(".box", { x: 100, duration: 1, repeat: 1, yoyo: true });
tween.pause();
tween.play();
tween.reverse();
tween.kill();
tween.progress(0.5);
tween.time(0.2);
tween.totalTime(1.5);
```
## Function-based values
Use a function for a `vars` value and it will get called **once for each target** the first time the tween renders, and whatever is returned by that function will be used as the animation value.
```javascript
gsap.to(".item", {
x: (i, target, targetsArray) => i * 50, // first item animates to 0, the second to 50, the third to 100, etc.
stagger: 0.1
});
```
## Relative values
Use a `+=`, `-=`, `*=`, or `/=` prefix to indicate a **relative** value. For example, the following will animate x to 20 pixels less than whatever it is when the tween renders for the first time.
```javascript
gsap.to(".class", {x: "-=20" });
```
`x: "+=20"` would add 20 to the current value. `"*=2"` would multiply by 2, and `"/=2"` would divide by 2.
## Defaults
Set project-wide Tween defaults with **gsap.defaults()**:
```javascript
gsap.defaults({ duration: 0.6, ease: "power2.out" });
```
## Do Not
- Animate layout-heavy properties (e.g. `width`, `height`, `top`, `left`) when **transform** properties (`x`, `y`, `scaleX`, `scaleY`, `rotation`) can achieve the same effect; prefer transforms for better performance.
- Use invalid or non-existent ease names; stick to documented eases.
- Forget that **gsap.from()** uses the element’s current state as the end state; the initial values in the tween will be applied immediately unless `immediateRender: false` is in the `vars`.
+70
View File
@@ -0,0 +1,70 @@
---
name: gsap-performance
description: Animate efficiently with GSAP—prefer transforms, avoid layout thrashing, use will-change wisely, and batch updates. Use when optimizing GSAP animations, reducing jank, or when the user asks about animation performance, FPS, or smooth 60fps.
license: MIT
---
# GSAP Performance
## When to Use This Skill
Apply when optimizing GSAP animations for smooth 60fps, reducing layout/paint cost, or when the user asks about performance, jank, or best practices for fast animations.
## Prefer Transform and Opacity
Animating **transform** (`x`, `y`, `scaleX`, `scaleY`, `rotation`, `rotationX`, `rotationY`, `skewX`, `skewY`) and **opacity** keeps work on the compositor and avoids layout and most paint. Avoid animating layout-heavy properties when a transform can achieve the same effect.
- Prefer: **x**, **y**, **scale**, **rotation**, **opacity**.
- Avoid when possible: **width**, **height**, **top**, **left**, **margin**, **padding** (they trigger layout and can cause jank).
GSAP’s **x** and **y** use transforms (translate) by default; use them instead of **left**/**top** for movement.
## will-change
Use **will-change** in CSS on elements that will animate. It hints the browser to promote the layer.
```css
will-change: transform;
```
## Batch Reads and Writes
GSAP batches updates internally. When mixing GSAP with direct DOM reads/writes or layout-dependent code, avoid interleaving reads and writes in a way that causes repeated layout thrashing. Prefer doing all reads first, then all writes (or let GSAP handle the writes in one go).
## Many Elements (Stagger, Lists)
- Use **stagger** instead of many separate tweens with manual delays when the animation is the same; it’s more efficient.
- For long lists, consider **virtualization** or animating only visible items; avoid creating hundreds of simultaneous tweens if it causes jank.
- Reuse timelines where possible; avoid creating new timelines every frame.
## Frequently updated properties (e.g. mouse followers)
Prefer **gsap.quickTo()** for properties that are updated often (e.g. mouse-follower x/y). It reuses a single tween instead of creating new tweens on each update.
```javascript
let xTo = gsap.quickTo("#id", "x", { duration: 0.4, ease: "power3" }),
yTo = gsap.quickTo("#id", "y", { duration: 0.4, ease: "power3" });
document.querySelector("#container").addEventListener("mousemove", (e) => {
xTo(e.pageX);
yTo(e.pageY);
});
```
## ScrollTrigger and Performance
- **pin: true** promotes the pinned element; pin only what’s needed.
- **scrub** with a small value (e.g. `scrub: 1`) can reduce work during scroll; test on low-end devices.
- Call **ScrollTrigger.refresh()** only when layout actually changes (e.g. after content load), not on every resize; debounce when possible.
## Reduce Simultaneous Work
- Pause or kill off-screen or inactive animations when they’re not visible (e.g. when the user navigates away).
- Avoid animating huge numbers of properties on many elements at once; simplify or sequence if needed.
## Do Not
- Animate **width**/ **height**/ **top**/ **left** for movement when **x**/ **y**/ **scale** can achieve the same look.
- Set **will-change** or **force3D** on every element “just in case”; use for elements that are actually animating.
- Create hundreds of overlapping tweens or ScrollTriggers without testing on low-end devices.
- Ignore cleanup; stray tweens and ScrollTriggers keep running and can hurt performance and correctness.
+128
View File
@@ -0,0 +1,128 @@
---
name: gsap-react
description: Use GSAP correctly in React — useGSAP hook, refs, gsap.context(), and cleanup. Use when animating in React, Next.js, or when the user asks about GSAP with React, useGSAP, or cleanup on unmount.
license: MIT
---
# GSAP with React
## When to Use This Skill
Apply when writing or reviewing GSAP code in React (or React-based frameworks like Next.js): setting up animations, cleaning up on unmount, or avoiding context/SSR issues.
## Installation
```bash
# Install the GSAP library
npm install gsap
# Install the GSAP React package
npm install @gsap/react
```
## Prefer the useGSAP() Hook
When **@gsap/react** is available, use the **useGSAP()** hook instead of `useEffect()` for GSAP setup. It handles cleanup automatically and provides a scope and **contextSafe** for callbacks.
```javascript
import { useGSAP } from "@gsap/react";
gsap.registerPlugin(useGSAP); // register before running useGSAP or any GSAP code
const containerRef = useRef(null);
useGSAP(() => {
gsap.to(".box", { x: 100 });
gsap.from(".item", { opacity: 0, stagger: 0.1 });
}, { scope: containerRef });
```
- Pass a **scope** (ref or element) so selectors like `.box` are scoped to that root.
- Cleanup (reverting animations and ScrollTriggers) runs automatically on unmount.
- Use **contextSafe** from the hook's return value to wrap callbacks (e.g. onComplete) so they no-op after unmount and avoid React warnings.
## Refs for Targets
Use **refs** so GSAP targets the actual DOM nodes after render. Do not rely on selector strings that might match multiple or wrong elements across re-renders unless a `scope` is defined. With useGSAP, pass the ref as **scope**; with useEffect, pass it as the second argument to `gsap.context()`. For multiple elements, use a ref to the container and query children, or use an array of refs.
## Dependency array, scope, and revertOnUpdate
By default, useGSAP() passes an empty dependency array to the internal useEffect()/useLayoutEffect() so that it doesn't get called on every render. The 2nd argument is optional; it can pass either a dependency array (like useEffect()) or a config object for more flexibility:
```javascript
useGSAP(() => {
// gsap code here, just like in a useEffect()
},{
dependencies: [endX], // dependency array (optional)
scope: container, // scope selector text (optional, recommended)
revertOnUpdate: true // causes the context to be reverted and the cleanup function to run every time the hook re-synchronizes (when any dependency changes)
});
```
## gsap.context() in useEffect (when useGSAP isn't used)
It's okay to use **gsap.context()** inside a regular **useEffect()** when @gsap/react is not used or when the effect's dependency/trigger behavior is needed. When doing so, **always** call **ctx.revert()** in the effect's cleanup function so animations and ScrollTriggers are killed and inline styles are reverted. Otherwise this causes leaks and updates on detached nodes.
```javascript
useEffect(() => {
const ctx = gsap.context(() => {
gsap.to(".box", { x: 100 });
gsap.from(".item", { opacity: 0, stagger: 0.1 });
}, containerRef);
return () => ctx.revert();
}, []);
```
- Pass a **scope** (ref or element) as the second argument so selectors are scoped to that node.
- **Always** return a cleanup that calls **ctx.revert()**.
## Context-Safe Callbacks
If GSAP-related objects get created inside functions that run AFTER the useGSAP executes (like pointer event handlers) they won't get reverted on unmount/re-render because they're not in the context. Use **contextSafe** (from useGSAP) for those functions:
```javascript
const container = useRef();
const badRef = useRef();
const goodRef = useRef();
useGSAP((context, contextSafe) => {
// ✅ safe, created during execution
gsap.to(goodRef.current, { x: 100 });
// ❌ DANGER! This animation is created in an event handler that executes AFTER useGSAP() executes. It's not added to the context so it won't get cleaned up (reverted). The event listener isn't removed in cleanup function below either, so it persists between component renders (bad).
badRef.current.addEventListener('click', () => {
gsap.to(badRef.current, { y: 100 });
});
// ✅ safe, wrapped in contextSafe() function
const onClickGood = contextSafe(() => {
gsap.to(goodRef.current, { rotation: 180 });
});
goodRef.current.addEventListener('click', onClickGood);
// 👍 we remove the event listener in the cleanup function below.
return () => {
// <-- cleanup
goodRef.current.removeEventListener('click', onClickGood);
};
},{ scope: container });
```
## Server-Side Rendering (Next.js, etc.)
GSAP runs in the browser. Do not call gsap or ScrollTrigger during SSR.
- Use **useGSAP** (or useEffect) so all GSAP code runs only on the client.
- If GSAP is imported at top level, ensure the app does not execute gsap.* or ScrollTrigger.* during server render. Dynamic import inside useEffect is an option if tree-shaking or bundle size is a concern.
## Do Not
- Animate using selector strings that can match elements outside the current component unless a `scope` is defined in useGSAP or gsap.context() so only elements inside the component are affected.
- Skip cleanup; always revert context or revert tweens/ScrollTriggers in the effect return to avoid leaks and updates on unmounted nodes.
- Run GSAP or ScrollTrigger during SSR; keep all usage inside client-only lifecycle (e.g. useGSAP).
### Learn More
https://gsap.com/resources/React
+129
View File
@@ -0,0 +1,129 @@
---
name: gsap-scrolltrigger
description: Use ScrollTrigger for scroll-linked animations, pinning, scrub, and triggers. Use when building scroll-based animations, parallax, pinned sections, or when the user asks about ScrollTrigger, scroll animations, or pinning with GSAP.
license: MIT
---
# GSAP ScrollTrigger
## When to Use This Skill
Apply when implementing scroll-driven animations: triggering tweens/timelines on scroll, pinning elements, scrubbing animation to scroll position, or when the user mentions ScrollTrigger, scroll animations, or pinning.
## Registering the Plugin
ScrollTrigger is a plugin. After loading the script, register it once:
```javascript
gsap.registerPlugin(ScrollTrigger);
```
## Basic Trigger
Tie a tween or timeline to scroll position:
```javascript
gsap.to(".box", {
x: 500,
duration: 1,
scrollTrigger: {
trigger: ".box",
start: "top center", // when top of trigger hits center of viewport
end: "bottom center", // when the bottom of the trigger hits the center of the viewport
toggleActions: "play reverse play reverse" // onEnter play, onLeave reverse, onEnterBack play, onLeaveBack reverse
}
});
```
**start** / **end**: viewport position vs. trigger position. Format `"triggerPosition viewportPosition"`. Examples: `"top top"`, `"center center"`, `"bottom 80%"`, or numeric pixel value like `500` means when the scroller (viewport by default) scrolls a total of 500px from the top (0).
## Scrub
Scrub ties animation progress to scroll. Use for “scroll-driven” feel:
```javascript
gsap.to(".box", {
x: 500,
scrollTrigger: {
trigger: ".box",
start: "top center",
end: "bottom center",
scrub: true // or number (smoothness delay in seconds), so 0.5 means it'd take 0.5 seconds to "catch up" to the current scroll position.
}
});
```
With **scrub: true**, the animation progresses as the user scrolls through the start–end range. Use a number (e.g. `scrub: 1`) for smooth lag.
## Pinning
Pin the trigger element while the scroll range is active:
```javascript
scrollTrigger: {
trigger: ".section",
start: "top top",
end: "+=1000", // pin for 1000px scroll
pin: true,
scrub: 1
}
```
- **pinSpacing** — default `true`; adds spacer element so layout doesn’t collapse when the pinned element is set to `position: fixed`. Set `pinSpacing: false` only when layout is handled separately.
## Markers (Development)
Use during development to see trigger positions:
```javascript
scrollTrigger: {
trigger: ".box",
start: "top center",
end: "bottom center",
markers: true
}
```
Remove or set **markers: false** for production.
## Timeline + ScrollTrigger
Drive a timeline with scroll and optional scrub:
```javascript
const tl = gsap.timeline({
scrollTrigger: {
trigger: ".container",
start: "top top",
end: "+=2000",
scrub: 1,
pin: true
}
});
tl.to(".a", { x: 100 }).to(".b", { y: 50 }).to(".c", { opacity: 0 });
```
The timeline’s progress is tied to scroll through the trigger’s start/end range.
## Refresh and Cleanup
- **ScrollTrigger.refresh()** — recalculate positions (e.g. after DOM/layout changes, fonts loaded, or dynamic content).
- When removing animated elements or changing pages (e.g. in SPAs), **kill** associated ScrollTrigger instances so they don’t run on stale elements:
```javascript
ScrollTrigger.getAll().forEach(t => t.kill());
// or kill by trigger element
ScrollTrigger.getById("myTrigger")?.kill();
```
In React, kill in a cleanup (e.g. in useEffect return) when the component unmounts.
## Do Not
- Nest ScrollTriggered animations inside of a parent timeline. ScrollTriggers should only exist on top-level animations. Wrong: `gsap.timeline().to(..., {scrollTrigger: {...}})`. Correct: `gsap.timeline({scrollTrigger: {...}}).to(...)` (the tween inside the timeline shouldn't have a ScrollTrigger).
- Forget to **gsap.registerPlugin(ScrollTrigger)** before using ScrollTrigger.
- Use **scrub** and **toggleActions** together on the same ScrollTrigger; choose one behavior. If both exist, scrub wins.
- Leave **markers: true** in production.
- Omit **refresh()** after layout changes (new content, images, fonts) that affect trigger positions. ScrollTrigger will automatically call ScrollTrigger.refresh() when the viewport resizes.
+95
View File
@@ -0,0 +1,95 @@
---
name: gsap-timeline
description: Create and control GSAP timelines — gsap.timeline(), position parameter, nesting, and playback controls. Use when sequencing multiple animations, choreographing keyframes, or when the user asks about GSAP timelines, sequencing, or animation order.
license: MIT
---
# GSAP Timeline
## When to Use This Skill
Apply when building multi-step animations, coordinating several tweens in sequence or parallel, or when the user asks about timelines, sequencing, or keyframe-style animation in GSAP.
## Creating a Timeline
```javascript
const tl = gsap.timeline();
tl.to(".a", { x: 100, duration: 1 });
tl.to(".b", { y: 50, duration: 0.5 });
tl.to(".c", { opacity: 0, duration: 0.3 });
```
By default, tweens are **appended** one after another. Use the **position parameter** to place tweens at specific times or relative to other tweens.
## Position Parameter
Third argument (or position property in vars) controls placement:
- **Absolute**: `1` — start at 1 second.
- **Relative (default)**: `"+=0.5"` — 0.5s after end; `"-=0.2"` — 0.2s before end.
- **Label**: `"labelName"` — at that label; `"labelName+=0.3"` — 0.3s after label.
- **Placement**: `"<"` — start when recently-added animation starts; `">"` — start when recently-added animation ends (default); `"<0.2"` — 0.2s after recently-added animation start.
Examples:
```javascript
tl.to(".a", { x: 100 }, 0); // at 0
tl.to(".b", { y: 50 }, "+=0.5"); // 0.5s after last end
tl.to(".c", { opacity: 0 }, "<"); // same start as previous
tl.to(".d", { scale: 2 }, "<0.2"); // 0.2s after previous start
```
## Timeline Defaults
Pass defaults into the timeline so all child tweens inherit:
```javascript
const tl = gsap.timeline({ defaults: { duration: 0.5, ease: "power2.out" } });
tl.to(".a", { x: 100 }).to(".b", { y: 50 }); // both use 0.5s and power2.out
```
## Timeline Options (constructor)
- **paused: true** — create paused; call `.play()` to start.
- **repeat**, **yoyo** — same as tweens; apply to whole timeline.
- **onComplete**, **onStart**, **onUpdate** — timeline-level callbacks.
- **defaults** — vars merged into every child tween.
## Labels
Add and use labels for readable, maintainable sequencing:
```javascript
tl.addLabel("intro", 0);
tl.to(".a", { x: 100 }, "intro");
tl.addLabel("outro", "+=0.5");
tl.to(".b", { opacity: 0 }, "outro");
tl.play("outro"); // start from "outro"
tl.tweenFromTo("intro", "outro"); // pauses the timeline and returns a new Tween that animates the timeline's playhead from intro to outro with no ease.
```
## Nesting Timelines
Timelines can contain other timelines.
```javascript
const master = gsap.timeline();
const child = gsap.timeline();
child.to(".a", { x: 100 }).to(".b", { y: 50 });
master.add(child, 0);
master.to(".c", { opacity: 0 }, "+=0.2");
```
## Controlling Playback
- **tl.play()** / **tl.pause()**
- **tl.reverse()** / **tl.progress(1)** then **tl.reverse()**
- **tl.restart()** — from start.
- **tl.time(2)** — seek to 2 seconds.
- **tl.progress(0.5)** — seek to 50%.
- **tl.kill()** — kill timeline and (by default) its children.
## Do Not
- Forget that **duration** on the timeline constructor is not the same as tween duration; timeline “duration” is determined by its children.
- Nest animations that contain a ScrollTrigger; ScrollTriggers should only be on top-level Tweens/Timelines.