LAWSOFUX · motion lab
Concept · mental-model

Mental Model

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

Definition

Each user carries a compressed internal model of how a system works, built from prior experience with similar systems. They apply this model to new products on first contact — and any mismatch between the user's model and the actual system shows up as confusion, error, or abandonment. Designing without considering mental models guarantees friction.

Why it matters for ShurIQ reports

A CMO's mental model of "competitive intelligence" is shaped by McKinsey decks, Pitchbook tables, and Bloomberg terminals. The brief must use the metaphors they already hold — stack rank, score, evidence — and reserve novelty for the analytical content. The Brand Power Score earns trust because executives already know how composite scores work; the SBPI methodology earns adoption because evidence trails behave like footnotes.

Takeaways

Visual motion language

Familiar patterns enter with quiet fade-in and snap-grid alignment. Novel constructs use sequence-reveal to walk the reader through the unfamiliar logic before settling into static state.

Cavalry recreation seed. Hexagonal layout with 6 vertex nodes (40px cream circles) at radius 180px, plus center node (30px). Connect all vertices to center and to each adjacent vertex with 4px cream lines. Apply a slow 3D Y-axis rotation (or a 2D skew oscillation ±6°) over 6s, ease-in-out, infinite. Behind the wireframe place a static darker hexagon fill at 0.4 opacity for depth.

Origins

Kenneth Craik, 1943 — The Nature of Explanation.

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 · mental-model · Cavalry scene
// Motion family: single-form (rotating hex wireframe)
// 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_mental-model_";

  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 DARK  = "#6E5F2F";
  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 });

  // Backing hex (filled, depth)
  var backHex = api.primitive("polygon", PREFIX + "back_hex");
  api.set(backHex, {
    "generator.sides": 6, "generator.radius": 240,
    "position.x": 0, "position.y": 0,
    "opacity": 0
  });
  api.setFill(backHex, true);
  api.set(backHex, { "material.materialColor": DARK });
  api.keyframe(backHex, 0,  { "opacity": 0  });
  api.keyframe(backHex, 18, { "opacity": 40 });

  // Outline hex
  var outHex = api.primitive("polygon", PREFIX + "out_hex");
  api.set(outHex, {
    "generator.sides": 6, "generator.radius": 220,
    "position.x": 0, "position.y": 0,
    "opacity": 0
  });
  api.setFill(outHex, false);
  api.setStroke(outHex, true);
  api.set(outHex, { "stroke.strokeColor": CREAM, "stroke.width": 4 });
  api.keyframe(outHex, 6,  { "opacity": 0   });
  api.keyframe(outHex, 22, { "opacity": 100 });
  // Slow rotation
  api.keyframe(outHex, 0,   { "rotation.z": 0   });
  api.keyframe(outHex, 144, { "rotation.z": 360 });

  // 6 vertex nodes
  var R = 200;
  var nodeIds = [];
  for (var v = 0; v < 6; v++) {
    var theta = (v / 6) * Math.PI * 2 - Math.PI / 2;
    var ex = Math.cos(theta) * R;
    var ey = Math.sin(theta) * R;
    var nd = api.primitive("ellipse", PREFIX + "vert_" + v);
    api.set(nd, {
      "generator.radius": [22, 22],
      "position.x": ex, "position.y": ey,
      "opacity": 0,
      "scale.x": 0.4, "scale.y": 0.4
    });
    api.setFill(nd, true);
    api.set(nd, { "material.materialColor": CREAM });
    api.keyframe(nd, 18 + v * 2,  { "opacity": 0,   "scale.x": 0.4, "scale.y": 0.4 });
    api.keyframe(nd, 30 + v * 2,  { "opacity": 100, "scale.x": 1.0, "scale.y": 1.0 });
    api.magicEasing(nd, "scale.x", 30 + v * 2, "EaseOut", "");
    api.magicEasing(nd, "scale.y", 30 + v * 2, "EaseOut", "");
    nodeIds.push({ id: nd, x: ex, y: ey, theta: theta });
  }

  // 6 spokes from center to each vertex
  for (var v = 0; v < 6; v++) {
    var info = nodeIds[v];
    var spoke = api.primitive("rectangle", PREFIX + "spoke_" + v);
    var len = R;
    api.set(spoke, {
      "generator.dimensions": [len, 3],
      "position.x": info.x / 2,
      "position.y": info.y / 2,
      "rotation.z": info.theta * (180 / Math.PI),
      "opacity": 0,
      "scale.x": 0.001
    });
    api.setFill(spoke, true);
    api.set(spoke, { "material.materialColor": CREAM });
    api.keyframe(spoke, 24 + v * 2, { "opacity": 100, "scale.x": 0.001 });
    api.keyframe(spoke, 38 + v * 2, { "opacity": 100, "scale.x": 1     });
  }

  // Center node
  var center = api.primitive("ellipse", PREFIX + "center");
  api.set(center, {
    "generator.radius": [18, 18],
    "position.x": 0, "position.y": 0,
    "opacity": 0
  });
  api.setFill(center, true);
  api.set(center, { "material.materialColor": CREAM });
  api.keyframe(center, 16, { "opacity": 0   });
  api.keyframe(center, 28, { "opacity": 100 });

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