Definition
Effort accelerates as a perceived goal gets closer. The closer a user feels to completion, the harder they push to finish — and the more painful any added step feels. Even artificial progress (a head-start checkmark, a partial bar) reliably increases follow-through.
Why it matters for ShurIQ reports
A multi-page brief or evidence-trail walkthrough behaves like a multi-step task: visible progress through the report keeps the reader pushing to the end. Section indicators on the editorial brief, viewport pagination dots, and "3 of 5 viewports" affordances on the viz hub all convert ambient progress into motivation. Without them, a reader stalls in the middle and never reaches the recommendation.
Takeaways
- Show progress as a visible scaffold on every multi-step artifact — viewports, evidence chains, scoring breakdowns.
- Front-load a small win (auto-completed first step) to recruit the gradient effect from the start.
- Make the final step lighter than the middle steps; never end on the heaviest task.
- Use proximity-to-finish messaging on calls-to-action ("one final viewport" beats "next").
Visual motion language
Progress bars accelerate visibly as the goal nears (ease-out curve, not linear), with a glow-burst on completion. Numbers count-up faster on the last interval than the first.
Origins
Clark Hull, 1932 — proposed the goal-gradient hypothesis, demonstrated empirically in 1934.
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 · goal-gradient-effect · Cavalry scene
// CORRECTED 2026-04-30 · verified from production page
// Motion family: opacity-stepped horizontal bands (7 full-width stripes)
// Opacity gradient 1.0 -> 0.1 cascading top-to-bottom
// Palette: warm tan + purples
// 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_goal-gradient-effect_";
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 = "#C9B477";
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 });
// 7 full-width stripes, stepped opacity 1.0/0.8/0.6/0.4/0.3/0.2/0.1
// Stripe geometry: 880 wide x 84 tall, 19px gutter -> step=103
var stripeW = 880;
var stripeH = 84;
var step = 103; // 84 + 19
var topY = (6 * step) / 2; // top stripe Y (positive=up)
var opacities = [100, 80, 60, 40, 30, 20, 10];
for (var j = 0; j < 7; j++) {
var s = api.primitive("rectangle", PREFIX + "stripe_" + j);
api.set(s, {
"generator.dimensions": [stripeW, stripeH],
"position.x": 0,
"position.y": topY - j * step,
"opacity": 0
});
api.setFill(s, true);
api.set(s, { "material.materialColor": CREAM });
var t0 = j * 4; // ~120ms per stripe (assuming 30fps -> 4 frames)
var t1 = t0 + 8;
api.keyframe(s, t0, { "opacity": 0 });
api.keyframe(s, t1, { "opacity": opacities[j] });
api.magicEasing(s, "opacity", t1, "EaseOut", "");
}
var layerCount = api.getAllSceneLayers().length;
console.log("scene built: goal-gradient-effect (" + layerCount + " layers)");
})();