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).
This commit is contained in:
2026-06-05 13:51:50 -04:00
parent 04ad706de6
commit 73fe77f9aa
2 changed files with 27 additions and 38 deletions
+15 -23
View File
@@ -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);
}
}
// 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 targetPx = 100;
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.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);
+3 -6
View File
@@ -1,14 +1,11 @@
import Live2DCat from './Live2DCat';
export default function PetZone() {
return (
<div className="p-4 relative overflow-hidden" style={{ minHeight: 160 }}>
<div data-petzone className="p-4 relative overflow-hidden" style={{ minHeight: 140 }}>
<h3 className="text-sm font-bold text-kira-plum mb-2 flex items-center gap-2">
<span>🐱</span> Pet Zone
</h3>
<div className="flex flex-col items-center">
<Live2DCat className="w-28 h-28" />
<span className="text-[10px] text-kira-plum/60 mt-1 font-medium">Mochi</span>
<div className="flex flex-col items-center pt-2">
<span className="text-[10px] text-kira-plum/60 font-medium">Mochi</span>
</div>
</div>
);