# Story World Carousel Slider — Click-Advanced Clip-Path Reveal + Animated Hour Timeline

## Goal
Build a **fullscreen, click-advanced photo carousel** on a black page. Every click on the page swaps to the next image with a single cinematic move: the current image **slides left and off** while the next image is **wiped into view by expanding its `clip-path` from a zero-width sliver pinned to the right edge out to the full frame**, its own picture **easing in from the right** at the same time. Layered on top, a horizontal row of **hour labels (1pm → 8pm)** animates its `flex-grow` values so the "active" hour balloons wide while the rest compress — and the whole label strip **recycles itself so both the images and the clock loop forever**. Every tween shares one bespoke `CustomEase` called `"hop"` that gives the motion a soft, slightly overshooting settle. The whole stage sits under a 50% black scrim for an editorial, dusk-lit mood.

## Tech
Vanilla HTML/CSS/JS with ES module imports. Use `gsap` (npm) plus the GSAP plugin **`CustomEase`** (imported from `gsap/CustomEase` and registered). No ScrollTrigger, no SplitText, no smooth-scroll library — the component is **click-driven**, not scroll-driven. No images data module; the five `<img>` are written directly in the HTML.

```js
import gsap from "gsap";
import { CustomEase } from "gsap/CustomEase";
gsap.registerPlugin(CustomEase);
```

## Layout / HTML
A near-empty document — five stacked full-screen slides, a fixed nav and footer, and the hour timeline.

```
nav                         (fixed strip, top)
  a  "Motionprompts"
  a  "( Elite web designs )"

footer                      (fixed strip, bottom)
  p  "Click anywhere to advance the story"
  p  "Motionprompts"

.slider                     (absolute, full-viewport stage)
  .slide #slide1 > img      (initial visible slide)
  .slide #slide2 > img
  .slide #slide3 > img
  .slide #slide4 > img
  .slide #slide5 > img

.timeline                   (absolute, vertically centered, flex row)
  p  1<sup>pm</sup>
  p  2<sup>pm</sup>
  p  3<sup>pm</sup>
  p  4<sup>pm</sup>
  p  5<sup>pm</sup>
  p  6<sup>pm</sup>
  p  7<sup>pm</sup>
  p  8<sup>pm</sup>
```

There are **8** `<p>` hour labels in the HTML (1pm through 8pm), each an integer plus a `<sup>pm</sup>`.

## Styling
- Global reset: `* { margin:0; padding:0; box-sizing:border-box; }`. `html, body { width:100vw; height:100vh; background:#000; overflow:hidden; }` — the page never scrolls.
- Fonts: labels/nav/footer use **"Akkurat Mono"** (any mono fallback, e.g. `"Akkurat Mono", monospace`); the big hour numbers use **"PP Neue Montreal"** weight 500 (fallback a neutral grotesque, e.g. `"PP Neue Montreal", "Neue Montreal", Helvetica, Arial, sans-serif`).
- `a, p`: `text-decoration:none; color:rgba(255,255,255,0.75); font-family:"Akkurat Mono"; font-size:11px; text-transform:uppercase;`.
- `nav, footer`: `position:fixed; width:100%; padding:2em; display:flex; justify-content:space-between; z-index:2;`. `nav{top:0}`, `footer{bottom:0}`.
- `.slider`: `position:absolute; top:0; left:0; width:100%; height:100%; overflow:hidden;`.
- **Scrim** — `.slider::after`: a full-cover `content:""` overlay, `position:absolute; inset:0; background:rgba(0,0,0,0.5);` sitting above the images (darkens every slide equally).
- `.slide`: `position:absolute; top:0; left:0; width:100%; height:100%; display:flex; justify-content:flex-end; align-items:center; overflow:hidden; will-change:transform;`. All five slides are stacked in the same spot; later ones paint on top of earlier ones (DOM order = z-order).
- `img`: `position:absolute; top:0; left:0; width:100%; height:100%; object-fit:cover; will-change:transform;`. Full-bleed cover, so any landscape source fills the screen; the JS translates these on the x-axis.
- `.timeline`: `position:absolute; right:0; top:50%; transform:translateY(-50%); width:105%; z-index:2; display:flex;` — **wider than the viewport and anchored to the right**, so as labels collapse the leftmost ones spill off the left edge of the screen. On `@media (max-width:900px)` bump `width` to `110%`.
- `.timeline p`: `font-family:"PP Neue Montreal"; font-weight:500; font-size:28px; color:#fff; cursor:pointer;`.
- `.timeline p sup`: `position:relative; top:-4px; font-family:"Akkurat Mono"; font-size:11px; text-transform:uppercase;`.

