fix(lint,skills): make the reflow-prop fix faithful, not a lossy scale swap

The previous fixHint and skill guidance told agents to "settle via scale" for
text-reflow props — but uniform scale resizes the glyphs, it does not change
the gaps between them, so swapping a letterSpacing tween for scale lints clean
while silently animating a different thing.

Make the guidance faithful per property:
- fontSize -> scale (same visual, sub-pixel smooth).
- letterSpacing / wordSpacing -> split the text into per-character elements and
  animate each glyph's x (the spread); uniform scale is NOT equivalent. Or hold
  the value statically if it's a minor flourish.

Add a "preserve the intent" principle to gsap-transforms-and-perf.md: a fix must
reproduce the same start/end state and trajectory and be verified against the
ORIGINAL render, not just pass lint — lint-clean-and-smooth is not the bar,
faithful-and-smooth is.

Redo the vignette demo title with the faithful per-glyph x spread (15.12px =
(0.32em-0.18em)*108px, centered about 8 chars). Render-verified: settled
endpoint matches the original at 46.5 dB, settle is smooth (24/24 unique frames).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Vance Ingalls
2026-06-29 10:59:49 -07:00
co-authored by Claude Opus 4.8
parent 082280e8de
commit 6e21072f44
4 changed files with 52 additions and 11 deletions
+18 -7
View File
@@ -41,7 +41,7 @@
<div class="demo-subject"></div>
<div class="demo-text">
<div class="demo-title">VIGNETTE</div>
<div class="demo-title"><span class="ch">V</span><span class="ch">I</span><span class="ch">G</span><span class="ch">N</span><span class="ch">E</span><span class="ch">T</span><span class="ch">T</span><span class="ch">E</span></div>
<div class="demo-subtitle">Frame the eye. Hold the moment.</div>
</div>
</div>
@@ -151,6 +151,9 @@
text-shadow: 0 4px 24px rgba(0, 0, 0, 0.55);
opacity: 0;
}
.demo-title .ch {
display: inline-block;
}
.demo-subtitle {
font-size: 30px;
@@ -181,14 +184,22 @@
tl.to(".haze-near", { opacity: 1, duration: 0.8, ease: "power2.out" }, 0.15);
tl.to(".demo-subject", { opacity: 1, scale: 1, duration: 1.0, ease: "power3.out" }, 0.1);
// Title + subtitle settle in. Settle via scale (transform), not letterSpacing:
// animating letter-spacing reflows text and snaps glyph positions to the pixel
// grid, so a slow ease-out tail micro-stutters. letter-spacing holds at its CSS
// resting value (0.18em); the subtle scale gives the same "settle into place" feel.
// Title + subtitle settle in. The letters settle from wider spacing to their
// resting spacing — the faithful smooth equivalent of a letter-spacing tween is a
// per-glyph x spread (transform, sub-pixel smooth), NOT a uniform scale (which
// would resize the glyphs instead of closing the gaps). letter-spacing holds at
// its CSS resting 0.18em; each glyph starts offset by the extra gap the original
// tween opened — (0.32em 0.18em) × 108px ≈ 15.12px per gap — and converges to 0,
// centered about the word (8 chars → center at index 3.5).
tl.fromTo(
".demo-title",
{ opacity: 0, y: 18, scale: 1.04 },
{ opacity: 1, y: 0, scale: 1, duration: 1.0, ease: "power3.out" },
{ opacity: 0, y: 18 },
{ opacity: 1, y: 0, duration: 1.0, ease: "power3.out" },
0.6,
);
tl.from(
".demo-title .ch",
{ x: (i) => (i - 3.5) * 15.12, duration: 1.0, ease: "power3.out" },
0.6,
);
tl.to(".demo-subtitle", { opacity: 1, duration: 0.8, ease: "power3.out" }, 1.0);