LAWSOFUX · motion lab
Concept · jakobs-law

Jakob's Law

Single-form composition palette · warm tan / cream takeaways · 3

Definition

Users arrive at every new interface carrying expectations from every other interface they already use. The familiar pattern wins by default — a novel pattern has to earn its keep against the cumulative weight of learned conventions. Originality is a tax the user did not agree to pay.

Why it matters for ShurIQ reports

A brand-intelligence brief reads alongside Bloomberg, Pitchbook, McKinsey decks, and the client's internal dashboards. Stack rankings should resolve as a reader expects (sortable columns, sticky header, pagination); evidence trails should behave like footnote panels they already know. Reserve novelty for the analytical insight — never for the navigation.

Takeaways

Visual motion language

Initial layout snaps to a familiar dashboard grid with a steady fade-in; group-form animations follow the conventions the reader has already internalized from spreadsheets and BI tools.

Cavalry recreation seed. Two 280×280 square outlines (12px stroke cream). Square A starts at (-100, -100), Square B at (+100, +100). Animate A to (-60, -60) and B to (+60, +60) over 900ms ease-out so they overlap by ~70%. Hold. Slow horizontal nudge of B by ±8px on a 3s sine to suggest "matching with another site". Loop.

Origins

Jakob Nielsen — Nielsen Norman Group usability research, late 1990s onward.

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 · jakobs-law · Cavalry scene
// Motion family: single-form (two converging squares)
// Palette: olive-mustard + 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_jakobs-law_";

  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    = "#8C7C45";
  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 });

  var sqA = api.primitive("rectangle", PREFIX + "sqA");
  api.set(sqA, {
    "generator.dimensions": [320, 320],
    "position.x": -200, "position.y": 200,
    "opacity": 0
  });
  api.setFill(sqA, false);
  api.setStroke(sqA, true);
  api.set(sqA, { "stroke.strokeColor": CREAM, "stroke.width": 12 });

  var sqB = api.primitive("rectangle", PREFIX + "sqB");
  api.set(sqB, {
    "generator.dimensions": [320, 320],
    "position.x": 200, "position.y": -200,
    "opacity": 0
  });
  api.setFill(sqB, false);
  api.setStroke(sqB, true);
  api.set(sqB, { "stroke.strokeColor": CREAM, "stroke.width": 12 });

  // Fade in then converge
  api.keyframe(sqA, 0,  { "opacity": 0,   "position.x": -260, "position.y":  260 });
  api.keyframe(sqA, 12, { "opacity": 100, "position.x": -260, "position.y":  260 });
  api.keyframe(sqA, 36, { "opacity": 100, "position.x": -120, "position.y":  120 });
  api.magicEasing(sqA, "position.x", 36, "EaseOut", "");
  api.magicEasing(sqA, "position.y", 36, "EaseOut", "");

  api.keyframe(sqB, 0,  { "opacity": 0,   "position.x":  260, "position.y": -260 });
  api.keyframe(sqB, 12, { "opacity": 100, "position.x":  260, "position.y": -260 });
  api.keyframe(sqB, 36, { "opacity": 100, "position.x":  120, "position.y": -120 });
  api.magicEasing(sqB, "position.x", 36, "EaseOut", "");
  api.magicEasing(sqB, "position.y", 36, "EaseOut", "");

  // Subtle horizontal nudge on B
  api.keyframe(sqB, 60,  { "position.x": 120 });
  api.keyframe(sqB, 96,  { "position.x": 128 });
  api.keyframe(sqB, 132, { "position.x": 120 });

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