feat: redesign floating add cat
ci / gitleaks (push) Successful in 8s
ci / docker (push) Successful in 6m14s

This commit is contained in:
2026-09-11 22:08:53 +08:00
parent 48f1186f4d
commit e07a1de1f5
3 changed files with 114 additions and 31 deletions
+55 -9
View File
@@ -1047,19 +1047,19 @@ describe('unified floating add interaction', () => {
expect(css).toContain('@media(max-width:930px){.unified-fab{bottom:calc(82px + env(safe-area-inset-bottom))}')
})
it('renders a sleeping cream-orange cat that wakes while dragging and faces inward at an edge', () => {
it('renders the approved open-eyed orange cat and mirrors only its tail tip at an edge', () => {
expect(floatingAdd).toContain('class="fab-cat"')
expect(floatingAdd).toContain('class="fab-cat-eye fab-cat-eye--sleeping"')
expect(floatingAdd).toContain('class="fab-cat-eye fab-cat-eye--awake"')
expect(floatingAdd).toContain('class="fab-cat__eye fab-cat__eye--left"')
expect(floatingAdd).toContain('class="fab-cat__eye fab-cat__eye--right"')
expect(floatingAdd).toContain("const edge = ref<'left' | 'right'>('right')")
expect(floatingAdd).toContain("'edge-left': edge === 'left'")
expect(floatingAdd).toContain(':class="{ dragging, snapping, awake: dragging, sleeping: !dragging')
expect(floatingAdd).not.toContain('sleeping: !dragging')
expect(css).toContain('.unified-fab .fab-cat{')
expect(css).toContain('.unified-fab.sleeping .fab-cat-body{transform-origin:29px 40px;animation:fab-cat-breathe')
expect(css).toContain('.unified-fab.awake .fab-cat-eye--awake{opacity:1}')
expect(css).toContain('.unified-fab.edge-left .fab-cat{transform:scaleX(-1)}')
expect(css).toContain('@keyframes fab-cat-breathe')
expect(css).toContain('@media(prefers-reduced-motion:reduce){.unified-fab.sleeping .fab-cat-body{animation:none}')
expect(css).toContain('.fab-cat__eye{fill:#fff9f0;stroke:#98482c;stroke-width:1.15;')
expect(css).toContain('.unified-fab.edge-left .fab-cat__tail{transform:translateX(56px) scaleX(-1)}')
expect(css).not.toContain('.unified-fab.edge-left .fab-cat{transform:scaleX(-1)}')
expect(css).not.toContain('@keyframes fab-cat-breathe')
expect(css).toContain('@media(prefers-reduced-motion:reduce){.unified-fab,.unified-fab *')
})
it('keeps the countdown close button but removes its orange circular background', () => {
@@ -1317,3 +1317,49 @@ describe('sidebar layout', () => {
expect(app).toContain("writeStoredNavigation(window.localStorage, NAVIGATION_STORAGE_KEY, view, activeList.value)")
})
})
describe('orange dumpling cat floating add button', () => {
it('renders only the approved asymmetric cat head, tail tip, open eyes, and white forehead plus', () => {
expect(floatingAdd).toContain('class="fab-cat__tail"')
expect(floatingAdd).toContain('class="fab-cat__ear fab-cat__ear--left"')
expect(floatingAdd).toContain('class="fab-cat__ear fab-cat__ear--right"')
expect(floatingAdd).toContain('class="fab-cat__face-shape"')
expect(floatingAdd).toContain('class="fab-cat__pupils"')
expect(floatingAdd).toContain('class="fab-cat__plus"')
expect(floatingAdd).toContain('d="M24.8 20.4 H31.4 M28.1 17.1 V23.7"')
expect(floatingAdd).not.toMatch(/fab-cat-(body|back|sleep)|<text/)
})
it('tracks the latest drag vector for bounded pupil movement and keeps existing drag and activation contracts', () => {
expect(floatingAdd).toContain('const pupilOffset = ref({ x: 0, y: 0 })')
expect(floatingAdd).toContain('let suppressClickTimer: number | undefined')
expect(floatingAdd).toContain("'--pupil-x': `${pupilOffset.value.x}px`")
expect(floatingAdd).toContain("'--pupil-y': `${pupilOffset.value.y}px`")
expect(floatingAdd).toContain('const length = Math.hypot(vectorX, vectorY)')
expect(floatingAdd).toContain('x: vectorX / length * 1.5')
expect(floatingAdd).toContain('y: vectorY / length * 1.1')
expect(floatingAdd).toContain('isFabDrag(deltaX, deltaY)')
expect(floatingAdd).toContain('if (suppressClickTimer) window.clearTimeout(suppressClickTimer)')
expect(floatingAdd).toContain("emit('activate', { x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 })")
})
it('uses the approved colors and restrained state motion without mirroring the whole cat', () => {
expect(css).toContain('.unified-fab{display:grid;place-items:center;position:fixed;z-index:60;right:20px;bottom:22px;width:56px;height:56px;padding:0;border:1px solid #e9b88f;border-radius:50%;background:#fff3df;')
expect(css).toContain('.fab-cat__face-shape,.fab-cat__ear{fill:#f28c52;stroke:#98482c;stroke-width:1.5;')
expect(css).toContain('.fab-cat__plus{fill:none;stroke:#fff;stroke-width:2.4;')
expect(css).toContain('animation:fab-cat-blink 8s 2.4s infinite')
expect(css).toContain('@keyframes fab-cat-blink{0%,5%,100%{transform:scaleY(1)}1.875%,2.875%{transform:scaleY(.12)}}')
expect(css).toContain('@media(hover:hover) and (pointer:fine)')
expect(css).toContain('.unified-fab.dragging{box-shadow:inset 0 1px 0 rgba(255,255,255,.78),0 9px 20px rgba(112,65,35,.24)}')
expect(css).toContain('.unified-fab.snapping .fab-cat__tail{animation:fab-cat-tail-wag .46s cubic-bezier(.25,.8,.35,1) 1}')
expect(css).toContain('.unified-fab:focus-visible{outline:3px solid #a84628;outline-offset:3px}')
expect(css).not.toContain('.unified-fab.edge-left .fab-cat{transform:scaleX(-1)}')
expect(css).not.toContain('fab-cat-breathe')
})
it('disables every cat motion for reduced-motion users while leaving pointer behavior intact', () => {
expect(css).toContain('@media(prefers-reduced-motion:reduce){.unified-fab,.unified-fab *,.unified-fab:before,.unified-fab:after{animation:none!important;transition:none!important}')
expect(floatingAdd).toContain("window.matchMedia('(prefers-reduced-motion: reduce)').matches")
expect(floatingAdd).toContain('pupilOffset.value = { x: 0, y: 0 }')
})
})