LAWSOFUX · motion lab
Concept · law-of-pragnanz

Law of Prägnanz

Single-form composition palette · magenta / cream takeaways · 3

Definition

The eye resolves complex shapes into the simplest interpretation it can construct. Faced with ambiguity, perception collapses to symmetry, regularity, and unity. Designers cannot stop this — they can only choose whether the simplest read matches the intended meaning.

Why it matters for ShurIQ reports

A complex network viewport or layered chart will be read as its dominant simple shape — a circle, a column, a flow — long before the reader parses any individual node. The implication: every dense visualization must have a clean shape at first glance, with detail revealed on inspection. If the simplest read of a chart says the wrong thing, the chart is wrong.

Takeaways

Visual motion language

Complex visualizations enter as a simple aggregate shape (a fading circle, a single column), then mass-collapse outward into their detailed components. The opening shape primes the perceptual frame.

Cavalry recreation seed. 4 shape slots in a 2×2 grid. Animate sequential reveal: complex pentagram-hexagon at t=0, hex+bowtie at t=600ms, simple hexagon at t=1200ms, circle at t=1800ms. Then a "simplification arrow" or fade-pass sweeps from top-left to bottom-right over 800ms making the earlier shapes ghost out, leaving only the circle bright at the end. 1.5s hold, restart.

Origins

Max Wertheimer, 1910 — foundational Gestalt principle of perceptual simplicity.

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 · law-of-pragnanz · Cavalry scene
// Motion family: grid-stagger (2x2 morph cascade)
// Palette: hot-pink + 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_law-of-pragnanz_";

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

  // 2x2 cells: complex(star) → hex+star → hexagon → circle
  // Quadrant centers
  var quads = [
    { x: -160, y:  160, time: 0  },
    { x:  160, y:  160, time: 14 },
    { x: -160, y: -160, time: 28 },
    { x:  160, y: -160, time: 42 }
  ];

  // Q1 — complex pentagram inscribed (using 5-point star)
  var q1 = api.primitive("star", PREFIX + "q1_star");
  api.set(q1, {
    "generator.points": 5,
    "generator.outerRadius": 90,
    "generator.innerRadius": 38,
    "position.x": quads[0].x, "position.y": quads[0].y,
    "opacity": 0
  });
  api.setFill(q1, false);
  api.setStroke(q1, true);
  api.set(q1, { "stroke.strokeColor": CREAM, "stroke.width": 6 });

  var q1b = api.primitive("polygon", PREFIX + "q1_hex");
  api.set(q1b, {
    "generator.sides": 6, "generator.radius": 110,
    "position.x": quads[0].x, "position.y": quads[0].y,
    "opacity": 0
  });
  api.setFill(q1b, false);
  api.setStroke(q1b, true);
  api.set(q1b, { "stroke.strokeColor": CREAM, "stroke.width": 6 });

  // Q2 — hex with bowtie (we use 6-point star)
  var q2 = api.primitive("star", PREFIX + "q2_star");
  api.set(q2, {
    "generator.points": 6,
    "generator.outerRadius": 100,
    "generator.innerRadius": 50,
    "position.x": quads[1].x, "position.y": quads[1].y,
    "opacity": 0
  });
  api.setFill(q2, false);
  api.setStroke(q2, true);
  api.set(q2, { "stroke.strokeColor": CREAM, "stroke.width": 6 });

  // Q3 — simple hexagon
  var q3 = api.primitive("polygon", PREFIX + "q3_hex");
  api.set(q3, {
    "generator.sides": 6, "generator.radius": 100,
    "position.x": quads[2].x, "position.y": quads[2].y,
    "opacity": 0
  });
  api.setFill(q3, false);
  api.setStroke(q3, true);
  api.set(q3, { "stroke.strokeColor": CREAM, "stroke.width": 6 });

  // Q4 — circle (the simplest)
  var q4 = api.primitive("ellipse", PREFIX + "q4_circle");
  api.set(q4, {
    "generator.radius": [100, 100],
    "position.x": quads[3].x, "position.y": quads[3].y,
    "opacity": 0
  });
  api.setFill(q4, false);
  api.setStroke(q4, true);
  api.set(q4, { "stroke.strokeColor": CREAM, "stroke.width": 6 });

  // Reveal sequence
  api.keyframe(q1,  0,  { "opacity": 0   });
  api.keyframe(q1,  18, { "opacity": 100 });
  api.keyframe(q1b, 0,  { "opacity": 0   });
  api.keyframe(q1b, 18, { "opacity": 100 });
  api.keyframe(q2,  14, { "opacity": 0   });
  api.keyframe(q2,  32, { "opacity": 100 });
  api.keyframe(q3,  28, { "opacity": 0   });
  api.keyframe(q3,  46, { "opacity": 100 });
  api.keyframe(q4,  42, { "opacity": 0   });
  api.keyframe(q4,  60, { "opacity": 100 });

  // Razor sweep: dim the earlier, leave Q4 bright
  api.keyframe(q1,  72, { "opacity": 100 });
  api.keyframe(q1,  96, { "opacity": 25  });
  api.keyframe(q1b, 72, { "opacity": 100 });
  api.keyframe(q1b, 96, { "opacity": 25  });
  api.keyframe(q2,  78, { "opacity": 100 });
  api.keyframe(q2, 102, { "opacity": 25  });
  api.keyframe(q3,  84, { "opacity": 100 });
  api.keyframe(q3, 108, { "opacity": 30  });

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