{
  "id": "opus-showcase-07-sand-history-of-flight",
  "css": "",
  "fps": 30,
  "html": "<div style=\"left:0px;top:0px;width:1920px;height:1080px;background:#F6D49A;border-radius:0px\" data-jc-track=\"0\" data-jc-duration=\"30\" data-jc-start=\"0\" data-jc-name=\"Background / base colour\" data-jc-kind=\"shape\" data-jc-clip=\"\" id=\"shape-1\"></div><div style=\"left:0px;top:0px;width:1920px;height:1080px;overflow:hidden;box-sizing:border-box;background:transparent\" data-jc-track=\"1\" data-jc-duration=\"30\" data-jc-start=\"0\" data-jc-name=\"Sand lightbox / six poured scenes, carved details, hand sweeps (own code)\" data-jc-kind=\"custom\" data-jc-clip=\"\" id=\"custom-1\"></div><div data-jc-animation=\"{&quot;tracks&quot;:[{&quot;id&quot;:&quot;l0-kf-opacity-track&quot;,&quot;property&quot;:&quot;opacity&quot;,&quot;keyframes&quot;:[{&quot;id&quot;:&quot;l0-kf-opacity-track-key-1&quot;,&quot;time&quot;:0,&quot;value&quot;:0,&quot;easing&quot;:&quot;ease-out&quot;},{&quot;id&quot;:&quot;l0-kf-opacity-track-key-2&quot;,&quot;time&quot;:0.35,&quot;value&quot;:1,&quot;easing&quot;:&quot;ease-in-out&quot;},{&quot;id&quot;:&quot;l0-kf-opacity-track-key-3&quot;,&quot;time&quot;:2.8,&quot;value&quot;:1,&quot;easing&quot;:&quot;ease-in&quot;},{&quot;id&quot;:&quot;l0-kf-opacity-track-key-4&quot;,&quot;time&quot;:3.1,&quot;value&quot;:0,&quot;easing&quot;:&quot;ease-in-out&quot;}]},{&quot;id&quot;:&quot;l0-kf-blur-track&quot;,&quot;property&quot;:&quot;blur&quot;,&quot;keyframes&quot;:[{&quot;id&quot;:&quot;l0-kf-blur-track-key-1&quot;,&quot;time&quot;:0,&quot;value&quot;:12,&quot;easing&quot;:&quot;ease-out&quot;},{&quot;id&quot;:&quot;l0-kf-blur-track-key-2&quot;,&quot;time&quot;:0.4,&quot;value&quot;:0,&quot;easing&quot;:&quot;ease-in-out&quot;},{&quot;id&quot;:&quot;l0-kf-blur-track-key-3&quot;,&quot;time&quot;:2.8,&quot;value&quot;:0,&quot;easing&quot;:&quot;ease-in&quot;},{&quot;id&quot;:&quot;l0-kf-blur-track-key-4&quot;,&quot;time&quot;:3.1,&quot;value&quot;:12,&quot;easing&quot;:&quot;ease-in-out&quot;}]},{&quot;id&quot;:&quot;l0-kf-position-track&quot;,&quot;property&quot;:&quot;position&quot;,&quot;keyframes&quot;:[{&quot;id&quot;:&quot;l0-kf-position-track-key-1&quot;,&quot;time&quot;:0,&quot;value&quot;:{&quot;x&quot;:110,&quot;y&quot;:80},&quot;easing&quot;:&quot;ease-out&quot;},{&quot;id&quot;:&quot;l0-kf-position-track-key-2&quot;,&quot;time&quot;:0.55,&quot;value&quot;:{&quot;x&quot;:110,&quot;y&quot;:56},&quot;easing&quot;:&quot;ease-in-out&quot;}]}]}\" style=\"left:110px;top:56px;width:1300px;height:100px;color:#3A2212;font-family:Cormorant Garamond;font-size:72px;font-weight:700;text-align:left;white-space:normal;background:transparent;display:grid;align-content:center\" data-jc-track=\"3\" data-jc-duration=\"3.1\" data-jc-start=\"1\" data-jc-name=\"Era label / Long ago · The dream of Icarus\" data-jc-kind=\"text\" data-jc-clip=\"\" id=\"text-1\">Long ago · The dream of Icarus</div><div data-jc-animation=\"{&quot;tracks&quot;:[{&quot;id&quot;:&quot;l1-kf-opacity-track&quot;,&quot;property&quot;:&quot;opacity&quot;,&quot;keyframes&quot;:[{&quot;id&quot;:&quot;l1-kf-opacity-track-key-1&quot;,&quot;time&quot;:0,&quot;value&quot;:0,&quot;easing&quot;:&quot;ease-out&quot;},{&quot;id&quot;:&quot;l1-kf-opacity-track-key-2&quot;,&quot;time&quot;:0.35,&quot;value&quot;:1,&quot;easing&quot;:&quot;ease-in-out&quot;},{&quot;id&quot;:&quot;l1-kf-opacity-track-key-3&quot;,&quot;time&quot;:2.8,&quot;value&quot;:1,&quot;easing&quot;:&quot;ease-in&quot;},{&quot;id&quot;:&quot;l1-kf-opacity-track-key-4&quot;,&quot;time&quot;:3.1,&quot;value&quot;:0,&quot;easing&quot;:&quot;ease-in-out&quot;}]},{&quot;id&quot;:&quot;l1-kf-blur-track&quot;,&quot;property&quot;:&quot;blur&quot;,&quot;keyframes&quot;:[{&quot;id&quot;:&quot;l1-kf-blur-track-key-1&quot;,&quot;time&quot;:0,&quot;value&quot;:12,&quot;easing&quot;:&quot;ease-out&quot;},{&quot;id&quot;:&quot;l1-kf-blur-track-key-2&quot;,&quot;time&quot;:0.4,&quot;value&quot;:0,&quot;easing&quot;:&quot;ease-in-out&quot;},{&quot;id&quot;:&quot;l1-kf-blur-track-key-3&quot;,&quot;time&quot;:2.8,&quot;value&quot;:0,&quot;easing&quot;:&quot;ease-in&quot;},{&quot;id&quot;:&quot;l1-kf-blur-track-key-4&quot;,&quot;time&quot;:3.1,&quot;value&quot;:12,&quot;easing&quot;:&quot;ease-in-out&quot;}]},{&quot;id&quot;:&quot;l1-kf-position-track&quot;,&quot;property&quot;:&quot;position&quot;,&quot;keyframes&quot;:[{&quot;id&quot;:&quot;l1-kf-position-track-key-1&quot;,&quot;time&quot;:0,&quot;value&quot;:{&quot;x&quot;:110,&quot;y&quot;:80},&quot;easing&quot;:&quot;ease-out&quot;},{&quot;id&quot;:&quot;l1-kf-position-track-key-2&quot;,&quot;time&quot;:0.55,&quot;value&quot;:{&quot;x&quot;:110,&quot;y&quot;:56},&quot;easing&quot;:&quot;ease-in-out&quot;}]}]}\" style=\"left:110px;top:56px;width:1300px;height:100px;color:#3A2212;font-family:Cormorant Garamond;font-size:72px;font-weight:700;text-align:left;white-space:normal;background:transparent;display:grid;align-content:center\" data-jc-track=\"3\" data-jc-duration=\"3.1\" data-jc-start=\"6\" data-jc-name=\"Era label / 1783 · The first balloon\" data-jc-kind=\"text\" data-jc-clip=\"\" id=\"text-2\">1783 · The first balloon</div><div data-jc-animation=\"{&quot;tracks&quot;:[{&quot;id&quot;:&quot;l2-kf-opacity-track&quot;,&quot;property&quot;:&quot;opacity&quot;,&quot;keyframes&quot;:[{&quot;id&quot;:&quot;l2-kf-opacity-track-key-1&quot;,&quot;time&quot;:0,&quot;value&quot;:0,&quot;easing&quot;:&quot;ease-out&quot;},{&quot;id&quot;:&quot;l2-kf-opacity-track-key-2&quot;,&quot;time&quot;:0.35,&quot;value&quot;:1,&quot;easing&quot;:&quot;ease-in-out&quot;},{&quot;id&quot;:&quot;l2-kf-opacity-track-key-3&quot;,&quot;time&quot;:2.8,&quot;value&quot;:1,&quot;easing&quot;:&quot;ease-in&quot;},{&quot;id&quot;:&quot;l2-kf-opacity-track-key-4&quot;,&quot;time&quot;:3.1,&quot;value&quot;:0,&quot;easing&quot;:&quot;ease-in-out&quot;}]},{&quot;id&quot;:&quot;l2-kf-blur-track&quot;,&quot;property&quot;:&quot;blur&quot;,&quot;keyframes&quot;:[{&quot;id&quot;:&quot;l2-kf-blur-track-key-1&quot;,&quot;time&quot;:0,&quot;value&quot;:12,&quot;easing&quot;:&quot;ease-out&quot;},{&quot;id&quot;:&quot;l2-kf-blur-track-key-2&quot;,&quot;time&quot;:0.4,&quot;value&quot;:0,&quot;easing&quot;:&quot;ease-in-out&quot;},{&quot;id&quot;:&quot;l2-kf-blur-track-key-3&quot;,&quot;time&quot;:2.8,&quot;value&quot;:0,&quot;easing&quot;:&quot;ease-in&quot;},{&quot;id&quot;:&quot;l2-kf-blur-track-key-4&quot;,&quot;time&quot;:3.1,&quot;value&quot;:12,&quot;easing&quot;:&quot;ease-in-out&quot;}]},{&quot;id&quot;:&quot;l2-kf-position-track&quot;,&quot;property&quot;:&quot;position&quot;,&quot;keyframes&quot;:[{&quot;id&quot;:&quot;l2-kf-position-track-key-1&quot;,&quot;time&quot;:0,&quot;value&quot;:{&quot;x&quot;:110,&quot;y&quot;:80},&quot;easing&quot;:&quot;ease-out&quot;},{&quot;id&quot;:&quot;l2-kf-position-track-key-2&quot;,&quot;time&quot;:0.55,&quot;value&quot;:{&quot;x&quot;:110,&quot;y&quot;:56},&quot;easing&quot;:&quot;ease-in-out&quot;}]}]}\" style=\"left:110px;top:56px;width:1300px;height:100px;color:#3A2212;font-family:Cormorant Garamond;font-size:72px;font-weight:700;text-align:left;white-space:normal;background:transparent;display:grid;align-content:center\" data-jc-track=\"3\" data-jc-duration=\"3.1\" data-jc-start=\"11\" data-jc-name=\"Era label / 1891 · The glider\" data-jc-kind=\"text\" data-jc-clip=\"\" id=\"text-3\">1891 · The glider</div><div data-jc-animation=\"{&quot;tracks&quot;:[{&quot;id&quot;:&quot;l3-kf-opacity-track&quot;,&quot;property&quot;:&quot;opacity&quot;,&quot;keyframes&quot;:[{&quot;id&quot;:&quot;l3-kf-opacity-track-key-1&quot;,&quot;time&quot;:0,&quot;value&quot;:0,&quot;easing&quot;:&quot;ease-out&quot;},{&quot;id&quot;:&quot;l3-kf-opacity-track-key-2&quot;,&quot;time&quot;:0.35,&quot;value&quot;:1,&quot;easing&quot;:&quot;ease-in-out&quot;},{&quot;id&quot;:&quot;l3-kf-opacity-track-key-3&quot;,&quot;time&quot;:2.8,&quot;value&quot;:1,&quot;easing&quot;:&quot;ease-in&quot;},{&quot;id&quot;:&quot;l3-kf-opacity-track-key-4&quot;,&quot;time&quot;:3.1,&quot;value&quot;:0,&quot;easing&quot;:&quot;ease-in-out&quot;}]},{&quot;id&quot;:&quot;l3-kf-blur-track&quot;,&quot;property&quot;:&quot;blur&quot;,&quot;keyframes&quot;:[{&quot;id&quot;:&quot;l3-kf-blur-track-key-1&quot;,&quot;time&quot;:0,&quot;value&quot;:12,&quot;easing&quot;:&quot;ease-out&quot;},{&quot;id&quot;:&quot;l3-kf-blur-track-key-2&quot;,&quot;time&quot;:0.4,&quot;value&quot;:0,&quot;easing&quot;:&quot;ease-in-out&quot;},{&quot;id&quot;:&quot;l3-kf-blur-track-key-3&quot;,&quot;time&quot;:2.8,&quot;value&quot;:0,&quot;easing&quot;:&quot;ease-in&quot;},{&quot;id&quot;:&quot;l3-kf-blur-track-key-4&quot;,&quot;time&quot;:3.1,&quot;value&quot;:12,&quot;easing&quot;:&quot;ease-in-out&quot;}]},{&quot;id&quot;:&quot;l3-kf-position-track&quot;,&quot;property&quot;:&quot;position&quot;,&quot;keyframes&quot;:[{&quot;id&quot;:&quot;l3-kf-position-track-key-1&quot;,&quot;time&quot;:0,&quot;value&quot;:{&quot;x&quot;:110,&quot;y&quot;:80},&quot;easing&quot;:&quot;ease-out&quot;},{&quot;id&quot;:&quot;l3-kf-position-track-key-2&quot;,&quot;time&quot;:0.55,&quot;value&quot;:{&quot;x&quot;:110,&quot;y&quot;:56},&quot;easing&quot;:&quot;ease-in-out&quot;}]}]}\" style=\"left:110px;top:56px;width:1300px;height:100px;color:#3A2212;font-family:Cormorant Garamond;font-size:72px;font-weight:700;text-align:left;white-space:normal;background:transparent;display:grid;align-content:center\" data-jc-track=\"3\" data-jc-duration=\"3.1\" data-jc-start=\"16\" data-jc-name=\"Era label / 1903 · Powered flight\" data-jc-kind=\"text\" data-jc-clip=\"\" id=\"text-4\">1903 · Powered flight</div><div data-jc-animation=\"{&quot;tracks&quot;:[{&quot;id&quot;:&quot;l4-kf-opacity-track&quot;,&quot;property&quot;:&quot;opacity&quot;,&quot;keyframes&quot;:[{&quot;id&quot;:&quot;l4-kf-opacity-track-key-1&quot;,&quot;time&quot;:0,&quot;value&quot;:0,&quot;easing&quot;:&quot;ease-out&quot;},{&quot;id&quot;:&quot;l4-kf-opacity-track-key-2&quot;,&quot;time&quot;:0.35,&quot;value&quot;:1,&quot;easing&quot;:&quot;ease-in-out&quot;},{&quot;id&quot;:&quot;l4-kf-opacity-track-key-3&quot;,&quot;time&quot;:2.8,&quot;value&quot;:1,&quot;easing&quot;:&quot;ease-in&quot;},{&quot;id&quot;:&quot;l4-kf-opacity-track-key-4&quot;,&quot;time&quot;:3.1,&quot;value&quot;:0,&quot;easing&quot;:&quot;ease-in-out&quot;}]},{&quot;id&quot;:&quot;l4-kf-blur-track&quot;,&quot;property&quot;:&quot;blur&quot;,&quot;keyframes&quot;:[{&quot;id&quot;:&quot;l4-kf-blur-track-key-1&quot;,&quot;time&quot;:0,&quot;value&quot;:12,&quot;easing&quot;:&quot;ease-out&quot;},{&quot;id&quot;:&quot;l4-kf-blur-track-key-2&quot;,&quot;time&quot;:0.4,&quot;value&quot;:0,&quot;easing&quot;:&quot;ease-in-out&quot;},{&quot;id&quot;:&quot;l4-kf-blur-track-key-3&quot;,&quot;time&quot;:2.8,&quot;value&quot;:0,&quot;easing&quot;:&quot;ease-in&quot;},{&quot;id&quot;:&quot;l4-kf-blur-track-key-4&quot;,&quot;time&quot;:3.1,&quot;value&quot;:12,&quot;easing&quot;:&quot;ease-in-out&quot;}]},{&quot;id&quot;:&quot;l4-kf-position-track&quot;,&quot;property&quot;:&quot;position&quot;,&quot;keyframes&quot;:[{&quot;id&quot;:&quot;l4-kf-position-track-key-1&quot;,&quot;time&quot;:0,&quot;value&quot;:{&quot;x&quot;:110,&quot;y&quot;:80},&quot;easing&quot;:&quot;ease-out&quot;},{&quot;id&quot;:&quot;l4-kf-position-track-key-2&quot;,&quot;time&quot;:0.55,&quot;value&quot;:{&quot;x&quot;:110,&quot;y&quot;:56},&quot;easing&quot;:&quot;ease-in-out&quot;}]}]}\" style=\"left:110px;top:56px;width:1300px;height:100px;color:#3A2212;font-family:Cormorant Garamond;font-size:72px;font-weight:700;text-align:left;white-space:normal;background:transparent;display:grid;align-content:center\" data-jc-track=\"3\" data-jc-duration=\"3.1\" data-jc-start=\"21\" data-jc-name=\"Era label / 1939 · The jet\" data-jc-kind=\"text\" data-jc-clip=\"\" id=\"text-5\">1939 · The jet</div><div data-jc-animation=\"{&quot;tracks&quot;:[{&quot;id&quot;:&quot;l5-kf-opacity-track&quot;,&quot;property&quot;:&quot;opacity&quot;,&quot;keyframes&quot;:[{&quot;id&quot;:&quot;l5-kf-opacity-track-key-1&quot;,&quot;time&quot;:0,&quot;value&quot;:0,&quot;easing&quot;:&quot;ease-out&quot;},{&quot;id&quot;:&quot;l5-kf-opacity-track-key-2&quot;,&quot;time&quot;:0.35,&quot;value&quot;:1,&quot;easing&quot;:&quot;ease-in-out&quot;}]},{&quot;id&quot;:&quot;l5-kf-blur-track&quot;,&quot;property&quot;:&quot;blur&quot;,&quot;keyframes&quot;:[{&quot;id&quot;:&quot;l5-kf-blur-track-key-1&quot;,&quot;time&quot;:0,&quot;value&quot;:12,&quot;easing&quot;:&quot;ease-out&quot;},{&quot;id&quot;:&quot;l5-kf-blur-track-key-2&quot;,&quot;time&quot;:0.4,&quot;value&quot;:0,&quot;easing&quot;:&quot;ease-in-out&quot;}]},{&quot;id&quot;:&quot;l5-kf-position-track&quot;,&quot;property&quot;:&quot;position&quot;,&quot;keyframes&quot;:[{&quot;id&quot;:&quot;l5-kf-position-track-key-1&quot;,&quot;time&quot;:0,&quot;value&quot;:{&quot;x&quot;:110,&quot;y&quot;:80},&quot;easing&quot;:&quot;ease-out&quot;},{&quot;id&quot;:&quot;l5-kf-position-track-key-2&quot;,&quot;time&quot;:0.55,&quot;value&quot;:{&quot;x&quot;:110,&quot;y&quot;:56},&quot;easing&quot;:&quot;ease-in-out&quot;}]}]}\" style=\"left:110px;top:56px;width:1300px;height:100px;color:#FFF1D6;font-family:Cormorant Garamond;font-size:72px;font-weight:700;text-align:left;white-space:normal;background:transparent;display:grid;align-content:center\" data-jc-track=\"3\" data-jc-duration=\"4\" data-jc-start=\"26\" data-jc-name=\"Era label / 1969 · To the Moon\" data-jc-kind=\"text\" data-jc-clip=\"\" id=\"text-6\">1969 · To the Moon</div><div data-jc-animation=\"{&quot;tracks&quot;:[{&quot;id&quot;:&quot;fin-kf-opacity-track&quot;,&quot;property&quot;:&quot;opacity&quot;,&quot;keyframes&quot;:[{&quot;id&quot;:&quot;fin-kf-opacity-track-key-1&quot;,&quot;time&quot;:0,&quot;value&quot;:0,&quot;easing&quot;:&quot;ease-out&quot;},{&quot;id&quot;:&quot;fin-kf-opacity-track-key-2&quot;,&quot;time&quot;:0.35,&quot;value&quot;:1,&quot;easing&quot;:&quot;ease-in-out&quot;}]},{&quot;id&quot;:&quot;fin-kf-blur-track&quot;,&quot;property&quot;:&quot;blur&quot;,&quot;keyframes&quot;:[{&quot;id&quot;:&quot;fin-kf-blur-track-key-1&quot;,&quot;time&quot;:0,&quot;value&quot;:12,&quot;easing&quot;:&quot;ease-out&quot;},{&quot;id&quot;:&quot;fin-kf-blur-track-key-2&quot;,&quot;time&quot;:0.4,&quot;value&quot;:0,&quot;easing&quot;:&quot;ease-in-out&quot;}]},{&quot;id&quot;:&quot;fin-kf-position-track&quot;,&quot;property&quot;:&quot;position&quot;,&quot;keyframes&quot;:[{&quot;id&quot;:&quot;fin-kf-position-track-key-1&quot;,&quot;time&quot;:0,&quot;value&quot;:{&quot;x&quot;:1010,&quot;y&quot;:756},&quot;easing&quot;:&quot;ease-out&quot;},{&quot;id&quot;:&quot;fin-kf-position-track-key-2&quot;,&quot;time&quot;:0.55,&quot;value&quot;:{&quot;x&quot;:1010,&quot;y&quot;:720},&quot;easing&quot;:&quot;ease-in-out&quot;}]}]}\" style=\"left:1010px;top:720px;width:820px;height:130px;color:#FFF1D6;font-family:Cormorant Garamond;font-size:64px;font-weight:700;text-align:center;white-space:normal;background:transparent;display:grid;align-content:center\" data-jc-track=\"3\" data-jc-duration=\"2.7\" data-jc-start=\"27.3\" data-jc-name=\"Closing line / Every flight began as a dream\" data-jc-kind=\"text\" data-jc-clip=\"\" id=\"text-7\">Every flight began as a dream.</div>",
  "name": "Sand & Sky · A history of flight",
  "width": 1920,
  "format": "jsoncut-html",
  "height": 1080,
  "values": {},
  "version": 1,
  "duration": 30,
  "safeArea": {
    "top": 0,
    "left": 0,
    "right": 0,
    "bottom": 0
  },
  "variables": [],
  "customBlocks": [
    {
      "inputs": {
        "seed": 7
      },
      "adapter": "canvas-2d",
      "layerId": "custom-1",
      "javascript": "// Sand art on a lightbox: six scenes of flight are poured, carved and swept away by a hand. Own code, no presets.\nconst ctx = context, W = width, H = height, CX = W / 2, CY = H / 2;\nconst SCENE = 5, LAST = 5;\nconst hash = n => { const q = Math.sin(n * 127.1 + 311.7) * 43758.5453; return q - Math.floor(q); };\nconst span = (t, a, b) => Math.min(1, Math.max(0, (t - a) / (b - a)));\nconst ease = x => x * x * (3 - 2 * x);\nconst mk = (w, h) => { const c = document.createElement('canvas'); c.width = w; c.height = h; return c; };\n\n// ---------- textures: sand grain, soft sand puff, lightbox\nconst sandTex = mk(384, 384);\n{ const s = sandTex.getContext('2d'); s.fillStyle = '#34200F'; s.fillRect(0, 0, 384, 384);\n  for (let i = 0; i < 26000; i++) { const v = hash(i * 1.7); s.fillStyle = v > .66 ? 'rgba(120,78,40,.55)' : v > .33 ? 'rgba(18,9,3,.6)' : 'rgba(86,54,26,.5)'; s.fillRect(hash(i * 2.3) * 384, hash(i * 3.1) * 384, 1 + hash(i) * 1.6, 1 + hash(i + 5) * 1.6); } }\nconst SAND = ctx.createPattern(sandTex, 'repeat');\nconst PUFF = mk(256, 256);\n{ const s = PUFF.getContext('2d'); s.fillStyle = s.createPattern(sandTex, 'repeat'); s.fillRect(0, 0, 256, 256);\n  const gr = s.createRadialGradient(128, 128, 0, 128, 128, 128); gr.addColorStop(0, 'rgba(0,0,0,1)'); gr.addColorStop(.45, 'rgba(0,0,0,.75)'); gr.addColorStop(1, 'rgba(0,0,0,0)');\n  s.globalCompositeOperation = 'destination-in'; s.fillStyle = gr; s.fillRect(0, 0, 256, 256); }\nconst LIGHT = mk(W, H);\n{ const s = LIGHT.getContext('2d'); const gr = s.createRadialGradient(CX, CY - 20, 80, CX, CY, 1150);\n  gr.addColorStop(0, '#FFF7E6'); gr.addColorStop(.5, '#FCDDA6'); gr.addColorStop(1, '#D98A3E'); s.fillStyle = gr; s.fillRect(0, 0, W, H);\n  s.strokeStyle = 'rgba(150,90,40,.06)'; s.lineWidth = 1;\n  for (let i = 0; i < 40; i++) { const x = hash(i + 50) * W, y = hash(i + 80) * H; s.beginPath(); s.moveTo(x, y); s.lineTo(x + (hash(i) - .5) * 400, y + (hash(i + 2) - .5) * 60); s.stroke(); } }\nconst layer = mk(W, H), g = layer.getContext('2d');\nconst mask = mk(W, H), m = mask.getContext('2d');\n\n// ---------- drawing vocabulary (all on the sand layer)\nconst path = pts => { g.beginPath(); pts.forEach(([x, y], i) => i ? g.lineTo(x, y) : g.moveTo(x, y)); g.closePath(); };\nconst fill = (a = 1) => { g.globalAlpha = a; g.fillStyle = SAND; g.fill(); g.globalAlpha = 1; };\nconst line = (w, a = 1) => { g.globalAlpha = a; g.strokeStyle = SAND; g.lineWidth = w; g.lineCap = 'round'; g.lineJoin = 'round'; g.stroke(); g.globalAlpha = 1; };\nconst carve = (w, a = .92) => { g.save(); g.globalCompositeOperation = 'destination-out'; g.strokeStyle = `rgba(0,0,0,${a})`; g.lineWidth = w; g.lineCap = 'round'; g.lineJoin = 'round'; g.stroke(); g.restore(); };\nconst carveFill = (a = 1) => { g.save(); g.globalCompositeOperation = 'destination-out'; g.fillStyle = `rgba(0,0,0,${a})`; g.fill(); g.restore(); };\nconst puff = (x, y, r, a = 1) => { g.globalAlpha = a; g.drawImage(PUFF, x - r, y - r, 2 * r, 2 * r); g.globalAlpha = 1; };\nconst clearPuff = (x, y, r, a = 1) => { g.save(); g.globalCompositeOperation = 'destination-out'; g.globalAlpha = a; g.drawImage(PUFF, x - r, y - r, 2 * r, 2 * r); g.restore(); };\nconst circle = (x, y, r) => { g.beginPath(); g.arc(x, y, Math.max(0, r), 0, 6.2832); };\nconst cloud = (x, y, s, a) => { for (let j = 0; j < 6; j++) puff(x + (j - 2.5) * 70 * s, y + Math.sin(j * 2.1) * 22 * s - (j % 2) * 30 * s, (90 + hash(j + x) * 50) * s, a); };\nfunction person(x, y, s, armUp = 0, step = 0) {\n  circle(x, y - 66 * s, 10 * s); fill();\n  path([[x - 13 * s, y - 52 * s], [x + 13 * s, y - 52 * s], [x + 9 * s, y - 20 * s], [x - 9 * s, y - 20 * s]]); fill();\n  g.beginPath(); g.moveTo(x - 5 * s, y - 22 * s); g.lineTo(x - 8 * s - step * 10 * s, y); g.moveTo(x + 5 * s, y - 22 * s); g.lineTo(x + 8 * s + step * 10 * s, y); line(7 * s);\n  g.beginPath(); g.moveTo(x - 11 * s, y - 48 * s); g.lineTo(x - 22 * s, y - 48 * s - armUp * 30 * s + (1 - armUp) * 26 * s);\n  g.moveTo(x + 11 * s, y - 48 * s); g.lineTo(x + 22 * s, y - 48 * s - armUp * 34 * s + (1 - armUp) * 26 * s); line(6 * s);\n}\n\n// ---------- the six scenes, t = scene-local time\nconst icarusAt = t => [820 + t * 95, 560 - t * 55 + Math.sin(t * 2.2) * 14];\nconst scenes = [\n  t => { // the dream of Icarus\n    const sx = 1460, sy = 300;\n    for (let j = 0; j < 22; j++) { const a = j / 22 * 6.2832 + .2, rr = 330 + hash(j) * 70; puff(sx + Math.cos(a) * rr, sy + Math.sin(a) * rr * .9, 120 + hash(j + 3) * 60, .38); }\n    for (let j = 0; j < 20; j++) { const a = j / 20 * 6.2832 + t * .05; g.beginPath(); g.moveTo(sx + Math.cos(a) * 200, sy + Math.sin(a) * 200); g.lineTo(sx + Math.cos(a) * 560, sy + Math.sin(a) * 560); carve(9 + (j % 2) * 8, .8); }\n    circle(sx, sy, 172); line(6, .85);\n    g.beginPath(); g.moveTo(0, 860); for (let x = 0; x <= W; x += 40) g.lineTo(x, 852 + Math.sin(x * .01 + t * .8) * 8); g.lineTo(W, H); g.lineTo(0, H); g.closePath(); fill(.95);\n    for (let r = 0; r < 6; r++) for (let x = -60; x < W + 60; x += 92) { const y = 900 + r * 34, xx = x + (r % 2) * 46 + t * 18; g.beginPath(); g.arc(xx, y, 24, Math.PI * 1.15, Math.PI * 1.85); carve(4); }\n    path([[0, 510], [140, 530], [240, 590], [300, 690], [335, 800], [380, 900], [420, H], [0, H]]); fill();\n    g.beginPath(); g.moveTo(60, 620); g.quadraticCurveTo(170, 640, 230, 720); g.moveTo(40, 760); g.quadraticCurveTo(200, 780, 300, 880); carve(4, .7);\n    person(170, 528, 1, 1);\n    const [ix, iy] = icarusAt(t), f = Math.sin(t * 3.4);\n    g.save(); g.translate(ix, iy); g.rotate(-.3);\n    for (const s of [-1, 1]) {\n      const tip = [s * 290, -70 - f * 110], up = [s * 130, -112 - f * 60];\n      g.beginPath(); g.moveTo(s * 16, -30); g.quadraticCurveTo(up[0], up[1], tip[0], tip[1]);\n      const tr = [[s * 255, -20 - f * 72], [s * 200, 18 - f * 40], [s * 135, 30 - f * 14], [s * 70, 24]];\n      let prev = tip; for (const p of tr) { g.quadraticCurveTo((prev[0] + p[0]) / 2 + s * 4, (prev[1] + p[1]) / 2 + 22, p[0], p[1]); prev = p; }\n      g.lineTo(s * 14, 8); g.closePath(); fill();\n      for (let k = 0; k < 7; k++) { const u = (k + 1) / 8; g.beginPath(); g.moveTo(s * 24, -18); g.lineTo(s * (40 + 220 * u), -40 - f * 70 * u + 40 * u); carve(3, .75); }\n    }\n    g.beginPath(); g.ellipse(0, 22, 20, 64, 0, 0, 6.2832); fill(); circle(0, -60, 16); fill();\n    g.beginPath(); g.moveTo(-8, 80); g.lineTo(-16, 150); g.moveTo(8, 80); g.lineTo(18, 146); line(10);\n    g.restore();\n    for (let j = 0; j < 14; j++) {\n      const b = .3 + j * .33, a = t - b; if (a <= 0) continue;\n      const [bx, by] = icarusAt(b), x = bx + (hash(j) - .5) * 420 + Math.sin(a * 2.4 + j) * 36, y = by - 40 + a * 120 + a * a * 12;\n      g.save(); g.translate(x, y); g.rotate(Math.sin(a * 3 + j) * .8 + .4); g.beginPath(); g.ellipse(0, 0, 7, 22, 0, 0, 6.2832); fill(.85);\n      g.beginPath(); g.moveTo(0, -20); g.lineTo(0, 20); carve(1.6, .8); g.restore();\n    }\n  },\n  t => { // 1783 · the first balloon\n    cloud(330 + t * 12, 330, 1, .38); cloud(1600 + t * 9, 210, 1.2, .38); cloud(1290 + t * 14, 470, .7, .3); cloud(560 + t * 10, 600, .55, .28);\n    let x = -30, i = 0;\n    while (x < W + 30) {\n      const w = 90 + hash(i * 3.1) * 90, near = Math.abs(x + w / 2 - 960) < 260;\n      const h = near ? 70 + hash(i) * 30 : 130 + hash(i + 7) * 150, top = 960 - h, type = i % 5 === 3 ? 2 : i % 2;\n      g.beginPath(); g.rect(x, top, w + 1, H - top);\n      if (type === 0) { g.moveTo(x - 6, top); g.lineTo(x + w / 2, top - 46); g.lineTo(x + w + 6, top); }\n      if (type === 2 && !near) { g.moveTo(x + w / 2 - 14, top); g.lineTo(x + w / 2, top - 120); g.lineTo(x + w / 2 + 14, top); }\n      if (type === 1) g.rect(x + w * .7, top - 30, 16, 30);\n      fill();\n      for (let r = 0; r < Math.floor((h - 30) / 42); r++) for (let c = 0; c < Math.floor((w - 24) / 34); c++) if (hash(i * 31 + r * 7 + c) > .45) { g.beginPath(); g.rect(x + 16 + c * 34, top + 22 + r * 42, 12, 18); carveFill(.85); }\n      x += w; i++;\n    }\n    for (let j = 0; j < 34; j++) { const hx = j * 58 + hash(j) * 20, hy = 1012 + hash(j + 2) * 18; circle(hx, hy, 17); fill(); g.beginPath(); g.ellipse(hx, hy + 52, 30, 44, 0, 0, 6.2832); fill(); if (j % 4 === 1) { g.beginPath(); g.moveTo(hx + 18, hy + 20); g.lineTo(hx + 34, hy - 30 - Math.sin(t * 6 + j) * 8); line(8); } }\n    const bx = 960 + Math.sin(t * .9) * 12, by = 400 - t * 25;\n    g.beginPath(); g.moveTo(bx - 70, by + 300); g.bezierCurveTo(bx - 150, by + 190, bx - 215, by + 80, bx - 210, by - 10);\n    g.arc(bx, by - 10, 210, Math.PI, 0); g.bezierCurveTo(bx + 215, by + 80, bx + 150, by + 190, bx + 70, by + 300); g.closePath(); fill();\n    for (let j = 1; j <= 3; j++) for (const s of [-1, 1]) { g.beginPath(); g.ellipse(bx, by - 10, 210 * j / 4, 212, 0, Math.PI * 1.5, s < 0 ? Math.PI : Math.PI * 2, s < 0); g.bezierCurveTo(bx + s * 210 * j / 4, by + 120, bx + s * 120 * j / 4, by + 220, bx + s * 70 * j / 4, by + 298); carve(4, .7); }\n    g.beginPath(); for (let k = 0; k <= 24; k++) { const xx = bx - 185 + k * (370 / 24); g.lineTo(xx, by + 92 + (k % 2 ? 14 : -14)); } carve(6, .9);\n    for (let k = 0; k < 9; k++) { circle(bx - 120 + k * 30, by + 150, 5); carveFill(.9); }\n    circle(bx, by - 60, 46); carve(7); circle(bx, by - 60, 14); carveFill();\n    for (let k = 0; k < 12; k++) { const a = k / 12 * 6.2832; g.beginPath(); g.moveTo(bx + Math.cos(a) * 58, by - 60 + Math.sin(a) * 58); g.lineTo(bx + Math.cos(a) * 80, by - 60 + Math.sin(a) * 80); carve(5); }\n    g.beginPath(); g.moveTo(bx - 70, by + 300); g.lineTo(bx - 46, by + 372); g.moveTo(bx + 70, by + 300); g.lineTo(bx + 46, by + 372); g.moveTo(bx, by + 300); g.lineTo(bx, by + 372); line(3);\n    g.beginPath(); g.rect(bx - 50, by + 370, 100, 60); fill();\n    for (let k = 1; k < 4; k++) { g.beginPath(); g.moveTo(bx - 46, by + 370 + k * 15); g.lineTo(bx + 46, by + 370 + k * 15); carve(2.5, .7); }\n    circle(bx - 20, by + 360, 11); fill(); circle(bx + 20, by + 357, 11); fill();\n  },\n  t => { // 1891 · the glider\n    g.beginPath(); g.moveTo(0, H); for (let x = 0; x <= W; x += 30) g.lineTo(x, 760 - Math.sin(x * .004 + 1) * 40 - x * .03); g.lineTo(W, H); g.closePath(); fill(.35);\n    g.beginPath(); g.moveTo(0, 520); g.quadraticCurveTo(420, 500, 760, 700); g.quadraticCurveTo(1100, 920, 1500, H); g.lineTo(0, H); g.closePath(); fill();\n    const hill = u => { const a = u < .5 ? u * 2 : (u - .5) * 2; if (u < .5) { const p = [0, 520], c = [420, 500], q = [760, 700]; return [(1 - a) * (1 - a) * p[0] + 2 * (1 - a) * a * c[0] + a * a * q[0], (1 - a) * (1 - a) * p[1] + 2 * (1 - a) * a * c[1] + a * a * q[1]]; } const p = [760, 700], c = [1100, 920], q = [1500, H]; return [(1 - a) * (1 - a) * p[0] + 2 * (1 - a) * a * c[0] + a * a * q[0], (1 - a) * (1 - a) * p[1] + 2 * (1 - a) * a * c[1] + a * a * q[1]]; };\n    for (let j = 0; j < 90; j++) { const [x, y] = hill(j / 90), sw = Math.sin(t * 2 + j * .7) * 5; g.beginPath(); g.moveTo(x, y + 2); g.lineTo(x - 6 + sw, y - 18 - hash(j) * 14); line(3); g.beginPath(); g.moveTo(x + 8, y + 30 + hash(j + 1) * 60); g.lineTo(x + 2, y + 16 + hash(j + 1) * 60); carve(3, .6); }\n    person(230, 517, 1, 1); person(300, 514, .9, .3);\n    for (let j = 0; j < 4; j++) { const x = 1560 - t * 40 + j * 120, y = 190 + j * 36 + (j % 2) * 20, f = Math.sin(t * 7 + j * 1.3) * 12; g.beginPath(); g.moveTo(x - 28, y - f); g.quadraticCurveTo(x - 12, y - 14, x, y); g.quadraticCurveTo(x + 12, y - 14, x + 28, y - f); line(4); }\n    const gx = 560 + t * 170, gy = 420 + t * 45 + Math.sin(t * 1.6) * 10;\n    g.save(); g.translate(gx, gy); g.rotate(.08 + Math.sin(t * 1.2) * .05); g.scale(1.25, 1.25);\n    for (const s of [-1, 1]) {\n      g.beginPath(); g.moveTo(0, -4); g.quadraticCurveTo(s * 120, -80, s * 300, -34);\n      let prev = [s * 300, -34]; for (const p of [[s * 230, 0], [s * 160, 14], [s * 90, 18], [s * 20, 14]]) { g.quadraticCurveTo((prev[0] + p[0]) / 2, (prev[1] + p[1]) / 2 - 18, p[0], p[1]); prev = p; }\n      g.closePath(); fill();\n      for (let k = 1; k <= 5; k++) { const u = k / 6; g.beginPath(); g.moveTo(s * 6, -2); g.lineTo(s * 300 * u, -34 - Math.sin(u * Math.PI) * 30 + u * 6); carve(2.6, .8); }\n    }\n    circle(0, 34, 11); fill(); g.beginPath(); g.moveTo(0, 42); g.lineTo(0, 92); line(12); g.beginPath(); g.moveTo(-3, 90); g.lineTo(-12 + Math.sin(t * 3) * 4, 136); g.moveTo(3, 90); g.lineTo(13 - Math.sin(t * 3) * 4, 132); line(8);\n    g.beginPath(); g.moveTo(-6, 52); g.lineTo(-60, 4); g.moveTo(6, 52); g.lineTo(60, 4); line(5);\n    g.restore();\n    for (let j = 0; j < 5; j++) { const y = 300 + j * 70, x0 = 1200 + hash(j) * 400 - t * 30; g.beginPath(); g.moveTo(x0, y); g.quadraticCurveTo(x0 + 120, y - 16, x0 + 260, y); line(2.5, .35); }\n  },\n  t => { // 1903 · powered flight\n    cloud(1500, 230, 1, .3); cloud(420, 300, .7, .25);\n    g.beginPath(); g.moveTo(0, H); for (let x = 0; x <= W; x += 30) g.lineTo(x, 790 + Math.sin(x * .003 + 2) * 30); g.lineTo(W, H); g.closePath(); fill(.4);\n    g.beginPath(); g.moveTo(0, 880); g.quadraticCurveTo(700, 800, 1400, 900); g.quadraticCurveTo(1700, 930, W, 860); g.lineTo(W, H); g.lineTo(0, H); g.closePath(); fill();\n    for (let r = 0; r < 6; r++) { g.beginPath(); for (let x = 0; x <= W; x += 24) g.lineTo(x, 930 + r * 26 + Math.sin(x * .02 + r) * 5); carve(2.5, .55); }\n    g.beginPath(); g.moveTo(150, 868); g.lineTo(620, 838); carve(6);\n    person(250, 862, .95, .2); person(330, 858, .95, 1); person(395, 856, .9, .5);\n    const run = Math.sin(t * 9); person(470 + Math.min(t, 3) * 80, 852, .95, .6, run);\n    const px = 420 + t * 230, py = 660 - t * 24 - Math.sin(t * 1.4) * 6;\n    for (let j = 0; j < 6; j++) { g.beginPath(); const y = py - 60 + j * 26; g.moveTo(px - 330 - j * 30, y); g.lineTo(px - 500 - j * 50 - hash(j) * 120, y); line(2.5, .35); }\n    g.save(); g.translate(px, py); g.scale(1.2, 1.2);\n    g.beginPath(); g.roundRect(-260, -72, 520, 20, 10); g.roundRect(-260, -2, 520, 20, 10); fill();\n    g.beginPath(); for (const x of [-240, -150, -60, 60, 150, 240]) { g.moveTo(x, -54); g.lineTo(x, -2); } line(4);\n    g.beginPath(); g.moveTo(260, -40); g.lineTo(310, -40); g.moveTo(260, 4); g.lineTo(310, -20); line(4);\n    g.beginPath(); g.roundRect(300, -50, 110, 12, 6); g.roundRect(300, -20, 110, 12, 6); fill();\n    g.beginPath(); g.moveTo(-260, -40); g.lineTo(-360, -36); g.moveTo(-260, 8); g.lineTo(-360, -6); line(4);\n    g.beginPath(); g.roundRect(-382, -68, 14, 72, 6); g.roundRect(-362, -68, 14, 72, 6); fill();\n    g.beginPath(); g.ellipse(10, -12, 42, 10, 0, 0, 6.2832); fill(); circle(44, -18, 10); fill();\n    for (const x of [-110, 110]) { g.beginPath(); g.ellipse(x, -26, 10, 50, 0, 0, 6.2832); fill(.35); const a = t * 38 + x; g.beginPath(); g.moveTo(x, -26 - 46 * Math.cos(a)); g.lineTo(x, -26 + 46 * Math.cos(a)); line(6); }\n    g.restore();\n  },\n  t => { // 1939 · the jet\n    const p0 = [260, 790], jx = 260 + t * 270, jy = 790 - t * 95, ang = Math.atan2(-95, 270);\n    for (let r = 0; r < 3; r++) for (let x = -80; x < W + 120; x += 90) puff(x + Math.sin(x + r) * 20, 760 + r * 110 + Math.sin(x * .01 + r * 2) * 30, 150 + hash(x + r) * 70 + r * 20, [.5, .72, .95][r]);\n    g.beginPath(); g.moveTo(p0[0] - 40, p0[1] + 14); g.lineTo(jx, jy); carve(26, .55);\n    const n = Math.floor(t * 270 / 16);\n    for (let k = 0; k < n; k++) { const u = k / Math.max(1, n), x = p0[0] + (jx - p0[0]) * u, y = p0[1] + (jy - p0[1]) * u, age = (1 - u) * t; puff(x - 30, y + 10, 10 + age * 9, Math.max(0, .42 - age * .05)); }\n    for (let k = 0; k < 9; k++) { const x = 1200 + k * 80 + Math.sin(k) * 30, base = 1000 - hash(k) * 160; g.beginPath(); g.rect(x, base, 60, H - base); fill(); }\n    g.save(); g.translate(jx, jy); g.rotate(ang); g.scale(1.45, 1.45);\n    path([[195, 0], [170, -12], [-150, -14], [-178, -6], [-178, 6], [-150, 14], [170, 12]]); fill();\n    path([[40, -10], [-60, -150], [-100, -150], [-24, -10]]); fill(); path([[40, 10], [-60, 150], [-100, 150], [-24, 10]]); fill();\n    path([[-140, -8], [-182, -62], [-200, -62], [-176, -6]]); fill(); path([[-140, 8], [-182, 62], [-200, 62], [-176, 6]]); fill();\n    g.beginPath(); g.moveTo(130, -6); g.lineTo(165, -4); carve(4); g.beginPath(); g.moveTo(-140, 0); g.lineTo(100, 0); carve(2.5, .6);\n    g.restore();\n  },\n  t => { // 1969 · to the Moon\n    g.globalAlpha = .93; g.fillStyle = SAND; g.fillRect(0, 0, W, H); g.globalAlpha = 1;\n    for (let j = 0; j < 170; j++) { const x = hash(j * 4.1) * W, y = hash(j * 5.3) * 860; if (x > 980 && y > 660) continue; if (Math.hypot(x - 1460, y - 290) < 260) continue; circle(x, y, (1.2 + hash(j) * 2.4) * (.7 + .3 * Math.sin(t * 3 + j))); carveFill(.9); }\n    for (let j = 0; j < 6; j++) { const x = 200 + hash(j + 90) * 1500, y = 80 + hash(j + 70) * 420; if (Math.hypot(x - 1460, y - 290) < 300) continue; g.beginPath(); g.moveTo(x - 14, y); g.lineTo(x + 14, y); g.moveTo(x, y - 14); g.lineTo(x, y + 14); carve(2.4); }\n    clearPuff(1460, 290, 340, .4); circle(1460, 290, 190); carveFill();\n    for (const [dx, dy, r] of [[-60, -40, 50], [50, 30, 38], [-20, 86, 30], [84, -74, 26], [-104, 40, 22], [10, -110, 18]]) puff(1460 + dx, 290 + dy, r * 1.4, .35);\n    for (let x = -100; x < W + 100; x += 120) clearPuff(x, 938, 120, .16);\n    g.beginPath(); for (const x of [640, 700]) { g.moveTo(x, 930); g.lineTo(x, 520); } for (let y = 930; y > 540; y -= 40) { g.moveTo(640, y); g.lineTo(700, y - 40); g.moveTo(700, y); g.lineTo(640, y - 40); } g.moveTo(700, 640); g.lineTo(736, 640); g.moveTo(700, 760); g.lineTo(736, 760); carve(4);\n    const u = span(t, .9, 5), ry = 820 - u * u * 780, rx = 760;\n    for (let j = 0; j < 30; j++) { const age = Math.max(0, t - .7 - j * .05); if (age <= 0) continue; const side = j % 2 ? 1 : -1; clearPuff(rx + side * (20 + age * (side < 0 ? 80 : 25) * (.3 + hash(j))), 925 - hash(j + 3) * 70 - age * 14 * hash(j + 6), (70 + age * 34 * (.6 + hash(j + 9))) * (side < 0 ? 1 : .6), .4); }\n    for (let k = 0; k < 18; k++) { const y = ry + 200 + k * 40; if (y > 930) break; clearPuff(rx + Math.sin(k + t) * 6, y, 30 + k * 4, .5); }\n    if (u > 0 || t > .7) { const fl = 110 + Math.sin(t * 30) * 18 + u * 60; g.beginPath(); g.moveTo(rx - 20, ry + 130); g.quadraticCurveTo(rx, ry + 130 + fl * 1.3, rx + 20, ry + 130); g.closePath(); carveFill(.95); }\n    path([[rx, ry - 170], [rx + 24, ry - 110], [rx + 24, ry + 120], [rx + 46, ry + 140], [rx + 24, ry + 132], [rx - 24, ry + 132], [rx - 46, ry + 140], [rx - 24, ry + 120], [rx - 24, ry - 110]]); carveFill();\n    g.beginPath(); g.moveTo(rx - 24, ry - 60); g.lineTo(rx + 24, ry - 60); g.moveTo(rx - 24, ry + 30); g.lineTo(rx + 24, ry + 30); line(4, .9); circle(rx, ry - 20, 8); fill(.9);\n  },\n];\n\n// ---------- hand sweeping sand across the glass (screen space)\nconst edgeAt = (X, y, k) => X + 70 * Math.sin(y * .0045 + k * 1.7) + 160 * ((y - 560) / 560) ** 2;\nfunction sweptPath(c, X, k, map) {\n  c.beginPath(); c.moveTo(...map(W + 400, -20));\n  for (let y = -20; y <= H + 20; y += 30) c.lineTo(...map(edgeAt(X, y, k), y));\n  c.lineTo(...map(W + 400, H + 20)); c.closePath();\n}\nfunction hand(X) {\n  ctx.save(); ctx.translate(X + 420, 1140); ctx.rotate(-.5); ctx.scale(1.4, 1.4); ctx.translate(-5000, 0);\n  ctx.shadowColor = 'rgba(26,12,5,.88)'; ctx.shadowBlur = 26; ctx.shadowOffsetX = 5000 * 1.4 * Math.cos(-.5); ctx.shadowOffsetY = 5000 * 1.4 * Math.sin(-.5);\n  ctx.fillStyle = '#000'; ctx.beginPath(); ctx.ellipse(0, -260, 160, 190, 0, 0, 6.2832); ctx.rect(-120, -140, 240, 900);\n  [[-105, 230, -.12], [-35, 270, -.04], [35, 255, .04], [100, 200, .12]].forEach(([x, l, a]) => { ctx.save(); ctx.translate(x, -380); ctx.rotate(a); ctx.roundRect(-31, -l, 62, l + 40, 31); ctx.restore(); });\n  ctx.save(); ctx.translate(-140, -200); ctx.rotate(-.9); ctx.roundRect(-35, -200, 70, 220, 35); ctx.restore();\n  ctx.fill(); ctx.restore();\n}\nfunction streaks(alpha) {\n  ctx.save(); ctx.globalAlpha = alpha; ctx.strokeStyle = SAND; ctx.lineCap = 'round';\n  for (let j = 0; j < 90; j++) { const y = hash(j * 1.3) * H, x = hash(j * 2.9) * W; ctx.lineWidth = 1.5 + hash(j + 4) * 3; ctx.beginPath(); ctx.moveTo(x, y); ctx.lineTo(x + 90 + hash(j + 8) * 320, y + (hash(j + 1) - .5) * 10); ctx.stroke(); }\n  ctx.restore();\n}\n\njsoncut.onFrame(() => {\n  const t = jsoncut.time, k = Math.min(LAST, Math.floor(t / SCENE)), lt = t - k * SCENE;\n  ctx.drawImage(LIGHT, 0, 0);\n  if (k > 0 && lt < 1.3) streaks(.2 * (1 - lt / 1.3));\n  // scene layer: poured, carved, revealed\n  g.setTransform(1, 0, 0, 1, 0, 0); g.globalCompositeOperation = 'source-over'; g.globalAlpha = 1; g.clearRect(0, 0, W, H);\n  const z = 1 + .03 * Math.min(lt, 4.1) / 4.1;\n  g.translate(CX, CY); g.scale(z, z); g.translate(-CX, -CY);\n  scenes[k](lt);\n  g.setTransform(1, 0, 0, 1, 0, 0);\n  if (lt < 1.75) {\n    // sand is poured from left to right: a soft wavy front with a trickle of loose grains ahead of it\n    const F = -260 + (W + 520) * ease(span(lt, .05, 1.7)), front = y => F + 60 * Math.sin(y * .006 + k * 2.1) + 28 * Math.sin(y * .017 + k);\n    m.setTransform(1, 0, 0, 1, 0, 0); m.clearRect(0, 0, W, H);\n    m.save(); m.fillStyle = '#000'; m.shadowColor = '#000'; m.shadowBlur = 46; m.beginPath(); m.moveTo(-80, -80);\n    for (let y = -80; y <= H + 80; y += 24) m.lineTo(front(y) - 40, y); m.lineTo(-80, H + 80); m.closePath(); m.fill(); m.restore();\n    for (let j = 0; j < 700; j++) { const y = hash(j * 1.13 + k * 7) * H, o = hash(j * 2.71 + k) * 170; m.globalAlpha = Math.max(0, 1 - o / 170); m.fillStyle = '#000'; m.fillRect(front(y) - 40 + o, y, 2 + hash(j) * 3, 2 + hash(j + 1) * 3); }\n    m.globalAlpha = 1;\n    g.globalCompositeOperation = 'destination-in'; g.drawImage(mask, 0, 0); g.globalCompositeOperation = 'source-over';\n  }\n  const sweeping = k < LAST && lt > 4.1, X = W + 250 - (W + 600) * ease(span(lt, 4.1, 5));\n  if (sweeping) {\n    const toLayer = (x, y) => [(x - CX) / z + CX, (y - CY) / z + CY];\n    g.save(); g.globalCompositeOperation = 'destination-out'; g.fillStyle = '#000'; g.shadowColor = '#000'; g.shadowBlur = 30; sweptPath(g, X, k, toLayer); g.fill(); g.restore();\n  }\n  ctx.drawImage(layer, 0, 0);\n  if (sweeping) {\n    ctx.save(); sweptPath(ctx, X, k, (x, y) => [x, y]); ctx.clip(); streaks(.22); ctx.restore();\n    // the pushed sand piles up in a grainy ridge along the edge of the hand\n    ctx.save(); for (let y = -20; y <= H + 20; y += 22) { const r = 22 + hash(y + k) * 16; ctx.globalAlpha = .55 + hash(y * 3 + k) * .3; ctx.drawImage(PUFF, edgeAt(X, y, k) - 22 - r, y - r, 2 * r, 2 * r); } ctx.restore();\n    hand(X);\n  }\n  // light breathing, grain of the glass and a soft vignette\n  ctx.fillStyle = `rgba(255,236,200,${.025 * hash(Math.floor(t * 12))})`; ctx.fillRect(0, 0, W, H);\n  const v = ctx.createRadialGradient(CX, CY, H * .42, CX, CY, W * .62); v.addColorStop(0, 'rgba(0,0,0,0)'); v.addColorStop(1, 'rgba(70,30,8,.32)');\n  ctx.fillStyle = v; ctx.fillRect(0, 0, W, H);\n});\n"
    }
  ],
  "aspectVariants": [],
  "compositionKind": "video",
  "canvasBackground": "#000000",
  "editabilityTarget": "hybrid"
}
