LAWSOFUX · motion lab
Concept · flow

Flow

Square / diamond inflate palette · magenta / cream takeaways · 4

Definition

Flow is the absorbed working state where challenge and skill are matched and the user loses track of friction. The state requires steady feedback, achievable next steps, and zero unnecessary interruption. Below the skill line the user gets bored; above it the user gets frustrated; on the line the user keeps going.

Why it matters for ShurIQ reports

A reader scanning a brief is in shallow flow at best — the design's job is to keep them there until the recommendation lands. Crisp transitions between sections, pre-loaded next viewports, and progress signifiers (where am I in this brief?) keep an executive moving. Any modal, surprise scroll-jack, or unexpected dialog drops them out of flow and the close-tab probability spikes.

Takeaways

Visual motion language

Section transitions resolve as smooth sequence-reveal (250ms ease-in-out per chunk) without page jumps. Numerical scores count-up on first paint to signal computation completing in real time.

Cavalry recreation seed. 6 concentric squares from 480px down to 60px in steps of 70px. Tint each interpolated between bg-darker and cream. Animate the entire stack scaling outward continuously: each square scales 1→1.18 over 4s while fading out at the outer edge, and a new tiny square spawns at center every 670ms. Loop seamlessly.

Origins

Mihály Csíkszentmihályi, 1975 — introduced the flow construct in Beyond Boredom and Anxiety.

Cavalry scene

The script below builds this concept's motion in Cavalry through the Stallion bridge. Pipe to cavalry_run_script via MCP, or paste into Cavalry's JavaScript Editor.

// Laws of UX · flow · Cavalry scene
// Motion family: center-out radial pulse (concentric squares zooming inward)
// Palette: plum-magenta + cream
// To run: pipe to cavalry_run_script tool, or paste into Cavalry's JavaScript Editor
// Built 2026-04-30 by ShurAI

(function () {
  var PREFIX = "claude_lawofux_flow_";

  var existing = api.getAllSceneLayers();
  for (var i = 0; i < existing.length; i++) {
    try {
      var nm = api.getNiceName(existing[i]);
      if (nm && nm.indexOf("claude_lawofux_") === 0) api.deleteLayer(existing[i]);
    } catch (e) {}
  }

  var BG    = "#5A1E4D";
  var TINTS = ["#6E2A60", "#8C4080", "#A35F8A", "#BF7AA0", "#D6CDB0"]; // dark→cream
  var CREAM = "#D6CDB0";

  var bg = api.primitive("rectangle", PREFIX + "bg");
  api.set(bg, { "generator.dimensions": [1080, 1080] });
  api.setFill(bg, true);
  api.set(bg, { "material.materialColor": BG });

  // 6 concentric squares from 480 down to 60
  var sizes = [480, 410, 340, 270, 200, 130];
  for (var i = 0; i < sizes.length; i++) {
    var sq = api.primitive("rectangle", PREFIX + "sq_" + i);
    api.set(sq, {
      "generator.dimensions": [sizes[i], sizes[i]],
      "position.x": 0, "position.y": 0,
      "opacity": 0
    });
    api.setFill(sq, false);
    api.setStroke(sq, true);
    var tint = TINTS[Math.min(i, TINTS.length - 1)];
    api.set(sq, {
      "stroke.strokeColor": tint,
      "stroke.width": 8
    });

    var inF = i * 5;
    api.keyframe(sq, inF,      { "opacity": 0,   "scale.x": 0.7, "scale.y": 0.7 });
    api.keyframe(sq, inF + 18, { "opacity": 100, "scale.x": 1.0, "scale.y": 1.0 });
    api.magicEasing(sq, "scale.x", inF + 18, "EaseOut", "");

    // Continuous outward zoom loop
    api.keyframe(sq, 60,  { "scale.x": 1.0,  "scale.y": 1.0  });
    api.keyframe(sq, 120, { "scale.x": 1.18, "scale.y": 1.18 });
    api.keyframe(sq, 121, { "scale.x": 1.0,  "scale.y": 1.0  });
  }

  // Center cream square
  var centerSq = api.primitive("rectangle", PREFIX + "center");
  api.set(centerSq, {
    "generator.dimensions": [60, 60],
    "position.x": 0, "position.y": 0,
    "opacity": 0
  });
  api.setFill(centerSq, true);
  api.set(centerSq, { "material.materialColor": CREAM });
  api.keyframe(centerSq, 30, { "opacity": 0   });
  api.keyframe(centerSq, 50, { "opacity": 100 });

  var layerCount = api.getAllSceneLayers().length;
  console.log("scene built: flow (" + layerCount + " layers)");
})();