oneshotlm
Compare all models on this prompt →

Animated loader · kimi-k2.7-code

Using the pre-provided gsap.min.js (global gsap), create a looping animated loading sequence on a dark background: morphing rounded shapes and a staggered row of dots orchestrated on a GSAP timeline with smooth easing.

⛨ sandboxed Open in new tab ↗
transcript · 14 messages
user

Using the pre-provided gsap.min.js (global gsap), create a looping animated loading sequence on a dark background: morphing rounded shapes and a staggered row of dots orchestrated on a GSAP timeline with smooth easing.

assistant
thinking
 We need respond to user. They want single-page web app, index.html in current directory. Current dir /workspace. Use gsap.min.js already present. Use plain <script src="gsap.min.js"></script>. Build looping loading sequence: dark background, morphing rounded shapes, staggered row of dots orchestrated on GSAP timeline.

We need check directory files maybe. Let's run ls. Then create index.html. Need no external resources, CDNs. Use GSAP global. Implement within one file with style and script. Could use SVG for morphing rounded shapes. GSAP has morphSVG plugin? gsap.min.js alone doesn't include morphSVG plugin. Wait the user said morphing rounded shapes; with gsap.min.js global, no MorphSVG plugin. Need achieve morphing without plugin. We can animate border-radius and scale to make a rounded shape morph from circle to rounded-rect/blob. Could use multiple divs with border-radius changes. Need use GSAP timeline smoothly. Staggered row of dots on timeline. All via CSS transforms, border-radius etc.

Need ensure self-contained, dark background. Use relative paths. Load gsap.min.js. Let's inspect existing file maybe to verify present and version.

bash {"command":"ls -la /workspace","timeout":10}
toolResult

total 132 drwxrwxrwx 3 node node 96 Jul 8 00:39 . drwxr-xr-x 1 root root 4096 Jul 8 00:39 .. -rw-r--r-- 1 node node 72214 Jul 8 00:39 gsap.min.js

assistant
thinking
  Need create index.html. Let's think about content.

