# Multiplayer Cursors

Enlarged two-color named cursors move across a canvas, leave comments, and drag items to reorder.

- Position in a launch film: Features
- Slug: multiplayer-cursors
- Preview video: https://media.motionboard.app/multiplayer-cursors.mp4
- Recipe page: https://motionboard.app/recipes/multiplayer-cursors/
- License: MIT. Free to use in your own projects, including commercial launch films. https://github.com/krisyangdev/motionboard/blob/main/LICENSE

## How to use this recipe

You are a coding agent. The user wants this shot for their own product. Follow these steps.

1. **Know the product.** If the product is clear from the user's repo or conversation, ask nothing and start. Otherwise ask once for: product name, logo file, brand colors.
2. **Pick the output.** If the user's message says a video or their website, do that. Otherwise ask once which one they want.
   - **Video (HyperFrames).** Create a folder named `multiplayer-cursors`. Download every file under "Source files" below with the curl commands, keeping relative paths. Adapt `index.html` (see "What to adapt"). Run `npx --yes hyperframes@0.8.209 check` on the untouched copy first and note its result. Preview with `npx --yes hyperframes@0.8.209 preview`, render an MP4 with `npx --yes hyperframes@0.8.209 render` (into `renders/`, named with a timestamp). The same commands are in `package.json` as `dev`, `check`, `render`; pass flags after `--`, as in `npm run render -- --quality draft`. Rendering needs network access for web fonts and GSAP.
   - **Website motion.** Port the GSAP timeline in `index.html` into the user's site, using their stack (React, Vue, Svelte, plain HTML). Build the same DOM structure, keep the timeline's durations, delays and easings exactly, and start it when the element scrolls into view (IntersectionObserver or ScrollTrigger). Respect `prefers-reduced-motion: reduce`: show the final frame as a static state and skip the movement.
3. **What to adapt.**
   - **Brand layer** (product name, logo, colors, fonts, copy): replace directly with the user's.
   - **UI layer** (the product screens, cards, windows, controls shown in the shot): rebuild the user's real UI as clean DOM, from their codebase (components, design tokens) or from a screenshot used as reference only. Never paste a screenshot into the shot.
   - **Motion** (timing, easing, camera moves, layering, stagger, order of events): keep exactly as in the source. This is what the recipe is.
   - The timeline targets UI elements by id and class, and camera moves land on fixed coordinates. Keep the ids the timeline uses, keep row counts and text lengths close to the source, and when the layout moves, update the target coordinates without changing the times.
4. **Fonts.** HyperFrames fetches Google Fonts such as Inter on its own. Some sources use macOS local fonts (PingFang, Songti via `local()`). Use the user's brand font when it can be bundled; for a system font such as SF Pro, use Inter. Check that text still fits its boxes.
5. **Verify.** Compare the render with the preview video above at the beat times in the Timing row. The beats, timing and camera should match; only brand and UI content differ.

## Spec

| Item | Value |
| --- | --- |
| Timing | Film 10.5s; avatars pop at 0.35, 0.8, 1.2, 1.53, 1.8s (0.25s each, spacing 0.45→0.27s); Liam box-select 1.9–2.65s; Maya's bubble 2.5s (0.3s), typing 2.65–4.05s at 15 characters per second, Enter 4.2s; push-in 4.3–4.7s (0.4s, 12 frames); press 4.8s, drag 4.95–5.75s (0.8s), reflow shift 0.4s each, drop 5.85s; pull-back 6.15s (0.6s); Present click 7.55s; lockup 8.75s |
| Easing | Push-in `expo.out` with a 3px blur pulse; drag `power2.inOut` with a 12px arc in y; avatars `back.out(2.2)`; bubble `back.out(1.7)`; reflow `expo.out` 0.4s; press `scale 0.86` over 0.08s, release `back.out(1.7)` 0.22s |
| Camera | Canvas content scrolls up 140px over 4.3s `linear` (about 3% of frame height per second) while the whole scene drifts `scale` 1→1.04 over 8.7s; at 4.3s push in 1.4x onto the slide, cursors counter-scale by 1/1.4 to keep their on-screen size; pull back at 6.15s |
| Subject | Cursors 46×56px (about 5% of frame height), one black `#141414` (Liam Ortiz) and one magenta `#d81283` (Maya Chen), each with a name tag; Liam box-selects three slides, Maya rests on a green slide and a same-color comment bubble types "Can we lead with this?", then she drags slide 12 two columns left to position 10 while slides 11 and 10 shift one column right; the slide numbers and left panel list update on the drop frame; Maya then clicks Present and the slide opens full screen |
| Sync | Enter changes the bubble header from "typing…" to "Just now" on one frame; push-in follows Enter by 0.1s; the drag starts 0.5s after the push-in begins; on the drop frame (5.85s) slide numbers and the panel order change together |
| Style | White window on a `#fafafa` canvas with a 33-slide pitch deck in neon green, yellow, orange, and dark green slides; cursors with a white outline and a soft shadow |

## Why it works

Enlarged cursors become characters in the frame, and the interaction between two cursors stages collaboration. The comment bubble matches its cursor's color, so it maps to a person at a glance. The thumbnail grid provides density, and after the push-in only a small part of it is visible.

## Prompt

> Demonstrate collaboration with a shot called Multiplayer Cursors. A white editor window holds a 33-slide thumbnail grid that scrolls up 140px over 4.3s with linear ease, and avatars pop into the top bar one by one (back.out(2.2), spacing shrinking from 0.45s to 0.27s). Two named cursors, 46×56px (about 5% of frame height), black for Liam and magenta for Maya, move across it: Liam box-selects three slides from 1.9s, Maya rests beside a green slide from 2.45s and a same-color comment bubble pops up (back.out(1.7)) and types at 15 characters per second. After Enter at 4.2s the view pushes in 1.4x over 0.4s with expo.out while the cursors counter-scale to keep their size. Maya presses at 4.8s and drags the slide two columns left to position 10 over 0.8s with power2.inOut, the other two thumbnails shifting a column with expo.out in 0.4s, and the numbers update on the drop frame. Pull back at 6.15s, click Present at 7.55s so the slide opens full screen, and land your wordmark at 8.75s with both cursors meeting on it. Cursor paths include pauses.

## Often confused with

