/* Home: interactive character hero — header + hero always fit one screen.
 *
 * The hero is laid out on a "canvas" designed at a fixed height (--D: 830 / 770 / 740 px
 * for desktop / tablet / phone). --k = min(1px, available height / --D) scales every
 * vertical measure, so a short window shrinks the whole composition instead of scrolling;
 * a tall window keeps the original size and centres it.
 * Breakpoints: ≥1500, 1001–1499 (default), 701–1000, ≤700, ≤380.
 */
.page-home { height: 100svh; min-height: 540px; overflow: hidden; overflow: clip; } /* clip: can't be scrolled sideways when something inside gets focus */
.hero { position: relative; flex: 1 1 0; min-height: 0; container-type: size; isolation: isolate; }
.hero-canvas {
  --D: 830;
  --k: min(1px, 100cqh / var(--D));
  position: absolute; left: 0; right: 0; top: 50%;
  height: calc(var(--D) * var(--k));
  transform: translateY(-50%);
}

.hero h1 { color: var(--title, inherit); background: var(--title-fill, none); -webkit-background-clip: text; background-clip: text; position: relative; z-index: 0; margin: calc(-4 * var(--k)) 0 0; text-align: center; font-family: var(--serif); font-style: italic; font-weight: 600; font-size: min(max(90px, 13.8vw), 220px, calc(236 * var(--k))); line-height: 1.18; letter-spacing: -.075em; white-space: nowrap; }
.hero h1 em { color: var(--title-em, inherit); background: var(--title-em-fill, none); -webkit-background-clip: text; background-clip: text; position: relative; font-weight: 600; cursor: pointer; border-radius: 12px; }

.orbit { position: absolute; left: 50%; border: 1px solid var(--orbit); border-radius: 50%; pointer-events: none; z-index: -1; }
.orbit-one { width: calc(700 * var(--k)); height: calc(540 * var(--k)); top: calc(120 * var(--k)); transform: translateX(-50%) rotate(-18deg); }
.orbit-two { width: calc(570 * var(--k)); height: calc(660 * var(--k)); top: calc(70 * var(--k)); transform: translateX(-50%) rotate(35deg); }

/* Dog */
.dog-stage { position: absolute; left: 50%; transform: translateX(-50%); z-index: 2; width: calc(620 * var(--k)); height: calc(620 * var(--k)); top: calc(155 * var(--k)); } /* sits over the title, the top of the letters stays visible */
.floor-shadow { position: absolute; width: 55%; height: 6%; bottom: 1%; left: 23%; border-radius: 50%; background: var(--floor); filter: blur(17px); }
.dog { position: absolute; inset: 0; border: 0; padding: 0; background: none; transform-origin: 50% 87%; animation: breathe 5s ease-in-out infinite; cursor: pointer; }
.dog img, .dog .face { position: absolute; inset: 0; width: 100%; height: 100%; user-select: none; pointer-events: none; }
/* every picture of the dog is its own GPU layer from the start, so the browser renders the base picture
   and the reaction frames the same way. Without this the idle picture (inside the breathing animation) was
   drawn from a scaled bitmap — softer — and a frame swapped in for a reaction looked sharper, as if
   the dog suddenly got outlines. */
.dog { will-change: transform; }
.dog > img, .dog > .shake-rig, .dog .face { will-change: opacity; }
.eye { transform-box: view-box; transform-origin: 0 0; }
.happy-mouth { opacity: 0; transition: opacity .14s ease; pointer-events: none; }
.dog.happy .happy-mouth, .dog.snack .happy-mouth { opacity: 1; }
.dog.sleeping .happy-mouth { opacity: 0; }
/* a drawn "eyes closed" face (features.eyesClosed) replaces the lids while asleep */
.dog .sleep-eyes { opacity: 0; transition: opacity .4s ease; }
.dog.sleeping .sleep-eyes { opacity: 1; }
.dog.blinking .sleep-eyes { opacity: 1; transition: none; }
.dog.blinking:has(.sleep-eyes) .eye { opacity: 0; transition: none; }
.dog:has(.sleep-eyes) .eye { transition: opacity .4s ease; }
.dog.sleeping:has(.sleep-eyes) .eye { opacity: 0; transition: none; }
.dog.sleeping .sleep-eyes { transition: none; }   /* falling asleep (after the yawn): eyes shut at once, never open in between */
/* 點鼻子 (features.lickNose): the tongue-only layer is revealed from the mouth upward and pulled back down.
   1.6 s: tongue out 0.8 · on the nose 0.3 · back 0.5 */