Design:
- dark background (like #0b0d12).
- centered scene.
- Top: "Loading" text optional.
- Morphing rounded shapes: container with large shape (maybe 200x200) that animates border-radius and rotation to create organic rounded square/blobs. Use pseudo-element or actual div with gradient. Use CSS filter for blur maybe shapes. But user requested morphing rounded shapes; could make a few "blobs" using border-radius animation and scaling/rotating.
- Staggered row of dots below: six dots that scale up/down with stagger, opacity/fade.
- Orchestrated on GSAP timeline: use main timeline repeat -1 yoyo or looping. Include shape morphing and dot stagger with labels. Use smooth ease e.g. "power2.inOut", "sine.inOut", "expo.inOut".

Need script loads gsap.min.js. Let's check version maybe 3.x. Use gsap.timeline etc. Could use `document.addEventListener('DOMContentLoaded')`.

Detailed timeline:
- initial: shapes rotated, border-radius etc.
- Phase 1: 2 seconds large blob rotates -90 to 90, borderRadius morphs from 50% to 30% 70% etc. Scale pulse 1 to 1.15 to 1.
- second smaller blob offset.
- Dots stagger: scale from .4 to 1, y -12 to 0, opacity .3 to 1, stagger .1, eased sine.inOut, repeat yoyo within timeline maybe using nested timeline? Since parent timeline loops, we can place dots animation spanning section and set repeat on their own? In GSAP, timeline child tween with repeat -1 and yoyo true will keep repeating but timeline waits? Actually if tween has repeat -1 and is in timeline, timeline won't wait, but it will continue. We'll just do one cycle stagger and let main timeline yoyo repeat: main timeline will reverse, so dots will animate back as part of loop. If we use yoyo and repeat on main timeline, good stagger both directions. But text maybe not. Could also have dots tween with repeat: -1 and yoyo within their own separate timeline, independent, but main provides the shape morphing. User wants orchestrated on GSAP timeline, so include dots in same timeline with stagger.

Timeline:
```
const tl = gsap.timeline({ repeat: -1, yoyo: true, defaults: { ease: "power2.inOut" } });
tl.to(blob1, { rotation: 180, borderRadius: "50% 30% 60% 40%", scale: 1.1, duration: 2 }, 0)
  .to(blob2, { rotation: -180, borderRadius: "30% 50% 40% 60%", scale: 0.9, duration: 2 }, 0)
  .to(".dot", { y: -20, scale: 1.4, opacity:1, stagger: { each:0.12, yoyo:true, repeat:1 }, duration: 0.6, ease:"sine.inOut" }, 0.2)
```
With main timeline yoyo true, dot stagger repeats? Hmm if we set yoyo/repeat in stagger, that would define the stagger itself. Need be careful. If main timeline yoyo, the main timeline will reverse after reaching end; tween will play backwards, causing dots to go backwards (descale). The stagger with repeat and yoyo would create two cycles in forward direction, maybe odd effect.

Better: Use a separate `gsap.timeline({ repeat:-1, yoyo:true })` for dots but `master.add(dotsTl, 0)` to orchestrate. The requirement: "staggered row of dots orchestrated on a GSAP timeline with smooth easing." Could use master timeline with all tweens. Use stagger in a tween inside the master. Repeat master with yoyo.

Let's plan visual.

HTML:
```
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Morphing Loader</title>
<style>
  * { box-sizing: border-box; }
  html,body { margin:0; width:100%; height:100%; background:#0b0d12; overflow:hidden; color:#c9d6e3; font-family: system-ui, sans-serif; }
  .loader { display:flex; flex-direction:column; align-items:center; justify-content:center; position:relative; width:100%; height:100%; }
  .artboard { position:relative; width:280px; height:280px; margin-bottom:30px; }
  .blob { position:absolute; top:50%; left:50%; transform:translate(-50%,-50%); background:radial-gradient(circle at 30% 30%, ...); filter: blur(0px); }
  .blob1 { width:160px;height:160px; background: linear-gradient(135deg, #7c3aed, #c026d3); opacity:.9; }
  .blob2 { width:120px;height:120px; background: linear-gradient(135deg, #2563eb, #06b6d4); opacity:.85; }
  .blob3 { width:80px;height:80px; background: linear-gradient(135deg, #10b981, #84cc16); opacity:.8; }
  .dots { display:flex; gap:14px; }
  .dot { width:14px; height:14px; border-radius:50%; background:#e2e8f0; opacity:.35; transform-origin:center; }
  .status { margin-top:24px; font-size:14px; letter-spacing: .2em; text-transform:uppercase; opacity:.7; }
</style>
</head>
<body>
<div class="loader">
  <div class="artboard">
    <div class="blob blob1"></div>
    <div class="blob blob2"></div>
    <div class="blob blob3"></div>
  </div>
  <div class="dots">
    <div class="dot"></div><div class="dot"></div><div class="dot"></div><div class="dot"></div><div class="dot"></div><div class="dot"></div>
  </div>
  <div class="status">Loading</div>
</div>
<script src="gsap.min.js"></script>
<script>
 document.addEventListener('DOMContentLoaded', () => {
   const master = gsap.timeline({ repeat:-1, yoyo:true, defaults:{duration:2, ease:"power2.inOut"} });
   master.to('.blob1', { rotation:135, borderRadius:"40% 60% 60% 40% / 60% 40% 60% 40%", scale:1.15 }, 0)
         .to('.blob2', { rotation:-160, borderRadius:"60% 40% 40% 60% / 40% 60% 40% 60%", scale:.85 }, 0)
         .to('.blob3', { rotation:180, borderRadius:"50% 50% 40% 60% / 40% 60% 60% 40%", scale:1.05 }, 0)
         .to('.dot', { y:-18, scale:0.4, opacity:1, stagger:{ each:0.08, from:"center" }, duration:.8, ease:"back.inOut(1.7)" }, 0.2);
 });
</script>
</body></html>
```

But with `borderRadius` CSS syntax like "40% 60% 60% 40% / 60% 40% 60% 40%" strings. GSAP can animate strings? `borderRadius` as string only if both start/end string values. If start values set in CSS standard `50%`; end complex string. GSAP uses an end value with a complex string may just set final. For smooth morph, both should have compatible string with same number of values. We can start with `border-radius: 50%` in CSS, but GSAP animating to complex string may interpolate to complex? GSAP treats borderRadius as normal property; if start is not set inline, it will read computed pixel value? But morphing border-radius from 50% to complex percent might be okay? Might need set initial via `.set` to a complex string and animate to another complex string. Or set initial borderRadius in CSS as `50% / 50%` etc. Better use gsap.set on elements for start borderRadius and then animate to end. Since shape morphing requires start and end strings with equal length.

Could do `gsap.set('.blob', { borderRadius: ... })` and `.to` to another. Also rotation/transform with `transform: translate`? Setting borderRadius won't conflict.

Also need to handle `top:50% left:50% transform:translate(-50%,-50%)`; if we set rotation/scale via GSAP transform, it overwrites translate because GSAP uses transform inline? Actually CSS transform from class remains if not set via GSAP? GSAP reads current transform; it can combine. But it may convert translate(-50%,-50%) into xPercent/yPercent? Could cause issue. Cleaner: Position blobs with flex in artboard, use `position:absolute; top:0;left:0; right:0;bottom:0; margin:auto;` to center without transform. Then GSAP rotation/scale works around center. Use `margin:auto` with absolute positioning. That is stable.

Let's consider shape positions maybe overlapping. It's fine.

Add `will-change: transform` for performance. Use CSS gradients. Could use `mix-blend-mode: screen` for overlapping colors. But on dark background gradients okay.

The instructions say "morphing rounded shapes and a staggered row of dots orchestrated on a GSAP timeline with smooth easing." We should emphasize timeline with labels maybe. Let's build a richer timeline with labels:
- `master.fromTo('.blob1', { borderRadius: '50% 50% 50% 50% / 50% 50% 50% 50%', rotation:0, scale:1 }, { borderRadius:'...', rotation:120, scale:1.2, duration:2.5 }, 'start')`
- Use fromTo for all with explicit states.
- Dots tween with stagger inside timeline.

Since user wants "looping animated loading sequence" maybe main timeline `repeat:-1` but yoyo true will reverse and create continuous loop. Could also not yoyo and use a `onRepeat`? But yoyo is easiest. Need ensure no visual jump when reversing; the start/end states are symmetrical? With yoyo, it will reverse exactly. Good.

Potential issue: borderRadius strings with slash between horizontal/vertical radii. Both start and end exactly same pattern. For morphing to appear organic, animate between different asymmetrical forms.

One concern: using `background: radial-gradient(circle at 30% 30%, ...)` and `filter: blur` can be heavy; avoid blur for crispness. Could use `box-shadow: 0 0 40px ...` for glow.

Need add `aria-label` or minimal. Not necessary.

Let's ensure GSAP exists. Use `window.gsap?`. Use fromTo. Good.

Let's design final index.html.

Let's include basic CSS reset. Use full viewport. Maybe dark radial background from #0a0c10 to #151922 for depth.

Detailed code:

```
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Morphing GSAP Loader</title>
  <style>
    * { box-sizing: border-box; }
    html, body {
      margin: 0;
      width: 100%;
      height: 100%;
      background: radial-gradient(circle at 50% 50%, #151922 0%, #0a0c10 100%);
      overflow: hidden;
      font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
      color: #e2e8f0;
    }
    .loader {
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      width: 100%;
      height: 100%;
      gap: 30px;
    }
    .stage {
      position: relative;
      width: 320px;
      height: 320px;
    }
    .blob {
      position: absolute;
      top: 0; left: 0; right: 0; bottom: 0;
      margin: auto;
      width: 160px;
      height: 160px;
      will-change: transform, border-radius;
    }
    .blob-a { background: linear-gradient(135deg, #7c3aed, #db2777); opacity: .95; width: 170px; height: 170px; }
    .blob-b { background: linear-gradient(135deg, #2563eb, #06b6d4); opacity: .85; width: 130px; height: 130px; }
    .blob-c { background: linear-gradient(135deg, #10b981, #84cc16); opacity: .8; width: 90px; height: 90px; }
    .dots {
      display: flex;
      align-items: center;
      gap: 14px;
    }
    .dot {
      width: 12px;
      height: 12px;
      border-radius: 50%;
      background: #e2e8f0;
      opacity: .25;
      transform-origin: center;
      will-change: transform, opacity;
    }
    .status {
      font-size: 12px;
      letter-spacing: 0.25em;
      text-transform: uppercase;
      opacity: .6;
    }
  </style>
</head>
<body>
  <div class="loader">
    <div class="stage">
      <div class="blob blob-a"></div>
      <div class="blob blob-b"></div>
      <div class="blob blob-c"></div>
    </div>
    <div class="dots">
      <div class="dot"></div>
      <div class="dot"></div>
      <div class="dot"></div>
      <div class="dot"></div>
      <div class="dot"></div>
    </div>
    <div class="status">Loading</div>
  </div>

  <script src="gsap.min.js"></script>
  <script>
    document.addEventListener('DOMContentLoaded', function () {
      if (typeof gsap === 'undefined') {
        console.error('GSAP not loaded');
        return;
      }

      const master = gsap.timeline({
        repeat: -1,
        yoyo: true,
        defaults: { duration: 2.2, ease: 'power2.inOut' }
      });

      // Start morphing border-radius values common for all blobs
      const startRadius = '50% 50% 50% 50% / 50% 50% 50% 50%';

      // Motion-blur-free organic rounded shapes
      master
        .fromTo('.blob-a',
          { borderRadius: startRadius, rotation: 0, scale: 1 },
          { borderRadius: '38% 62% 63% 37% / 55% 45% 55% 45%', rotation: 140, scale: 1.18 },
          0
        )
        .fromTo('.blob-b',
          { borderRadius: startRadius, rotation: 0, scale: 1 },
          { borderRadius: '62% 38% 37% 63% / 45% 55% 45% 55%', rotation: -150, scale: 0.88 },
          0
        )
        .fromTo('.blob-c',
          { borderRadius: startRadius, rotation: 0, scale: 1 },
          { borderRadius: '45% 55% 55% 45% / 62% 38% 62% 38%', rotation: 170, scale: 1.08 },
          0
        )
        .fromTo('.dot',
          { y: 0, scale: 1, opacity: 0.25 },
          {
            y: -16,
            scale: 0.4,
            opacity: 1,
            duration: 0.9,
            ease: 'sine.inOut',
            stagger: { each: 0.1, from: 'center', yoyo: true, repeat: 1 }
          },
          0.25
        );
    });
  </script>
</body>
</html>
```

Wait the stagger with yoyo true and repeat 1 means each dot does yoyo twice while staggered. Since master timeline yoyos too, the overall effect may be weird (forward: dot scale down+up twice? Actually stagger object `repeat:1` means each dot repeats 1 time forward after initial, so the tween will have two cycles forward. Combined with timeline yoyo, in reverse the entire two cycles play backwards. Actually if each dot repeats within master timeline, the master timeline will wait for tween duration (0.9 * 2? plus yoyo). This is okay but dot opacity scale may appear to blink. The requirement: "staggered row of dots orchestrated on a GSAP timeline with smooth easing." Simple stagger without internal yoyo/repeat in stagger could be enough. But I want ripple effect. Simplify: Use `stagger: { each: 0.1, from: 'center' }` and master yoyo. Then each dot's forward animation is ripple scale-down, then master reverses to scale up. This yields smooth easing.

Also need to consider when using `fromTo` with master defaults `duration`, the stagger for `.dot` overrides it with 0.9. The master defaults `ease` will be used for blobs. Dots `ease` overridden.

However, the `.fromTo('.dot', ...)` start state sets opacity and scale inline; the CSS class sets opacity .25 and transformation. Since fromTo sets inline start as a style, flash? Should be okay. The start state uses scale 1 not 0.25.

Maybe to make a more classic stagger loading row, animate each dot's `scale` from 0.5 to 1.25 and `opacity` 0.3 to 1, `y` 0 to -12 in a wave. Good.

Need avoid initial state conflict: CSS `.dot { opacity: .25 }` but fromTo `.fromTo` will set `opacity: 0.25` at first render (same).

Need ensure blobs are centered with absolute margin:auto; `width:...`. Good.

Need test? We can write index.html then maybe use a headless browser? We don't have browser. Could check syntax with node? Could inspect file. But no different.

Let's refine the shape morphing: We need "morphing rounded shapes". Could use two shapes instead of three? Three is fine. Use `mix-blend-mode: screen` to make colors glow. Without blur, gradients. Maybe add `box-shadow` matching colors. That's fine.

The user explicitly says "Using the pre-provided gsap.min.js (global gsap)". They might expect global `gsap` available. Our script works.

Potential issue: `gsap.min.js` in /workspace. The script `src="gsap.min.js"` relative to index.html. We will create index.html in /workspace. Good.

Need use no external resources, no CDNs. Good.

Now create index.html.

We should also maybe include comments. Fine.

Let's use write.

write /workspace/index.html (3923 bytes)
toolResult

Successfully wrote 3923 bytes to /workspace/index.html

assistant
thinking
  Now we should verify the file exists and there is no syntax problem. We can do basic text checks. Could maybe run node to parse? Use node to read file? No need. But can verify `gsap.min.js` exposed global by loading in Node? Not necessary because browser. Could also open with `python -m http.server`? No browser. But can verify HTML structure. Let's use read to see file lines.

read {"path":"/workspace/index.html","limit":120}
toolResult

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Morphing GSAP Loader</title> <style> * { box-sizing: border-box; } html, body { margin: 0; width: 100%; height: 100%; background: radial-gradient(circle at 50% 50%, #151922 0%, #0a0c10 100%); overflow: hidden; font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; color: #e2e8f0; } .loader { display: flex; flex-direction: column; align-items: center; justify-content: center; width: 100%; height: 100%; gap: 30px; } .stage { position: relative; width: 320px; height: 320px; } .blob { position: absolute; top: 0; right: 0; bottom: 0; left: 0; margin: auto; width: 160px; height: 160px; will-change: transform, border-radius; } .blob-a { width: 170px; height: 170px; background: linear-gradient(135deg, #7c3aed, #db2777); opacity: 0.95; box-shadow: 0 0 40px rgba(124, 58, 237, 0.35); } .blob-b { width: 130px; height: 130px; background: linear-gradient(135deg, #2563eb, #06b6d4); opacity: 0.85; box-shadow: 0 0 40px rgba(37, 99, 235, 0.3); } .blob-c { width: 90px; height: 90px; background: linear-gradient(135deg, #10b981, #84cc16); opacity: 0.8; box-shadow: 0 0 40px rgba(16, 185, 129, 0.3); } .dots { display: flex; align-items: center; gap: 14px; } .dot { width: 12px; height: 12px; border-radius: 50%; background: #e2e8f0; opacity: 0.25; transform-origin: center; will-change: transform, opacity; } .status { font-size: 12px; letter-spacing: 0.25em; text-transform: uppercase; opacity: 0.6; } </style> </head> <body> <div class="loader"> <div class="stage"> <div class="blob blob-a"></div> <div class="blob blob-b"></div> <div class="blob blob-c"></div> </div> <div class="dots"> <div class="dot"></div> <div class="dot"></div> <div class="dot"></div> <div class="dot"></div> <div class="dot"></div> </div> <div class="status">Loading</div> </div> <script src="gsap.min.js"></script> <script> document.addEventListener('DOMContentLoaded', function () { if (typeof gsap === 'undefined') { [51 more lines in file. Use offset=121 to continue.]

assistant
thinking
   Need continue reading lower part.

read {"path":"/workspace/index.html","offset":121,"limit":80}
toolResult

console.error('GSAP failed to load'); return; } const roundedCircle = '50% 50% 50% 50% / 50% 50% 50% 50%'; const master = gsap.timeline({ repeat: -1, yoyo: true, defaults: { duration: 2.2, ease: 'power2.inOut' } }); master .fromTo('.blob-a', { borderRadius: roundedCircle, rotation: 0, scale: 1 }, { borderRadius: '38% 62% 63% 37% / 55% 45% 55% 45%', rotation: 140, scale: 1.18 }, 0 ) .fromTo('.blob-b', { borderRadius: roundedCircle, rotation: 0, scale: 1 }, { borderRadius: '62% 38% 37% 63% / 45% 55% 45% 55%', rotation: -150, scale: 0.88 }, 0 ) .fromTo('.blob-c', { borderRadius: roundedCircle, rotation: 0, scale: 1 }, { borderRadius: '45% 55% 55% 45% / 62% 38% 62% 38%', rotation: 170, scale: 1.08 }, 0 ) .fromTo('.dot', { y: 0, scale: 1, opacity: 0.25 }, { y: -16, scale: 0.35, opacity: 1, duration: 0.9, ease: 'sine.inOut', stagger: { each: 0.1, from: 'center' } }, 0.25 ); }); </script> </body> </html>

assistant
thinking
   The file looks good. However, I want to consider potential issues with `borderRadius` and `fromTo`. GSAP should interpolate borderRadius strings if same number of values, okay.

One issue: `borderRadius` default is not set in CSS; so at load elements will have `border-radius: 50%` from `.dot` but blobs have no explicit border-radius, so default `0` (rectangular) until JavaScript runs. If JS runs after DOMContentLoaded, might be a flash (rectangular blobs). We can set `border-radius: 50%` in `.blob` class to ensure initial rounded. Also `overflow` not needed. Add `border-radius: 50%` to `.blob` to ensure initial circle shape. But then `gsap.fromTo` start will set inline style to roundedCircle (which is same) so no conflict. We can add. Need edit CSS.

Also, `.blob` width/height default 160, overridden by classes; fine.

Let's edit to add `border-radius: 50%` to `.blob`. Also maybe add `transform-origin:center`. Default is center for block. Fine.

Also `margin:auto` with absolute positioning does center; with rotation scale around box center. Good.

Potential issue: When GSAP applies `rotation`, it writes `transform: translate3d(0,0,0) rotate(140deg) scale(1.18)` etc. The blobs are centered via absolute margin auto, so transform around center. Good.

Potential issue: `.blob-a` box-shadow with high spread maybe heavy. Fine.

Potential issue: On mobile, `gap:30px` fine.

Let's apply small edit.

edit {"path":"/workspace/index.html","edits":[{"oldText":" .blob {\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0
toolResult

Successfully replaced 1 block(s) in /workspace/index.html.

assistant
thinking
    We should also test availability of global gsap via Node? gsap.min.js may not work in node due to window. But src in browser. No external resources. Good.

User asked: "Using the pre-provided gsap.min.js (global gsap), create a looping animated loading sequence on a dark background: morphing rounded shapes and a staggered row of dots orchestrated on a GSAP timeline with smooth easing." We have done. Need final message saying created index.html, describe, no extra.

Before final, let's verify file with `ls`.

bash {"command":"ls -l /workspace/index.html /workspace/gsap.min.js","timeout":10}
toolResult

-rw-r--r-- 1 node node 72214 Jul 8 00:39 /workspace/gsap.min.js -rw-r--r-- 1 node node 3949 Jul 8 00:40 /workspace/index.html

assistant
thinking
   Good. Answer concise.

Created `/workspace/index.html` — a self-contained, single-file loading animation. What it does: - Dark radial background. - Three overlapping gradient “blobs” that morph between rounded shapes by animating `borderRadius`, `rotation`, and `scale`. - A row of five dots with a center-out stagger that scales and fades them. - Everything is driven by one GSAP master timeline with `repeat: -1` and `yoyo: true` for a smooth, seamless loop using `power2.inOut` / `sine.inOut` easings. - Loads the local `gsap.min.js` directly with `<script src="gsap.min.js"></script>` — no external resources or CDNs. Open `index.html` in a browser to see the loader loop.

TAKE IT TO CODE

A single self-contained HTML file — copy it and open in any browser, or reuse the prompt with your own pi-agent run.

RUN

modelkimi-k2.7-code
vendorMoonshot · Kimi
harnesspi-agent
generated2026-07-08

EVALUATION

4/5 working

Dark background with a morphing glowing blob shape and staggered pulsing dots reading LOADING, animates continuously as intended.

Vision grade · anthropic/claude-sonnet-5

MEASUREMENTS

Raw readings from the capture, not a verdict — the grade above is the only judgement. Percentages are how much of the screen changed; the measure under-reports sparse motion on a dark background.

idle motion0.9%
on click2.3%
on drag2.2%
on wheel2.1%
on enter + space1.8%
on arrow keys2.3%
on w/a/s/d2.2%
frame spread17 / 255
console errors0
js errors none

METRICS

runtime83.7s
tokens in16k
tokens out7.0k
cached31k
cost / run$0.0408