# Outline Text Filled by Media — the word is hollow until the image fills it

## Goal

Build a pinned stage where a giant word sits **hollow** — stroked, transparent fill — over a
photograph, and a scroll-driven wipe uncovers a second copy of the same word **filled with that
photograph**, letter by letter, left to right.

Use it when the name of something has to *be* the image: a season title, a film title, a record
sleeve.

## Tech

Vanilla HTML/CSS/JS with ES modules: `gsap` + `ScrollTrigger`, and `lenis`.

Wire Lenis to ScrollTrigger — this is not optional:

```js
const lenis = new Lenis();
lenis.on("scroll", ScrollTrigger.update);
gsap.ticker.add((t) => lenis.raf(t * 1000));
gsap.ticker.lagSmoothing(0);
```

Lenis animates its own scroll position; ScrollTrigger reads the native one. Without that first line
the two clocks drift and the triggers fire at the wrong moment or not at all — the component looks
broken in a way that never reaches the console. `lagSmoothing(0)` stops GSAP from swallowing a long
frame, which on a scrubbed mechanic shows up as a jump.

## The mechanic in one line

Two copies of the same word, pixel-aligned, one stroked and hollow and one filled via
`background-clip: text`; a `clip-path` wipe uncovers the filled copy.

```html
<div class="word" aria-label="Undertow">
  <span class="hollow" aria-hidden="true">Undertow</span>
  <span class="filled" aria-hidden="true">Undertow</span>
</div>
```

## The four decisions that ARE this component

### 1. Two copies, not one element that changes.

`-webkit-text-stroke` with a transparent fill and a background clipped to text are **different
paint paths**. Any crossfade between them flickers on the sub-pixel edge of every letter. Two
stacked copies with a `clip-path` wipe are exact, cheap and perfectly reversible.

### 2. The alignment is the whole job.

```css
.word { display: grid; }
.word span { grid-area: 1 / 1; /* identical everything else */ }
```

Both copies share **one grid cell** and identical typography. Everything that affects where a glyph
lands — family, size, weight, `line-height`, `letter-spacing`, `padding`, `white-space` — must be
identical in the two rules. Drift them by a single pixel and the outline shows as a ghost beside the
fill: the effect is dead and it looks like a rendering bug.

What the filled copy *does* add is only paint, never metrics: `background-image`,
`background-size`, `background-position`, `background-clip: text`, and the `clip-path` wipe. If you
find yourself adding anything to the filled rule that is not in that list, it will move the text.

### 3. The fill image comes from the background's own `src`, set in JS.

```js
filled.style.setProperty("--fill", `url("${bg.getAttribute("src")}")`);
```

Two reasons. The letters end up **in register** with what is behind them, so the word reads as a
window cut into the photograph rather than as a word containing a different picture. And there is
one place to change the photo instead of two.

### 4. The wipe finishes early — at 78% of the track.

```js
const w = gsap.utils.clamp(0, 1, p / 0.78);
```

If it completes exactly as the section leaves, the reader never sees the finished word. The payoff
has to land while the sentence is still on screen, and then **hold**. The last fifth of the track
is that hold.

## The light comes up with it

```js
bg.style.opacity = 0.42 + w * 0.3;
```

Brightening the background in step with the wipe is what makes it read as light arriving rather
than as a mask sliding across. It is one line and it changes the whole register of the effect.

## Details

- `padding-block: 0.08em` on the word: at `clamp(3.2rem, 15vw, 13rem)` the glyphs overflow their
  box and clip against the ascenders.
- `aria-label` on the wrapper, `aria-hidden` on both copies — otherwise the word is announced
  twice.
- `-webkit-text-stroke` has no universal standard equivalent. Where it is unsupported the hollow
  copy is simply invisible and the filled one carries the section: degrade, not break.

## Reduced motion

Show the word already filled (`clip-path: none`) and collapse the track. The image inside the
letters is the point and it survives; only the wipe goes.

## Adapting

Change the word (one word, up to nine or ten characters — the wipe needs width to read), the
photograph, the palette, the type. A heavy serif or a fat grotesk both work; a light weight does
not, because there is not enough counter area for the image to show. Keep: two copies, one grid
cell, the fill sourced from the background, the early finish, and the background brightening in
step.
