From 7add5cbda035decc79b0fdcd06d080c795a9fda7 Mon Sep 17 00:00:00 2001 From: dev Date: Sun, 21 Jun 2026 00:40:39 +0200 Subject: [PATCH] feat(motion): tooltip entrance is now a bounce (rebote) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- src/uix/eidos/components/tooltip/tooltip.css | 80 ++++++-------------- 1 file changed, 23 insertions(+), 57 deletions(-) diff --git a/src/uix/eidos/components/tooltip/tooltip.css b/src/uix/eidos/components/tooltip/tooltip.css index 16bf9c1e0..e8affad41 100644 --- a/src/uix/eidos/components/tooltip/tooltip.css +++ b/src/uix/eidos/components/tooltip/tooltip.css @@ -86,40 +86,20 @@ stroke-width: 1px; } -/* ── Entrance / exit per anchored side ────────────────────────────────────── - * Tooltip morfo's data-state values: 'closed' | 'delayed-open' | - * 'instant-open'. Both open variants should animate in. Soma's Floating - * layer writes data-side (top/right/bottom/left). 'instant-open' is used - * for keyboard focus (no entrance) and 'delayed-open' for hover. - * - * Distance is smaller than Popover (2px vs 4px) — tooltip is lighter. */ +/* ── Entrance / exit — bounce ──────────────────────────────────────────────── + * Tooltip morfo's data-state values: 'closed' | 'delayed-open' | 'instant-open'. + * 'delayed-open' (hover) BOUNCES in; 'instant-open' (keyboard focus) appears with + * no entrance; 'closed' scales out. The bounce scales from `transform-origin` + * (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 + * 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'] { - animation: eidos-tooltip-in-top var(--duration-fast) var(--ease-out) both; +[data-tooltip-content][data-state='delayed-open'] { + animation: eidos-tooltip-bounce-in var(--duration-moderate) var(--ease-out) both; } -[data-tooltip-content][data-state='closed'][data-side='top'] { - animation: eidos-tooltip-out-top var(--duration-fast) var(--ease-default) 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; +[data-tooltip-content][data-state='closed'] { + animation: eidos-tooltip-bounce-out var(--duration-fast) var(--ease-in) both; } /* instant-open: no entrance — element appears at final position. */ @@ -128,32 +108,18 @@ transform: none; } -@keyframes eidos-tooltip-in-top { - from { opacity: 0; transform: translateY(2px); } -} -@keyframes eidos-tooltip-out-top { - to { opacity: 0; transform: translateY(2px); } -} - -@keyframes eidos-tooltip-in-bottom { - from { opacity: 0; transform: translateY(-2px); } -} -@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); } +/* A real bounce: grow past 1 (overshoot 1.06), dip slightly under (undershoot + * 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) + * composes with `transform-origin` and never clobbers a sibling transform. */ +@keyframes eidos-tooltip-bounce-in { + 0% { opacity: 0; scale: 0.7; } + 50% { opacity: 1; scale: 1.06; } + 74% { scale: 0.97; } + 100% { scale: 1; } } -@keyframes eidos-tooltip-out-left { - to { opacity: 0; transform: translateX(2px); } +@keyframes eidos-tooltip-bounce-out { + to { opacity: 0; scale: 0.9; } } /* ── Trigger states ─────────────────────────────────────────────────────────