- [Control-Driven Change](https://motionboard.app/r/control-driven-change.md): Control-Driven Change has a single control change the result; Multiplayer Cursors has several cursors acting in parallel on a canvas.
- [Focus Hop](https://motionboard.app/r/focus-hop.md): In Focus Hop the cursor only guides a highlight block; in Multiplayer Cursors the cursors are characters and carry out comments and drags.

## Source files

Runnable HyperFrames project, 3 files.

- https://motionboard.app/source/multiplayer-cursors/hyperframes.json (318 B)
- https://motionboard.app/source/multiplayer-cursors/index.html (59.5 KB)
- https://motionboard.app/source/multiplayer-cursors/package.json (308 B)

Download all of them:

```sh
curl -fsSL --create-dirs -o "multiplayer-cursors/hyperframes.json" "https://motionboard.app/source/multiplayer-cursors/hyperframes.json"
curl -fsSL --create-dirs -o "multiplayer-cursors/index.html" "https://motionboard.app/source/multiplayer-cursors/index.html"
curl -fsSL --create-dirs -o "multiplayer-cursors/package.json" "https://motionboard.app/source/multiplayer-cursors/package.json"
```

## index.html

```html
<!doctype html>
<html lang="en" data-resolution="landscape">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=1920, height=1080" />
    <script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
    <style>
      :root {
        --ink: #18181b;
        --sub: #5f5f69;
        --mut: #9a9aa4;
        --line: #e6e6ea;
        --line2: #f0f0f2;
        --blue: #1f6feb;
        --maya: #d81283;
        --liam: #141414;
        --font-ui: "Inter", -apple-system, "SF Pro Text", "Helvetica Neue", sans-serif;
      }
      * { margin: 0; padding: 0; box-sizing: border-box; }
      html, body { width: 1920px; height: 1080px; overflow: hidden; background: #e7e5e0; }
      #root {
        position: relative; width: 1920px; height: 1080px; overflow: hidden; background: #e7e5e0;
        font-family: var(--font-ui); color: var(--ink); -webkit-font-smoothing: antialiased;
      }
      .full { position: absolute; left: 0; top: 0; width: 1920px; height: 1080px; }
      .a { position: absolute; }
      svg { display: block; }
      .gar { font-family: "EB Garamond", serif; }
      .mono { font-family: "JetBrains Mono", monospace; }

      #wall {
        background:
          radial-gradient(ellipse 60% 70% at 6% 100%, rgba(255, 196, 168, 0.9) 0%, rgba(255, 196, 168, 0) 70%),
          radial-gradient(ellipse 55% 65% at 98% 4%, rgba(184, 232, 196, 0.95) 0%, rgba(184, 232, 196, 0) 70%),
          radial-gradient(ellipse 50% 55% at 62% 110%, rgba(250, 190, 222, 0.7) 0%, rgba(250, 190, 222, 0) 70%),
          linear-gradient(150deg, #ecebe6 0%, #e4e2dc 100%);
      }

      /* ---------- window ---------- */
      #win {
        position: absolute; left: 60px; top: 40px; width: 1800px; height: 1000px;
        border-radius: 12px; overflow: hidden; background: #fff;
        box-shadow: 0 0 0 1px rgba(20, 20, 30, 0.14), 0 2px 6px rgba(20, 20, 30, 0.1), 0 30px 70px rgba(30, 30, 40, 0.22), 0 80px 160px rgba(30, 30, 40, 0.18);
      }
      .tl { position: absolute; top: 18px; width: 12px; height: 12px; border-radius: 50%; }
      .ico { color: #6b6b75; }

      /* top bar */
      #top { position: absolute; left: 0; top: 0; width: 1800px; height: 48px; border-bottom: 1px solid var(--line); background: #fff; }
      .tbl { position: absolute; left: 86px; top: 0; height: 48px; display: flex; align-items: center; gap: 10px; font-size: 13px; white-space: nowrap; }
      .tbl .crumb { color: var(--mut); }
      .tbl .fn { font-weight: 600; color: var(--ink); display: flex; align-items: center; gap: 3px; }
      .tag { height: 20px; padding: 0 7px; border-radius: 5px; background: #f1f1f3; color: var(--sub); font-size: 11px; font-weight: 500; display: flex; align-items: center; }
      .tbc { position: absolute; left: 790px; top: 8px; height: 32px; display: flex; align-items: center; gap: 10px; }
      .seg { display: flex; height: 30px; padding: 2px; border-radius: 7px; background: #f1f1f3; }
      .seg span { width: 34px; height: 26px; border-radius: 5px; display: flex; align-items: center; justify-content: center; color: #7a7a84; }
      .seg span.on { background: #fff; color: var(--ink); box-shadow: 0 1px 2px rgba(0, 0, 0, 0.12); }
      .zm { display: flex; align-items: center; gap: 3px; font-size: 13px; color: var(--sub); font-weight: 500; }
      .tbr { position: absolute; right: 14px; top: 8px; height: 32px; display: flex; align-items: center; gap: 12px; }
      .avs { display: flex; align-items: center; }
      .tav { width: 30px; height: 30px; border-radius: 50%; margin-left: 7px; display: flex; align-items: center; justify-content: center; font-size: 11px; font-weight: 600; color: #fff; box-shadow: 0 0 0 2px #fff; }
      .tav i { position: absolute; }
      .msg { position: relative; width: 32px; height: 32px; display: flex; align-items: center; justify-content: center; }
      .badge { position: absolute; left: 18px; top: 2px; min-width: 16px; height: 16px; border-radius: 8px; background: var(--maya); color: #fff; font-size: 10px; font-weight: 600; display: grid; place-items: center; box-shadow: 0 0 0 2px #fff; }
      .badge span { grid-area: 1 / 1; }
      .btn { height: 32px; display: flex; align-items: center; gap: 7px; padding: 0 13px; border-radius: 7px; font-size: 13px; font-weight: 500; white-space: nowrap; }
      .btn.out { border: 1px solid #dedee3; color: var(--ink); background: #fff; }
      .btn.pri { background: var(--blue); color: #fff; }
      #presBtnHi { position: absolute; left: 0; top: 0; width: 100%; height: 100%; border-radius: 7px; background: rgba(0, 0, 0, 0.06); opacity: 0; }

      /* left panel */
      #left { position: absolute; left: 0; top: 48px; width: 244px; height: 952px; border-right: 1px solid var(--line); background: #fff; overflow: hidden; }
      .ltabs { height: 44px; display: flex; align-items: center; gap: 16px; padding: 0 16px; font-size: 13px; font-weight: 500; color: var(--mut); border-bottom: 1px solid var(--line2); }
      .ltabs b { color: var(--ink); font-weight: 600; }
      .ltabs .r { margin-left: auto; display: flex; gap: 10px; }
      .lsec { height: 30px; display: flex; align-items: center; gap: 4px; padding: 0 12px; font-size: 12px; font-weight: 600; color: var(--ink); }
      .lsec span { margin-left: auto; color: var(--mut); font-weight: 500; }
      .lrow { position: relative; height: 38px; display: flex; align-items: center; gap: 9px; padding: 0 12px 0 10px; font-size: 12.5px; color: #34343a; white-space: nowrap; }
      .lrow .ln { width: 18px; text-align: right; font-size: 11px; color: var(--mut); font-weight: 500; display: grid; }
      .lrow .ln span { grid-area: 1 / 1; }
      .lrow .lt { position: relative; width: 48px; height: 27px; border-radius: 3px; overflow: hidden; box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.1); flex: none; }
      .lrow .lt .sd { transform: scale(0.075); }
      .lrow .lnm { overflow: hidden; text-overflow: ellipsis; }
      .lsel { position: absolute; left: 36px; top: 4.5px; width: 52px; height: 31px; border-radius: 4px; box-shadow: 0 0 0 2px var(--maya); opacity: 0; }

      /* right panel */
      #right { position: absolute; left: 1556px; top: 48px; width: 244px; height: 952px; border-left: 1px solid var(--line); background: #fff; overflow: hidden; font-size: 12px; }
      .rtabs { height: 44px; display: flex; align-items: center; gap: 16px; padding: 0 16px; font-size: 13px; font-weight: 500; color: var(--mut); border-bottom: 1px solid var(--line2); }
      .rtabs b { color: var(--ink); font-weight: 600; }
      .rtabs .r { margin-left: auto; font-size: 12px; color: var(--sub); display: flex; align-items: center; gap: 2px; }
      .rsec { padding: 14px 16px; border-bottom: 1px solid var(--line2); }
      .rh { display: flex; align-items: center; justify-content: space-between; font-size: 12px; font-weight: 600; color: var(--ink); margin-bottom: 10px; }
      .rh .ico { color: #8a8a94; }
      .theme { border: 1px solid var(--line); border-radius: 8px; padding: 10px; }
      .theme .thn { font-size: 12.5px; font-weight: 600; display: flex; justify-content: space-between; }
      .theme .thn span { color: var(--mut); font-weight: 500; }
      .sw { display: flex; gap: 6px; margin-top: 9px; }
      .sw i { width: 22px; height: 22px; border-radius: 50%; box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1); }
      .field { display: flex; align-items: center; gap: 8px; height: 30px; padding: 0 9px; border-radius: 6px; background: #f4f4f6; font-size: 12px; color: var(--ink); }
      .field .chip { width: 14px; height: 14px; border-radius: 3px; box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.12); }
      .field .r { margin-left: auto; color: var(--mut); }
      .ts { display: flex; align-items: center; gap: 10px; height: 30px; font-size: 12px; }
      .ts b { width: 26px; height: 22px; border-radius: 4px; background: #f4f4f6; display: grid; place-items: center; font-size: 11px; font-weight: 600; }
      .ts span { color: var(--mut); margin-left: auto; }
      .row2 { display: flex; gap: 8px; }
      .tog { width: 28px; height: 16px; border-radius: 8px; background: var(--blue); position: relative; }
      .tog i { position: absolute; right: 2px; top: 2px; width: 12px; height: 12px; border-radius: 50%; background: #fff; }
      .rfoot { position: absolute; left: 16px; right: 16px; bottom: 14px; display: flex; align-items: center; justify-content: space-between; font-size: 11.5px; color: var(--mut); }
      .rfoot .dot { width: 7px; height: 7px; border-radius: 50%; background: #2fb36b; display: inline-block; margin-right: 6px; }

      /* canvas */
      #canvas { position: absolute; left: 244px; top: 48px; width: 1312px; height: 952px; overflow: hidden; background: #fafafa; }
      #content { position: absolute; left: 0; top: 0; width: 1312px; height: 1200px; }
      .swr { position: absolute; width: 184px; height: 126px; }
      .sn { position: absolute; left: 0; top: 0; height: 16px; font-size: 11.5px; font-weight: 500; color: #8c8c96; display: grid; }
      .sn span { grid-area: 1 / 1; }
      .th { position: absolute; left: 0; top: 22px; width: 184px; height: 103.5px; border-radius: 4px; overflow: hidden; box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.08), 0 1px 2px rgba(0, 0, 0, 0.05); }
      .th .sd { transform: scale(0.2875); }
      .sd { position: absolute; left: 0; top: 0; width: 640px; height: 360px; overflow: hidden; transform-origin: 0 0; font-family: var(--font-ui); color: #151515; }
      .so { position: absolute; left: 0; top: 22px; width: 184px; height: 103.5px; border-radius: 4px; box-shadow: 0 0 0 2.5px var(--maya); opacity: 0; }
      #sw12 { z-index: 5; }
      #lift { position: absolute; left: 0; top: 22px; width: 184px; height: 103.5px; border-radius: 4px; opacity: 0; box-shadow: 0 18px 36px rgba(20, 20, 30, 0.28), 0 4px 10px rgba(20, 20, 30, 0.14); }
      .selL { position: absolute; width: 184px; height: 103.5px; border-radius: 4px; box-shadow: 0 0 0 2.5px var(--liam); opacity: 0; }
      #mq { position: absolute; left: 446px; top: 474px; width: 0; height: 0; border: 1px solid rgba(20, 20, 20, 0.7); background: rgba(20, 20, 20, 0.06); opacity: 0; }

      .pin { position: absolute; width: 30px; height: 30px; border-radius: 50% 50% 50% 0; background: #fff; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.22); display: grid; place-items: center; z-index: 3; }
      .pin span { width: 24px; height: 24px; border-radius: 50%; display: grid; place-items: center; color: #fff; font-size: 9.5px; font-weight: 600; }
      .thread { position: absolute; width: 204px; border-radius: 10px; background: #fff; box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.07), 0 8px 24px rgba(20, 20, 30, 0.14); padding: 11px 12px 10px; z-index: 3; font-size: 11.5px; }
      .thread .hd { display: flex; align-items: center; gap: 7px; white-space: nowrap; }
      .thread .hd b { font-weight: 600; font-size: 12px; }
      .thread .hd .t { color: var(--mut); }
      .thread .hd .ck { margin-left: auto; color: #8a8a94; }
      .thread .bd { margin-top: 6px; line-height: 16px; color: #2a2a30; }
      .thread .ft { margin-top: 8px; display: flex; align-items: center; gap: 6px; color: var(--blue); font-weight: 500; }
      .av { display: inline-flex; align-items: center; justify-content: center; border-radius: 50%; color: #fff; font-weight: 600; flex: none; }

      #dock { position: absolute; left: 448px; top: 884px; height: 48px; padding: 0 8px; display: flex; align-items: center; gap: 4px; border-radius: 12px; background: #fff; box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.08), 0 6px 20px rgba(20, 20, 30, 0.12); z-index: 6; }
      #dock .t { width: 34px; height: 34px; border-radius: 8px; display: grid; place-items: center; color: #3a3a42; }
      #dock .t.on { background: var(--blue); color: #fff; }
      #dock .sep { width: 1px; height: 22px; background: var(--line); margin: 0 4px; }
      #dock .ns { height: 34px; display: flex; align-items: center; gap: 6px; padding: 0 10px; font-size: 13px; font-weight: 500; color: #3a3a42; white-space: nowrap; }

      /* cursors */
      #curLayer { position: absolute; left: 244px; top: 48px; width: 0; height: 0; z-index: 20; }
      .cur { position: absolute; left: 0; top: 0; width: 0; height: 0; opacity: 0; }
      .ci { position: absolute; left: -4px; top: -3px; width: 46px; height: 56px; }
      .ci svg { width: 46px; height: 56px; filter: drop-shadow(0 3px 5px rgba(0, 0, 0, 0.28)); }
      .nm { position: absolute; left: 30px; top: 46px; height: 30px; padding: 0 11px; border-radius: 8px; color: #fff; font-size: 16px; font-weight: 600; line-height: 30px; white-space: nowrap; box-shadow: 0 0 0 2px #fff, 0 4px 10px rgba(0, 0, 0, 0.18); }
      #nmM { background: var(--maya); }
      #nmL { background: var(--liam); }
      #bub {
        position: absolute; left: 26px; top: 44px; display: flex; align-items: flex-start; gap: 10px; padding: 9px 16px 10px 9px;
        border-radius: 6px 20px 20px 20px; background: var(--maya); color: #fff; white-space: nowrap; opacity: 0;
        box-shadow: 0 0 0 2px #fff, 0 10px 24px rgba(160, 10, 90, 0.3);
      }
      #bub .av { width: 30px; height: 30px; font-size: 11px; background: #fff; color: var(--maya); margin-top: 2px; }
      #bub .bh { display: flex; align-items: center; gap: 6px; font-size: 12px; line-height: 16px; }
      #bub .bh b { font-weight: 600; }
      #bub .bh .st { display: grid; opacity: 0.8; }
      #bub .bh .st span { grid-area: 1 / 1; }
      #bNow { opacity: 0; }
      #bx { font-size: 17px; line-height: 24px; font-weight: 500; min-height: 24px; }
      #bx .c { display: none; }
      #car { display: inline-block; width: 2px; height: 18px; margin-left: 1px; vertical-align: -3px; background: #fff; }

      /* presentation */
      #presIn { position: absolute; left: 0; top: 0; width: 1920px; height: 1080px; overflow: hidden; opacity: 0; }
      #presZ { position: absolute; left: 0; top: 0; width: 1920px; height: 1080px; }
      #presZ .sd { transform: scale(3); }
      #presBar { position: absolute; left: 852px; top: 994px; height: 40px; padding: 0 6px; border-radius: 10px; background: rgba(20, 30, 22, 0.78); color: #fff; display: flex; align-items: center; gap: 4px; font-size: 13px; font-weight: 500; opacity: 0; white-space: nowrap; }
      #presBar .t { width: 30px; height: 30px; display: grid; place-items: center; color: rgba(255, 255, 255, 0.8); }
      #presBar .n { padding: 0 8px; }

      /* finish */
      #vig { pointer-events: none; background: radial-gradient(ellipse at 50% 48%, rgba(20, 20, 30, 0) 62%, rgba(20, 20, 30, 0.16) 100%); }
      #end { opacity: 0; background: #fafaf8; }
      #lockup { position: absolute; left: 0; top: 0; width: 1920px; height: 1080px; }
      #mark { position: absolute; left: 540px; top: 400px; width: 168px; height: 168px; }
      #wm { position: absolute; left: 742px; top: 382px; font-size: 180px; line-height: 200px; font-weight: 600; letter-spacing: -0.055em; color: #141414; white-space: nowrap; }
      #wsub { position: absolute; left: 0; width: 1920px; top: 640px; text-align: center; font-size: 30px; line-height: 40px; font-weight: 500; color: #85858f; letter-spacing: -0.01em; }
      .ecur { position: absolute; left: 0; top: 0; width: 0; height: 0; opacity: 0; }
    </style>
  </head>
  <body>
    <div id="root" data-composition-id="main" data-start="0" data-duration="10.5" data-width="1920" data-height="1080">
      <div id="scene" class="clip full" data-start="0" data-duration="10.5" data-track-index="0">
        <div id="wall" class="full"></div>
        <div id="drift" class="full" data-layout-allow-overlap>
          <div id="cam" class="full" data-layout-allow-overlap>
            <div id="win" data-layout-allow-overlap>
              <div id="top" data-layout-allow-overlap data-layout-ignore></div>
              <div id="left" data-layout-ignore></div>
              <div id="right" data-layout-ignore></div>
              <div id="canvas" data-layout-allow-overlap data-layout-allow-overflow data-layout-ignore>
                <div id="content" data-layout-allow-overlap></div>
                <div id="dock"></div>
              </div>
              <div id="curLayer" data-layout-allow-overlap>
                <div id="curL" class="cur" data-layout-allow-overlap>
                  <div id="ciL" class="ci"><svg viewBox="0 0 23 28"><path d="M3 2.2 L3 23.2 L8.4 18.2 L11.9 25.8 L15.6 24.2 L12.2 16.8 L19.4 16.6 Z" fill="#141414" stroke="#fff" stroke-width="1.6" stroke-linejoin="round"/></svg></div>
                  <div id="nmL" class="nm">Liam Ortiz</div>
                </div>
                <div id="curM" class="cur" data-layout-allow-overlap>
                  <div id="ciM" class="ci"><svg viewBox="0 0 23 28"><path d="M3 2.2 L3 23.2 L8.4 18.2 L11.9 25.8 L15.6 24.2 L12.2 16.8 L19.4 16.6 Z" fill="#d81283" stroke="#fff" stroke-width="1.6" stroke-linejoin="round"/></svg></div>
                  <div id="nmM" class="nm">Maya Chen</div>
                  <div id="bub">
                    <span class="av">MC</span>
                    <div>
                      <div class="bh"><b>Maya Chen</b><span class="st"><span id="bTy">typing…</span><span id="bNow">Just now</span></span></div>
                      <div id="bx"></div>
                    </div>
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>

      <div id="pres" class="clip full" data-start="0" data-duration="10.5" data-track-index="1" data-layout-allow-overlap>
        <div id="presIn"><div id="presZ"></div></div>
        <div id="presBar"></div>
      </div>

      <div id="vig" class="clip full" data-start="0" data-duration="10.5" data-track-index="2"></div>

      <div id="end" class="clip full" data-start="0" data-duration="10.5" data-track-index="3" data-layout-allow-overlap>
        <div id="lockup" data-layout-allow-overlap>
          <div id="mark"></div>
          <div id="wm">Juniper</div>
          <div id="wsub">The multiplayer canvas for slides, design and whiteboards</div>
          <div id="eL" class="ecur" data-layout-allow-overlap></div>
          <div id="eM" class="ecur" data-layout-allow-overlap></div>
        </div>
      </div>
    </div>

    <script>
      const EIO = "power2.inOut";
      const EXPO = "expo.out";
      const BACK = "back.out(1.7)";
      const POP = "back.out(2.2)";

      /* ---------- icons (lucide-style, 1.5px strokes) ---------- */
      const ICON = {
        chD: '<path d="m6 9 6 6 6-6"/>',
        chL: '<path d="m15 18-6-6 6-6"/>',
        chR: '<path d="m9 18 6-6-6-6"/>',
        play: '<path d="M7 4.5v15l12-7.5z"/>',
        msg: '<path d="M7.9 20A9 9 0 1 0 4 16.1L2 22Z"/>',
        search: '<circle cx="11" cy="11" r="7"/><path d="m21 21-4.3-4.3"/>',
        plus: '<path d="M5 12h14"/><path d="M12 5v14"/>',
        minus: '<path d="M5 12h14"/>',
        ptr: '<path d="M4.04 4.04a.5.5 0 0 1 .62-.62l15.5 5.5a.5.5 0 0 1-.03.95l-6.35 1.9a2 2 0 0 0-1.33 1.33l-1.9 6.35a.5.5 0 0 1-.95.03Z"/>',
        frame: '<path d="M22 6H2"/><path d="M22 18H2"/><path d="M6 2v20"/><path d="M18 2v20"/>',
        type: '<path d="M4 7V4h16v3"/><path d="M9 20h6"/><path d="M12 4v16"/>',
        square: '<rect x="3" y="3" width="18" height="18" rx="2"/>',
        image: '<rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="9" cy="9" r="2"/><path d="m21 15-3.1-3.1a2 2 0 0 0-2.8 0L6 21"/>',
        pen: '<path d="M12 20h9"/><path d="M16.4 3.6a2.1 2.1 0 1 1 3 3L7 19l-4 1 1-4Z"/>',
        grid: '<rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/>',
        single: '<rect x="2" y="5" width="20" height="14" rx="2"/>',
        chart: '<path d="M18 20V10"/><path d="M12 20V4"/><path d="M6 20v-6"/>',
        table: '<rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18"/><path d="M3 15h18"/><path d="M9 3v18"/>',
        more: '<circle cx="12" cy="12" r="1"/><circle cx="19" cy="12" r="1"/><circle cx="5" cy="12" r="1"/>',
        check: '<path d="M20 6 9 17l-5-5"/>',
        checkC: '<circle cx="12" cy="12" r="9"/><path d="m8.5 12.5 2.5 2.5 4.5-5"/>',
        link: '<path d="M10 13a5 5 0 0 0 7.5.5l3-3a5 5 0 0 0-7-7l-1.7 1.7"/><path d="M14 11a5 5 0 0 0-7.5-.5l-3 3a5 5 0 0 0 7 7l1.7-1.7"/>',
        sliders: '<path d="M20 7h-9"/><path d="M14 17H5"/><circle cx="17" cy="17" r="3"/><circle cx="7" cy="7" r="3"/>',
        undo: '<path d="M9 14 4 9l5-5"/><path d="M4 9h10.5a5.5 5.5 0 0 1 0 11H11"/>',
        x: '<path d="M18 6 6 18"/><path d="m6 6 12 12"/>',
        sticky: '<path d="M15.5 3H5a2 2 0 0 0-2 2v14c0 1.1.9 2 2 2h14a2 2 0 0 0 2-2V8.5L15.5 3Z"/><path d="M15 3v6h6"/>',
        layout: '<rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18"/><path d="M9 21V9"/>',
        mic: '<rect x="9" y="2" width="6" height="12" rx="3"/><path d="M19 10v1a7 7 0 0 1-14 0v-1"/><path d="M12 18v4"/>',
        max: '<path d="M8 3H5a2 2 0 0 0-2 2v3"/><path d="M21 8V5a2 2 0 0 0-2-2h-3"/><path d="M3 16v3a2 2 0 0 0 2 2h3"/><path d="M16 21h3a2 2 0 0 0 2-2v-3"/>'
      };
      const ic = (n, s, sw, st) =>
        '<svg width="' + s + '" height="' + s + '" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="' + (sw || 1.5) +
        '" stroke-linecap="round" stroke-linejoin="round"' + (st ? ' style="' + st + '"' : "") + ">" + ICON[n] + "</svg>";

      const P = {
        maya: ["Maya Chen", "MC", "#d81283"],
        liam: ["Liam Ortiz", "LO", "#141414"],
        priya: ["Priya Nair", "PN", "#7c5cf0"],
        dana: ["Dana Brooks", "DB", "#ef8a32"],
        omar: ["Omar Haddad", "OH", "#1f9e8f"],
        alex: ["Alex Rivera", "AR", "#3b82f6"]
      };
      const av = (k, s, f) => '<span class="av" style="width:' + s + "px;height:" + s + "px;font-size:" + f + "px;background:" + P[k][2] + '">' + P[k][1] + "</span>";

      /* ---------- slide designs (640 x 360 design space) ---------- */
      const CRM = "#f3efe6", INK = "#151515", GR = "#6d685e", LN = "#d9d3c5", FG = "#3ce27c", DG = "#1f4d3a", YL = "#f4ea4c", OR = "#ff7a3d", BK = "#161616";
      const A = (x, y, css, html) => {
        let cls = "";
        if (css.indexOf("'EB Garamond'") >= 0) { cls = ' class="gar"'; css = css.replace(/font-family:'EB Garamond';?/, ""); }
        if (css.indexOf("'JetBrains Mono'") >= 0) { cls = ' class="mono"'; css = css.replace(/font-family:'JetBrains Mono';?/, ""); }
        return "<div" + cls + ' style="position:absolute;left:' + x + "px;top:" + y + "px;" + css + '">' + (html || "") + "</div>";
      };
      const foot = (c) => A(40, 330, "font-size:10px;color:" + c + ";opacity:.65;white-space:nowrap", "Halcyon · Launch Keynote") + A(500, 330, "width:100px;text-align:right;font-size:10px;color:" + c + ";opacity:.65", "Confidential");
      const logoDots = (x, y, s) => A(x, y, "width:" + s * 2.2 + "px;height:" + s + "px", '<svg width="' + s * 2.2 + '" height="' + s + '" viewBox="0 0 44 20"><circle cx="10" cy="10" r="9" fill="' + DG + '"/><circle cx="24" cy="10" r="6" fill="' + FG + '"/><circle cx="36" cy="10" r="4" fill="' + OR + '"/></svg>');
      const H = (x, y, size, w, color, txt, extra) => A(x, y, "font-size:" + size + "px;font-weight:600;letter-spacing:-0.03em;line-height:1.08;color:" + (color || INK) + ";width:" + w + "px;" + (extra || ""), txt);
      const T = (x, y, size, color, txt, extra) => A(x, y, "font-size:" + size + "px;color:" + color + ";line-height:1.35;white-space:nowrap;" + (extra || ""), txt);
      const sdv = (bg, inner) => '<div class="sd" style="background:' + bg + '">' + inner + "</div>";

      const S = {};
      S.title = () => sdv(CRM,
        A(410, -70, "width:300px;height:300px;border-radius:50%;background:" + FG) +
        A(486, 172, "width:150px;height:150px;border-radius:50%;border:26px solid " + DG) +
        A(590, 120, "width:44px;height:44px;border-radius:50%;background:" + OR) +
        logoDots(40, 40, 18) +
        A(36, 112, "font-size:100px;font-weight:600;letter-spacing:-0.05em;line-height:1;color:" + INK, "Halcyon") +
        T(42, 222, 24, "#4a463f", "Launch Keynote", "font-weight:500;letter-spacing:-0.01em") +
        A(42, 308, "display:flex;gap:28px;font-size:13px;color:" + GR + ";white-space:nowrap", "<span>Nov 12, 2026</span><span>Moscone West, SF</span><span>Maya Chen, CEO</span>"));
      S.agenda = () => {
        const it = [["The problem", "Maya Chen"], ["Introducing Halcyon", "Liam Ortiz"], ["Traction", "Dana Brooks"], ["The ask", "Maya Chen"], ["Q&A", "All"]];
        let h = H(40, 38, 34, 400, INK, "Agenda");
        it.forEach((r, i) => {
          const y = 106 + i * 46;
          h += A(40, y + 34, "width:560px;height:1px;background:" + LN);
          h += T(40, y + 6, 14, GR, "0" + (i + 1), "font-family:'JetBrains Mono';");
          h += T(92, y + 2, 20, INK, r[0], "font-weight:500;letter-spacing:-0.01em");
          h += A(420, y + 6, "width:180px;text-align:right;font-size:14px;color:" + GR, r[1]);
        });
        return sdv(CRM, h);
      };
      S.div = (num, title, bg) => sdv(bg || DG,
        T(40, 40, 15, "#9fe3b8", num, "font-family:'JetBrains Mono';font-weight:500") +
        A(500, 36, "width:100px;height:100px", '<svg width="100" height="100" viewBox="0 0 100 100"><circle cx="50" cy="50" r="38" fill="none" stroke="' + FG + '" stroke-width="10" stroke-dasharray="180 400" transform="rotate(-90 50 50)"/></svg>') +
        H(40, 226, 58, 560, "#fff", title, "letter-spacing:-0.04em") + foot("#fff"));
      S.quote = (bg, q, who, role, ini, avc) => sdv(bg,
        A(40, 34, "font-family:'EB Garamond';font-size:38px;line-height:1.1;letter-spacing:-0.01em;color:" + INK + ";width:556px", q) +
        A(40, 286, "display:flex;align-items:center;gap:12px;white-space:nowrap",
          '<span style="width:36px;height:36px;border-radius:50%;background:' + avc + ';color:#fff;display:grid;place-items:center;font-size:13px;font-weight:600">' + ini + "</span>" +
          '<span><b style="display:block;font-size:14px;font-weight:600">' + who + '</b><span style="font-size:12.5px;color:#4b4a3c">' + role + "</span></span>"));
      S.bars = (bg, title, vals, labs, bc, fmt) => {
        let h = H(40, 36, 28, 520, INK, title);
        const mx = Math.max.apply(null, vals);
        h += A(40, 314, "width:560px;height:1px;background:" + LN);
        vals.forEach((v, i) => {
          const x = 48 + i * 112, hh = Math.round(v / mx * 140);
          h += A(x, 314 - hh, "width:72px;height:" + hh + "px;border-radius:4px 4px 0 0;background:" + (i === vals.length - 1 ? bc : bc + "88"));
          h += A(x, 314 - hh - 22, "width:72px;text-align:center;font-size:13px;font-weight:600", fmt(v));
          h += A(x, 322, "width:72px;text-align:center;font-size:12px;color:" + GR, labs[i]);
        });
        return sdv(bg, h);
      };
      S.stats = (bg, fg, title, items, boxbg) => {
        let h = H(40, 38, 28, 540, fg, title);
        items.forEach((it, i) => {
          const x = 40 + i * 192;
          h += A(x, 128, "width:176px;height:168px;border-radius:12px;background:" + boxbg);
          h += A(x + 18, 150, "font-size:52px;font-weight:600;letter-spacing:-0.045em;line-height:1;color:" + fg, it[0]);
          h += A(x + 18, 232, "width:140px;font-size:14px;line-height:1.3;color:" + fg + ";opacity:.8", it[1]);
        });
        return sdv(bg, h + foot(fg));
      };
      S.list = (bg, fg, title, items, two, mark) => {
        let h = H(40, 38, 30, 540, fg, title);
        items.forEach((it, i) => {
          const col = two && i >= Math.ceil(items.length / 2) ? 1 : 0;
          const r = col ? i - Math.ceil(items.length / 2) : i;
          const x = 40 + col * 290, y = 118 + r * 42;
          h += A(x, y + 2, "width:20px;height:20px;border-radius:50%;background:" + (mark || DG) + ";display:grid;place-items:center",
            '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="' + (bg === DG ? DG : "#fff") + '" stroke-width="3.4" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5"/></svg>');
          h += T(x + 32, y, 17, fg, it, "letter-spacing:-0.005em");
        });
        return sdv(bg, h + foot(fg));
      };
      S.ui = () => {
        let h = T(40, 62, 13, FG, "Product", "font-weight:600") + H(40, 86, 32, 250, "#fff", "Instant payouts for every worker") +
          A(40, 214, "width:240px;font-size:14px;line-height:1.4;color:#a8a8a8", "Workers tap once. Money lands in their bank in under two minutes.");
        h += A(316, 30, "width:290px;height:300px;border-radius:14px;background:#222224;box-shadow:inset 0 0 0 1px #333");
        h += T(334, 48, 15, "#fff", "Payouts", "font-weight:600") + A(536, 46, "padding:2px 9px;border-radius:10px;background:#2c3a30;color:" + FG + ";font-size:11px;font-weight:600", "Live");
        const rows = [["Dana R.", "#ef8a32", "$1,240.00", "0:52"], ["Marcus T.", "#3b82f6", "$386.40", "1:04"], ["Aisha K.", "#7c5cf0", "$912.75", "0:47"], ["Leo M.", "#1f9e8f", "$254.10", "1:21"], ["Sofia P.", "#d81283", "$1,018.00", "0:58"]];
        rows.forEach((r, i) => {
          const y = 84 + i * 48;
          h += A(334, y + 40, "width:254px;height:1px;background:#303033");
          h += A(334, y + 4, "width:28px;height:28px;border-radius:50%;background:" + r[1]);
          h += T(372, y, 13.5, "#fff", r[0], "font-weight:500") + T(372, y + 18, 11.5, "#8d8d93", "Sent · " + r[3]);
          h += A(470, y + 1, "width:118px;text-align:right;font-size:13.5px;font-weight:600;color:#fff", r[2]);
          h += A(470, y + 19, "width:118px;text-align:right;font-size:11px;color:" + FG, "Instant");
        });
        return sdv(BK, h);
      };
      S.steps = () => {
        let h = H(40, 38, 30, 540, INK, "How it works");
        [["Connect payroll", "Sync shifts and earnings from 40+ payroll systems."], ["Verify in 30 seconds", "Bank and identity checks run in the background."], ["Pay out instantly", "RTP, FedNow or push-to-card, routed per bank."]].forEach((s, i) => {
          const x = 40 + i * 192;
          h += A(x, 128, "width:44px;height:44px;border-radius:50%;background:" + DG + ";color:#fff;display:grid;place-items:center;font-size:18px;font-weight:600", String(i + 1));
          h += A(x, 192, "width:170px;font-size:18px;font-weight:600;letter-spacing:-0.01em;line-height:1.2", s[0]);
          h += A(x, 244, "width:166px;font-size:13px;line-height:1.4;color:" + GR, s[1]);
        });
        return sdv(CRM, h + foot(INK));
      };
      S.pricing = () => {
        let h = H(40, 36, 28, 540, INK, "Simple, per-payout pricing");
        [["Starter", "0.8%", "per payout", ["Up to 500 workers", "RTP + push-to-card", "Email support"]], ["Growth", "0.6%", "per payout", ["Unlimited workers", "FedNow routing", "Slack support"]], ["Enterprise", "Custom", "volume pricing", ["Dedicated BIN", "SOC 2 reports", "24/7 on-call"]]].forEach((p, i) => {
          const x = 40 + i * 192, hi = i === 1;
          h += A(x, 104, "width:176px;height:222px;border-radius:12px;background:" + (hi ? DG : "#fff") + ";box-shadow:inset 0 0 0 1px " + (hi ? DG : LN));
          h += T(x + 16, 120, 14, hi ? FG : GR, p[0], "font-weight:600");
          h += A(x + 16, 146, "font-size:40px;font-weight:600;letter-spacing:-0.04em;line-height:1;color:" + (hi ? "#fff" : INK), p[1]);
          h += T(x + 16, 192, 12, hi ? "#b9d6c6" : GR, p[2]);
          p[3].forEach((f, j) => { h += T(x + 16, 228 + j * 26, 12.5, hi ? "#fff" : INK, "✓&nbsp; " + f); });
        });
        return sdv(CRM, h);
      };
      S.logos = (title, names, sub) => {
        let h = H(40, 36, 28, 540, INK, title) + T(40, 76, 14, GR, sub);
        const st = ["font-weight:700;letter-spacing:-0.03em", "font-weight:500;font-style:italic;font-family:'EB Garamond';font-size:30px", "font-weight:800;letter-spacing:0.14em;font-size:17px", "font-weight:600;font-family:'JetBrains Mono';font-size:19px", "font-weight:400;letter-spacing:-0.02em", "font-weight:700;font-size:20px;letter-spacing:0.02em"];
        names.forEach((n, i) => {
          const x = 40 + (i % 3) * 190, y = 128 + Math.floor(i / 3) * 96;
          h += A(x, y, "width:178px;height:84px;border-radius:10px;background:#fff;box-shadow:inset 0 0 0 1px " + LN + ";display:flex;align-items:center;justify-content:center;font-size:24px;color:#222;white-space:nowrap;" + st[i], n);
        });
        return sdv(CRM, h);
      };
      S.big = () => sdv(FG,
        T(40, 40, 14, "#0d3b22", "Product", "font-weight:600") +
        A(40, 78, "font-size:70px;font-weight:600;letter-spacing:-0.05em;line-height:0.98;color:#0b2416;width:420px", "Payouts in 90&nbsp;seconds.") +
        A(470, 38, "width:130px;height:130px", '<svg width="130" height="130" viewBox="0 0 130 130"><circle cx="65" cy="65" r="52" fill="none" stroke="#0b2416" stroke-opacity=".15" stroke-width="12"/><circle cx="65" cy="65" r="52" fill="none" stroke="#0b2416" stroke-width="12" stroke-linecap="round" stroke-dasharray="245 400" transform="rotate(-90 65 65)"/></svg>') +
        A(470, 84, "width:130px;text-align:center;font-size:28px;font-weight:600;letter-spacing:-0.03em;color:#0b2416", "90s") +
        A(40, 262, "width:330px;font-size:14px;line-height:1.4;color:#0d3b22", "Median time from tap to bank account across 3.2K employers, October 2026."));
      S.ask = () => sdv(OR,
        T(40, 40, 14, "#4a1c05", "The ask", "font-weight:600") +
        A(40, 80, "font-size:66px;font-weight:600;letter-spacing:-0.05em;line-height:1;color:#1a0b03;width:560px", "$40M Series B") +
        A(40, 164, "display:flex;gap:12px", ["18 months runway", "3 new markets", "120 hires"].map((s) => '<span style="padding:6px 12px;border-radius:16px;background:rgba(255,255,255,.3);font-size:13px;font-weight:500;color:#2a1004;white-space:nowrap">' + s + "</span>").join("")) +
        foot("#2a1004"));
      S.line = () => {
        const pts = [22, 30, 34, 41, 47, 58, 66, 79, 92, 110];
        const xs = pts.map((v, i) => 40 + i * 62), ys = pts.map((v) => 312 - v * 1.1);
        const pl = xs.map((x, i) => x + "," + ys[i]).join(" ");
        let h = H(40, 36, 26, 400, INK, "Monthly payout volume") +
          A(40, 78, "font-size:46px;font-weight:600;letter-spacing:-0.04em;color:" + INK, "$412M") +
          A(208, 96, "padding:3px 9px;border-radius:10px;background:" + FG + ";font-size:12px;font-weight:600;color:#0b2416", "+18% MoM");
        h += A(0, 0, "width:640px;height:360px", '<svg width="640" height="360"><polygon points="40,320 ' + pl + ' 598,320" fill="' + DG + '" fill-opacity=".12"/><polyline points="' + pl + '" fill="none" stroke="' + DG + '" stroke-width="3"/><circle cx="' + xs[9] + '" cy="' + ys[9] + '" r="6" fill="' + DG + '"/></svg>');
        ["Jan", "Mar", "May", "Jul", "Sep", "Oct"].forEach((m, i) => { h += T(36 + [0, 2, 4, 6, 8, 9][i] * 62, 326, 11, GR, m); });
        return sdv(CRM, h);
      };
      S.team = () => {
        let h = H(40, 36, 30, 540, INK, "Leadership");
        [["maya", "CEO", "ex-Stripe"], ["liam", "CTO", "ex-Plaid"], ["priya", "Head of Design", "ex-Linear"], ["dana", "COO", "ex-DoorDash"]].forEach((p, i) => {
          const x = 40 + i * 146;
          h += A(x, 112, "width:96px;height:96px;border-radius:50%;background:" + P[p[0]][2] + ";color:#fff;display:grid;place-items:center;font-size:30px;font-weight:600", P[p[0]][1]);
          h += T(x, 224, 15, INK, P[p[0]][0], "font-weight:600") + T(x, 246, 13, GR, p[1]) + T(x, 266, 12, "#9a9488", p[2]);
        });
        return sdv(CRM, h + foot(INK));
      };
      S.road = () => {
        let h = H(40, 36, 30, 540, INK, "2027 roadmap");
        const c = [["Q1", ["EU launch", "FedNow GA"], FG], ["Q2", ["Tax forms", "Earned wage access"], YL], ["Q3", ["Halcyon Card", "Mexico"], OR], ["Q4", ["Treasury API", "APAC pilot"], "#b7d3c4"]];
        c.forEach((q, i) => {
          const x = 40 + i * 142;
          h += A(x, 110, "width:132px;height:3px;background:" + INK);
          h += T(x, 124, 15, INK, q[0], "font-weight:600");
          q[1].forEach((s, j) => { h += A(x, 160 + j * 46, "width:132px;height:36px;border-radius:8px;background:" + q[2] + ";font-size:12.5px;font-weight:500;line-height:36px;padding-left:10px;white-space:nowrap", s); });
        });
        return sdv(CRM, h + foot(INK));
      };
      S.thanks = () => sdv(BK,
        logoDots(40, 40, 16) +
        A(40, 132, "font-size:64px;font-weight:600;letter-spacing:-0.045em;color:#fff", "Thank you.") +
        T(42, 222, 16, "#9a9a9a", "maya@halcyon.com") + T(42, 246, 16, "#9a9a9a", "halcyon.com/keynote"));
      S.table = (title, cols, rows) => {
        let h = H(40, 36, 26, 540, INK, title);
        const w = 560 / cols.length;
        cols.forEach((c, i) => { h += A(40 + i * w, 98, "width:" + w + "px;font-size:12px;font-weight:600;color:" + GR + (i ? ";text-align:right" : ""), c); });
        h += A(40, 120, "width:560px;height:1px;background:" + INK);
        rows.forEach((r, j) => {
          const y = 130 + j * 34;
          r.forEach((c, i) => { h += A(40 + i * w, y + 6, "width:" + w + "px;font-size:14px;white-space:nowrap;" + (i ? "text-align:right;font-family:'JetBrains Mono';font-size:13px" : "font-weight:500"), c); });
          h += A(40, y + 33, "width:560px;height:1px;background:" + LN);
        });
        return sdv(CRM, h);
      };

      const SL = {
        1: ["Title", S.title],
        2: ["Agenda", S.agenda],
        3: ["The problem", () => S.div("01", "The problem")],
        4: ["Customer quote · Parcel", () => S.quote(YL, "“Our drivers wait three to five days to get paid. Half of them quit within 90 days.”", "Dana Ruiz", "VP Operations, Parcel", "DR", "#1f4d3a")],
        5: ["Cost of payout delays", () => S.bars(CRM, "Payout delays cost gig workers $2.1B a year", [0.9, 1.2, 1.4, 1.7, 2.1], ["2022", "2023", "2024", "2025", "2026"], DG, (v) => "$" + v + "B")],
        6: ["Payday loans", () => S.stats(OR, "#1d0c03", "Workers borrow to bridge the gap", [["62%", "use payday loans between paychecks"], ["$318", "average loan per pay cycle"], ["4.2d", "average wait for earned pay"]], "rgba(255,255,255,.28)")],
        7: ["Why now", () => S.list(CRM, INK, "Why now", ["FedNow reaches 9,000+ banks", "Push-to-card under $0.25", "64M gig workers in the US", "Payroll APIs are standard"], false)],
        8: ["Introducing Halcyon", () => S.div("02", "Introducing Halcyon")],
        9: ["Product overview", S.ui],
        10: ["How it works", S.steps],
        11: ["Pricing", S.pricing],
        12: ["Payouts in 90 seconds", S.big],
        13: ["Integrations", () => S.logos("Works with your payroll stack", ["Ledgerly", "Payfront", "BRIGHTROLL", "corvid.io", "Mintline", "TALLY"], "40+ integrations, live in a day")],
        14: ["Traction", () => S.div("03", "Traction")],
        15: ["Since launch", () => S.stats(FG, "#0b2416", "Since launch in March", [["+47%", "repeat payouts per worker"], ["96×", "faster than ACH settlement"], ["3.2K", "employers live on Halcyon"]], "rgba(255,255,255,.35)")],
        16: ["Payout volume", S.line],
        17: ["Customers", () => S.logos("Trusted by fast-growing fleets", ["Parcel", "Northgate", "FLEETWISE", "shiftly", "Cartwheel", "DASHLINE"], "3.2K employers, 410K workers paid")],
        18: ["Customer quote · Shiftly", () => S.quote(YL, "“Halcyon cut our payout support tickets by 38% in the first month.”", "Omar Haddad", "Head of Ops, Shiftly", "OH", "#1f9e8f")],
        19: ["Markets", () => S.list(CRM, INK, "Live in 14 countries", ["United States", "Canada", "United Kingdom", "Mexico", "Germany", "Netherlands", "Ireland", "Australia"], true)],
        20: ["2027 roadmap", S.road],
        21: ["Leadership", S.team],
        22: ["The ask", S.ask],
        23: ["Next steps", () => S.list(CRM, INK, "Next steps", ["Term sheet by Dec 4", "Pilot with Parcel EU", "Hire Head of Risk", "Board meeting Jan 14"], false)],
        24: ["Thank you", S.thanks],
        25: ["Appendix", () => S.div("A", "Appendix")],
        26: ["Unit economics", () => S.table("Unit economics", ["Metric", "Q2", "Q3", "Q4E"], [["Revenue per payout", "$0.41", "$0.44", "$0.47"], ["Cost per payout", "$0.19", "$0.17", "$0.15"], ["Gross margin", "54%", "61%", "68%"], ["CAC payback", "7.1 mo", "5.8 mo", "4.9 mo"], ["Net retention", "131%", "138%", "142%"]])],
        27: ["Pricing detail", () => S.table("Pricing detail", ["Plan", "Fee", "Min.", "SLA"], [["Starter", "0.8%", "$0", "99.9%"], ["Growth", "0.6%", "$2K", "99.95%"], ["Enterprise", "Custom", "$10K", "99.99%"], ["Card payouts", "+$0.10", "—", "—"]])],
        28: ["Security", () => S.list(DG, "#fff", "Security & compliance", ["SOC 2 Type II", "PCI DSS Level 1", "Money transmitter, 48 states", "Annual pen test by NCC"], false, FG)],
        29: ["FAQ", () => S.list(CRM, INK, "FAQ", ["What does it cost workers?", "Who holds the funds?", "How do you handle fraud?", "What about ACH returns?"], false)],
        30: ["Board metrics", () => S.table("Board metrics", ["KPI", "Sep", "Oct", "Δ"], [["Payout volume", "$349M", "$412M", "+18%"], ["Active workers", "362K", "410K", "+13%"], ["Employers", "2.9K", "3.2K", "+10%"], ["Fraud rate", "0.04%", "0.03%", "−0.01"]])],
        31: ["Hiring plan", () => S.bars(YL, "Hiring plan 2027, headcount by team", [42, 28, 18, 14, 10], ["Eng", "GTM", "Risk", "Ops", "G&A"], DG, (v) => String(v))],
        32: ["Risks", () => S.list(CRM, INK, "Risks we track", ["Bank partner concentration", "Rate pressure on RTP", "Fraud on new accounts", "State licensing timing"], false, OR)],
        33: ["Sources", () => S.list(CRM, INK, "Sources", ["Federal Reserve, FedNow Q3 2026", "Gig Economy Data Hub, 2026", "Halcyon payout ledger, Oct 2026", "Customer interviews, n = 64"], false, "#8b867a")]
      };
      const ROWS = [[1, 2], [3, 4, 5, 6, 7], [8, 9, 10, 11, 12, 13], [14, 15, 16, 17, 18, 19], [20, 21, 22, 23, 24], [25, 26, 27, 28, 29], [30, 31, 32, 33]];

      /* ---------- grid geometry (canvas content coordinates) ---------- */
      const GX = 44, GS = 208, GY = 28, RS = 152;
      const colX = (c) => GX + c * GS;
      const rowY = (r) => GY + r * RS; // label top; thumb top = rowY + 22

      /* ---------- top bar ---------- */
      const logoMark = (s, bg) => '<svg width="' + s + '" height="' + s + '" viewBox="0 0 40 40"><rect width="40" height="40" rx="10" fill="' + bg + '"/><circle cx="15" cy="16" r="6.2" fill="#fff"/><circle cx="26.5" cy="18.5" r="4.6" fill="#3ce27c"/><circle cx="18" cy="28" r="4" fill="#fff"/></svg>';
      let TB = '<i class="tl" style="left:18px;background:#ff5f57"></i><i class="tl" style="left:38px;background:#febc2e"></i><i class="tl" style="left:58px;background:#28c840"></i>';
      TB += '<div class="tbl">' + logoMark(22, "#141414") + '<span class="ico" style="margin-left:-6px">' + ic("chD", 14, 1.8) + "</span>" +
        '<span class="crumb">Marketing</span><span class="crumb">/</span><span class="fn">Halcyon Launch Keynote' + '<span class="ico">' + ic("chD", 14, 1.8) + "</span></span>" +
        '<span class="tag">Slides</span></div>';
      TB += '<div class="tbc"><div class="seg"><span>' + ic("single", 16) + '</span><span class="on">' + ic("grid", 16) + '</span></div><div class="zm">64%' + ic("chD", 14, 1.8) + "</div></div>";
      const tavs = [["maya", "#d81283"], ["liam", "#141414"], ["priya", "#7c5cf0"], ["dana", "#ef8a32"]];
      TB += '<div class="tbr"><div class="avs">' +
        tavs.map((t, i) => '<span id="tav' + i + '" class="tav" style="background:' + P[t[0]][2] + ";box-shadow:0 0 0 2px #fff, 0 0 0 3.5px " + t[1] + '">' + P[t[0]][1] + "</span>").join("") +
        '<span id="tav4" class="tav" style="background:#ececef;color:#55555e">+3</span></div>' +
        '<div class="msg ico">' + ic("msg", 19) + '<span class="badge"><span id="bdA">4</span><span id="bdB" style="opacity:0">5</span></span></div>' +
        '<div id="presBtn" class="btn out" style="position:relative"><span id="presBtnHi"></span>' + ic("play", 14, 1.8) + "Present</div>" +
        '<div class="btn pri">Share</div></div>';
      document.getElementById("top").insertAdjacentHTML("beforeend", TB);

      /* ---------- left panel ---------- */
      const LR = 38;
      let L = '<div class="ltabs"><b>Slides</b><span>Assets</span><span class="r ico">' + ic("search", 16) + ic("plus", 16) + "</span></div>";
      const SECS = [["Opening", 0], ["Problem", 1], ["Product", 2], ["Traction", 3], ["Close", 4], ["Appendix", 5]];
      SECS.forEach((s) => {
        const ids = ROWS[s[1]];
        L += '<div class="lsec"><span class="ico" style="margin-left:0;color:#8a8a94">' + ic("chD", 13, 1.8) + "</span>" + s[0] + "<span>" + ids.length + "</span></div>";
        ids.forEach((n) => {
          const NEW = { 10: 11, 11: 12, 12: 10 };
          const lab = NEW[n] ? '<span class="o">' + n + '</span><span class="n" style="opacity:0">' + NEW[n] + "</span>" : "<span>" + n + "</span>";
          L += '<div class="lrow" id="lr' + n + '"><span class="ln">' + lab + '</span><span class="lt">' + SL[n][1]() + '</span><span class="lnm">' + SL[n][0] + "</span>" +
            (n === 12 ? '<span id="lsel" class="lsel"></span>' : "") + "</div>";
        });
      });
      document.getElementById("left").insertAdjacentHTML("beforeend", L);

      /* ---------- right panel ---------- */
      let R = '<div class="rtabs"><b>Design</b><span>Animate</span><span class="r">Deck</span></div>';
      R += '<div class="rsec"><div class="rh">Theme<span class="ico">' + ic("sliders", 15) + '</span></div><div class="theme"><div class="thn">Halcyon 2026<span>12 layouts</span></div><div class="sw">' +
        [FG, DG, YL, OR, BK, CRM].map((c) => '<i style="background:' + c + '"></i>').join("") + "</div></div></div>";
      R += '<div class="rsec"><div class="rh">Background<span class="ico">' + ic("plus", 15) + '</span></div><div class="field"><span class="chip" style="background:' + CRM + '"></span>F3EFE6<span class="r">100%</span></div></div>';
      R += '<div class="rsec"><div class="rh">Text styles<span class="ico">' + ic("plus", 15) + "</span></div>" +
        [["Title", "Inter 100"], ["Heading", "Inter 58"], ["Subhead", "Inter 28"], ["Body", "Inter 17"], ["Quote", "EB Garamond 38"], ["Data", "JetBrains Mono 14"]].map((t) => '<div class="ts"><b>Ag</b>' + t[0] + "<span>" + t[1] + "</span></div>").join("") + "</div>";
      R += '<div class="rsec"><div class="rh">Slide size</div><div class="row2"><div class="field" style="flex:1">16:9<span class="r ico">' + ic("chD", 13, 1.8) + '</span></div><div class="field" style="flex:1">1920 × 1080</div></div></div>';
      R += '<div class="rsec"><div class="rh" style="margin:0">Slide numbers<span class="tog"><i></i></span></div></div>';
      R += '<div class="rsec"><div class="rh" style="margin:0">Presenter notes<span class="ico">' + ic("plus", 15) + "</span></div></div>";
      R += '<div class="rsec"><div class="rh" style="margin:0">Export<span class="ico">' + ic("plus", 15) + "</span></div></div>";
      R += '<div class="rfoot"><span><i class="dot"></i>Saved</span><span>33 slides · v41</span></div>';
      document.getElementById("right").insertAdjacentHTML("beforeend", R);

      /* ---------- canvas content ---------- */
      let C = "";
      const NEWNUM = { 10: 11, 11: 12, 12: 10 };
      ROWS.forEach((ids, r) => {
        ids.forEach((n, c) => {
          const lab = NEWNUM[n] ? '<span class="o">' + n + '</span><span class="n" style="opacity:0">' + NEWNUM[n] + "</span>" : "<span>" + n + "</span>";
          C += '<div class="swr" id="sw' + n + '" style="left:' + colX(c) + "px;top:" + rowY(r) + 'px">' +
            (n === 12 ? '<div id="lift"></div>' : "") +
            '<div class="sn">' + lab + '</div><div class="th">' + SL[n][1]() + "</div>" +
            (n === 12 ? '<div id="so12" class="so"></div>' : "") + "</div>";
        });
      });
      /* Liam's selection outlines on 16-18 */
      [16, 17, 18].forEach((n, i) => {
        C += '<div id="selL' + n + '" class="selL" style="left:' + colX(2 + i) + "px;top:" + (rowY(3) + 22) + 'px"></div>';
      });
      C += '<div id="mq"></div>';
      /* comment pins + an open thread */
      const pin = (k, x, y) => '<div class="pin" style="left:' + x + "px;top:" + y + 'px"><span style="background:' + P[k][2] + '">' + P[k][1] + "</span></div>";
      C += pin("priya", colX(4) + 168, rowY(1) + 6);
      C += pin("dana", colX(1) + 168, rowY(1) + 6);
      C += pin("omar", colX(2) + 168, rowY(4) + 6);
      C += '<div class="thread" style="left:' + (colX(5) - 6) + "px;top:" + (rowY(1) + 4) + 'px">' +
        '<div class="hd">' + av("priya", 22, 9) + "<b>Priya Nair</b><span class=\"t\">2h</span><span class=\"ck\">" + ic("checkC", 15) + "</span></div>" +
        '<div class="bd">Can we cite the source for the 64M gig workers stat?</div>' +
        '<div class="ft"><span style="display:flex">' + av("liam", 16, 7) + '<span style="margin-left:-4px;display:flex">' + av("maya", 16, 7) + "</span></span>2 replies</div></div>";
      document.getElementById("content").insertAdjacentHTML("beforeend", C);

      /* dock */
      document.getElementById("dock").insertAdjacentHTML("beforeend",
        '<span class="t on">' + ic("ptr", 18) + '</span><span class="t">' + ic("frame", 18) + '</span><span class="t">' + ic("type", 18) + '</span><span class="t">' + ic("square", 18) +
        '</span><span class="t">' + ic("image", 18) + '</span><span class="t">' + ic("pen", 18) + '</span><span class="t">' + ic("table", 18) + '</span><span class="t">' + ic("chart", 18) +
        '</span><span class="t">' + ic("msg", 18) + '</span><span class="sep"></span><span class="ns">' + ic("plus", 16) + "New slide</span>");

      /* comment text */
      const MSG = "Can we lead with this?";
      document.getElementById("bx").innerHTML = MSG.split("").map((ch, i) => '<span class="c" id="ch' + i + '">' + (ch === " " ? "&nbsp;" : ch) + "</span>").join("") + '<i id="car"></i>';

      /* presentation */
      document.getElementById("presZ").innerHTML = S.big();
      document.getElementById("presBar").innerHTML = '<span class="t">' + ic("chL", 18) + '</span><span class="n">10 of 33</span><span class="t">' + ic("chR", 18) + '</span><span class="t">' + ic("max", 16) + "</span>";

      /* lockup */
      document.getElementById("mark").innerHTML = logoMark(168, "#141414");
      const curSvg = (fill) => '<div class="ci"><svg viewBox="0 0 23 28"><path d="M3 2.2 L3 23.2 L8.4 18.2 L11.9 25.8 L15.6 24.2 L12.2 16.8 L19.4 16.6 Z" fill="' + fill + '" stroke="#fff" stroke-width="1.6" stroke-linejoin="round"/></svg></div>';
      document.getElementById("eM").innerHTML = curSvg("#d81283") + '<div class="nm" style="background:#d81283">Maya Chen</div>';
      document.getElementById("eL").innerHTML = curSvg("#141414") + '<div class="nm" style="background:#141414">Liam Ortiz</div>';

      /* ---------- camera ---------- */
      const WX = 60, WY = 40, CX = 244, CY = 48, SCROLL = 140, T_SCROLL = 4.3;
      const Z = (px, py, sx, sy, s) => ({ x: sx - s * px, y: sy - s * py, scale: s });
      const FULL = { x: 0, y: 0, scale: 1 };
      const ZS = 1.4;
      const ZM = Z(1225, 354, 960, 410, ZS);

      gsap.set("#cam", { transformOrigin: "0px 0px" });
      gsap.set("#drift", { transformOrigin: "50% 50%" });
      gsap.set([".cur"], { transformOrigin: "0px 0px" });
      gsap.set([".ci"], { transformOrigin: "4px 3px" });
      gsap.set("#sw12", { transformOrigin: "92px 74px" });
      gsap.set("#presIn", { transformOrigin: "0px 0px" });
      gsap.set("#presZ", { transformOrigin: "50% 50%" });
      gsap.set("#bub", { transformOrigin: "0px 0px" });

      const tl = gsap.timeline({ paused: true });
      function F(target, from, to, at) { tl.fromTo(target, from, Object.assign({ immediateRender: false }, to), at); }
      function camMove(from, to, at, dur, blur) {
        F("#cam", from, Object.assign({ duration: dur, ease: EXPO }, to), at);
        F("#cam", { filter: "blur(0px)" }, { filter: "blur(" + blur + "px)", duration: 0.1, ease: "power1.out" }, at);
        tl.to("#cam", { filter: "blur(0px)", duration: 0.25, ease: "power1.in" }, at + 0.1);
      }
      function press(id, at) { F(id, { scale: 1 }, { scale: 0.86, duration: 0.08, ease: "power2.out" }, at); }
      function release(id, at) { F(id, { scale: 0.86 }, { scale: 1, duration: 0.22, ease: BACK }, at); }
      /* cursor path helper: sequential moves with holds */
      function path(id, pts) {
        for (let i = 1; i < pts.length; i++) {
          const p = pts[i - 1], q = pts[i];
          F(id, { x: p[0], y: p[1] }, { x: q[0], y: q[1], duration: q[3], ease: q[4] || EIO }, q[2]);
        }
      }
      /* time at which an eased tween reaches a fraction */
      function tAt(ease, t0, dur, frac) {
        const e = gsap.parseEase(ease);
        for (let i = 0; i <= 1000; i++) { if (e(i / 1000) >= frac) return t0 + dur * i / 1000; }
        return t0 + dur;
      }

      /* 1. window arrives, canvas scrolls slowly (0 - 4.3) */
      F("#win", { y: 28, scale: 0.98 }, { y: 0, scale: 1, duration: 0.9, ease: EXPO }, 0);
      F("#drift", { scale: 1 }, { scale: 1.04, duration: 8.7, ease: "none" }, 0);
      F(["#content", "#curLayer"], { y: 0 }, { y: -SCROLL, duration: T_SCROLL, ease: "none" }, 0);

      /* 2. collaborators join: avatars pop, intervals shrink */
      [0.35, 0.8, 1.2, 1.53, 1.8].forEach((t, i) => {
        F("#tav" + i, { scale: 0, opacity: 0 }, { scale: 1, opacity: 1, duration: 0.25, ease: POP }, t);
      });

      /* 3. Liam: box-selects the three traction slides (0.95 - 2.7) */
      const MQ0 = [446, 474], MQ1 = [1072, 624];
      gsap.set("#curL", { x: 1220, y: 900 });
      F("#curL", { opacity: 0 }, { opacity: 1, duration: 0.25, ease: "power2.out" }, 0.9);
      path("#curL", [
        [1220, 900],
        [478, 500, 0.9, 0.65, "power3.out"],
        [MQ0[0], MQ0[1], 1.6, 0.2, "sine.inOut"]
      ]);
      press("#ciL", 1.82);
      const MQT = 1.9, MQD = 0.75;
      F("#curL", { x: MQ0[0], y: MQ0[1] }, { x: MQ1[0], y: MQ1[1], duration: MQD, ease: EIO }, MQT);
      tl.set("#mq", { opacity: 1 }, MQT);
      F("#mq", { width: 0, height: 0 }, { width: MQ1[0] - MQ0[0], height: MQ1[1] - MQ0[1], duration: MQD, ease: EIO }, MQT);
      /* each slide turns selected on the frame the box first touches it */
      const ty = tAt(EIO, MQT, MQD, (rowY(3) + 22 - MQ0[1]) / (MQ1[1] - MQ0[1]));
      [16, 17, 18].forEach((n, i) => {
        const tx = tAt(EIO, MQT, MQD, (colX(2 + i) - MQ0[0]) / (MQ1[0] - MQ0[0]));
        tl.set("#selL" + n, { opacity: 1 }, Math.max(tx, ty));
      });
      release("#ciL", MQT + MQD + 0.08);
      tl.set("#mq", { opacity: 0 }, MQT + MQD + 0.08);
      path("#curL", [
        [MQ1[0], MQ1[1]],
        [1112, 662, 2.85, 0.5, "power2.out"],
        [1140, 678, 3.5, 0.9, "sine.inOut"],
        [1118, 690, 4.6, 0.6, "sine.inOut"],
        [1146, 672, 5.05, 0.9, "sine.inOut"],
        [1128, 700, 6.2, 1.2, "sine.inOut"]
      ]);

      /* 4. Maya: drifts in, lands on the green slide, comments (0.5 - 4.2) */
      const MT = [1022, 436]; // rests in the blank lower-right of slide 12
      gsap.set("#curM", { x: 30, y: 300 });
      F("#curM", { opacity: 0 }, { opacity: 1, duration: 0.25, ease: "power2.out" }, 0.45);
      path("#curM", [
        [30, 300],
        [440, 468, 0.45, 0.7, "power3.out"],
        [452, 474, 1.15, 0.35, "sine.inOut"],
        [1034, 446, 1.55, 0.65, EIO],
        [MT[0], MT[1], 2.2, 0.25, "sine.out"]
      ]);
      tl.to("#nmM", { opacity: 0, duration: 0.12, ease: "power1.out" }, 2.5);
      F("#bub", { opacity: 0, scale: 0.6 }, { opacity: 1, scale: 1, duration: 0.3, ease: BACK }, 2.5);
      const TY0 = 2.65;
      MSG.split("").forEach((c, i) => {
        const jit = [0, 0.012, -0.01, 0.015, 0, -0.012, 0.01][i % 7];
        tl.set("#ch" + i, { display: "inline" }, TY0 + i / 15 + jit);
      });
      /* Enter: header and badge change on the same frame */
      const ENTER = 4.2;
      tl.set("#car", { opacity: 0 }, ENTER);
      tl.set("#bTy", { opacity: 0 }, ENTER);
      tl.set("#bNow", { opacity: 1 }, ENTER);
      tl.set("#bdA", { opacity: 0 }, ENTER);
      tl.set("#bdB", { opacity: 1 }, ENTER);

      /* 5. push in 1.4x, cursors keep their on-screen size (4.3 - 4.7) */
      const ZIN = 4.3;
      camMove(FULL, ZM, ZIN, 0.4, 3);
      F([".cur"], { scale: 1 }, { scale: 1 / ZS, duration: 0.4, ease: EXPO }, ZIN);

      /* 6. drag slide 12 to position 10; 11 and 10 make room (4.8 - 5.85) */
      const PRESS = 4.8, DT = 4.95, DD = 0.8, DX = -2 * GS;
      press("#ciM", PRESS);
      tl.set("#so12", { opacity: 1 }, PRESS);
      tl.to("#bub", { opacity: 0, y: 6, duration: 0.2, ease: "power2.in" }, PRESS);
      F("#nmM", { opacity: 0 }, { opacity: 1, duration: 0.2, ease: "power2.out" }, PRESS + 0.1);
      F("#sw12", { scale: 1, y: 0 }, { scale: 1.06, y: -4, duration: 0.2, ease: EXPO }, PRESS);
      F("#lift", { opacity: 0 }, { opacity: 1, duration: 0.2, ease: EXPO }, PRESS);
      F("#curM", { x: MT[0] }, { x: MT[0] + DX, duration: DD, ease: EIO }, DT);
      F("#sw12", { x: 0 }, { x: DX, duration: DD, ease: EIO }, DT);
      F("#curM", { y: MT[1] }, { y: MT[1] - 12, duration: DD / 2, ease: "sine.out" }, DT);
      tl.to("#curM", { y: MT[1], duration: DD / 2, ease: "sine.in" }, DT + DD / 2);
      F("#sw12", { y: -4 }, { y: -16, duration: DD / 2, ease: "sine.out" }, DT);
      tl.to("#sw12", { y: -4, duration: DD / 2, ease: "sine.in" }, DT + DD / 2);
      /* reflow when the dragged slide's centre crosses each column midpoint */
      const t11 = tAt(EIO, DT, DD, (GS / 2) / (2 * GS));
      const t10 = tAt(EIO, DT, DD, (1.5 * GS) / (2 * GS));
      F("#sw11", { x: 0 }, { x: GS, duration: 0.4, ease: EXPO }, t11);
      F("#sw10", { x: 0 }, { x: GS, duration: 0.4, ease: EXPO }, t10);
      /* drop: slide settles, numbers and the slide list update on the same frame */
      const DROP = 5.85;
      release("#ciM", DROP);
      F("#sw12", { scale: 1.06, y: -4 }, { scale: 1, y: 0, duration: 0.3, ease: EXPO }, DROP);
      tl.to("#lift", { opacity: 0, duration: 0.3, ease: EXPO }, DROP);
      tl.set(["#sw10 .o", "#sw11 .o", "#sw12 .o", "#lr10 .o", "#lr11 .o", "#lr12 .o"], { opacity: 0 }, DROP);
      tl.set(["#sw10 .n", "#sw11 .n", "#sw12 .n", "#lr10 .n", "#lr11 .n", "#lr12 .n"], { opacity: 1 }, DROP);
      tl.set("#lr12", { y: -2 * LR }, DROP);
      tl.set(["#lr10", "#lr11"], { y: LR }, DROP);
      tl.set("#lsel", { opacity: 1 }, DROP);

      /* 7. pull back to the whole deck (6.15 - 6.75) */
      const ZOUT = 6.15;
      camMove(ZM, FULL, ZOUT, 0.6, 2);
      F([".cur"], { scale: 1 / ZS }, { scale: 1, duration: 0.6, ease: EXPO }, ZOUT);

      /* 8. Maya hits Present; the reordered slide opens full screen (6.85 - 8.6) */
      const PB = [1640 - CX, 24 - CY + SCROLL];
      path("#curM", [
        [MT[0] + DX, MT[1]],
        [PB[0] + 14, PB[1] + 10, 6.85, 0.55, EIO],
        [PB[0], PB[1], 7.4, 0.12, "sine.out"]
      ]);
      const PCLICK = 7.55;
      press("#ciM", PCLICK);
      release("#ciM", PCLICK + 0.08);
      tl.set("#presBtnHi", { opacity: 1 }, PCLICK);
      /* thumbnail rect on screen at that moment (drift scale about frame centre) */
      const ds = 1 + 0.04 * (PCLICK / 8.7);
      const thx = WX + CX + colX(2), thy = WY + CY + rowY(2) + 22 - SCROLL;
      const sx0 = 960 + ds * (thx - 960), sy0 = 540 + ds * (thy - 540), sc0 = ds * 184 / 1920;
      F("#presIn", { opacity: 0 }, { opacity: 1, duration: 0.01, ease: "none" }, PCLICK + 0.04);
      F("#presIn", { x: sx0, y: sy0, scale: sc0 }, { x: 0, y: 0, scale: 1, duration: 0.65, ease: EXPO }, PCLICK + 0.04);
      F("#presZ", { scale: 1 }, { scale: 1.035, duration: 1.4, ease: "none" }, PCLICK + 0.5);
      F("#presBar", { opacity: 0, y: 8 }, { opacity: 1, y: 0, duration: 0.35, ease: EXPO }, 8.05);

      /* 9. lockup: the two collaborators meet on the wordmark (8.7 - 10.5) */
      const END = 8.75;
      F("#end", { opacity: 0 }, { opacity: 1, duration: 0.35, ease: "power2.inOut" }, END);
      F("#mark", { opacity: 0, scale: 0.8 }, { opacity: 1, scale: 1, duration: 0.6, ease: EXPO }, END + 0.2);
      F("#wm", { opacity: 0, x: -24 }, { opacity: 1, x: 0, duration: 0.6, ease: EXPO }, END + 0.3);
      F("#wsub", { opacity: 0, y: 12 }, { opacity: 1, y: 0, duration: 0.5, ease: EXPO }, END + 0.55);
      F("#eL", { x: 300, y: 210, opacity: 0 }, { x: 452, y: 300, opacity: 1, duration: 0.75, ease: EXPO }, END + 0.35);
      F("#eM", { x: 1640, y: 760, opacity: 0 }, { x: 1392, y: 560, opacity: 1, duration: 0.75, ease: EXPO }, END + 0.45);
      F("#lockup", { scale: 1 }, { scale: 1.03, duration: 1.75, ease: "none" }, END);

      window.__timelines = window.__timelines || {};
      window.__timelines["main"] = tl;
    </script>
  </body>
</html>
```