.dog .lick-nose { opacity: 0; }
.dog .lick-nose image { transform-box: fill-box; transform-origin: 50% 100%; }
.dog.licking .lick-nose { opacity: 1; }
.dog.licking .lick-nose image { animation: lick 1.6s both; }
.dog.licking .happy-mouth { opacity: 0; }
@keyframes lick {
  0% { clip-path: inset(100% 0 0 0); transform: translateY(10px); animation-timing-function: ease-out; }
  50% { clip-path: inset(0 0 0 0); transform: none; }
  68.75% { clip-path: inset(0 0 0 0); transform: none; animation-timing-function: ease-in; }
  100% { clip-path: inset(100% 0 0 0); transform: translateY(10px); }
}
@media (prefers-reduced-motion: reduce) { .dog.licking .lick-nose image { animation: none; } }
.dog.happy { animation: happy .6s ease-in-out 2; }
.dog.snack { animation: snack .35s ease-in-out 4; }
.dog.sleeping { animation: breathe 7s ease-in-out infinite; filter: saturate(.82); }
/* head tilt (叫名字 / looking for you): cross-fade to the head-tilted frame (tools/make_tilt.py —
   the original body with a redrawn head). The tilted frame comes in first and the base leaves a
   moment later, so the dog never turns see-through; the eye/tongue overlay belongs to the upright
   head and hides meanwhile. */
.dog > .tilt-frame { opacity: 0; transition: opacity .22s ease .08s; }
.dog > img.base { transition: opacity .3s ease-in-out; }
.dog .face { transition: opacity .12s ease; }
.dog.tilt-left .tilt-frame { opacity: 1; transition: opacity .22s ease; }
.dog.tilt-left > img.base { opacity: 0; transition: opacity .22s ease; }
.dog.tilt-left .face { opacity: 0; }
/* 摸摸頭 with a drawn happy face (features.petHappy: 飛機耳 + tongue, tools/make_tilt.py <pet> happy):
   same cross-fade as the tilt; the eye / tongue overlay belongs to the plain face and hides meanwhile */
.dog > .happy-frame { opacity: 0; transition: opacity .15s ease .05s; }
.dog.happy .happy-frame { opacity: 1; transition: opacity .15s ease; }
.dog.happy:has(.happy-frame.ready) > img.base { opacity: 0; transition: opacity .15s ease; }
.dog.happy:has(.happy-frame) .face { opacity: 0; }
/* 打哈欠 before falling asleep (features.yawn, tools/make_tilt.py <pet> yawn) */
/* the mouth opens slowly (0.8s), stays wide (0.8s), closes (0.5s); the base stays underneath (the yawn
   frame is the same picture apart from the face) so the cross-fade never turns see-through */
/* frame in / base out at the same time and the same speed: stacking the two semi-transparent fur
   edges at full strength would make a hard white outline around the body */
/* 打哈欠 in steps (home.js): .y1 = half-open mouth, .y2 = wide open (shown over the half frame, so the dog
   never turns see-through); then back to half, then asleep */
.dog > .yawn-half-frame, .dog > .yawn-frame { opacity: 0; transition: opacity .28s ease-in-out; }
.dog.y1 > .yawn-half-frame { opacity: 1; }
.dog.y2 > .yawn-frame { opacity: 1; }
.dog.y1:has(.yawn-half-frame.ready) > img.base, .dog.y2:has(.yawn-frame.ready) > img.base { opacity: 0; transition: opacity .28s ease-in-out; }
.dog.y1 .face, .dog.y2 .face { opacity: 0; transition: opacity .2s ease; }
/* frame-by-frame yawn (character.yawnSeq, tools/make_yawn_frames.py): one picture flipped through the
   in-between frames; while it plays it stands in for the base picture */
