The browser is running your scroll listener now.
The common read: positioning one box against another is geometry the platform does not do for you, so the numbers have to come from getBoundingClientRect and be written back every frame.
That decision, re-made at every step, is the whole job the listener existed to do: read where the trigger is, decide which side the tooltip fits on, write the numbers back, and do it again the moment anything moves. Two CSS properties hold the relationship instead.
anchor-name marks the trigger as an anchor under a name of your choosing, position-anchor on the tooltip says which named anchor this element is positioned against, and from there anchor() reads the anchor's edges into inset properties and position-area places the tooltip in one of the nine regions around it.
Chrome, Firefox and Safari all ship anchor positioning, and they spent its entire rollout disagreeing about what an unset position-anchor resolves to, which is why the second of those two properties is written out in every listing in this lesson.
The listener was right, and it was right because the platform gave you no way to say "under that element" in a stylesheet. Everything that listener grew after the first version is maintenance of that gap: the scroll handler, the resize handler, the frame throttle so the reads do not tank the main thread, the flip logic for the last row in the table, the teardown so none of it outlives the tooltip. None of it was ever about your product.
Takeaway: Sixty times a second, to confirm the box had not moved.
The trade is real and it is small. You give up the ability to read the position back out of an inline style attribute, which is what your integration tests were asserting on, and you give up doing anything clever with the number, because you no longer have it. What you get is a tooltip that is correct during a scroll you never subscribed to.
