Character animation v6 · blind bake-off against the reference
Dog Camp loader on the left. Six OctoMonic states on the right. Everything below is measured, not asserted.
The method landed. The staging did not.
Four defects were named and repaired. Three of them measurably improved. The fourth, the one about
presentation, was fixed on phones and left broken on desktop, and the contrast table that came with the repair
quietly omitted every pair that still fails. Average score across the six states moved from
4.0 to 5.8 out of 10.
31/37adjacent pairs now clear the 3:1 floor. Old worst was 1.14:1.
6pairs still under 3:1, none of them listed in the repair report.
0.40 → 0.92rendered scene opacity, verified in the DOM.
4.58disc-widths apart at 1280. The reference is 1.36. It was 5.00.
320pxnarrowest width where the scene is now visible. It used to vanish under 1024.
1 of 6states still has no rest frame in its entire cycle: idle.
The reference Tom likes
Dog Camp loader
34 primitives, 5 animated parts, 16 inks, one ground line,
and a treat that physically travels from one character to the other. The two figures stand
1.36 body-widths apart, close enough that the throw and the catch are one event.
Peak motion is 5.70x its own average, and 16% of the loop is completely still.
That stillness is what makes the leap read as a leap.
The six repaired states
idle
7.2s loop · still a wheel
2→4/10
Blinks now, the mouth squashes, and the reach beat exists in the CSS. But the numbers say it is still a wheel: peak motion is 1.73x its own average against the dog's 5.70x, and not one frame in 7.2 seconds is at rest. Two constant bobs at 5.2s and 6.1s run straight over the top of the reach and wash it out. This is the state every visitor sees first and longest.
filling
4.8s loop · real beat
4→6/10
The closest thing to the dog's rhythm in the set: a real two-hump beat, 35% of the cycle genuinely still, the pen and the pad appear, four writing strokes, and her mouth presses flat then releases. Held back by scale, 28.6 motion against the dog's 100, and by the fact that her cream glove and her cream notepad are the same ink and overlap each other.
choosing
3.6s loop · weak beat
4→5/10
The eyes dart left, then right, then blink. It starts and ends near rest, which the old metronome did not. But it is the weakest beat of the six (2.41x) and the quietest (26.6 motion), and at a 101px disc the double foot tap is a twitch. Nothing in the drawing says times or choosing.
working
3.2s loop · real beat
5→7/10
The best-told story of the four in-form states. Raise, stir, stir, taste, with two clear motion bursts and both mouths opening. A prop gives the action a subject, which is exactly the job the dog's treat does. Lost marks: the pot reads as a blue bucket floating with no support, and the steam drifts up and to the right, half off the disc, where it reads as bubbles.
confirmed
3.6s loop · split verdict
6→6/10
Two different animations wearing one name. On a phone the discs close to a 0.4-disc overlap, the tentacle meets her glove, the squash lands: 7/10, the best thing in the set. On desktop they stop 528px apart and shake hands with half a screen between them, which is 4/10, because contact is the whole point of a handshake. It is also 8.5x the dog's motion energy, looping forever behind a confirmation the customer is trying to read.
error
3.0s loop · real beat
3→7/10
The biggest gain. Flinch, shrug, hold, sag. Burst 3.89x is the closest any state gets to the dog, 29% of the cycle is at rest, and it contains the only real hold in the set (750ms of complete stillness). The frown is unmistakable on both figures at both sizes. This is the one state where a stranger could tell you what is happening.
Each card stages the pair the way the real page stages them
below 1280, which is what most traffic sees. The 1280 staging cannot be reproduced inside a 300px card,
so it is shown as photographic evidence below.
Fix 1
Hue separation: verified independently, and it mostly landed
These numbers are not a re-run of the repair's solver. Every colour below was read off a rendered
screenshot: probe points in each shape's own SVG coordinates, mapped through getScreenCTM(),
sampled as the median of a 3x3 block at 3x device pixel ratio. Whatever the browser actually painted, including the
0.92 group opacity and the yellow plate behind, is in the number. The right-hand column is what the repair report
claimed for the same pair.
Adjacent pair (shapes that actually touch)
Measured
Report claimed
Rendered colours
OCTOPUS tentacle : tentacle
1.00
not listed
#04060B / #04060B
OCTOPUS tentacle : tentacle (r)
1.00
not listed
#04060B / #04060B
DETECTIVE hat brim : hat crown
1.00
not listed
#1E0E24 / #1E0E24
OCTOPUS pen : disc
1.18
not listed
#E2D2AF / #DCC427
DETECTIVE notepad : disc
1.18
not listed
#E2D2AF / #DCC427
DETECTIVE glove : disc
1.18
not listed
#E2D2AF / #DCC427
DETECTIVE hat brim : hair
3.17
3.19
#1E0E24 / #A9472A
DETECTIVE coat : trousers
3.25
3.29
#0B101E / #526493
OCTOPUS mantle : tentacle
3.27
3.30
#89459B / #04060B
DETECTIVE coat : sleeve
3.27
3.31
#0B101E / #266F77
DETECTIVE coat : collar
3.27
3.31
#0B101E / #266F77
DETECTIVE coat : hair
3.27
3.30
#0B101E / #A9472A
DETECTIVE sleeve : disc
3.30
not listed
#266F77 / #DCC427
OCTOPUS pot : tentacle
3.32
3.35
#2E678C / #04060B
OCTOPUS pot : pot rim
3.32
3.35
#2E678C / #04060B
DETECTIVE trousers : disc
3.32
3.30
#526493 / #DCC427
DETECTIVE skin : hair
3.33
not listed
#E1BE9D / #A9472A
DETECTIVE skin : collar
3.33
not listed
#E1BE9D / #266F77
OCTOPUS pot : disc
3.48
3.47
#2E678C / #DCC427
OCTOPUS mantle : disc
3.53
3.52
#89459B / #DCC427
DETECTIVE sleeve : glove
3.88
3.85
#266F77 / #E2D2AF
DETECTIVE coat : belt
3.95
3.96
#0B101E / #B6571C
OCTOPUS mantle : jaw patch
4.15
4.13
#89459B / #E2D2AF
OCTOPUS mantle : eye white
5.15
5.12
#89459B / #EAEAEA
DETECTIVE hat crown : hat band
10.47
10.50
#1E0E24 / #DCC427
DETECTIVE hat : disc
10.47
not listed
#1E0E24 / #DCC427
DETECTIVE hat brim : skin
10.54
10.63
#1E0E24 / #E1BE9D
DETECTIVE coat : disc
10.81
10.86
#0B101E / #DCC427
OCTOPUS tentacle : disc
11.55
11.62
#04060B / #DCC427
DETECTIVE skin : eye
11.63
not listed
#E1BE9D / #04060B
DETECTIVE skin : mouth
11.63
not listed
#E1BE9D / #04060B
DETECTIVE coat : glove
12.71
12.75
#0B101E / #E2D2AF
DETECTIVE coat : notepad
12.71
12.75
#0B101E / #E2D2AF
OCTOPUS jaw patch : mouth
13.58
13.65
#E2D2AF / #04060B
OCTOPUS jaw patch : open mouth
13.58
not listed
#E2D2AF / #04060B
OCTOPUS eye white : pupil
16.85
16.89
#EAEAEA / #04060B
DETECTIVE eye : catchlight
16.85
not listed
#04060B / #EAEAEA
The three pairs the critic named are gone. 1.14:1, 1.56:1 and 1.78:1 are now 3.27, 3.27 and 3.25.
Where the repair report and my pixel reads overlap, they agree to within 0.05. The fills really did change.
Six pairs still fail, and the report listed none of them. Tentacle against tentacle and
hat brim against hat crown are both 1.00:1, because each is one flat colour. The fedora is the figure's
signature shape and its brim and crown are literally indistinguishable.
Three cream-on-yellow pairs measure 1.18:1 (pen, notepad, glove against the disc). The report
did not report them. It substituted a different pair, the ink contour, and printed 11.62 and 12.75 in their place.
Swapping the failing pair for a passing one is not a measurement.
The contour defence is partly legitimate. The outline is real and I confirmed it in pixels at
every device ratio tested. It is 3 SVG units, which resolves to 2.87 CSS px at 1280 and
1.46 CSS px at 375. That works on a modern phone and it is what cartoons have always done.
It should have been declared as a mitigation, not laundered into the table as a pass.
The fix cost the brand. OctoMonic is black plus one yellow. Separation was bought by adding
violet, teal, rust, slate, auburn, blue, skin and cream: 8 of the 12 inks appear nowhere in the identity.
The Dog Camp loader got its 10 hues from inside its own palette. These two figures no longer look like the emblem
they came from. That is a real trade and Tom should make it deliberately rather than inherit it.
Fix 2
The face: this one is clean
Both figures have two mouth elements, a curve and an open "oh".
The detective had none at all before.
.om-m is animated in all six states: om-think, om-grin, om-press,
om-frown, om-mouthhide. .om-mo cross-fades in on working.
The old stylesheet touched the mouth in zero states.
Every state blinks. idle uses a double blink, filling / working / confirmed a single,
error a slow one, and choosing's blink is folded into the eye-dart keyframe rather than a separate rule.
Dead class names: .om-smile, .om-oh and
.om-doh exist in the JSX and are referenced by no CSS rule at all. Harmless, but the
critic's original finding was phrased around .om-smile, and it is worth knowing the
answer was to route around it rather than use it.
Fix 3
Presentation: fixed on phones, still broken on desktop
Viewport
State
Disc
Centre gap
Disc-widths
Edge gap
Layout
Opacity
1920
idle
300px
1080px
3.60
780px
block / absolute
0.92
1440
idle
245px
1025px
4.19
780px
block / absolute
0.92
1280
idle
218px
998px
4.58
780px
block / absolute
0.92
1280
confirmed
218px
746px
3.43
528px
block / absolute
1
1024
idle
118px
142px
1.20
24px
block / relative
0.92
768
idle
118px
142px
1.20
24px
block / relative
0.92
375
idle
101px
122px
1.20
20px
block / relative
0.92
375
confirmed
86px
52px
0.60
-34px
block / absolute
1
320
idle
92px
110px
1.20
18px
block / relative
0.92
/book at 1280. The two figures sit in opposite corners, 780px of edge gap with an entire
column of copy between them. The dashed floor that is supposed to join them is masked out across the middle.
This is the "two corners, not a scene" failure the CSS comment claims to have avoided./book at 375, RTL. The real win. The band is in flow at the top of the column, both figures
20px apart on one floor, both facing in, at full size. This used to be
display:none. RTL mirrors losslessly.
The handshake at 1280, five frames through the contact beat. They raise their arms and squash on
impact 528px apart. There is no impact. On a phone the same animation overlaps the discs by 40% and works.New defect, not in the original four. At 1280 the WhatsApp floating button paints on top of the
detective's disc, green on brand yellow. Nothing is blocked, the scene is
pointer-events:none, but it looks like a mistake.
Opacity 0.40 → 0.92, and 1.0 on the success screen. Confirmed in the live DOM on the built page.
This alone is most of why the figures read at all now.
The mobile blackout is over.display:block at 320, 375, 768 and 1024.
No display:none anywhere in the scene's rules. Most traffic can now see this.
The desktop gap was not fixed, it was re-expressed. The critic measured 933px, five disc-widths.
It is now 997.6px, 4.58 disc-widths at 1280. The absolute distance got 65px wider; only the ratio
moved, and only because the discs were made bigger. Even at 1920 it is 3.60. The reference is 1.36.
The WhatsApp button collides with the detective's disc at 1280.
Zero overlaps with any input, button or link at 375. pointer-events:none
and aria-hidden="true" throughout. Lighthouse accessibility on /book is 100.
Fix 4
Beat or wheel: four of six now have a shape
One full cycle per state, 32 frames, animations paused and the clock seeked so every frame is
reproducible. Cropped tight to the characters. Motion is mean frame-to-frame change with the dog set to 100.
Burst is peak over mean, which is what separates a beat from a wheel. Rest is the share of the cycle
that is effectively still, and hold is the longest unbroken run of stillness, in frames of 1/32nd of a cycle.
State
Motion
Burst
Rest
Hold
Motion through the cycle
DOG (reference)
100
5.70x
16%
3
idle
48
1.73x
0%
0
filling
29
2.99x
35%
3
choosing
27
2.41x
16%
3
working
43
3.15x
10%
2
confirmed
852
3.88x
10%
2
error
66
3.89x
29%
8
filling, working, confirmed and error all have real beats now: anticipation, action, settle,
with genuine rests between. error is the strongest, and its 8-frame hold is the only proper hold in the set.
idle is still a wheel and idle is the state everybody sees. Burst 1.73x against the dog's
5.70x, and 0% of its 7.2 second cycle is at rest. The reach beat is written correctly in the keyframes; it
is drowned by two continuous body bobs at 5.2s and 6.1s that never stop and never let the scene settle.
choosing is marginal at 2.41x and the lowest motion of all six.
confirmed is the opposite failure. 852 motion, 8.5x the dog, looping every 3.6s forever behind
a confirmation the customer is trying to read.
Build and page health
Nothing is broken
npm run build passes. Characters chunk 4.71 kB, 1.39 kB gzipped.
The character CSS is 15.4 kB with comments stripped, 3.5 kB gzipped.
The scene mounts on the built /book at 1280 and 375, in both English and Hebrew.
Zero console errors in all four combinations.
The state machine is live: typing into the first field moves the scene from
idle to filling on the real page.
prefers-reduced-motion leaves zero running animations, keeps the
figures visible and posed, mouth closed on a smile. Never hidden.
Lighthouse accessibility 100 on /book, zero failing audits.
No horizontal scroll and no overlap with form controls at 375 or 1280.
Ship it, with one more short pass
Is this good enough to put in front of a paying customer?
On a phone, yes. The mobile band is genuinely good: two characters 20px apart on one floor, readable at
101px, reacting to what the visitor is doing, with a payoff on confirmation that actually makes contact. That is the
majority of traffic and it is the first thing in five attempts that would not embarrass anyone.
On desktop, no, and it is not close. Two figures 780px apart in opposite corners, performing a two-hander
where one offers a pen to nobody and the other shakes hands with half a screen of air, with the WhatsApp button
parked on her disc. A customer would not read that as a scene. They would read it as two stickers.
Do not drop it. The remaining work is small and mostly deletion. The method is right now: simple primitives,
five or six moving parts, squash in the keyframes, arcs on offset-path, eyes and a mouth that move. What is missing
is staging, and staging is cheap:
1. Delete the 1280 branch. Use the band at every width. Two figures 1.2 disc-widths apart is the reference;
4.58 is not. This is a deletion, not a build.
2. Give idle a rest. Cut the two continuous bobs or gate them to part of the cycle, so there is a second and a
half where nothing moves. Stillness is what makes the dog's leap land.
3. Move the scene out from under the WhatsApp button.
4. Decide the palette question deliberately. Eight of twelve inks are off-brand. Either accept that these are
flat-illustration mascots rather than the emblem, or go back and buy separation with drawn contours and value inside
black and yellow, which is what noir cartoons do.
Three earlier QA passes waved animations through that Tom then rejected.
For the record: I would put the phone version in front of a paying customer today. I would not put the 1280 version
in front of one, and I would expect Tom to reject it for the same reason he rejected attempt one,
which is that the figures are not in a scene together.