.dog > .yawn-seq { opacity: 0; transition: none; }
.dog.yseq > .yawn-seq { opacity: 1; }
.dog.yseq > img.base { opacity: 0; transition: none; }
.dog.yseq .face { opacity: 0; transition: none; }
/* closing the mouth after the yawn: the drawn closed eyes already lie over the frames, eyes then stay shut */
.dog.yseq.drowsy .face { opacity: 1; }
.dog.drowsy:has(.sleep-eyes) .eye { opacity: 0; transition: none; }
.dog.drowsy .sleep-eyes { opacity: 1; transition: none; }
/* 嗷嗚 (features.howl, tools/make_tilt.py <pet> howl): head lifts slowly, holds, comes back */
.dog > .howl-frame { opacity: 0; transition: opacity .5s ease-in-out; }
.dog.howling .howl-frame { opacity: 1; transition: opacity .6s ease-in-out; }
.dog.howling:has(.howl-frame.ready) > img.base { opacity: 0; transition: opacity .6s ease-in-out; }
.dog.howling .face { opacity: 0; transition: opacity .3s ease; }
/* 搖尾巴 (features.wag, tools/make_wag.py): the left-tail frame fades in, the right-tail frame flips on and
   off over it while the left one flips the other way (the two differ only in the tail zones) */
/* home.js toggles .tail-right every 180 ms; the frames swap instantly (they are identical outside the tail
   zones and the face), and the base is only hidden once the frames are decoded */
/* one still body (happy face, no tail) for the whole wag; only the two tail layers toggle on top */
.dog > img.wag-frame { opacity: 0; transition: none; }
.dog.wagging:has(.wag-r.ready) > .wag-body { opacity: 1; }
/* 摸摸: the same wag with the airplane-ears face */
.dog.wagging.happywag:has(.wag-body-happy.ready) > .wag-body { opacity: 0; }
.dog.wagging.happywag:has(.wag-body-happy.ready) > .wag-body-happy { opacity: 1; }
.dog.wagging:has(.wag-r.ready) > .wag-l { opacity: 1; }
.dog.wagging.tail-right:has(.wag-r.ready) > .wag-l { opacity: 0; }
.dog.wagging.tail-right:has(.wag-r.ready) > .wag-r { opacity: 1; }
.dog.wagging:has(.wag-r.ready) > img.base { opacity: 0; transition: none; }
.dog.wagging:has(.wag-r.ready) .face { opacity: 0; }
.zone-body { border-radius: 45% 45% 30% 30%; }
.ask { position: absolute; left: -4%; top: 17%; z-index: 4; white-space: nowrap; pointer-events: none;
  font: italic 17px Georgia, 'PingFang TC', serif; color: var(--hint-strong); letter-spacing: 1px;
  padding: 7px 16px 8px; background: var(--bubble); border: 1px solid var(--bubble-border); border-radius: 20px; box-shadow: 0 6px 18px rgb(var(--shadow-rgb) / .078);
  transform-origin: 90% 100%; animation: ask-pop 1.9s ease-out forwards; }
/* the bubble's tail, pointing down-right at his head */
.ask::after { content: ""; position: absolute; right: 14px; bottom: -6px; width: 11px; height: 11px; background: inherit;
  border-right: 1px solid var(--bubble-border); border-bottom: 1px solid var(--bubble-border); transform: rotate(45deg); }
@keyframes ask-pop { 0% { opacity: 0; transform: scale(.6) rotate(-4deg); } 14% { opacity: 1; transform: scale(1.05) rotate(-4deg); } 24% { transform: scale(1) rotate(-4deg); } 82% { opacity: 1; } 100% { opacity: 0; transform: translateY(-8px) rotate(-4deg); } }
.heart { position: absolute; font-family: Arial, sans-serif; color: var(--heart); font-size: 26px; animation: float-up 1.6s ease-out forwards; pointer-events: none; }

/* Hot zones over the head and the right front paw (invisible, keyboard-focusable) */
/* the stage box overlaps the title: only the hot zones take clicks, so the letters of the name
   behind the transparent corners stay clickable (叫名字) */
.dog-stage { pointer-events: none; }
.zone { position: absolute; border: 0; padding: 0; background: transparent; cursor: pointer; border-radius: 50%; pointer-events: auto; }
/* .zone-head / .zone-paw position and size: generated per pet from pet.json (character.zoneHead / zonePaw) */
.zone-paw { border-radius: 40% 40% 30% 30%; }
.zone-nose { z-index: 1; }
.zone:focus-visible { outline: 2px dashed var(--focus-soft); outline-offset: -2px; }

