Live demo, running in your browser

Paper crane

A bold orange hero with giant LAUNCH lettering where a WebGL paper crane, with a soft shadow, flaps and glides upward and passes between the back and front copies of the word, over a drifting glow and rising specks.

Assets

Code: MIT license. three.js 0.184.0 (MIT) · Imagery generated with Google Gemini

<!doctype html>
<html lang="en"
  ><head
    ><script>
      /* 6to9 pattern harness */ (function () {
        var parentPaused = false,
          hidden = document.hidden,
          p = (window.__pattern = { paused: false, reducedMotion: false, maxDpr: 2 });
        function sync() {
          p.paused = parentPaused || hidden;
        }
        sync();
        try {
          p.reducedMotion = matchMedia("(prefers-reduced-motion: reduce)").matches;
        } catch (e) {}
        document.addEventListener("visibilitychange", function () {
          hidden = document.hidden;
          sync();
        });
        addEventListener("message", function (e) {
          var d = e.data || {};
          if (d.pattern === "pause") {
            parentPaused = true;
            sync();
          }
          if (d.pattern === "play") {
            parentPaused = false;
            sync();
          }
        });
      })();</script
    ><meta charset="utf-8" /><meta
      name="viewport"
      content="width=device-width,initial-scale=1"
    /><title>Fold — Launch</title
    ><script type="importmap">
      {
        "imports": { "three": "https://cdn.jsdelivr.net/npm/three@0.184.0/build/three.module.js" }
      }</script
    ><style>
      * {
        box-sizing: border-box;
      }
      html,
      body {
        margin: 0;
        height: 100%;
        background: #ff8a1f;
        overflow: hidden;
        font-family: "Helvetica Neue", Helvetica, Arial, system-ui, sans-serif;
        color: #120b07;
      }
      .hero {
        position: relative;
        width: 100vw;
        height: 100vh;
        overflow: hidden;
        background: radial-gradient(120% 90% at 50% 38%, #ffa23c 0%, #ff8a1f 55%, #f57a10 100%);
      }
      .glow {
        position: absolute;
        z-index: 0;
        width: 70vmax;
        height: 70vmax;
        left: 50%;
        top: 50%;
        margin: -35vmax 0 0 -35vmax;
        background: radial-gradient(circle, rgba(255, 214, 150, 0.55), rgba(255, 214, 150, 0) 60%);
        animation: gl 9s ease-in-out infinite alternate;
      }
      @keyframes gl {
        from {
          transform: translate(-18vw, 8vh) scale(0.9);
        }
        to {
          transform: translate(18vw, -8vh) scale(1.1);
        }
      }
      .specks {
        position: absolute;
        inset: 0;
        z-index: 0;
        overflow: hidden;
        pointer-events: none;
      }
      .specks i {
        position: absolute;
        bottom: -20px;
        width: 10px;
        height: 10px;
        border-radius: 50%;
        background: rgba(255, 236, 200, 0.6);
        animation: rise 9s linear infinite;
      }
      @keyframes rise {
        from {
          transform: translateY(0);
        }
        to {
          transform: translateY(-115vh);
        }
      }
      .rm .glow,
      .rm .specks i,
      .rm .fb {
        animation: none;
      }
      .pz .glow,
      .pz .specks i,
      .pz .fb {
        animation-play-state: paused;
      }
      .stack {
        position: absolute;
        inset: 0;
        display: flex;
        align-items: center;
        justify-content: center;
        pointer-events: none;
      }
      .stack h1 {
        margin: 0;
        margin-top: -7vh;
        font-family: "Arial Black", "Helvetica Neue", Impact, system-ui, sans-serif;
        font-weight: 900;
        font-size: min(21.5vw, 50vh);
        line-height: 0.8;
        letter-spacing: -0.06em;
        color: #120b07;
        text-align: center;
        white-space: nowrap;
        padding-right: 0.06em;
      }
      .stack h1 span {
        display: inline;
      }
      .back {
        z-index: 1;
      }
      canvas {
        position: absolute;
        inset: 0;
        width: 100%;
        height: 100%;
        z-index: 2;
        display: block;
      }
      .front {
        z-index: 3;
        --y: 300vh;
        -webkit-mask-image: linear-gradient(
          to bottom,
          transparent calc(var(--y) - 6px),
          #000 calc(var(--y) + 6px)
        );
        mask-image: linear-gradient(
          to bottom,
          transparent calc(var(--y) - 6px),
          #000 calc(var(--y) + 6px)
        );
      }
      nav {
        position: absolute;
        z-index: 5;
        top: 0;
        left: 0;
        right: 0;
        display: flex;
        justify-content: space-between;
        align-items: center;
        padding: 28px 4vw;
        font-weight: 800;
        letter-spacing: -0.02em;
      }
      nav .brand {
        font-size: 20px;
        display: flex;
        align-items: center;
        gap: 10px;
      }
      nav .brand i {
        display: block;
        width: 18px;
        height: 18px;
        background: #120b07;
        clip-path: polygon(0 100%, 50% 0, 100% 100%, 50% 70%);
      }
      nav .m {
        font-size: 14px;
        font-weight: 700;
        border: 2px solid #120b07;
        border-radius: 99px;
        padding: 8px 18px;
      }
      .bottom {
        position: absolute;
        z-index: 5;
        left: 4vw;
        right: 4vw;
        bottom: 5vh;
        display: flex;
        justify-content: space-between;
        align-items: flex-end;
        gap: 24px;
      }
      .bottom p {
        margin: 0;
        max-width: 30ch;
        font-size: clamp(16px, 1.5vw, 22px);
        line-height: 1.35;
        font-weight: 600;
        letter-spacing: -0.01em;
      }
      .btn {
        display: inline-flex;
        align-items: center;
        gap: 12px;
        background: #120b07;
        color: #ffa23c;
        font-weight: 800;
        font-size: 17px;
        padding: 18px 30px;
        border-radius: 99px;
        text-decoration: none;
        transition:
          transform 0.25s ease,
          box-shadow 0.25s ease;
        box-shadow: 0 10px 30px rgba(90, 30, 0, 0.3);
      }
      .btn:hover {
        transform: translateY(-3px) scale(1.03);
      }
      .btn b {
        display: inline-block;
        transition: transform 0.25s;
      }
      .btn:hover b {
        transform: translateX(4px);
      }
      .fb {
        display: none;
        position: absolute;
        z-index: 2;
        left: 50%;
        top: 50%;
        width: min(56vw, 520px);
        height: min(40vw, 380px);
        margin: -20vh 0 0 -28vw;
        animation: fl 6s ease-in-out infinite alternate;
      }
      .fb div {
        position: absolute;
        inset: 0;
        background: repeating-linear-gradient(0deg, #e9e2d0 0 6px, #cfc7b2 6px 8px);
        filter: drop-shadow(30px 50px 25px rgba(90, 30, 0, 0.35));
      }
      .fb .a {
        clip-path: polygon(0 30%, 55% 55%, 100% 0, 62% 70%, 48% 100%, 40% 62%);
      }
      .fb .b {
        clip-path: polygon(88% 12%, 100% 8%, 96% 22%, 70% 62%);
        filter: brightness(0.85);
      }
      @keyframes fl {
        from {
          transform: translateY(60px) rotate(-3deg);
        }
        to {
          transform: translateY(-60px) rotate(3deg);
        }
      }
      .nogl .fb {
        display: block;
      }
      @media (max-aspect-ratio: 1/1) {
        .stack h1 {
          font-size: 38vw;
          line-height: 0.82;
          margin-top: -10vh;
        }
        .stack h1 span {
          display: block;
        }
        .bottom {
          flex-direction: column;
          align-items: flex-start;
          gap: 20px;
          bottom: 4.5vh;
        }
        .fb {
          margin-left: -28vw;
          width: 70vw;
        }
      }
    </style></head
  ><body
    ><div class="hero" id="hero"
      ><div class="glow"></div
      ><div class="specks"
        ><i style="left: 8%; animation-delay: -1s; animation-duration: 8s"></i
        ><i
          style="left: 22%; animation-delay: -5s; animation-duration: 11s; width: 6px; height: 6px"
        ></i
        ><i style="left: 37%; animation-delay: -3s; animation-duration: 9s"></i
        ><i
          style="left: 53%; animation-delay: -7s; animation-duration: 12s; width: 7px; height: 7px"
        ></i
        ><i style="left: 68%; animation-delay: -2s; animation-duration: 10s"></i
        ><i
          style="left: 82%; animation-delay: -6s; animation-duration: 8.5s; width: 6px; height: 6px"
        ></i
        ><i style="left: 93%; animation-delay: -4s; animation-duration: 13s"></i></div
      ><nav
        ><div class="brand"><i></i>Fold</div><div class="m">Menu</div></nav
      ><div class="stack back"
        ><h1><span>LAU</span><span>NCH</span></h1></div
      ><canvas id="c"></canvas
      ><div class="stack front" id="front" aria-hidden="true"
        ><h1><span>LAU</span><span>NCH</span></h1></div
      ><div class="fb"><div class="a"></div><div class="b"></div></div
      ><div class="bottom"
        ><p
          >Fold your ideas into flight. A calm, lightweight toolkit for shipping what comes next.</p
        ><a class="btn" href="#">Start folding <b>&rarr;</b></a></div
      ></div
    ><script type="module">
      const P = window.__pattern || { paused: false, reducedMotion: false, maxDpr: 2 };
      const hero = document.getElementById("hero");
      const front = document.getElementById("front");
      const canvas = document.getElementById("c");
      if (P.reducedMotion) hero.classList.add("rm");
      function fallback() {
        hero.classList.add("nogl");
        canvas.style.display = "none";
      }
      setInterval(function () {
        hero.classList.toggle("pz", !!P.paused);
      }, 200);
      async function boot() {
        let THREE;
        try {
          THREE = await import("three");
        } catch (e) {
          fallback();
          return;
        }
        try {
          init(THREE);
        } catch (e) {
          fallback();
        }
      }
      function makeStrip() {
        const c = document.createElement("canvas");
        c.width = 2360;
        c.height = 258;
        const x = c.getContext("2d");
        for (let i = 0; i < 8; i++) {
          const fl = Math.sin((i / 8) * Math.PI * 2);
          x.save();
          x.translate(i * 295 + 147, 135);
          const wy = -70 - fl * 45;
          x.fillStyle = "#efe8d6";
          x.strokeStyle = "#8a8372";
          x.lineWidth = 2;
          x.beginPath();
          x.moveTo(-10, 0);
          x.lineTo(-95, wy);
          x.lineTo(35, -8);
          x.closePath();
          x.fill();
          x.stroke();
          x.fillStyle = "#e0d8c2";
          x.beginPath();
          x.moveTo(-110, 38);
          x.lineTo(-10, -8);
          x.lineTo(60, 12);
          x.lineTo(95, -48);
          x.lineTo(104, -44);
          x.lineTo(70, 24);
          x.lineTo(-20, 40);
          x.closePath();
          x.fill();
          x.stroke();
          x.fillStyle = "#f5f0e2";
          x.beginPath();
          x.moveTo(5, 0);
          x.lineTo(110, wy + 10);
          x.lineTo(50, 10);
          x.closePath();
          x.fill();
          x.stroke();
          x.fillStyle = "#3a362c";
          for (let k = 0; k < 5; k++) x.fillRect(-70 + k * 14, 12 + k * 2 - k * 0, 10, 2);
          x.restore();
        }
        return c;
      }
      function init(THREE) {
        const renderer = new THREE.WebGLRenderer({ canvas: canvas, alpha: true, antialias: true });
        renderer.setClearColor(0x000000, 0);
        const scene = new THREE.Scene();
        const cam = new THREE.OrthographicCamera(-1, 1, 1, -1, -1000, 1000);
        const vert =
          "uniform float uTime;varying vec2 vUv;void main(){vUv=uv;vec3 p=position;float ax=abs(p.x)*2.0;float flap=sin(uTime*2.4);p.y+=flap*0.045*pow(ax,1.6);p.y+=sin(uTime*1.6+p.x*6.0)*0.01;p.x+=sin(uTime*1.1+p.y*4.0)*0.008;p.x*=1.0-0.02*flap*ax;gl_Position=projectionMatrix*modelViewMatrix*vec4(p,1.0);}";
        const frag =
          "uniform sampler2D uMap;uniform float uFrame;uniform float uShadow;uniform float uOpacity;varying vec2 vUv;vec4 fr(float i,vec2 uv){float x=(mod(i,8.0)+clamp(uv.x,0.003,0.997))/8.0;return texture2D(uMap,vec2(x,clamp(uv.y,0.0,1.0)));}vec4 mf(vec2 uv){float f=uFrame;float i=floor(f);float m=smoothstep(0.55,1.0,fract(f));vec4 a=fr(i,uv);vec4 b=fr(i+1.0,uv);float al=mix(a.a,b.a,m);vec3 c=mix(a.rgb*a.a,b.rgb*b.a,m);return vec4(c/max(al,0.0001),al);}void main(){if(uShadow>0.5){float s=0.0;for(int k=0;k<12;k++){float a=float(k)*2.39996;float r=sqrt(float(k)+0.5)/3.5*0.04;vec2 o=vec2(cos(a),sin(a))*r;vec4 t=mf(vUv+o);float inside=step(0.0,vUv.x+o.x)*step(vUv.x+o.x,1.0);s+=t.a*inside;}s/=12.0;gl_FragColor=vec4(0.25,0.07,0.0,s*0.34*uOpacity);}else{vec4 c=mf(vUv);gl_FragColor=vec4(c.rgb,c.a*uOpacity);}}";
        let loaded = false;
        let dirty = true;
        let mC = null,
          mS = null;
        const tex = new THREE.TextureLoader().setCrossOrigin("anonymous").load(
          "https://agents-dev.6to9.ai/visuals/ed56d36c3c1eb464dc62253585a8c493.png",
          function () {
            loaded = true;
            dirty = true;
          },
          undefined,
          function () {
            useFallbackTex();
          },
        );
        tex.minFilter = THREE.LinearFilter;
        tex.magFilter = THREE.LinearFilter;
        tex.generateMipmaps = false;
        function useFallbackTex() {
          const t2 = new THREE.CanvasTexture(makeStrip());
          t2.minFilter = THREE.LinearFilter;
          t2.magFilter = THREE.LinearFilter;
          t2.generateMipmaps = false;
          if (mC) {
            mC.uniforms.uMap.value = t2;
            mS.uniforms.uMap.value = t2;
          }
          loaded = true;
          dirty = true;
        }
        function mk(sh) {
          return new THREE.ShaderMaterial({
            vertexShader: vert,
            fragmentShader: frag,
            transparent: true,
            depthTest: false,
            depthWrite: false,
            uniforms: {
              uMap: { value: tex },
              uTime: { value: 0 },
              uFrame: { value: 0 },
              uShadow: { value: sh },
              uOpacity: { value: 0 },
            },
          });
        }
        const geo = new THREE.PlaneGeometry(1, 1, 32, 24);
        mC = mk(0);
        mS = mk(1);
        const crane = new THREE.Mesh(geo, mC);
        const shadow = new THREE.Mesh(geo, mS);
        shadow.renderOrder = 0;
        crane.renderOrder = 1;
        scene.add(shadow);
        scene.add(crane);
        let vw = 1,
          vh = 1,
          W = 1,
          H = 1;
        function resize() {
          vw = window.innerWidth;
          vh = window.innerHeight;
          const dpr = Math.min(window.devicePixelRatio || 1, (P && P.maxDpr) || 2);
          renderer.setPixelRatio(dpr);
          renderer.setSize(vw, vh, false);
          cam.left = -vw / 2;
          cam.right = vw / 2;
          cam.top = vh / 2;
          cam.bottom = -vh / 2;
          cam.updateProjectionMatrix();
          const portrait = vw < vh;
          W = portrait ? vw * 1.05 : Math.min(vw * 0.44, (vh * 0.78 * 295) / 258);
          H = (W * 258) / 295;
          dirty = true;
        }
        window.addEventListener("resize", resize);
        resize();
        let op = 0;
        function update(t, dt) {
          const portrait = vw < vh;
          const p = (t / 11) % 1;
          const yc = -vh / 2 - H * 0.55 + p * (vh + H * 1.1);
          const amp = portrait ? 0.08 : 0.14;
          const xc = Math.sin(p * Math.PI * 1.8 + 0.5) * vw * amp;
          const rot = Math.cos(p * Math.PI * 1.8 + 0.5) * 0.1 + Math.sin(t * 0.9) * 0.035;
          crane.position.set(xc, yc, 0);
          crane.scale.set(W, H, 1);
          crane.rotation.z = rot;
          shadow.position.set(xc + W * 0.07, yc - H * 0.22, 0);
          shadow.scale.set(W * 1.05, H * 1.05, 1);
          shadow.rotation.z = rot;
          const f = (t * 8) % 8;
          for (const m of [mC, mS]) {
            m.uniforms.uTime.value = t;
            m.uniforms.uFrame.value = f;
          }
          op = loaded ? Math.min(1, op + dt * 1.2) : 0;
          if (P.reducedMotion && loaded) op = 1;
          mC.uniforms.uOpacity.value = op;
          mS.uniforms.uOpacity.value = op;
          front.style.setProperty("--y", vh / 2 - yc - H * 0.08 + "px");
        }
        let t = 0.5,
          last = performance.now();
        const still = 5.5;
        function frame(now) {
          requestAnimationFrame(frame);
          const dt = Math.min(0.05, (now - last) / 1000);
          last = now;
          if (P.reducedMotion) {
            if (dirty) {
              update(still, 1);
              renderer.render(scene, cam);
              dirty = false;
            }
            return;
          }
          if (P.paused) return;
          t += dt;
          update(t, dt);
          renderer.render(scene, cam);
        }
        requestAnimationFrame(frame);
      }
      boot();
    </script></body
  ></html
>

Made by 6to9. Back to all motion patterns.