## GSAP effect (exhaustive — this is the whole component)

### CustomEase
Register one ease named **`"hop"`** with exactly this path (a curve that rises steeply, then eases and slightly settles into 1):
```js
CustomEase.create("hop", "M0,0 C0.083,0.294 0.117,0.767 0.413,0.908 0.606,1 0.752,1 1,1");
```
**Every** tween in the component (images, clip-path, and all the flex-grow label tweens) uses `ease: "hop"` and `duration: 1.5`.

### Constants & guards
- `const duration = 1.5;`
- `let animating = false;` — a re-entrancy lock. `handleSlider` returns immediately if `animating`, and only clears it in the clip-path tween's `onComplete`. This means clicks that arrive mid-transition are **ignored** (no queueing).
- Everything runs inside `window.addEventListener("load", …)`.

### Initial state (run once on load)
1. **Slides** — leave slide 1 alone; for every slide at index > 0, `gsap.set(slide, { clipPath: "polygon(100% 0%, 100% 0%, 100% 100%, 100% 100%)" })`. That polygon is a **degenerate zero-width sliver collapsed onto the right edge** (all four vertices at x = 100%), so slides 2–5 are effectively invisible until revealed. Slide 1 stays fully visible.
2. **Timeline flex seed** (`initializeFlexValues()`) — set inline `flexGrow` / `width` on the eight `<p>`:
   - `p[0] → flexGrow:5`, `p[1] → 4`, `p[2] → 3`, `p[3] → 1.5`, `p[4] → 1` (each also `width:"max-content"`).
   - `p[5], p[6], p[7] → flexGrow:0, width:"0px"` (fully collapsed).
   This yields the resting look: **1pm** widest, tapering down to **5pm**, with 6/7/8pm hidden at zero width off to the right.

### Click handler — `document.addEventListener("click", handleSlider)`
On each accepted click (`!animating`), two independent things animate in parallel over the same 1.5s: the **image swap** and the **timeline shift**.

#### A) Image swap
Re-query `slides = slider.querySelectorAll(".slide")`. `firstSlide = slides[0]`, `secondSlide = slides[1]`, and their `img`s. If there is only one slide, just release the lock. Otherwise fire three tweens together (no timeline object — three concurrent `gsap.to` calls):

1. **Incoming image glides in from the right.** `gsap.set(secondSlideImg, { x: 250 })` first, then
   `gsap.to(secondSlideImg, { x: 0, duration: 1.5, ease: "hop" })`.