/* Handwritten hints around the dog */
.hint { position: absolute; z-index: 3; margin: 0; display: flex; align-items: flex-end; gap: 8px; font: italic 17px Georgia, 'PingFang TC', serif; line-height: 1.55; color: var(--hint); pointer-events: none; }
.hint svg { width: 46px; height: 31px; flex: none; }
.hint-head { left: calc(50% - 560 * var(--k)); top: calc(315 * var(--k)); transform: rotate(-5deg); }
.hint-paw { left: calc(50% + 300 * var(--k)); top: calc(595 * var(--k)); transform: rotate(4deg); }
.hint-paw svg { transform: translateY(-14px); }
.hint-nose { left: calc(50% - 560 * var(--k)); top: calc(440 * var(--k)); transform: rotate(-3deg); }
.hint-nose svg { transform: translateY(-10px); }
/* 稱讚: a button like zzz — tapping it wags the tail (the body can be tapped too) */
.hint-body { left: calc(50% - 540 * var(--k)); top: calc(575 * var(--k)); flex-direction: column; align-items: flex-start; gap: 2px; pointer-events: auto; cursor: pointer; border: 0; background: transparent; padding: 6px 10px; border-radius: 14px; transform: rotate(-4deg); transition: background .2s; }
@media (hover: hover) { .hint-body:hover { background: var(--hover); } }
.hint-body .praise { font: italic 600 26px var(--serif); color: var(--accent); letter-spacing: 1px; }
.hint-body .zzz-label { font: 11px var(--sans); letter-spacing: 1.5px; color: var(--muted); }
.hint-name .short, .hint-nose .short, .hint-body .short { display: none; }
/* the name hint lives inside <em>name.</em>, so it follows the word whatever size the title is */
.hint-name { left: calc(100% + 16px); top: 30%; transform: rotate(-4deg); letter-spacing: normal; text-align: left; }
@media (max-width: 1280px) { .hint-name { font-size: 14px; } }
.hint-zzz { left: calc(50% + 400 * var(--k)); top: calc(295 * var(--k)); flex-direction: column; align-items: flex-start; gap: 2px; pointer-events: auto; cursor: pointer; border: 0; background: transparent; padding: 6px 10px; border-radius: 14px; transform: rotate(6deg); transition: background .2s; }
@media (hover: hover) { .hint-zzz:hover { background: var(--hover); } }
.hint-zzz .zzz { font: italic 600 30px var(--serif); color: var(--accent); letter-spacing: 1px; }
.hint-howl { left: calc(50% + 400 * var(--k)); top: calc(430 * var(--k)); flex-direction: column; align-items: flex-start; gap: 2px; pointer-events: auto; cursor: pointer; border: 0; background: transparent; padding: 6px 10px; border-radius: 14px; transform: rotate(-3deg); transition: background .2s; }
@media (hover: hover) { .hint-howl:hover { background: var(--hover); } }
.hint-howl .howl-word { font: italic 600 26px var(--serif); color: var(--accent); letter-spacing: 1px; }
.hint-howl .zzz-label { font: 11px var(--sans); letter-spacing: 1.5px; color: var(--muted); }
.hint-zzz .zzz-label { font: 11px var(--sans); letter-spacing: 1.5px; color: var(--muted); }
.hint-zzz[aria-pressed="true"] .zzz { font-size: 24px; }
.hint-zzz .sun { width: 26px; height: 26px; display: block; color: var(--sun); }

/* 握手: crossfade to a redrawn "paw up" frame (see tools/make_paw.py); the paw layer bobs on top */
.shake-rig { position: absolute; inset: 0; opacity: 0; transition: opacity .32s ease; pointer-events: none; }
.shake-rig img { position: absolute; inset: 0; width: 100%; height: 100%; user-select: none; }
.shake-paw { transform-origin: var(--paw-origin, 50% 70%); }   /* elbow, per pet: character.pawOrigin */
.dog > img { transition: opacity .32s ease; }
.dog.shaking > img { opacity: 0; }
.dog.shaking .shake-rig { opacity: 1; }
.dog.shaking .shake-paw { animation: paw-bob 2s ease-in-out .35s both; }

