Wanted by Dawid on 12.09.2026, on top of #153 (PR #171). Three things, all
about how the orbit's motion is controlled and how the cue points answer a
press. Named together because they are one feel, not three fixes.
1. The easing is a parameter, not a switch
Today (#153): motion is a two-way toggle per work — Eased or Constant
(paramGlide in the dictionaries, stored in works.r360_params). The curve
itself is fixed in the code.
Wanted: the owner sets how much it eases, and ease-in and ease-out are
separate — a travel may gather pace gently and land sharply, or the other
way round. Dawid's words: expose a parameter for the acceleration of the
movement, ease-in and ease-out in particular.
Two things follow:
2. A click on the navigation ring eases — and so does every other travel
Settled with Dawid, 12.09.2026: the click he means is the one on the
navigation ring, not on the picture. The picture stays what it is — a
surface to drag, and a tap opens the enlarged view; nothing there starts a
travel, and nothing should.
Which makes most of this a check rather than a build. In PR #171 the
easing lives inside travelAlong itself (use-orbit.ts calls
run(path, travelDuration(path.length), glide ? "eased" : "steady")), and
every travel in the product goes through that one call:
- a click on the ring (
orbit-ring.tsx),
- a click on a cue marker on the ring,
- a press on a cue button under the picture (
orbit-cues.tsx:92).
So all three inherit it. What is owed here is not new motion but the proof
that it is one motion: a test that a travel started from a button eases the
same as one started from the ring, so that the day someone gives the buttons
a path of their own they cannot quietly give them a feel of their own.
3. The cue buttons
They light on the press, not on the arrival
Today (src/components/ui/orbit-cues.tsx:80): a button's solid state is
cue.frame === orbit.frame — it is a readout of where the orbit is. So a
press sends the orbit, and the button only lights when the orbit gets there,
a second later.
Wanted: pressing a cue lights it at once, and the orbit then runs to it.
The button reports what was asked for, not what has arrived.
This means the row needs a second piece of state — the cue being travelled to
— and a rule for what happens when a hand interrupts the travel (a drag, a
key): the lit button should stop claiming a destination the orbit is no longer
going to.
The cursor
Today every button in this application shows the plain arrow. Tailwind 4's
preflight (verified in node_modules/tailwindcss/preflight.css, 4.3.3) no
longer sets cursor: pointer on button, and the shared Button base class
(src/components/ui/button.tsx:8) sets only disabled:cursor-not-allowed.
cursor-pointer appears in src/ on a handful of labels and the ring's SVG,
and nowhere else.
Wanted: a cue button feels like a button under the mouse. Worth fixing
where it belongs — the shared base and the cue row — rather than on this one
row, because it is the same everywhere: nothing in the product currently says
"this is pressable" to a mouse.
Passing through a cue must not make it blink
Today: because the solid state is cue.frame === orbit.frame, a travel
that crosses a cue's frame snaps that button into the solid tone for a single
frame and out again. At 28 ms a frame that is a blink.
Wanted (Dawid): it may pulse, gently, as the orbit goes past — it must not
flash.
Constraint, carried from #107 and written next to the code: the row
deliberately has no colour transition, because a fade between the two tones
passes through colours that cannot be read, and axe caught exactly that
mid-way in the lightbox. So a pulse has to stay legible the whole way — it
cannot simply be transition-colors between the rest and the solid tone. Some
other property (a ring, a scale, an opacity that keeps contrast) is the way in.
Verification
Dependencies
Starts after PR #171 closes — Dawid's sequencing, 12.09.2026. That pull
request introduces the motion and the switch; this turns the switch into
parameters and rests on the travelAlong it leaves behind, so it is a next
step rather than a change to it. Related: #107 (the cue points and the reason the row has no colour
transition), #173 (the other motion Dawid asked for, on dragging a list).
Spec: SPEC.md A13 · Labels: ux
Wanted by Dawid on 12.09.2026, on top of #153 (PR #171). Three things, all
about how the orbit's motion is controlled and how the cue points answer a
press. Named together because they are one feel, not three fixes.
1. The easing is a parameter, not a switch
Today (#153): motion is a two-way toggle per work — Eased or Constant
(
paramGlidein the dictionaries, stored inworks.r360_params). The curveitself is fixed in the code.
Wanted: the owner sets how much it eases, and ease-in and ease-out are
separate — a travel may gather pace gently and land sharply, or the other
way round. Dawid's words: expose a parameter for the acceleration of the
movement, ease-in and ease-out in particular.
Two things follow:
with more positions; what is asked for is a value each way.
r360_paramsis JSON, so this needs no migration — the same placeR360 motion that eases: a ring click that speeds up and slows down, a drag that coasts, the owner's switch #153's switch lives. A work saved before them reads as whatever the switch
meant, so nothing already saved changes its feel without the owner asking.
2. A click on the navigation ring eases — and so does every other travel
Settled with Dawid, 12.09.2026: the click he means is the one on the
navigation ring, not on the picture. The picture stays what it is — a
surface to drag, and a tap opens the enlarged view; nothing there starts a
travel, and nothing should.
Which makes most of this a check rather than a build. In PR #171 the
easing lives inside
travelAlongitself (use-orbit.tscallsrun(path, travelDuration(path.length), glide ? "eased" : "steady")), andevery travel in the product goes through that one call:
orbit-ring.tsx),orbit-cues.tsx:92).So all three inherit it. What is owed here is not new motion but the proof
that it is one motion: a test that a travel started from a button eases the
same as one started from the ring, so that the day someone gives the buttons
a path of their own they cannot quietly give them a feel of their own.
3. The cue buttons
They light on the press, not on the arrival
Today (
src/components/ui/orbit-cues.tsx:80): a button's solid state iscue.frame === orbit.frame— it is a readout of where the orbit is. So apress sends the orbit, and the button only lights when the orbit gets there,
a second later.
Wanted: pressing a cue lights it at once, and the orbit then runs to it.
The button reports what was asked for, not what has arrived.
This means the row needs a second piece of state — the cue being travelled to
— and a rule for what happens when a hand interrupts the travel (a drag, a
key): the lit button should stop claiming a destination the orbit is no longer
going to.
The cursor
Today every button in this application shows the plain arrow. Tailwind 4's
preflight (verified in
node_modules/tailwindcss/preflight.css, 4.3.3) nolonger sets
cursor: pointeronbutton, and the sharedButtonbase class(
src/components/ui/button.tsx:8) sets onlydisabled:cursor-not-allowed.cursor-pointerappears insrc/on a handful of labels and the ring's SVG,and nowhere else.
Wanted: a cue button feels like a button under the mouse. Worth fixing
where it belongs — the shared base and the cue row — rather than on this one
row, because it is the same everywhere: nothing in the product currently says
"this is pressable" to a mouse.
Passing through a cue must not make it blink
Today: because the solid state is
cue.frame === orbit.frame, a travelthat crosses a cue's frame snaps that button into the solid tone for a single
frame and out again. At 28 ms a frame that is a blink.
Wanted (Dawid): it may pulse, gently, as the orbit goes past — it must not
flash.
Constraint, carried from #107 and written next to the code: the row
deliberately has no colour transition, because a fade between the two tones
passes through colours that cannot be read, and axe caught exactly that
mid-way in the lightbox. So a pulse has to stay legible the whole way — it
cannot simply be
transition-colorsbetween the rest and the solid tone. Someother property (a ring, a scale, an opacity that keeps contrast) is the way in.
Verification
difference; a work saved before them is unchanged
marker or a button
hand taking hold on the way clears that claim
the pointer
passes axe at every point of the animation, in both tones, light and
dark
prefers-reduced-motionstill wins over all of it (A13)Dependencies
Starts after PR #171 closes — Dawid's sequencing, 12.09.2026. That pull
request introduces the motion and the switch; this turns the switch into
parameters and rests on the
travelAlongit leaves behind, so it is a nextstep rather than a change to it. Related: #107 (the cue points and the reason the row has no colour
transition), #173 (the other motion Dawid asked for, on dragging a list).
Spec: SPEC.md A13 · Labels: ux