From 73fe77f9aa3c022c373217ce2dd5dbba9dd63a10 Mon Sep 17 00:00:00 2001 From: hobokenchicken Date: Fri, 5 Jun 2026 13:51:50 -0400 Subject: [PATCH] fix(pets): dynamically position cat at PetZone DOM element Cat position is now measured from the [data-petzone] DOM element's getBoundingClientRect(), so it always aligns with the PetZone section regardless of window size or sidebar content height. Removed Live2DCat import from PetZone (cat renders on shared stage). --- frontend/src/components/Live2DStage.tsx | 56 +++++++++++-------------- frontend/src/components/PetZone.tsx | 9 ++-- 2 files changed, 27 insertions(+), 38 deletions(-) diff --git a/frontend/src/components/Live2DStage.tsx b/frontend/src/components/Live2DStage.tsx index ae8a456..56c072a 100644 --- a/frontend/src/components/Live2DStage.tsx +++ b/frontend/src/components/Live2DStage.tsx @@ -12,49 +12,38 @@ interface Props { * - Mochi the cat (PetZone, bottom of right sidebar) * * Canvas sits behind UI panels (z-0, pointer-events: none). + * Cat position is dynamically measured from the [data-petzone] DOM element. */ -// --- Layout constants (must match Tailwind in App.tsx) --- -// Left sidebar: w-72 = 288px, gap-4 = 16px, px-4 = 16px -// Right sidebar: w-64 = 256px, bottom bar: ~40px const PAD = 16; const LEFT_W = 288; const GAP = 16; const RIGHT_W = 256; -const BOTTOM_BAR_H = 40; -function positionModels( - kira: any, cat: any, - appW: number, appH: number, -) { - // Center panel center +function positionKira(kira: any, appW: number, appH: number) { const centerLeft = PAD + LEFT_W + GAP; const centerRight = appW - GAP - RIGHT_W - PAD; const centerMidX = (centerLeft + centerRight) / 2; const centerMidY = appH * 0.46; - if (kira) { - const s = Math.min(((centerRight - centerLeft) * 0.78) / kira.width, (appH * 0.78) / kira.height); - kira.scale.set(s); - kira.anchor.set(0.5, 0.5); - kira.position.set(centerMidX, centerMidY); - } + const s = Math.min(((centerRight - centerLeft) * 0.78) / kira.width, (appH * 0.78) / kira.height); + kira.scale.set(s); + kira.anchor.set(0.5, 0.5); + kira.position.set(centerMidX, centerMidY); +} - // Right sidebar center - const rightLeft = appW - RIGHT_W - PAD; - const rightMidX = rightLeft + RIGHT_W / 2; - const catY = appH - BOTTOM_BAR_H - 50; +function positionCat(cat: any) { + const el = document.querySelector('[data-petzone]'); + if (!el || !cat) return; + const r = el.getBoundingClientRect(); - if (cat) { - // Set scale to 1 first to get natural bounds, then compute target - cat.scale.set(1); - const naturalH = cat.getLocalBounds().height; - const targetPx = 100; // ~100px tall rendered - const cs = naturalH > 0 ? targetPx / naturalH : 0.05; - cat.scale.set(cs); - cat.anchor.set(0.5, 0.5); - cat.position.set(rightMidX, catY); - } + cat.scale.set(1); + const naturalH = cat.getLocalBounds().height; + const targetPx = 100; + const cs = naturalH > 0 ? targetPx / naturalH : 0.05; + cat.scale.set(cs); + cat.anchor.set(0.5, 0.5); + cat.position.set(r.left + r.width / 2, r.top + r.height / 2 + 10); } export default function Live2DStage({ onKiraReady, onReady }: Props) { @@ -92,7 +81,8 @@ export default function Live2DStage({ onKiraReady, onReady }: Props) { const onResize = () => { app.renderer.resize(window.innerWidth, window.innerHeight); - positionModels(kiraModel, catModel, app.screen.width, app.screen.height); + if (kiraModel) positionKira(kiraModel, app.screen.width, app.screen.height); + if (catModel) positionCat(catModel); }; window.addEventListener('resize', onResize); @@ -102,7 +92,7 @@ export default function Live2DStage({ onKiraReady, onReady }: Props) { }); if (!mounted) { app.destroy(true); return; } - positionModels(kiraModel, null, app.screen.width, app.screen.height); + positionKira(kiraModel, app.screen.width, app.screen.height); (kiraModel as any).isInteractive = () => false; app.stage.addChild(kiraModel as any); @@ -118,7 +108,9 @@ export default function Live2DStage({ onKiraReady, onReady }: Props) { }); if (!mounted) return; - positionModels(kiraModel, catModel, app.screen.width, app.screen.height); + // Small delay to ensure PetZone DOM is rendered + await new Promise(r => setTimeout(r, 100)); + positionCat(catModel); (catModel as any).isInteractive = () => false; app.stage.addChild(catModel as any); diff --git a/frontend/src/components/PetZone.tsx b/frontend/src/components/PetZone.tsx index d530a8f..e1dd6eb 100644 --- a/frontend/src/components/PetZone.tsx +++ b/frontend/src/components/PetZone.tsx @@ -1,14 +1,11 @@ -import Live2DCat from './Live2DCat'; - export default function PetZone() { return ( -
+

🐱 Pet Zone

-
- - Mochi +
+ Mochi
);