@keyframes paw-bob {
  0%, 100% { transform: none; }
  22%, 66% { transform: translate(.3%, -1.4%) rotate(-4deg); }
  44%      { transform: translate(.1%, .3%) rotate(1deg); }
  85%      { transform: translate(.1%, .2%); }
}
@keyframes breathe { 50% { transform: translateY(-5px) scale(1.006); } }
@keyframes happy { 25% { transform: rotate(-3deg) translateY(-8px); } 75% { transform: rotate(3deg) translateY(-8px); } }
@keyframes snack { 50% { transform: translateY(5px) rotate(1deg); } }
@keyframes float-up { 0% { opacity: 0; transform: translateY(0) scale(.5); } 20% { opacity: 1; } 100% { opacity: 0; transform: translateY(-120px) rotate(15deg) scale(1.3); } }

@media (min-width: 1500px) {
  .hero-canvas { max-width: 1500px; margin-inline: auto; }
  .dog-stage { width: calc(670 * var(--k)); height: calc(670 * var(--k)); top: calc(160 * var(--k)); }
}
@media (max-width: 1000px) {
  .hero-canvas { --D: 770; }
  .hint { font-size: 15px; }
  /* narrow tablets: keep the hints inside the viewport */
  .hint-head { left: max(10px, calc(50% - 470 * var(--k))); top: calc(290 * var(--k)); }
  .hint-paw { left: min(calc(100% - 230px), calc(50% + 255 * var(--k))); top: calc(580 * var(--k)); }
  .hint-nose { left: max(10px, calc(50% - 470 * var(--k))); top: calc(420 * var(--k)); }
  .hint-body { left: max(10px, calc(50% - 470 * var(--k))); top: calc(555 * var(--k)); }
  .hint-zzz { left: min(calc(100% - 150px), calc(50% + 330 * var(--k))); top: calc(270 * var(--k)); }
  .hint-howl { left: min(calc(100% - 150px), calc(50% + 330 * var(--k))); top: calc(410 * var(--k)); }
  .hint-name { left: auto; right: 0; top: calc(100% - 2px); text-align: right; transform: rotate(-3deg); } /* under the end of the word */
  .dog-stage { width: calc(575 * var(--k)); height: calc(575 * var(--k)); top: calc(135 * var(--k)); }
  .floor-shadow { bottom: 7%; }
}
@media (max-width: 700px) {
  /* Phones: a top-to-bottom stack instead of hints in the corners (which kept colliding with the
     ears). The canvas fills the hero; the character's size is whatever fits between the title (--T) and
     the hint strip at the bottom (--B), capped at 112vw, and he is centred in that space. */
  .page-home { min-height: 520px; }
  .hero-canvas { --T: 150px; --B: 76px; --S: min(120vw, 100cqh - var(--T) - var(--B)); --top: calc(var(--T) + (100cqh - var(--T) - var(--B) - var(--S)) / 2);
    top: 0; height: 100%; transform: none; }
  .hero h1 { font-size: min(16.5vw, 64px); margin-top: 30px; }
  .dog-stage { width: var(--S); height: var(--S); top: var(--top); }
  .floor-shadow { bottom: 1%; }
  .orbit-one { width: calc(var(--S) * .92); height: calc(var(--S) * .9); top: calc(var(--top) + var(--S) * .06); }
  .orbit-two { width: calc(var(--S) * .78); height: calc(var(--S) * 1.02); top: calc(var(--top) - var(--S) * .02); }

  /* the hint strip */
  .hint { font-size: 13px; gap: 4px; line-height: 1.5; white-space: nowrap; }
  .hint svg { display: none; } /* no arrows on phones */
  /* 摸摸頭: upper right of his head, the other side from the 叫我嗎？ bubble */
  .hint-head { left: auto; right: 18px; top: max(118px, calc(var(--top) - var(--S) * .02 - 22px)); text-align: right; transform: rotate(3deg); }
  /* bottom row under his paws: zzz on the left, 握手 on the right, level with each other */
  .hint-paw { left: auto; right: 18px; top: min(calc(var(--top) + var(--S) * .99 + 22px), calc(100% - 56px)); bottom: auto; flex-direction: column; align-items: flex-end; text-align: right; transform: rotate(3deg); }
  .hint-nose { left: 14px; right: auto; top: calc(var(--top) + var(--S) * .38); text-align: left; transform: rotate(-3deg); }
  .hint-body { left: 10px; right: auto; top: calc(var(--top) + var(--S) * .58); transform: rotate(-3deg); padding: 6px 10px; white-space: nowrap; }
  .hint-body .praise { font-size: 18px; }
  .hint-body .zzz-label { font-size: 10px; }
  .hint-zzz { left: 10px; right: auto; top: min(calc(var(--top) + var(--S) * .99 + 16px), calc(100% - 66px)); bottom: auto; transform: rotate(-3deg); flex-direction: column; align-items: flex-start; gap: 0; padding: 6px 10px; white-space: nowrap; }
  .hint-zzz .zzz { font-size: 20px; }
  .hint-howl { left: 10px; right: auto; top: calc(var(--top) + var(--S) * .2); transform: rotate(-3deg); padding: 6px 10px; white-space: nowrap; }
  .hint-howl .howl-word { font-size: 18px; }
  .hint-howl .zzz-label { font-size: 10px; }
  .hint-zzz .zzz-label { font-size: 10px; }
  .hint-zzz .sun { width: 20px; height: 20px; }

  /* name hint: one small line under "hello," */
  /* under "hello,": the hint is positioned on the h1 (em made static); "hello" starts 2.33 title
     font-sizes left of centre (Playfair italic, measured) */
  .hero h1 em { position: static; }
  .hint.hint-name { font-size: 14px; left: calc(50% - 2.33 * min(16.5vw, 64px) + 6px); right: auto; top: calc(100% + 2px); bottom: auto; transform: rotate(-3deg); }
  /* the name hint gets a short one-line label on phones */
  .hint-name .long { display: none; }
  .hint-name .short { display: inline; }
  .hint-nose .long, .hint-body .long { display: none; }
  .hint-nose .short, .hint-body .short { display: inline; }

  /* 叫我嗎？ bubble: over the empty corner left of his head */
  .ask { left: max(calc((var(--S) - 100vw) / 2 + 14px), 16%); top: 0%; font-size: 14px; padding: 5px 13px 6px; }   /* up-left of the head, but never off screen */ /* 14px in from the screen edge (the stage is wider than the screen) */
}
@media (prefers-reduced-motion: reduce) { .heart { opacity: 0; } .dog { transition: none; } .ask { animation-duration: .01s; } }

