feat(motion): tooltip entrance is now a bounce (rebote)

Replaces the tooltip's per-side 2px slide-in with a scale BOUNCE: grow from 0.7,
overshoot to 1.06, dip to 0.97 (the undershoot — the "rebote"), settle to 1, over
`--duration-moderate`. It scales from `transform-origin` (the floating origin,
toward the anchor), so it keeps a directional feel without per-side keyframes —
8 keyframes collapse to 2.

Bespoke recipe keyframe, NOT a registered `motion` preset: the tooltip's state is
3-valued (`delayed-open` / `instant-open` / `closed`), not the generic `open` /
`closed` the shared presets key on — so a registered preset wouldn't fire here.
This also confirms the architecture boundary: components whose state shape doesn't
match open/closed use a bespoke recipe animation, not the `motion` selector.

`instant-open` (keyboard focus) keeps no entrance; the reduced-motion block (kills
the animation) already covers the new rules — verified the bounce curve in-browser
(scale 0.7 → 1.06 → 0.97 → 1). Recipe-only change, no generator/base.css touched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
active-uix
dev 4 months ago
parent f2ed308d33
commit 7add5cbda0

@ -86,40 +86,20 @@
stroke-width: 1px; stroke-width: 1px;
} }
/* ── Entrance / exit per anchored side ────────────────────────────────────── /* ── Entrance / exit — bounce ────────────────────────────────────────────────
* Tooltip morfo's data-state values: 'closed' | 'delayed-open' | * Tooltip morfo's data-state values: 'closed' | 'delayed-open' | 'instant-open'.
* 'instant-open'. Both open variants should animate in. Soma's Floating * 'delayed-open' (hover) BOUNCES in; 'instant-open' (keyboard focus) appears with
* layer writes data-side (top/right/bottom/left). 'instant-open' is used * no entrance; 'closed' scales out. The bounce scales from `transform-origin`
* for keyboard focus (no entrance) and 'delayed-open' for hover. * (the floating origin, toward the anchor — set above), so it keeps a directional
* * feel without per-side keyframes. The tooltip's 3-value state (delayed-open, not
* Distance is smaller than Popover (2px vs 4px) — tooltip is lighter. */ * the generic open/closed) is why this is a bespoke recipe keyframe, not a
* registered `motion` preset. */
[data-tooltip-content][data-state='delayed-open'][data-side='top'] { [data-tooltip-content][data-state='delayed-open'] {
animation: eidos-tooltip-in-top var(--duration-fast) var(--ease-out) both; animation: eidos-tooltip-bounce-in var(--duration-moderate) var(--ease-out) both;
} }
[data-tooltip-content][data-state='closed'][data-side='top'] { [data-tooltip-content][data-state='closed'] {
animation: eidos-tooltip-out-top var(--duration-fast) var(--ease-default) both; animation: eidos-tooltip-bounce-out var(--duration-fast) var(--ease-in) both;
}
[data-tooltip-content][data-state='delayed-open'][data-side='bottom'] {
animation: eidos-tooltip-in-bottom var(--duration-fast) var(--ease-out) both;
}
[data-tooltip-content][data-state='closed'][data-side='bottom'] {
animation: eidos-tooltip-out-bottom var(--duration-fast) var(--ease-default) both;
}
[data-tooltip-content][data-state='delayed-open'][data-side='right'] {
animation: eidos-tooltip-in-right var(--duration-fast) var(--ease-out) both;
}
[data-tooltip-content][data-state='closed'][data-side='right'] {
animation: eidos-tooltip-out-right var(--duration-fast) var(--ease-default) both;
}
[data-tooltip-content][data-state='delayed-open'][data-side='left'] {
animation: eidos-tooltip-in-left var(--duration-fast) var(--ease-out) both;
}
[data-tooltip-content][data-state='closed'][data-side='left'] {
animation: eidos-tooltip-out-left var(--duration-fast) var(--ease-default) both;
} }
/* instant-open: no entrance — element appears at final position. */ /* instant-open: no entrance — element appears at final position. */
@ -128,32 +108,18 @@
transform: none; transform: none;
} }
@keyframes eidos-tooltip-in-top { /* A real bounce: grow past 1 (overshoot 1.06), dip slightly under (undershoot
from { opacity: 0; transform: translateY(2px); } * 0.97), settle to 1. Subtle in absolute terms (a tooltip is small) but clearly a
} * "rebote". `scale` (the individual property, not the `transform` shorthand)
@keyframes eidos-tooltip-out-top { * composes with `transform-origin` and never clobbers a sibling transform. */
to { opacity: 0; transform: translateY(2px); } @keyframes eidos-tooltip-bounce-in {
} 0% { opacity: 0; scale: 0.7; }
50% { opacity: 1; scale: 1.06; }
@keyframes eidos-tooltip-in-bottom { 74% { scale: 0.97; }
from { opacity: 0; transform: translateY(-2px); } 100% { scale: 1; }
}
@keyframes eidos-tooltip-out-bottom {
to { opacity: 0; transform: translateY(-2px); }
}
@keyframes eidos-tooltip-in-right {
from { opacity: 0; transform: translateX(-2px); }
}
@keyframes eidos-tooltip-out-right {
to { opacity: 0; transform: translateX(-2px); }
}
@keyframes eidos-tooltip-in-left {
from { opacity: 0; transform: translateX(2px); }
} }
@keyframes eidos-tooltip-out-left { @keyframes eidos-tooltip-bounce-out {
to { opacity: 0; transform: translateX(2px); } to { opacity: 0; scale: 0.9; }
} }
/* ── Trigger states ───────────────────────────────────────────────────────── /* ── Trigger states ─────────────────────────────────────────────────────────

Loading…
Cancel
Save

Powered by TurnKey Linux.