← GPT-AI-VIDEO
WEBSITE · 14.8 KB

vendor/OpenMontage/.agents/skills/hyperframes-animation/examples/cta-morph-press.html

Workspace snapshot · 09/04 14:52

Preview

Source
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=1920, height=1080" />
    <title>Scene 10 — HyperFrames Morph Press Interact</title>

    <!--
      HyperFrames composition.tsx.

      Choreography (4 phases, 5.5 seconds total):
        0.17 – 0.64s   Hero "HyperFrames" + logo asset fade up (opacity + y rise)
        0.17 – 5.50s   Logo asset rotates ±4° continuously (sine onUpdate)
        2.17 – 2.62s   Hero shrinks (1 → 0.6) and fades (1 → 0)
        2.17 – 2.62s   "Build video from HTML" CTA pill pops in with back.out(2) overshoot
        2.33 – 2.66s   CTA text fades + lifts into place (after container is recognizable)
        2.83s          Cursor hard-cuts in at off-screen bottom-right
        2.83 – 3.83s   Cursor approaches via spring path to (W/2 + 120, H/2 + 50)
        3.83 – 3.98s   Click DOWN: both cursor and CTA compress to scale 0.9 (sync)
        4.17 – 4.42s   Click UP: both return to scale 1.0 (sync)
          DOM + GSAP opacity tweens; z-index keeps incoming above outgoing
        - Press synchronization uses single GSAP target array ["#cta", "#cursor"]
          to guarantee perfectly identical tween values
        - Cursor opacity uses a 0.001-second fromTo for hard-cut step change
        - Logo uses a local static image asset referenced with a plain URL
        - Breathing rotation only on the logo, not on the whole hero (avoids
          conflict with the morph exit scale tween)
    -->

    <script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>

    <style>
      :root {
        --hf-bg: #050711;
        --hf-bg-2: #0b1020;
        --hf-ink: #f8fafc;
        --hf-muted: #cbd5e1;
        --hf-purple: #7c3aed;
        --hf-purple-soft: #a78bfa;
        --hf-cyan: #18d9e8;
        --hf-green: #22c55e;
        --hf-glass: rgba(248, 250, 252, 0.08);
        --white: #ffffff;
      }
      * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
      }
      html,
      body {
        margin: 0;
        width: 1920px;
        height: 1080px;
        overflow: hidden;
        background:
          radial-gradient(ellipse at 50% 42%, rgba(24, 217, 232, 0.14), transparent 50%),
          radial-gradient(ellipse at 62% 48%, rgba(124, 58, 237, 0.2), transparent 56%),
          radial-gradient(ellipse at 42% 58%, rgba(34, 197, 94, 0.1), transparent 58%),
          linear-gradient(135deg, #050711 0%, #090b18 48%, #120717 100%);
        font-family:
          "Inter",
          system-ui,
          -apple-system,
          sans-serif;
        color: var(--hf-ink);
      }

      .stage {
        position: absolute;
        inset: 0;
        display: flex;
        align-items: center;
        justify-content: center;
      }

      /* ============================================================
         HERO LOCKUP (Phase 1 — exits during morph)
         ============================================================ */
      .hero {
        position: absolute;
        width: 100%;
        height: 100%;
        display: flex;
        align-items: center;
        justify-content: center;
        gap: 54px;
        will-change: transform, opacity;
      }
      .hero-text {
        font-size: 220px;
        font-weight: 800;
        color: var(--hf-ink);
        letter-spacing: 0;
        line-height: 1;
        margin: 0;
        text-shadow:
          0 0 34px rgba(167, 139, 250, 0.18),
          0 0 72px rgba(24, 217, 232, 0.1);
      }
      .hero-text .brand-accent {
        color: var(--hf-purple-soft);
        text-shadow:
          0 0 34px rgba(124, 58, 237, 0.45),
          0 0 82px rgba(167, 139, 250, 0.22);
      }
      .hero-logo {
        width: 300px;
        height: 300px;
        margin-left: 8px;
        will-change: transform;
        display: flex;
        align-items: center;
        justify-content: center;
      }
      .hero-logo .logo-mark {
        width: 100%;
        height: 100%;
        display: block;
        border-radius: 36px;
        box-shadow:
          0 0 64px rgba(24, 217, 232, 0.32),
          0 0 112px rgba(34, 197, 94, 0.18),
          0 0 150px rgba(124, 58, 237, 0.12);
      }

      /* ============================================================
         CTA PILL (Phase 2 — pops in during morph)
         ============================================================ */
      .cta {
        background: linear-gradient(
          135deg,
          rgba(124, 58, 237, 0.96) 0%,
          rgba(24, 217, 232, 0.9) 48%,
          rgba(34, 197, 94, 0.96) 100%
        );
        padding: 70px 200px;
        border-radius: 200px;
        display: flex;
        align-items: center;
        justify-content: center;
        overflow: hidden;
        border: 1px solid rgba(24, 217, 232, 0.55);
        box-shadow:
          0 24px 60px rgba(0, 0, 0, 0.42),
          0 0 72px rgba(24, 217, 232, 0.24),
          0 0 120px rgba(124, 58, 237, 0.24),
          inset 0 1px 0 rgba(255, 255, 255, 0.24),
          inset 0 0 34px var(--hf-glass);
        will-change: transform, opacity;
        /* initial scale 0 + opacity 0 set by gsap.set() */
      }
      .cta-text {
        color: var(--hf-bg);
        font-size: 110px;
        font-weight: 700;
        white-space: nowrap;
        text-shadow:
          0 1px 0 rgba(255, 255, 255, 0.3),
          0 0 22px rgba(255, 255, 255, 0.12);
        will-change: transform, opacity;
        /* initial opacity 0 + y 10 set by gsap.set() */
      }

      /* ============================================================
         CURSOR (Phase 3 — hard-cuts in)
         ============================================================ */
      .cursor {
        position: absolute;
        left: 0;
        top: 0;
        width: 120px;
        height: 120px;
        will-change: transform, opacity;
        /* initial x/y/opacity set by gsap.set() */
      }
      .cursor .cursor-svg {
        width: 100%;
        height: 100%;
        display: block;
      }
    </style>
  </head>
  <body>
    <div
      id="root"
      data-composition-id="main"
      data-start="0"
      data-duration="5.5"
      data-width="1920"
      data-height="1080"
    >
      <div class="stage clip" data-start="0" data-duration="5.5" data-track-index="1" id="stage">
        <!-- =====================================================
             HERO LOCKUP — "HyperFrames" + logo
             ===================================================== -->
        <div
          class="hero clip"
          id="hero"
          data-start="0"
          data-duration="2.62"
          data-track-index="2"
          data-layout-allow-overflow
          aria-hidden="true"
        >
          <h1 class="hero-text">Hyper<span class="brand-accent">Frames</span></h1>
          <div class="hero-logo" id="hero-logo">
            <!-- Inline-SVG placeholder mark — swap for your logo image -->
            <svg class="logo-mark" viewBox="0 0 100 100" role="img" aria-label="HyperFrames">
              <defs>
                <linearGradient id="hfMark" x1="0" y1="0" x2="1" y2="1">
                  <stop offset="0" stop-color="#8b7bff" />
                  <stop offset="1" stop-color="#3ddc97" />
                </linearGradient>
              </defs>
              <rect x="4" y="4" width="92" height="92" rx="22" fill="url(#hfMark)" />
              <text
                x="50"
                y="63"
                text-anchor="middle"
                font-family="Inter, system-ui, sans-serif"
                font-size="40"
                font-weight="800"
                fill="#fff"
              >
                HF
              </text>
            </svg>
          </div>
        </div>

        <!-- =====================================================
             CTA PILL — "Build video from HTML"
             ===================================================== -->
        <div class="cta" id="cta">
          <span class="cta-text">Build video from HTML</span>
        </div>

        <!-- =====================================================
             CURSOR — inline SVG arrow with drop-shadow
             ===================================================== -->
        <div class="cursor" id="cursor">
          <svg
            class="cursor-svg"
            viewBox="0 0 24 24"
            fill="none"
            xmlns="http://www.w3.org/2000/svg"
            aria-hidden="true"
          >
            <defs>
              <filter id="cursorShadow" x="-50%" y="-50%" width="200%" height="200%">
                <feDropShadow dx="2" dy="4" stdDeviation="3" flood-opacity="0.3" />
              </filter>
            </defs>
            <path
              d="M5.5 3.5L19 10.5L11.5 12.5L15.5 19.5L13.5 20.5L9.5 13.5L5.5 17.5V3.5Z"
              fill="#050711"
              stroke="#f8fafc"
              stroke-width="1.5"
              stroke-linejoin="round"
              filter="url(#cursorShadow)"
            />
          </svg>
        </div>
      </div>
    </div>

    <script>
      /* ================================================================
         TIMING
         ================================================================ */
      const W = 1920,
        H = 1080;
      const TOTAL_DUR = 5.5;

      const TIMING = {
        introStart: 0.17, // ~5 frames at 30 fps
        introDur: 0.47, // hero enter spring settle

        morphAt: 2.17, // ~65 frames
        morphExitDur: 0.5,
        morphFadeDur: 0.15, // 30% of exit
        morphEntDur: 0.45, // CTA pop-in

        textRevealAt: 2.33, // morph + 0.17 s
        textRevealDur: 0.33,

        cursorEnterAt: 2.83,
        cursorPathDur: 1.0,

        clickDownAt: 3.83,
        clickDownDur: 0.15,
        clickUpAt: 4.17,
        clickUpDur: 0.25,

        pressIntensity: 0.1,
      };

      /* ================================================================
         INITIAL STATES (via gsap.set, before the timeline runs)
         ================================================================ */
      // Hero: starts at opacity 0, y +40 (will fade up)
      gsap.set("#hero", { opacity: 0, y: 40 });

      // CTA: starts invisible (scale 0, opacity 0) — morph pops it in
      gsap.set("#cta", { scale: 0, opacity: 0 });

      // CTA text: starts at opacity 0 + y 10 — reveals after container is recognizable
      gsap.set(".cta-text", { opacity: 0, y: 10 });

      // Cursor: starts off-screen bottom-right + invisible + scale 1 baseline
      gsap.set("#cursor", {
        x: W + 100,
        y: H + 200,
        scale: 1,
        opacity: 0,
      });

      /* ================================================================
         TIMELINE
         ================================================================ */
      window.__timelines = window.__timelines || {};
      const tl = gsap.timeline({ paused: true });
      window.__timelines["main"] = tl;

      /* ----------------------------------------------------------------
         PHASE 1: Hero entrance + breathing rotation on logo
         ---------------------------------------------------------------- */
      tl.to(
        "#hero",
        {
          opacity: 1,
          y: 0,
          duration: TIMING.introDur,
          ease: "power3.out", // spring(stiffness:120, damping:14)
        },
        TIMING.introStart,
      );

      // Continuous breathing rotation on the logo. ±4° over ~6.3 s period.
      // For a 5.5 s composition this is just under one cycle — subtle but alive.
      // Use Form 2 (onUpdate reading tl.time) per sine-wave-loop rule.
      const logoEl = document.querySelector("#hero-logo");
      tl.to(
        { tick: 0 },
        {
          tick: 1,
          duration: TOTAL_DUR,
          ease: "none",
          onUpdate: function () {
            const t = tl.time();
            gsap.set(logoEl, { rotation: Math.sin(t * 1.0) * 4 });
          },
        },
        0,
      );

      /* ----------------------------------------------------------------
         PHASE 2: Scale-swap morph
         Three concurrent tween clusters from MORPH_AT.
         ---------------------------------------------------------------- */
      // (1) Hero shrinks
      tl.to(
        "#hero",
        {
          scale: 0.6,
          duration: TIMING.morphExitDur,
          ease: "power3.out", // spring(stiffness:150, damping:18)
        },
        TIMING.morphAt,
      );

      // (2) Hero fades fast (30% of shrink dur)
      tl.to(
        "#hero",
        {
          opacity: 0,
          duration: TIMING.morphFadeDur,
          ease: "power2.out",
        },
        TIMING.morphAt,
      );

      tl.set(
        "#hero",
        {
          x: -4000,
        },
        TIMING.morphAt + TIMING.morphFadeDur,
      );

      // (3) CTA pops in with overshoot
      tl.to(
        "#cta",
        {
          scale: 1,
          opacity: 1,
          duration: TIMING.morphEntDur,
          ease: "back.out(2)", // spring(stiffness:200, damping:15, mass:0.6)
        },
        TIMING.morphAt,
      );

      // (4) CTA text reveals after container reaches recognizable scale
      tl.to(
        ".cta-text",
        {
          opacity: 1,
          y: 0,
          duration: TIMING.textRevealDur,
          ease: "power2.out",
        },
        TIMING.textRevealAt,
      );

      /* ----------------------------------------------------------------
         PHASE 3: Cursor entry + motion path
         ---------------------------------------------------------------- */
      // Hard-cut opacity (cursors don't fade in — they appear).
      // 0.001 s tween creates a step change that scrubs correctly.
      tl.fromTo(
        "#cursor",
        { opacity: 0 },
        { opacity: 1, duration: 0.001, ease: "none" },
        TIMING.cursorEnterAt,
      );

      // Spring-driven approach to a target slightly offset from center
      // (where a human would naturally aim — not dead-center).
      tl.to(
        "#cursor",
        {
          x: W / 2 + 120,
          y: H / 2 + 50,
          duration: TIMING.cursorPathDur,
          ease: "power2.out", // spring(stiffness:60, damping:20)
        },
        TIMING.cursorEnterAt,
      );

      /* ----------------------------------------------------------------
         PHASE 4: Physics-based press
         Both CTA and cursor compress together — single target array.
         ---------------------------------------------------------------- */
      // Press DOWN — both elements compress to (1 - intensity)
      tl.to(
        ["#cta", "#cursor"],
        {
          scale: 1 - TIMING.pressIntensity,
          duration: TIMING.clickDownDur,
          ease: "power3.out", // spring(stiffness:300, damping:20)
        },
        TIMING.clickDownAt,
      );

      // RELEASE — back to scale 1.0
      tl.to(
        ["#cta", "#cursor"],
        {
          scale: 1.0,
          duration: TIMING.clickUpDur,
          ease: "power2.out", // spring(stiffness:200, damping:15)
        },
        TIMING.clickUpAt,
      );
    </script>
  </body>
</html>