/* ---------- props bar (pet.json character.props): toys / gestures to play with the dog ----------
   Two columns beside the dog on wide screens, one row along the bottom on phones. When a pet has props,
   the old handwritten hints step aside. */
.hero-canvas:has(.props) .hint:not(.hint-head):not(.hint-paw):not(.hint-name):not(.hint-touch) { display: none; }
/* with props, the dog itself only answers on the head (click → pet) and the paw (hover / tap → handshake);
   nose / body taps are the props' job */
.hero-canvas:has(.props) .zone:not(.zone-head):not(.zone-paw):not(.zone-touch) { display: none; }
/* direct touches (pet.json character.hover / tap, home.js): the mouse near a spot / a tap on it plays frames.
   Zone boxes and hint positions are generated per pet by tools/build.py (touch_parts) */
.zone-touch { border-radius: 45%; z-index: 1; }
.hint-touch { align-items: center; }
.hint-touch .short { display: none; }
@media (max-width: 700px) { .hint-touch .long { display: none; } .hint-touch .short { display: inline; } }
.props { position: absolute; z-index: 3; display: flex; flex-direction: column; gap: calc(26 * var(--k)); top: calc(300 * var(--k)); }
.props-left { left: calc(50% - 520 * var(--k)); }
/* the head hint sits beside his head on the left: that column starts lower so they never overlap */
@media (min-width: 701px) { .hero-canvas:has(.hint-head) .props-left { top: calc(430 * var(--k)); } }
.props-right { left: calc(50% + 400 * var(--k)); }
.prop { display: flex; flex-direction: column; align-items: center; gap: 7px; padding: 0; border: 0; background: none; cursor: pointer; color: var(--hint-strong); }
.prop-disc { width: max(52px, calc(78 * var(--k))); height: max(52px, calc(78 * var(--k))); border-radius: 50%; display: grid; place-items: center; overflow: hidden;
  background: var(--bubble); border: 1px solid var(--bubble-border); box-shadow: 0 6px 18px rgb(var(--shadow-rgb) / .08); transition: transform .18s ease, box-shadow .18s ease; }
