Kitten yields to cards: fade, shrink, click-through; plus UX review doc
When a suggestion card drifts into the mascot's corner, the kitten turns translucent (15%), steps back 10% (standalone `scale` so it composes with the bob animation), and lets clicks pass through to the card. It wakes while its bubble or the picker is open, or once the corner clears. Also adds UX_REVIEW_2026-07-27.md — the hands-on review of the live deploy turned into implementation-ready items (repro, location, fix, acceptance). Claude-Session: https://claude.ai/code/session_016y6gyuHkQXPiEuW8RGQyua
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
import { useEffect, useState, type RefObject } from 'react'
|
||||
|
||||
// How often to re-measure outside of scroll/resize events. Cards re-pack when
|
||||
// suggestions arrive, expand, or get accepted — none of which fire an event we
|
||||
// can hear from here, so a slow poll picks those up.
|
||||
const POLL_MS = 500
|
||||
|
||||
// True while any suggestion card (.petal-rail-card) overlaps the given element.
|
||||
// Used to fade the corner mascot out of the way when the rail grows down into
|
||||
// its corner, so a card is never hidden (or made unclickable) by the kitten.
|
||||
export function useCardOverlap(ref: RefObject<HTMLElement | null>): boolean {
|
||||
const [overlapped, setOverlapped] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
let raf = 0
|
||||
|
||||
const check = () => {
|
||||
const el = ref.current
|
||||
if (!el) return
|
||||
const r = el.getBoundingClientRect()
|
||||
let hit = false
|
||||
for (const card of document.querySelectorAll('.petal-rail-card')) {
|
||||
const b = card.getBoundingClientRect()
|
||||
if (b.left < r.right && b.right > r.left && b.top < r.bottom && b.bottom > r.top) {
|
||||
hit = true
|
||||
break
|
||||
}
|
||||
}
|
||||
setOverlapped(hit)
|
||||
}
|
||||
|
||||
const schedule = () => {
|
||||
if (raf) return
|
||||
raf = requestAnimationFrame(() => {
|
||||
raf = 0
|
||||
check()
|
||||
})
|
||||
}
|
||||
|
||||
check()
|
||||
// Capture phase so scrolls inside nested scrollers (History panel, rail) count.
|
||||
window.addEventListener('scroll', schedule, true)
|
||||
window.addEventListener('resize', schedule)
|
||||
const timer = window.setInterval(check, POLL_MS)
|
||||
return () => {
|
||||
window.removeEventListener('scroll', schedule, true)
|
||||
window.removeEventListener('resize', schedule)
|
||||
window.clearInterval(timer)
|
||||
cancelAnimationFrame(raf)
|
||||
}
|
||||
}, [ref])
|
||||
|
||||
return overlapped
|
||||
}
|
||||
Reference in New Issue
Block a user