2. **Outgoing image slides off left.** `gsap.to(firstSlideImg, { x: -500, duration: 1.5, ease: "hop" })`.
3. **Clip-path wipe reveals the incoming slide.**
   `gsap.to(secondSlide, { clipPath: "polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%)", duration: 1.5, ease: "hop", onComplete: … })`.
   The clip animates from the right-edge sliver to the **full rectangle** — its left edge sweeps from x = 100% to x = 0%, uncovering the picture **right-to-left**. Because `secondSlide` is later in the DOM it paints above `firstSlide`, so you see the new image being unveiled over the old one that's sliding away underneath.
   **`onComplete`** (this is the recycle that makes the carousel endless): remove `firstSlide` from the DOM and **re-append it to the end** of `.slider` (`slider.appendChild(firstSlide)`), then `gsap.set(firstSlide, { clipPath: "polygon(100% 0%, 100% 0%, 100% 100%, 100% 100%)" })` to collapse it back to the hidden sliver, and finally `animating = false`. Next click, the old second slide is the new first, and the just-used slide waits at the back — an infinite ring of the same five images. (Note: the img's `x` is not reset, so the recycled slide keeps whatever x it ended on; it only becomes visible again after five more clicks, freshly clip-collapsed.)

#### B) Timeline shift (the animated clock)
Runs every click, in parallel with the image swap, all tweens `duration:1.5, ease:"hop"`.

Query `counters = timeline.querySelectorAll("p")`. Capture `lastFlexGrow = counters[last].style.flexGrow` **before** touching anything.

**Recycle check:** if a label currently carrying the class `.last` has text `"7pm"`, call `appendNewcounters()` (below) to graft a fresh 1→8 pm block on the end and drop the exhausted labels off the front. Then strip `.first` and `.last` classes from all counters.

**The shift** — walk `i` from the **last index down to 1** and, for each, tween its `flexGrow` to the value of its **left neighbour**:
```js
gsap.to(counters[i], {
  flexGrow: counters[i-1].style.flexGrow,
  duration: 1.5, ease: "hop",
  onStart: () => {
    gsap.set(counters[i], { width: counters[i-1].style.flexGrow > 0 ? "max-content" : "0px" });
    if (counters[i-1].style.flexGrow === "5")      { counters[i].classList.add("first"); firstAssigned = true; }
    else if (counters[i-1].style.flexGrow === "1" && !firstAssigned) counters[i].classList.add("last");
  },
});
```
Then wrap the **first** counter around to the captured tail value:
```js
gsap.to(counters[0], {
  flexGrow: lastFlexGrow, duration: 1.5, ease: "hop",
  onStart: () => {
    gsap.set(counters[0], { width: lastFlexGrow > 0 ? "max-content" : "0px" });
    if (lastFlexGrow === "5") counters[0].classList.add("first");
    else if (lastFlexGrow === "1" && !firstAssigned) counters[0].classList.add("last");
  },
});
```
Net effect: the whole flex-grow pattern **[5,4,3,1.5,1,0,0,0] rotates one step to the right** on every click (index 0 receives what fell off the end). Visually the widest "active hour" marches rightward one slot per click, each hour expanding to `max-content` as it grows and snapping to `0px` width as it collapses — a live, elastic clock that reads like a horizontal timeline sliding under a fixed viewport. The `.first` marker tracks the widest (flex 5) label, the `.last` marker the flex-1 label; these class flags drive the recycling.

**`appendNewcounters()`** — keeps the row from running out of labels: find the index of the `.first` counter, remove every counter before it, then append **eight new `<p>` labels numbered 1–8** each with a `<sup>pm</sup>`, `flexGrow:0`, `width:"0px"`. So the strip perpetually sheds spent hours off the left and grows new ones off the right, looping forever in sync with the image ring.

## Assets / images
**5 full-bleed landscape photographs** (screen-aspect ~16:9, but `object-fit:cover` fills any landscape/portrait source). They read as one cohesive **cinematic, editorial, moody** art-direction series (all sit under a 50% black scrim). By role and order:
1. **Slide 1 (initial visible):** black-and-white close-up of a woman's lower face wrapped in translucent tulle/veil fabric.
2. **Slide 2:** moody product still-life — two dark-red wine bottles on draped blue fabric with hard directional shadows.
3. **Slide 3:** a lone backpacker walking a shoreline under an intense orange-and-red duotone sky.
4. **Slide 4:** editorial fashion portrait — a woman with an afro and a dark silk neck scarf on a blue gradient backdrop.
5. **Slide 5:** long-exposure interior of a grand museum's spiral staircase above a busy lobby.

No logos or brand marks in the imagery.

## Behavior notes
- **Trigger is a click anywhere on the document** (`document.addEventListener("click", …)`), including on the hour labels (which are `cursor:pointer`). There is no autoplay, no scroll, no hover.
- **Re-entrancy lock:** clicks during a 1.5s transition are dropped, not queued.
- **Truly infinite** in both the image ring (slides recycled to the DOM tail) and the clock (labels recycled via `appendNewcounters()`), so it never reaches an end state.
- Desktop and mobile both work (single media query only widens the timeline to 110% at ≤900px); no reduced-motion handling in the original.
- Text is neutral/fictional: nav "Motionprompts" / "( Elite web designs )", footer "Click anywhere to advance the story" / "Motionprompts", labels 1pm–8pm.