.prop-icon { width: 64%; height: 64%; fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; }
.prop-photo { width: 100%; height: 100%; object-fit: contain; }
/* a picture prop (e.g. a friend's drawn head on a transparent background) stands on its own: no disc, no border */
.prop-disc:has(.prop-photo) { background: none; border: 0; box-shadow: none; border-radius: 0; overflow: visible; }
.prop-label { font: italic 15px Georgia, 'PingFang TC', serif; letter-spacing: 1px; color: var(--hint); white-space: nowrap; }
@media (hover: hover) { .prop:hover .prop-disc { transform: translateY(-3px); box-shadow: 0 10px 24px rgb(var(--shadow-rgb) / .14); } }
.prop:focus-visible { outline: none; }
.prop:focus-visible .prop-disc { outline: 2px dashed var(--focus-soft); outline-offset: 3px; }
.prop:active .prop-disc { transform: scale(.94); }
/* the prop flying over to the dog (a copy of the icon, position: fixed, animated from home.js) */
.prop-ghost { position: fixed; z-index: 20; pointer-events: none; display: grid; place-items: center; color: var(--hint-strong); }
.prop-ghost .prop-icon { width: 100%; height: 100%; }
.prop-ghost .prop-photo { filter: drop-shadow(0 6px 14px rgb(var(--shadow-rgb) / .2)); }
/* cream on his nose (features.creamNose) — shown when the cream lands, gone while he licks it off */
.dog .cream-nose { opacity: 0; transition: opacity .25s ease; }
.dog.creamy .cream-nose { opacity: 1; }
/* licked off: as the tongue comes up (0.8 s into the 1.6 s lick) the cream is swept toward it and gone */
.dog .cream-nose image { transform-box: fill-box; transform-origin: 50% 60%; }
.dog.creamy.licking .cream-nose image { animation: cream-eaten 1.6s both; }
@keyframes cream-eaten { 0%, 40% { transform: none; opacity: 1; } 62%, 100% { transform: translate(22px, 10px) scale(.15); opacity: 0; } }
@media (max-width: 1000px) {
  .props { top: calc(290 * var(--k)); gap: calc(22 * var(--k)); }
  .props-left { left: max(14px, calc(50% - 450 * var(--k))); }
  .props-right { left: auto; right: max(14px, calc(50% - 450 * var(--k))); }
}
.props-wrap { display: contents; }
@media (max-width: 700px) {
  /* phones: all props in one column on the left, beside his body */
  .props-wrap { display: flex; flex-direction: column; gap: 10px; position: absolute; z-index: 5; left: 10px;
    top: calc(var(--top) + var(--S) * .3); }
  .props, .hero-canvas:has(.hint-head) .props-left { position: static; top: auto; left: auto; right: auto; bottom: auto; width: auto; flex-direction: column; gap: 10px; }
  .prop-disc { width: 50px; height: 50px; }
  .prop-label { font-size: 12px; letter-spacing: 0; }
  /* many props: two rows of four under the pet instead of a column (the pet gets smaller to make room) */
  .hero-canvas:has(.props-many) { --B: 196px; }
  .props-wrap.props-many { top: auto; bottom: 12px; left: 8px; right: 8px; display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px 4px; }
  .props-wrap.props-many .props { display: contents; }
  .props-wrap.props-many .prop-disc { width: 46px; height: 46px; }
}

/* ---------- prop animations made of whole frames (pet.json character.frames, e.g. 哈蜜's belly-up swatting):
   the frames replace the sitting picture (cross-fade in, flip between frames, cross-fade back) ---------- */
.dog > .frame-seq { opacity: 0; transition: opacity .3s ease; }
.dog.framing > .frame-seq { opacity: 1; }
.dog.framing > img.base, .dog.framing .face { opacity: 0; transition: opacity .3s ease; }

/* ears turned toward the pointer (character.ears): swap, no fade (stacked soft edges would show an outline) */
.dog > img.ears-frame { opacity: 0; transition: none; }
.dog.ears-l:has(img.ears-left.ready) > img.ears-left, .dog.ears-r:has(img.ears-right.ready) > img.ears-right { opacity: 1; }
.dog.ears-l:has(img.ears-left.ready) > img.base, .dog.ears-r:has(img.ears-right.ready) > img.base { opacity: 0; transition: none; }
.dog.framing > img.ears-frame { opacity: 0 !important; }

/* 散步 (prop action 'walk', home.js walk()): the sitting picture cross-fades to the walking frames (.frame-seq),
   which home.js slides off to the right; .away = she is gone (empty stage, the hot zones off) until 回來 */
.floor-shadow { transition: opacity .6s ease; }
.dog > .frame-seq { will-change: opacity, transform; }
.dog.walking > .frame-seq { opacity: 1; }
.dog.walking.returning > .frame-seq { transition: none; }
.dog.walking > img:not(.frame-seq), .dog.walking .face, .dog.walking > .shake-rig,
.dog.away > img, .dog.away .face, .dog.away > .shake-rig { opacity: 0 !important; transition: opacity .3s ease; }
.dog-stage:has(.dog.walking, .dog.away) .floor-shadow { opacity: 0; }
.dog-stage:has(.dog.walking, .dog.away) .zone { pointer-events: none; }

/* while she walks out / is away, the other props step aside (only the walk prop — 回來 — stays) */
.prop { transition: opacity .3s ease; }
.hero-canvas:has(.dog.walking, .dog.away) .prop:not([data-action="walk"]) { opacity: 0; pointer-events: none; }

/* 晚安 (prop action 'rest', home.js rest()): the sitting picture cross-fades to a sleeping frame (.frame-seq) until
   起床; the eyes overlay, hot zones and the other props are off meanwhile, and a few "z" float up (.heart.zz) */
.dog.resting > .frame-seq { opacity: 1; transition-duration: .6s; }
.dog.resting > img:not(.frame-seq), .dog.resting .face, .dog.resting > .shake-rig { opacity: 0 !important; transition: opacity .6s ease; }
.dog-stage:has(.dog.resting) .zone { pointer-events: none; }
.hero-canvas:has(.dog.resting) .prop:not([data-action="rest"]) { opacity: 0; pointer-events: none; }
.heart.zz { font: italic 600 24px Georgia, 'Times New Roman', serif; color: var(--hint-strong); animation: zz-up 3.4s ease-out both; }
@keyframes zz-up { 0% { opacity: 0; transform: translate(0, 0) scale(.6); } 25% { opacity: .85; } 100% { opacity: 0; transform: translate(46px, -120px) rotate(-12deg) scale(1.3); } }

/* ambient flick (home.js ambient(), pet.json character.ambient, e.g. 哈蜜's lazy tail): the frame REPLACES the sitting
   picture outright — no fade either way (.flick-end for the step back), never two soft-edged pictures stacked; the eyes stay */
.dog.flicking > .frame-seq, .dog.flick-end > .frame-seq { transition: none; }
.dog.flicking > .frame-seq { opacity: 1; }
.dog.flicking > img.base, .dog.flicking > img.ears-frame { opacity: 0 !important; transition: none; }
.dog.flick-end > img.base { transition: none; }

.dog > .frame-seq { transform-origin: 50% 98%; }   /* scaled walking frames keep their feet on the floor */

/* phones: the chin hint has no free spot beside her (it would sit on her fur) and there's no mouse to hover with */
/* phones: the chin hint sits in the free corner up-right of her head (it would cover her fur beside the chin) */
@media (max-width: 700px) {
  .hint-touch.hint-chin { left: auto; right: 12px; top: calc(var(--top) + var(--S) * .02); bottom: auto; text-align: right; transform: rotate(3deg); }
  .hint-touch.hint-chin svg { display: none; }
  .dog.resting > .frame-seq { transform: scale(.8); transform-origin: 50% 98%; }   /* lying down she's wider than the screen */
}

/* while an animation plays, the handwritten hints step aside (they come back when it ends) */
.hint { transition: opacity .25s ease; }
.hero-canvas:has(.dog.framing, .dog.walking, .dog.away, .dog.resting, .dog.shaking, .dog.happy, .dog.wagging, .dog.howling, .dog.licking, .dog.yawning, .dog.tilt-left) .hint:not(.hint-zzz):not(.hint-body):not(.hint-howl) { opacity: 0 !important; visibility: hidden; pointer-events: none; }

/* phones: the stage is wider than the screen — whole-frame prop animations shrink so the pose fits (home.js FRAME_K) */
@media (max-width: 700px) { .dog.frame-wide > .frame-seq { transform: scale(.8); transform-origin: 50% 98%; } }   /* only wide poses (frames.wide) */
