/* The two of you: rig, wardrobe, poses and props.
 *
 * A pose is a set of joint angles, nothing more. `public/cast.js` sets one
 * `data-pose` on a character and everything below decides what the shoulders,
 * elbows, hips and knees do about it, which is why sitting on a throne, being
 * dragged down a corridor and lying flat on the floor all come out of the same
 * two drawings.
 *
 * Everything animates transform and opacity only. No filters on anything that
 * moves: a drop shadow on a walking character repaints it every frame, and this
 * room is open on a phone all evening.
 */

.cast {
  position: fixed;
  inset: 0;
  z-index: 38; /* over the room, under the sheets and the toasts */
  overflow: hidden;
  /* Scenery does not take taps. The characters opt back in below, and SVG only
     hit-tests painted pixels, so a tap in the gap between his legs still
     reaches whatever button is under him. */
  pointer-events: none;
  transition: opacity 0.3s ease;

  --skin-1: #edc19b;
  --skin-2: #f8d3b4;
  --hair: #4a3227;
  --hair-hi: #6d4c34;
  --dress: #ff7aa8;
  --dress-dk: #dd4f86;
  --shirt: #7f95cd;
  --jeans: #3f4d6b;
  --shoe: #3a2b33;
  --ink: #3a2b33;
  --line: rgba(58, 43, 51, 0.24);
  --s: 0.9;
}

/* The GIF picker opens exactly where they like to stand. */
body.picking .cast { opacity: 0.16; }

.toon {
  position: absolute;
  left: 0;
  top: 0;
  width: 120px;
  height: 220px;
  /* The soles. Scaling and leaning both happen around this point, so nobody
     ever sinks into the floor or floats above it. */
  transform-origin: 60px 214px;
  transform:
    translate(calc(var(--x, 0px) - 60px), calc(var(--y, 0px) - 214px))
    scale(var(--s))
    scaleX(var(--flip, 1))
    rotate(var(--lean, 0deg));
  will-change: transform;
  transition: opacity 0.3s ease;
}

.t-svg {
  width: 100%;
  height: 100%;
  overflow: visible;
  pointer-events: auto;
}

.toon.t-hidden { opacity: 0; }

/* ------------------------------------------------------------ wardrobe */

.t-shadow { fill: rgba(58, 43, 51, 0.15); }

.t-seg, .t-hand, .t-face, .t-neck, .t-dress, .t-shirt, .t-jeans, .t-shoe, .t-hair, .t-hair-back {
  stroke: var(--line);
  stroke-width: 1.3;
  stroke-linejoin: round;
}

.toon-him .t-seg, .toon-him .t-face, .toon-him .t-neck { fill: var(--skin-1); }
.toon-her .t-seg, .toon-her .t-face, .toon-her .t-neck { fill: var(--skin-2); }

.toon-him [class*="t-leg"] .t-seg { fill: var(--jeans); }
.toon-him .t-arm-b > .t-seg, .toon-him .t-arm-f > .t-seg { fill: var(--shirt); }

.t-shoe { fill: var(--shoe); }
.t-ear { fill: inherit; stroke: var(--line); stroke-width: 1.2; }
.toon-him .t-ear { fill: var(--skin-1); }
.toon-her .t-ear { fill: var(--skin-2); }
.t-dress { fill: var(--dress); }
.t-dress-hem { fill: var(--dress-dk); }
.t-bust { stroke: rgba(58, 43, 51, 0.16); stroke-width: 2; fill: none; stroke-linecap: round; }
.t-jawline { stroke: rgba(58, 43, 51, 0.16); stroke-width: 1.6; fill: none; stroke-linecap: round; }
.t-hair-fringe { fill: var(--hair-hi); }
.t-hair-swoop { fill: var(--hair); }
.t-hair-top { fill: var(--hair-hi); opacity: 0.9; }
.t-stubble { fill: rgba(58, 43, 51, 0.1); }
.t-strap { stroke: var(--dress-dk); stroke-width: 3.5; fill: none; stroke-linecap: round; }
.t-shirt { fill: var(--shirt); }
.t-shirt-hi { fill: #fff; opacity: 0.09; }
.t-collar { stroke: rgba(58, 43, 51, 0.3); stroke-width: 2; fill: none; stroke-linecap: round; }
.t-jeans { fill: var(--jeans); }

/* One soft shade down the far side of the body. Cheap depth, and it is what
   keeps them from reading as flat stickers at the size they are drawn. */
.t-shade { fill: rgba(58, 43, 51, 0.13); }

/* ------------------------------------------------------------ nude */

/* Clothes on by default. Anatomy sits under the wardrobe and only shows
   when someone presses strip. */
.t-nude-bits { display: none; }
.toon.t-nude .t-nude-bits { display: block; }

.toon-her.t-nude .t-dress,
.toon-her.t-nude .t-dress-hem,
.toon-her.t-nude .t-bust,
.toon-her.t-nude .t-strap,
.toon-her.t-nude .t-dress-shade { display: none; }

.toon-him.t-nude .t-shirt,
.toon-him.t-nude .t-jeans,
.toon-him.t-nude .t-collar,
.toon-him.t-nude .t-shirt-shade { display: none; }

/* Arms and legs go skin once the sleeves and jeans are gone. */
.toon-him.t-nude [class*="t-leg"] .t-seg { fill: var(--skin-1); }
.toon-him.t-nude .t-arm-b > .t-seg,
.toon-him.t-nude .t-arm-f > .t-seg { fill: var(--skin-1); }
.toon.t-nude .t-shoe { opacity: 0.55; }

.toon-her .t-torso { fill: var(--skin-2); stroke: var(--line); stroke-width: 1.3; stroke-linejoin: round; }
.toon-him .t-torso { fill: var(--skin-1); stroke: var(--line); stroke-width: 1.3; stroke-linejoin: round; }

.t-boob { fill: var(--skin-2); stroke: var(--line); stroke-width: 1.2; }
.t-boob-shade { fill: rgba(217, 64, 122, 0.12); }
.t-nipple { fill: #e08a9a; stroke: rgba(58, 43, 51, 0.18); stroke-width: 0.8; }
.t-mound { fill: var(--skin-2); stroke: var(--line); stroke-width: 1.1; }
.t-pussy { stroke: #c46b7c; stroke-width: 2.2; fill: none; stroke-linecap: round; }
.t-nude-shade { fill: rgba(58, 43, 51, 0.12); }

.t-balls { fill: var(--skin-1); stroke: var(--line); stroke-width: 1.2; }
.t-dick { fill: var(--skin-1); stroke: var(--line); stroke-width: 1.2; stroke-linejoin: round; }
.t-dick-tip { fill: #e8a896; stroke: var(--line); stroke-width: 1; }

/* Hard / excited: a little more present. */
.toon.t-hard .t-dick { transform: translateY(-4px) scaleY(1.18); transform-box: fill-box; transform-origin: center top; }
.toon.t-hard .t-dick-tip { transform: translateY(-6px); transform-box: fill-box; transform-origin: center; }

/* Half naked: bottoms off, tops stay (his shirt; her dress is one piece so
   she goes bare — still the joke of a poop date, not a full strip show). */
.toon.t-half .t-nude-bits { display: block; }
.toon-him.t-half .t-jeans { display: none; }
.toon-him.t-half [class*="t-leg"] .t-seg { fill: var(--skin-1); }
.toon-her.t-half .t-dress,
.toon-her.t-half .t-dress-hem,
.toon-her.t-half .t-bust,
.toon-her.t-half .t-strap,
.toon-her.t-half .t-dress-shade { display: none; }
/* His shirt stays on for half. Her has no separate top, so torso is bare. */
.toon.t-half .t-shoe { opacity: 0.55; }

/* ------------------------------------------------------------ rear */

/* Turned around: hide the face, show the back of the head, and when nude
   (or half) show the ass instead of front bits. */
.t-rear-bits { display: none; }
.t-hair-back-head { display: none; fill: var(--hair); }

.toon.t-back .t-eyes,
.toon.t-back .t-ko-eyes,
.toon.t-back .t-mouth,
.toon.t-back .t-blush,
.toon.t-back .t-brows,
.toon.t-back .t-shine,
.toon.t-back .t-stubble,
.toon.t-back .t-jawline,
.toon.t-back .t-bruise,
.toon.t-back .t-hair-fringe,
.toon.t-back .t-hair-hi { display: none; }

.toon.t-back .t-hair,
.toon.t-back .t-hair-swoop,
.toon.t-back .t-hair-top { opacity: 0; }
.toon.t-back .t-hair-back-head { display: block; }

/* Front anatomy off; rear on when bare. */
.toon.t-back .t-front-bits { display: none; }
.toon.t-back.t-nude .t-rear-bits,
.toon.t-back.t-half .t-rear-bits { display: block; }

/* Clothed back still reads as turned: hide bust line, keep clothes. */
.toon.t-back .t-bust { display: none; }
.toon.t-back .t-collar { display: none; }

.toon-her .t-ass { fill: var(--skin-2); stroke: var(--line); stroke-width: 1.2; }
.toon-him .t-ass { fill: var(--skin-1); stroke: var(--line); stroke-width: 1.2; }
.t-ass-shade { fill: rgba(58, 43, 51, 0.1); }
.t-ass-cleft { stroke: rgba(58, 43, 51, 0.28); stroke-width: 1.8; fill: none; stroke-linecap: round; }
.t-hole { fill: rgba(58, 43, 51, 0.22); stroke: rgba(58, 43, 51, 0.2); stroke-width: 0.8; }
.toon-her .t-back-torso { fill: var(--skin-2); stroke: var(--line); stroke-width: 1.3; stroke-linejoin: round; }
.toon-him .t-back-torso { fill: var(--skin-1); stroke: var(--line); stroke-width: 1.3; stroke-linejoin: round; }

/* A little wiggle when someone is showing off. */
.toon.t-back[data-pose="present"] .t-ass-l { animation: t-ass-wiggle 0.7s ease-in-out infinite alternate; }
.toon.t-back[data-pose="present"] .t-ass-r { animation: t-ass-wiggle 0.7s ease-in-out infinite alternate-reverse; }

@keyframes t-ass-wiggle {
  from { transform: translateX(-1px); }
  to { transform: translateX(1px); }
}

.t-hair, .t-hair-back, .t-hair-side { fill: var(--hair); }
.t-hair-hi { fill: var(--hair-hi); }

.t-eye { fill: var(--ink); }
.t-shine { fill: #fff; }
.t-brow { stroke: var(--hair); stroke-width: 2.6; fill: none; stroke-linecap: round; }
.t-mouth { stroke: var(--ink); stroke-width: 2.2; fill: none; stroke-linecap: round; }
.t-blush { fill: #ff7fa6; opacity: 0.3; transition: opacity 0.3s ease; }
.t-ko-eyes { display: none; }
.t-ko-eyes path { stroke: var(--ink); stroke-width: 2.6; fill: none; stroke-linecap: round; }
.t-crown { fill: #ffcb47; stroke: #d99b16; stroke-width: 1.2; display: none; }
.t-bruise, .t-sweat, .t-cup, .t-bouquet { display: none; }
.t-bruise ellipse { fill: #b56ad0; opacity: 0.5; }
.t-plaster { fill: #ffdfa8; stroke: var(--line); stroke-width: 1; }
.t-sweat { fill: #8fd3ff; stroke: var(--line); stroke-width: 1; }

/* Coffee mug: ceramic body, dark brew, foam, little heart. Always readable
   (hardcoded colours so dark mode does not swallow the cup into the room). */
.t-mug-body { fill: #fff8f2; stroke: #c4a090; stroke-width: 1.2; stroke-linejoin: round; }
.t-mug-rim { fill: #fff8f2; stroke: #c4a090; stroke-width: 1.1; }
.t-mug-coffee { fill: #5c3317; }
.t-mug-foam { fill: #f5e6d3; opacity: 0.95; }
.t-mug-heart { fill: #ff7aa8; stroke: #e85a8a; stroke-width: 0.6; }
.t-cup-handle { stroke: #c4a090; stroke-width: 2.2; fill: none; stroke-linecap: round; }
.t-steam { stroke: #c8b8b0; stroke-width: 1.4; fill: none; stroke-linecap: round; opacity: 0.85; }
.toon.t-holding .t-steam-1 { animation: t-steam 1.4s ease-in-out infinite; }
.toon.t-holding .t-steam-2 { animation: t-steam 1.4s ease-in-out 0.25s infinite; }
.toon.t-holding .t-steam-3 { animation: t-steam 1.4s ease-in-out 0.5s infinite; }

@keyframes t-steam {
  0%, 100% { opacity: 0.35; transform: translateY(0); }
  50% { opacity: 0.95; transform: translateY(-3px); }
}

/* Bouquet of flowers — big, readable, stays until another scene clears it. */
.toon.t-flowers .t-bouquet { display: block; }
.t-bq-stems path { stroke: #3d7a45; stroke-width: 2.2; fill: none; stroke-linecap: round; }
.t-bq-leafs ellipse { fill: #4caf50; stroke: #2e7d32; stroke-width: 0.8; }
.t-bq-a circle:not(.t-bq-center) { fill: #ff7aa8; stroke: #e85a8a; stroke-width: 0.7; }
.t-bq-b circle:not(.t-bq-center) { fill: #ff9ec0; stroke: #f070a0; stroke-width: 0.7; }
.t-bq-c circle:not(.t-bq-center) { fill: #fff0f5; stroke: #f0b0c8; stroke-width: 0.7; }
.t-bq-d circle:not(.t-bq-center) { fill: #ff5a9a; stroke: #d9407a; stroke-width: 0.7; }
.t-bq-e circle:not(.t-bq-center) { fill: #ffc0d8; stroke: #f090b0; stroke-width: 0.7; }
.t-bq-center { fill: #ffd978; stroke: #e8b840; stroke-width: 0.6; }
.t-bq-wrap { fill: #fff8e8; stroke: #e8d0a8; stroke-width: 1.1; }
.t-bq-ribbon { stroke: #ff7aa8; stroke-width: 1.8; fill: none; stroke-linecap: round; }
.toon.t-flowers .t-bouquet { animation: t-bq-sway 2.8s ease-in-out infinite; transform-box: fill-box; transform-origin: center bottom; }
@keyframes t-bq-sway {
  0%, 100% { transform: rotate(-3deg); }
  50% { transform: rotate(4deg); }
}

/* ---------------------------------------------------------------- rig */

/* Every joint rotates about its own origin, which the static translate on its
   parent group has already placed at the shoulder, elbow, hip or knee. */
.j {
  transform-box: view-box;
  transform-origin: 0 0;
  transition: transform 0.2s cubic-bezier(0.3, 1.2, 0.4, 1);
}

.t-arm-b { transform: rotate(8deg); }
.t-arm-b-2 { transform: rotate(6deg); }
.t-arm-f { transform: rotate(-8deg); }
.t-arm-f-2 { transform: rotate(6deg); }

.t-eyes {
  transform-box: fill-box;
  transform-origin: center;
  animation: t-blink 6.2s ease-in-out infinite;
}

@keyframes t-blink {
  0%, 46%, 52%, 100% { transform: scaleY(1); }
  49% { transform: scaleY(0.08); }
}

.t-say {
  position: absolute;
  left: 50%;
  bottom: 78%;
  transform-origin: center bottom;
  /* Counter-flips the parent, or the words come out mirrored when she is
     walking left. */
  transform: translateX(-50%) scaleX(var(--flip, 1)) scale(0.7);
  opacity: 0;
  padding: 5px 10px;
  border-radius: 999px;
  background: var(--card);
  border: 2px solid var(--dress);
  /* Not `--ink`: that one is defined on `.cast` above as the colour you draw a
     cartoon outline with, and this element is inside it. See app.css. */
  color: var(--say-ink, #4a3540);
  font: 800 11px/1.3 var(--font, sans-serif);
  white-space: nowrap;
  box-shadow: 0 6px 16px rgba(217, 64, 122, 0.28);
  pointer-events: none;
  transition: transform 0.22s cubic-bezier(0.2, 1.6, 0.4, 1), opacity 0.22s;
}

.t-say.on { opacity: 1; transform: translateX(-50%) scaleX(var(--flip, 1)) scale(1); }

/* -------------------------------------------------------------- states */

.toon.t-blushing .t-blush { opacity: 0.85; }
.toon.t-crowned .t-crown { display: block; }
.toon.t-hurt .t-bruise { display: block; }
.toon.t-holding .t-cup { display: block; }
.toon.t-messy .t-hair, .toon.t-messy .t-hair-back { transform: rotate(-4deg) translateY(-2px); transform-box: fill-box; transform-origin: center bottom; }
.toon.t-messy .t-sweat { display: block; }

/* Angry: brows down and in, mouth flat. */
.toon.t-angry .t-brow-l { transform: rotate(22deg) translateY(2px); }
.toon.t-angry .t-brow-r { transform: rotate(-22deg) translateY(2px); }
.toon.t-angry .t-brow-l, .toon.t-angry .t-brow-r { transform-box: fill-box; transform-origin: center; }
.toon.t-angry .t-mouth { d: path("M-6 11 q6 -3 12 0"); }

/* ------------------------------------------------------------- walking */

@keyframes t-legA {
  0%, 100% { transform: rotate(26deg); }
  50% { transform: rotate(-24deg); }
}

@keyframes t-legB {
  0%, 100% { transform: rotate(-24deg); }
  50% { transform: rotate(26deg); }
}

@keyframes t-kneeA {
  0%, 100% { transform: rotate(3deg); }
  30% { transform: rotate(34deg); }
  65% { transform: rotate(6deg); }
}

@keyframes t-kneeB {
  0%, 100% { transform: rotate(30deg); }
  35% { transform: rotate(4deg); }
  70% { transform: rotate(28deg); }
}

@keyframes t-bobY {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-2.5px); }
}

[data-pose="walk"] .t-leg-f, [data-pose="run"] .t-leg-f,
[data-pose="haul"] .t-leg-f, [data-pose="carry"] .t-leg-f { animation: t-legA var(--gait, 0.62s) linear infinite; }
[data-pose="walk"] .t-leg-b, [data-pose="run"] .t-leg-b,
[data-pose="haul"] .t-leg-b, [data-pose="carry"] .t-leg-b { animation: t-legB var(--gait, 0.62s) linear infinite; }
[data-pose="walk"] .t-leg-f-2, [data-pose="run"] .t-leg-f-2,
[data-pose="haul"] .t-leg-f-2, [data-pose="carry"] .t-leg-f-2 { animation: t-kneeA var(--gait, 0.62s) linear infinite; }
[data-pose="walk"] .t-leg-b-2, [data-pose="run"] .t-leg-b-2,
[data-pose="haul"] .t-leg-b-2, [data-pose="carry"] .t-leg-b-2 { animation: t-kneeB var(--gait, 0.62s) linear infinite; }
[data-pose="walk"] .t-arm-f, [data-pose="run"] .t-arm-f { animation: t-legB var(--gait, 0.62s) linear infinite; }
[data-pose="walk"] .t-arm-b, [data-pose="run"] .t-arm-b { animation: t-legA var(--gait, 0.62s) linear infinite; }
[data-pose="walk"] .t-svg, [data-pose="run"] .t-svg,
[data-pose="haul"] .t-svg, [data-pose="carry"] .t-svg { animation: t-bobY calc(var(--gait, 0.62s) / 2) ease-in-out infinite; }

[data-pose="run"] { --gait: 0.38s; --lean: 9deg; }
[data-pose="haul"] { --gait: 0.8s; --lean: 15deg; }
[data-pose="carry"] { --gait: 0.75s; --lean: 7deg; }
[data-pose="haul"] .t-arm-f { transform: rotate(78deg); }
[data-pose="haul"] .t-arm-b { transform: rotate(64deg); }
[data-pose="carry"] .t-arm-f { transform: rotate(52deg); }
[data-pose="carry"] .t-arm-b { transform: rotate(58deg); }

/* --------------------------------------------------------------- poses */

[data-pose="idle"] .t-head-j { animation: t-headbob 4s ease-in-out infinite; }

@keyframes t-headbob {
  0%, 100% { transform: rotate(-1.6deg); }
  50% { transform: rotate(1.6deg); }
}

/* Sitting drops the hips to seat height, since the feet stay where they are. */
[data-pose="sit"] .t-leg-f, [data-pose="sit"] .t-leg-b { transform: rotate(-84deg); }
[data-pose="sit"] .t-leg-f-2, [data-pose="sit"] .t-leg-b-2 { transform: rotate(86deg); }
[data-pose="sit"] .t-arm-f { transform: rotate(-22deg); }
[data-pose="sit"] .t-arm-b { transform: rotate(20deg); }
.toon-him[data-pose="sit"] .t-svg { transform: translateY(42px); }
.toon-her[data-pose="sit"] .t-svg { transform: translateY(34px); }

[data-pose="ko"] { --lean: -76deg; }
[data-pose="ko"] .t-arm-f { transform: rotate(-52deg); }
[data-pose="ko"] .t-arm-b { transform: rotate(28deg); }
[data-pose="ko"] .t-leg-f { transform: rotate(22deg); }
[data-pose="ko"] .t-leg-b { transform: rotate(-16deg); }
[data-pose="ko"] .t-leg-f-2 { transform: rotate(26deg); }
[data-pose="ko"] .t-eyes { display: none; }
[data-pose="ko"] .t-ko-eyes { display: block; }
[data-pose="ko"] .t-mouth { d: path("M-5 12 q5 -5 10 0"); }

[data-pose="recoil"] { --lean: -17deg; }
[data-pose="recoil"] .t-arm-f { transform: rotate(-142deg); }
[data-pose="recoil"] .t-arm-b { transform: rotate(150deg); }
[data-pose="recoil"] .t-head-j { transform: rotate(-14deg); }
[data-pose="recoil"] .t-eyes { transform: scaleY(0.2); }

[data-pose="grab"] .t-arm-f { transform: rotate(-96deg); }
[data-pose="grab"] .t-arm-f-2 { transform: rotate(18deg); }
[data-pose="grab"] { --lean: 6deg; }

[data-pose="grabbed"] .t-arm-f { transform: rotate(-160deg); }
[data-pose="grabbed"] .t-arm-b { transform: rotate(158deg); }
[data-pose="grabbed"] { --lean: -8deg; }

/* Heels in, body tipped back, going nowhere by choice. */
[data-pose="dragged"] { --lean: 52deg; }
[data-pose="dragged"] .t-arm-f { transform: rotate(-172deg); }
[data-pose="dragged"] .t-arm-b { transform: rotate(166deg); }
[data-pose="dragged"] .t-leg-f { transform: rotate(-26deg); }
[data-pose="dragged"] .t-leg-b { transform: rotate(-16deg); }
[data-pose="dragged"] .t-eyes { transform: scaleY(0.25); }

[data-pose="shove"] .t-arm-f { transform: rotate(-94deg); }
[data-pose="shove"] .t-arm-b { transform: rotate(-78deg); }
[data-pose="shove"] { --lean: 10deg; }

[data-pose="reach"] .t-arm-f { transform: rotate(-88deg); }
[data-pose="reach"] .t-arm-f-2 { transform: rotate(12deg); }

[data-pose="swing"] .t-arm-f { transform: rotate(84deg); }
[data-pose="swing"] .t-arm-f-2 { transform: rotate(-58deg); }
[data-pose="swing"] { --lean: -8deg; }

[data-pose="hit"] .t-arm-f { transform: rotate(-108deg); }
[data-pose="hit"] .t-arm-f-2 { transform: rotate(14deg); }
[data-pose="hit"] { --lean: 12deg; }

[data-pose="lean"] { --lean: 11deg; }
[data-pose="lean"] .t-head-j { transform: rotate(6deg); }
[data-pose="lean"] .t-arm-f { transform: rotate(-34deg); }
[data-pose="lean"] .t-arm-b { transform: rotate(26deg); }

[data-pose="hug"] .t-arm-f { transform: rotate(-96deg); }
[data-pose="hug"] .t-arm-b { transform: rotate(-84deg); }
[data-pose="hug"] .t-arm-f-2 { transform: rotate(34deg); }
[data-pose="hug"] .t-arm-b-2 { transform: rotate(30deg); }
[data-pose="hug"] { --lean: 8deg; }

[data-pose="crouch"] .t-leg-f, [data-pose="crouch"] .t-leg-b { transform: rotate(-34deg); }
[data-pose="crouch"] .t-leg-f-2, [data-pose="crouch"] .t-leg-b-2 { transform: rotate(62deg); }
[data-pose="crouch"] .t-svg { transform: translateY(22px); }
[data-pose="crouch"] { --lean: 12deg; }

[data-pose="ride"] .t-leg-f { transform: rotate(-72deg); }
[data-pose="ride"] .t-leg-b { transform: rotate(-64deg); }
[data-pose="ride"] .t-leg-f-2 { transform: rotate(48deg); }
[data-pose="ride"] .t-leg-b-2 { transform: rotate(44deg); }
[data-pose="ride"] .t-arm-f { transform: rotate(-104deg); }
[data-pose="ride"] .t-arm-b { transform: rotate(-96deg); }

[data-pose="climb"] .t-arm-f { animation: t-climbA 0.7s ease-in-out infinite; }
[data-pose="climb"] .t-arm-b { animation: t-climbB 0.7s ease-in-out infinite; }
[data-pose="climb"] .t-leg-f { animation: t-climbLegA 0.7s ease-in-out infinite; }
[data-pose="climb"] .t-leg-b { animation: t-climbLegB 0.7s ease-in-out infinite; }
[data-pose="climb"] .t-leg-f-2, [data-pose="climb"] .t-leg-b-2 { transform: rotate(46deg); }

@keyframes t-climbA {
  0%, 100% { transform: rotate(-168deg); }
  50% { transform: rotate(-122deg); }
}

@keyframes t-climbB {
  0%, 100% { transform: rotate(126deg); }
  50% { transform: rotate(170deg); }
}

@keyframes t-climbLegA {
  0%, 100% { transform: rotate(-34deg); }
  50% { transform: rotate(-8deg); }
}

@keyframes t-climbLegB {
  0%, 100% { transform: rotate(-6deg); }
  50% { transform: rotate(-32deg); }
}

[data-pose="fall"] .t-arm-f { transform: rotate(-166deg); }
[data-pose="fall"] .t-arm-b { transform: rotate(164deg); }
[data-pose="fall"] .t-leg-f { transform: rotate(-14deg); }
[data-pose="fall"] .t-leg-b { transform: rotate(14deg); }
[data-pose="fall"] .t-svg { animation: t-tumble 0.5s linear infinite; }

@keyframes t-tumble {
  0%, 100% { transform: rotate(-4deg); }
  50% { transform: rotate(4deg); }
}

[data-pose="wave"] .t-arm-f { transform: rotate(-146deg); }
[data-pose="wave"] .t-arm-f-2 { animation: t-wave 0.42s ease-in-out infinite alternate; }

@keyframes t-wave {
  from { transform: rotate(-28deg); }
  to { transform: rotate(24deg); }
}

[data-pose="stretch"] .t-arm-f { transform: rotate(-172deg); }
[data-pose="stretch"] .t-arm-b { transform: rotate(170deg); }
[data-pose="stretch"] .t-svg { animation: t-stretch 1.6s ease-in-out; }

@keyframes t-stretch {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-5px); }
}

[data-pose="look"] .t-head-j { transform: rotate(16deg); }

[data-pose="poked"] .t-svg { animation: t-squish 0.42s cubic-bezier(0.3, 1.5, 0.4, 1); }

@keyframes t-squish {
  0% { transform: scale(1, 1); }
  35% { transform: scale(1.12, 0.88); }
  70% { transform: scale(0.96, 1.05); }
  100% { transform: scale(1, 1); }
}

[data-pose="dance"] .t-svg { animation: t-dance 0.52s ease-in-out infinite; }
[data-pose="dance"] .t-arm-f { animation: t-danceArmA 1.04s ease-in-out infinite; }
[data-pose="dance"] .t-arm-b { animation: t-danceArmB 1.04s ease-in-out infinite; }

@keyframes t-dance {
  0%, 100% { transform: translateY(0) rotate(-5deg); }
  50% { transform: translateY(-8px) rotate(5deg); }
}

@keyframes t-danceArmA {
  0%, 100% { transform: rotate(-150deg); }
  50% { transform: rotate(-40deg); }
}

@keyframes t-danceArmB {
  0%, 100% { transform: rotate(40deg); }
  50% { transform: rotate(152deg); }
}

/* Kneeling: hips drop, knees fold. Used for oral and strip beats. */
[data-pose="kneel"] .t-leg-f, [data-pose="kneel"] .t-leg-b { transform: rotate(-78deg); }
[data-pose="kneel"] .t-leg-f-2, [data-pose="kneel"] .t-leg-b-2 { transform: rotate(92deg); }
[data-pose="kneel"] .t-arm-f { transform: rotate(-28deg); }
[data-pose="kneel"] .t-arm-b { transform: rotate(22deg); }
[data-pose="kneel"] .t-svg { transform: translateY(48px); }
[data-pose="kneel"] { --lean: 4deg; }

/* Eating her out: head in, arms around her thighs. */
[data-pose="eat"] .t-leg-f, [data-pose="eat"] .t-leg-b { transform: rotate(-78deg); }
[data-pose="eat"] .t-leg-f-2, [data-pose="eat"] .t-leg-b-2 { transform: rotate(92deg); }
[data-pose="eat"] .t-arm-f { transform: rotate(-110deg); }
[data-pose="eat"] .t-arm-b { transform: rotate(-98deg); }
[data-pose="eat"] .t-arm-f-2 { transform: rotate(28deg); }
[data-pose="eat"] .t-arm-b-2 { transform: rotate(24deg); }
[data-pose="eat"] .t-head-j { transform: rotate(18deg) translateY(6px); }
[data-pose="eat"] .t-svg { transform: translateY(44px); }
[data-pose="eat"] { --lean: 10deg; }

/* Receiving oral: legs open, hands in his hair. */
[data-pose="receive"] .t-leg-f { transform: rotate(-42deg); }
[data-pose="receive"] .t-leg-b { transform: rotate(18deg); }
[data-pose="receive"] .t-leg-f-2 { transform: rotate(18deg); }
[data-pose="receive"] .t-leg-b-2 { transform: rotate(12deg); }
[data-pose="receive"] .t-arm-f { transform: rotate(-72deg); }
[data-pose="receive"] .t-arm-b { transform: rotate(64deg); }
[data-pose="receive"] .t-head-j { transform: rotate(-8deg); }
[data-pose="receive"] { --lean: -4deg; }

/* Blowjob giver: on her knees, head at his hips. */
[data-pose="blow"] .t-leg-f, [data-pose="blow"] .t-leg-b { transform: rotate(-78deg); }
[data-pose="blow"] .t-leg-f-2, [data-pose="blow"] .t-leg-b-2 { transform: rotate(92deg); }
[data-pose="blow"] .t-arm-f { transform: rotate(-96deg); }
[data-pose="blow"] .t-arm-b { transform: rotate(-82deg); }
[data-pose="blow"] .t-arm-f-2 { transform: rotate(28deg); }
[data-pose="blow"] .t-arm-b-2 { transform: rotate(22deg); }
[data-pose="blow"] .t-head-j { transform: rotate(18deg) translateY(8px); }
[data-pose="blow"] .t-svg { transform: translateY(52px); }
[data-pose="blow"] { --lean: 12deg; }

/* Standing for a blowjob: hips out a little, looking down. */
[data-pose="stood"] .t-arm-f { transform: rotate(-36deg); }
[data-pose="stood"] .t-arm-b { transform: rotate(40deg); }
[data-pose="stood"] .t-head-j { transform: rotate(-14deg); }
[data-pose="stood"] { --lean: 4deg; }
.toon-him[data-pose="stood"] .t-svg { transform: translateY(2px); }

/* Sex / grind: her receiving, him thrusting — different leans. */
.toon-her[data-pose="sex"] .t-leg-f { transform: rotate(-22deg); }
.toon-her[data-pose="sex"] .t-leg-b { transform: rotate(20deg); }
.toon-her[data-pose="sex"] .t-leg-f-2 { transform: rotate(30deg); }
.toon-her[data-pose="sex"] .t-arm-f { transform: rotate(-110deg); }
.toon-her[data-pose="sex"] .t-arm-b { transform: rotate(-96deg); }
.toon-her[data-pose="sex"] { --lean: -6deg; }
.toon-her[data-pose="sex"] .t-svg { animation: t-sex-her 0.38s ease-in-out infinite; }

.toon-him[data-pose="sex"] .t-leg-f { transform: rotate(-18deg); }
.toon-him[data-pose="sex"] .t-leg-b { transform: rotate(12deg); }
.toon-him[data-pose="sex"] .t-leg-f-2 { transform: rotate(28deg); }
.toon-him[data-pose="sex"] .t-arm-f { transform: rotate(-100deg); }
.toon-him[data-pose="sex"] .t-arm-b { transform: rotate(-88deg); }
.toon-him[data-pose="sex"] { --lean: 14deg; }
.toon-him[data-pose="sex"] .t-svg { animation: t-sex-him 0.38s ease-in-out infinite; }

@keyframes t-sex-her {
  0%, 100% { transform: translateY(0) rotate(0deg); }
  50% { transform: translateY(-4px) rotate(2deg); }
}
@keyframes t-sex-him {
  0%, 100% { transform: translateY(0) rotate(-2deg); }
  50% { transform: translateY(-9px) rotate(4deg); }
}

/* On her back-ish: hips tipped, legs up for missionary beat. */
[data-pose="spread"] .t-leg-f { transform: rotate(-58deg); }
[data-pose="spread"] .t-leg-b { transform: rotate(42deg); }
[data-pose="spread"] .t-leg-f-2 { transform: rotate(44deg); }
[data-pose="spread"] .t-leg-b-2 { transform: rotate(40deg); }
[data-pose="spread"] .t-arm-f { transform: rotate(-140deg); }
[data-pose="spread"] .t-arm-b { transform: rotate(130deg); }
[data-pose="spread"] .t-svg { transform: translateY(28px); }
[data-pose="spread"] { --lean: -22deg; }

/* Him over her in missionary: lower and closer. */
.toon-him[data-pose="mission"] .t-leg-f { transform: rotate(-30deg); }
.toon-him[data-pose="mission"] .t-leg-b { transform: rotate(18deg); }
.toon-him[data-pose="mission"] .t-leg-f-2 { transform: rotate(40deg); }
.toon-him[data-pose="mission"] .t-arm-f { transform: rotate(-70deg); }
.toon-him[data-pose="mission"] .t-arm-b { transform: rotate(60deg); }
.toon-him[data-pose="mission"] .t-head-j { transform: rotate(8deg); }
.toon-him[data-pose="mission"] { --lean: 22deg; }
.toon-him[data-pose="mission"] .t-svg { animation: t-sex-him 0.36s ease-in-out infinite; transform: translateY(8px); }

/* Straining a little. The green cloud does the rest. */
[data-pose="fart"] { --lean: -12deg; }
[data-pose="fart"] .t-arm-f { transform: rotate(28deg); }
[data-pose="fart"] .t-arm-b { transform: rotate(-22deg); }
[data-pose="fart"] .t-leg-f { transform: rotate(-8deg); }
[data-pose="fart"] .t-leg-b { transform: rotate(10deg); }
[data-pose="fart"] .t-head-j { transform: rotate(-6deg); }
[data-pose="fart"] .t-eyes { transform: scaleY(0.35); }

/* On the toilet: knees folded, hips dropped onto the seat. Feet stay at the
   place() anchor (the seat height); the SVG drop sinks the butt onto the rim. */
[data-pose="poop"] .t-leg-f, [data-pose="poop"] .t-leg-b { transform: rotate(-88deg); }
[data-pose="poop"] .t-leg-f-2, [data-pose="poop"] .t-leg-b-2 { transform: rotate(92deg); }
[data-pose="poop"] .t-arm-f { transform: rotate(-40deg); }
[data-pose="poop"] .t-arm-b { transform: rotate(36deg); }
[data-pose="poop"] .t-arm-f-2 { transform: rotate(48deg); }
[data-pose="poop"] .t-arm-b-2 { transform: rotate(44deg); }
[data-pose="poop"] .t-head-j { transform: rotate(6deg); }
[data-pose="poop"] { --lean: 2deg; }
.toon-him[data-pose="poop"] .t-svg { transform: translateY(38px); }
.toon-her[data-pose="poop"] .t-svg { transform: translateY(32px); }
/* While seated, hide the shoes so they don't stick through the bowl. */
[data-pose="poop"] .t-shoe { opacity: 0; }

/* Under the shower: arms up, rinsing. */
[data-pose="shower"] .t-arm-f { transform: rotate(-150deg); }
[data-pose="shower"] .t-arm-b { transform: rotate(148deg); }
[data-pose="shower"] .t-arm-f-2 { transform: rotate(-20deg); }
[data-pose="shower"] .t-head-j { transform: rotate(-8deg); }
[data-pose="shower"] .t-svg { animation: t-shower 1.1s ease-in-out infinite; }

@keyframes t-shower {
  0%, 100% { transform: translateY(0) rotate(-2deg); }
  50% { transform: translateY(-3px) rotate(2deg); }
}

/* Bent over / doggy: hips out (use with .t-back so ass faces him). */
.toon-her[data-pose="doggy"] { --lean: 32deg; }
.toon-her[data-pose="doggy"] .t-leg-f { transform: rotate(-22deg); }
.toon-her[data-pose="doggy"] .t-leg-b { transform: rotate(16deg); }
.toon-her[data-pose="doggy"] .t-leg-f-2 { transform: rotate(34deg); }
.toon-her[data-pose="doggy"] .t-leg-b-2 { transform: rotate(28deg); }
.toon-her[data-pose="doggy"] .t-arm-f { transform: rotate(78deg); }
.toon-her[data-pose="doggy"] .t-arm-b { transform: rotate(86deg); }
.toon-her[data-pose="doggy"] .t-arm-f-2 { transform: rotate(46deg); }
.toon-her[data-pose="doggy"] .t-head-j { transform: rotate(-10deg); }
.toon-her[data-pose="doggy"] .t-svg { transform: translateY(16px); }

/* Him behind her for doggy — hips into hers, slight thrust. */
.toon-him[data-pose="thrust"] .t-leg-f { transform: rotate(-14deg); }
.toon-him[data-pose="thrust"] .t-leg-b { transform: rotate(10deg); }
.toon-him[data-pose="thrust"] .t-leg-f-2 { transform: rotate(24deg); }
.toon-him[data-pose="thrust"] .t-arm-f { transform: rotate(-70deg); }
.toon-him[data-pose="thrust"] .t-arm-b { transform: rotate(-58deg); }
.toon-him[data-pose="thrust"] .t-head-j { transform: rotate(-6deg); }
.toon-him[data-pose="thrust"] { --lean: 12deg; }
.toon-him[data-pose="thrust"] .t-svg { animation: t-thrust 0.34s ease-in-out infinite; }

@keyframes t-thrust {
  0%, 100% { transform: translateX(0) translateY(0); }
  50% { transform: translateX(-6px) translateY(-3px); }
}

/* Squirt: legs wide, hips tipped, shaking. */
[data-pose="squirt"] .t-leg-f { transform: rotate(-48deg); }
[data-pose="squirt"] .t-leg-b { transform: rotate(28deg); }
[data-pose="squirt"] .t-leg-f-2 { transform: rotate(22deg); }
[data-pose="squirt"] .t-arm-f { transform: rotate(-150deg); }
[data-pose="squirt"] .t-arm-b { transform: rotate(140deg); }
[data-pose="squirt"] .t-head-j { transform: rotate(-10deg); }
[data-pose="squirt"] { --lean: -8deg; }
[data-pose="squirt"] .t-svg { animation: t-squirt 0.28s ease-in-out infinite; }

@keyframes t-squirt {
  0%, 100% { transform: translateY(0) scaleY(1); }
  50% { transform: translateY(-5px) scaleY(1.03); }
}

/* Presenting: hands on knees, ass out. */
[data-pose="present"] { --lean: 22deg; }
[data-pose="present"] .t-arm-f { transform: rotate(48deg); }
[data-pose="present"] .t-arm-b { transform: rotate(54deg); }
[data-pose="present"] .t-leg-f { transform: rotate(-10deg); }
[data-pose="present"] .t-leg-b { transform: rotate(8deg); }
[data-pose="present"] .t-head-j { transform: rotate(-16deg); }

/* Spank reaction: jump. */
[data-pose="spanked"] { --lean: -14deg; }
[data-pose="spanked"] .t-arm-f { transform: rotate(-150deg); }
[data-pose="spanked"] .t-arm-b { transform: rotate(140deg); }
[data-pose="spanked"] .t-svg { animation: t-squish 0.35s cubic-bezier(0.3, 1.5, 0.4, 1); }

/* Handjob: her hands at his hips, him standing. */
[data-pose="stroke"] .t-arm-f { transform: rotate(-70deg); }
[data-pose="stroke"] .t-arm-b { transform: rotate(-58deg); }
[data-pose="stroke"] .t-arm-f-2 { transform: rotate(30deg); }
[data-pose="stroke"] .t-arm-b-2 { transform: rotate(26deg); }
[data-pose="stroke"] { --lean: 6deg; }

/* Cowgirl: her on top (ride-like but sexy). */
[data-pose="cowgirl"] .t-leg-f { transform: rotate(-64deg); }
[data-pose="cowgirl"] .t-leg-b { transform: rotate(-58deg); }
[data-pose="cowgirl"] .t-leg-f-2 { transform: rotate(52deg); }
[data-pose="cowgirl"] .t-leg-b-2 { transform: rotate(48deg); }
[data-pose="cowgirl"] .t-arm-f { transform: rotate(-40deg); }
[data-pose="cowgirl"] .t-arm-b { transform: rotate(36deg); }
[data-pose="cowgirl"] .t-svg { animation: t-sex-her 0.42s ease-in-out infinite; transform: translateY(-8px); }

/* Spoon: both lean same way, close. */
[data-pose="spoon"] { --lean: 8deg; }
[data-pose="spoon"] .t-arm-f { transform: rotate(-90deg); }
[data-pose="spoon"] .t-arm-b { transform: rotate(-70deg); }
[data-pose="spoon"] .t-leg-f { transform: rotate(-20deg); }
[data-pose="spoon"] .t-leg-b { transform: rotate(-12deg); }

/* 69: both kind of inverted lean. */
[data-pose="sixty"] .t-leg-f { transform: rotate(-40deg); }
[data-pose="sixty"] .t-leg-b { transform: rotate(20deg); }
[data-pose="sixty"] .t-arm-f { transform: rotate(-100deg); }
[data-pose="sixty"] .t-head-j { transform: rotate(20deg); }
[data-pose="sixty"] { --lean: 16deg; }

/* Face sit: him flat-ish, her above. */
[data-pose="facesit"] .t-leg-f { transform: rotate(-70deg); }
[data-pose="facesit"] .t-leg-b { transform: rotate(-62deg); }
[data-pose="facesit"] .t-leg-f-2 { transform: rotate(50deg); }
[data-pose="facesit"] .t-arm-f { transform: rotate(-30deg); }
[data-pose="facesit"] .t-svg { transform: translateY(-16px); animation: t-sex-her 0.5s ease-in-out infinite; }

/* Wall: pressed close, arms up. */
[data-pose="wall"] { --lean: 4deg; }
[data-pose="wall"] .t-arm-f { transform: rotate(-160deg); }
[data-pose="wall"] .t-arm-b { transform: rotate(155deg); }
[data-pose="wall"] .t-leg-f { transform: rotate(-12deg); }

/* Finger: him reaches between her legs. */
[data-pose="finger"] .t-arm-f { transform: rotate(-80deg); }
[data-pose="finger"] .t-arm-f-2 { transform: rotate(20deg); }
[data-pose="finger"] { --lean: 10deg; }

/* Cuddle sit: close sit side by side energy. */
[data-pose="cuddle"] .t-arm-f { transform: rotate(-88deg); }
[data-pose="cuddle"] .t-arm-b { transform: rotate(-70deg); }
[data-pose="cuddle"] .t-leg-f { transform: rotate(-20deg); }
[data-pose="cuddle"] { --lean: 6deg; }

/* Big squeeze cuddle. */
[data-pose="squeeze"] .t-arm-f { transform: rotate(-110deg); }
[data-pose="squeeze"] .t-arm-b { transform: rotate(-100deg); }
[data-pose="squeeze"] .t-arm-f-2 { transform: rotate(40deg); }
[data-pose="squeeze"] .t-arm-b-2 { transform: rotate(36deg); }
[data-pose="squeeze"] { --lean: 10deg; }
[data-pose="squeeze"] .t-svg { animation: t-squeeze 1.2s ease-in-out infinite; }

@keyframes t-squeeze {
  0%, 100% { transform: scale(1); }
  50% { transform: scale(1.03) translateY(-2px); }
}

/* Head on chest / shoulder. */
[data-pose="nestle"] { --lean: 14deg; }
[data-pose="nestle"] .t-head-j { transform: rotate(18deg) translateY(4px); }
[data-pose="nestle"] .t-arm-f { transform: rotate(-70deg); }
[data-pose="nestle"] .t-arm-b { transform: rotate(20deg); }

/* Lying down for a cuddlenap. */
[data-pose="nap"] { --lean: -72deg; }
[data-pose="nap"] .t-arm-f { transform: rotate(-40deg); }
[data-pose="nap"] .t-arm-b { transform: rotate(50deg); }
[data-pose="nap"] .t-leg-f { transform: rotate(-8deg); }
[data-pose="nap"] .t-leg-b { transform: rotate(10deg); }
[data-pose="nap"] .t-eyes { transform: scaleY(0.12); }
[data-pose="nap"] .t-mouth { d: path("M-4 10 q4 2 8 0"); }

/* Soft breathing while napping. */
[data-pose="nap"] .t-svg { animation: t-nap-breathe 2.4s ease-in-out infinite; }
@keyframes t-nap-breathe {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-2px); }
}

/* --------------------------------------------------------------- props */

.cast-prop {
  position: absolute;
  left: 0;
  top: 0;
  width: var(--w, 120px);
  transform-origin: 50% 100%;
  transform: translate(calc(var(--x, 0px) - var(--w, 120px) / 2), calc(var(--y, 0px) - 100%));
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.4s ease, visibility 0.4s;
}

.cast-prop svg { display: block; width: 100%; height: auto; }
.cast-prop.on { opacity: 1; visibility: visible; }
.cast-throne.arriving { animation: prop-in 0.6s cubic-bezier(0.2, 1.5, 0.4, 1); }

@keyframes prop-in {
  from { transform: translate(calc(var(--x) - var(--w) / 2), calc(var(--y) - 100% - 60px)) scale(0.6); opacity: 0; }
}

.p-gold { fill: #e8b53f; stroke: #b8811c; stroke-width: 1.5; }
.p-velvet { fill: #b3305c; }
.p-cushion { fill: #d94a7a; stroke: #a52c55; stroke-width: 1.5; }
.p-jewel { fill: #ff7aa8; stroke: #b8811c; stroke-width: 1.5; }

/* Floor-to-ceiling bars across the front, drawn over both of them. The rails
   are what make it a cage rather than a striped wall. */
.c-cage rect { fill: #14262f; stroke: #070d11; stroke-width: 1.5; }
.c-rail { stroke: #14262f; stroke-width: 7; fill: none; stroke-linecap: round; }
.c-lock circle { fill: #22323c; stroke: #070d11; stroke-width: 2; }
.c-lock rect { fill: #0b1318; }

.c-wall { fill: #2b1e28; stroke: #150d13; stroke-width: 2.5; }
.c-brick { stroke: rgba(255, 255, 255, 0.06); stroke-width: 2; fill: none; }
.c-window { fill: #7d97c4; opacity: 0.55; }
.c-bars rect { fill: #100a0e; }
.c-frame { fill: none; stroke: #100a0e; stroke-width: 5; }
.c-floor { fill: #180f16; }
.c-straw { stroke: #6b5a3a; stroke-width: 2; stroke-linecap: round; }

/* One shaft of light from the window. The only nice thing down there. */
.c-beam { fill: #9db6dd; opacity: 0.09; }
.c-spider { fill: #0f0a0e; }
.c-thread, .c-legs { stroke: #5b4c56; stroke-width: 1.2; fill: none; stroke-linecap: round; }

/* Behind the characters: he sits inside it, not in front of it. The bars are a
   separate element appended after them, so they close over him. */
.cast-cell { z-index: -1; }
.cast-bars { z-index: 1; }

/* The duvet. This is as far as it goes, and it is the same joke The Sims
   makes: you see the blanket move and nothing else. */
.cast-bed {
  position: absolute;
  left: 0;
  top: 0;
  width: 190px;
  height: 96px;
  transform: translate(calc(var(--x) - 95px), calc(var(--y) - 96px));
  animation: prop-drop 0.42s cubic-bezier(0.2, 1.4, 0.4, 1);
}

.bed-base {
  position: absolute;
  inset: auto 0 0 0;
  height: 26px;
  border-radius: 8px;
  background: #7a5a48;
  box-shadow: 0 6px 14px rgba(0, 0, 0, 0.25);
}

.bed-duvet {
  position: absolute;
  inset: 22px 0 18px 0;
  border-radius: 26px 26px 12px 12px;
  background: linear-gradient(160deg, #ffd7e6, #ffb0cd);
  border: 2px solid #f191b4;
}

.cast-bed.shaking .bed-duvet { animation: bed-shake 0.26s ease-in-out infinite; }
.cast-bed.gone { animation: prop-out 0.6s ease forwards; }

/* ---- hangouts: movie night + stargaze (persist like the throne) ---- */

/* `display: none` when it is off, and that word is load bearing.
 *
 * These sets were hidden with `visibility: hidden`, which leaves the boxes in
 * the layout and their animations running: twenty-eight twinkling stars and a
 * flickering television, repainting sixty times a second, invisibly, for the
 * whole of every evening. It was the entire idle animation cost of the room —
 * measured on the live page as twenty-nine of the thirty-six running
 * animations, none of them on screen. A skipped display puts them to sleep.
 *
 * The fade-in survives on Safari 17.4+ and Chrome via `@starting-style`;
 * anywhere else the set simply appears, which is what `.arriving` is already
 * doing over the top of it anyway. */
.cast-hangout {
  position: absolute;
  left: 0;
  top: 0;
  pointer-events: none;
  display: none;
  opacity: 0;
  transition: opacity 0.4s ease, display 0.4s allow-discrete;
  z-index: 0;
}

.cast-hangout.on {
  display: block;
  opacity: 1;
}

@starting-style {
  .cast-hangout.on { opacity: 0; }
}
.cast-hangout.arriving { animation: prop-in 0.55s cubic-bezier(0.2, 1.4, 0.4, 1); }

.cast-movie {
  width: 220px;
  height: 160px;
  transform: translate(calc(var(--x) - 110px), calc(var(--y) - 150px));
}
.mv-tv {
  position: absolute;
  left: 50%;
  bottom: 48px;
  transform: translateX(-50%);
  width: 120px;
}
.mv-screen {
  height: 72px;
  border-radius: 8px 8px 4px 4px;
  background: linear-gradient(160deg, #1a2744, #0d1528);
  border: 3px solid #2a3548;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  position: relative;
}
.mv-flicker {
  position: absolute;
  inset: 0;
  background: linear-gradient(120deg, transparent 40%, rgba(255, 255, 255, 0.08) 50%, transparent 60%);
  animation: mv-flick 2.8s linear infinite;
}
.mv-pop { font-size: 28px; position: relative; z-index: 1; filter: drop-shadow(0 2px 6px rgba(0, 0, 0, 0.4)); }
.mv-bezel {
  height: 8px;
  background: #3a4558;
  border-radius: 0 0 4px 4px;
}
.mv-stand {
  width: 28px;
  height: 14px;
  margin: 0 auto;
  background: #2a3548;
  border-radius: 0 0 6px 6px;
}
.mv-couch {
  position: absolute;
  left: 10%;
  right: 10%;
  bottom: 0;
  height: 36px;
  border-radius: 18px 18px 8px 8px;
  background: linear-gradient(180deg, #6b4a6e, #4a3250);
  border: 2px solid #3a2840;
  box-shadow: 0 6px 14px rgba(0, 0, 0, 0.25);
}

@keyframes mv-flick {
  0%, 100% { transform: translateX(-30%); opacity: 0.4; }
  50% { transform: translateX(30%); opacity: 0.9; }
}

.cast-stars {
  width: 260px;
  height: 180px;
  transform: translate(calc(var(--x) - 130px), calc(var(--y) - 170px));
}
.st-sky {
  position: absolute;
  inset: 0 10px 40px 10px;
  border-radius: 50% 50% 12px 12px;
  background: radial-gradient(ellipse at 50% 20%, #2a3a6a, #0c1020 70%);
  opacity: 0.92;
}
.st-moon {
  position: absolute;
  top: 18px;
  right: 36px;
  font-size: 22px;
  filter: drop-shadow(0 0 10px rgba(255, 240, 180, 0.5));
}
.st-field { position: absolute; inset: 0 10px 40px 10px; }
.st-dot {
  position: absolute;
  color: #fff8d8;
  opacity: 0.85;
  animation: st-twinkle 2.4s ease-in-out infinite;
  line-height: 1;
}
.st-blanket {
  position: absolute;
  left: 18%;
  right: 18%;
  bottom: 4px;
  height: 32px;
  border-radius: 20px 20px 10px 10px;
  background: linear-gradient(160deg, #3a2a50, #2a1e3a);
  border: 2px solid #5a4a70;
}

@keyframes st-twinkle {
  0%, 100% { opacity: 0.45; transform: scale(0.9); }
  50% { opacity: 1; transform: scale(1.15); }
}

/* Soft blanket for cuddlenaps — same anchor idea as the bed. */
.cast-blanket {
  position: absolute;
  left: 0;
  top: 0;
  width: 200px;
  height: 70px;
  transform: translate(calc(var(--x) - 100px), calc(var(--y) - 70px));
  pointer-events: none;
  animation: prop-drop 0.4s cubic-bezier(0.2, 1.4, 0.4, 1);
  z-index: 1;
}
.cast-blanket .bl-sheet {
  position: absolute;
  inset: 10px 8px 8px 8px;
  border-radius: 28px 28px 16px 16px;
  background: linear-gradient(160deg, #e8f0ff, #c8d9ff);
  border: 2px solid #9bb4e8;
  box-shadow: 0 6px 16px rgba(80, 100, 160, 0.25);
}
.cast-blanket .bl-heart {
  position: absolute;
  left: 50%;
  top: 22px;
  transform: translateX(-50%);
  font-size: 18px;
  filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.12));
}
.cast-blanket.gone { animation: prop-out 0.55s ease forwards; }

@keyframes bed-shake {
  0%, 100% { transform: translate(-3px, 0) rotate(-1.4deg) scaleY(1); }
  50% { transform: translate(3px, -4px) rotate(1.4deg) scaleY(1.08); }
}

@keyframes prop-drop {
  from { transform: translate(calc(var(--x) - 95px), calc(var(--y) - 200px)); opacity: 0; }
}

@keyframes prop-out {
  to { opacity: 0; transform: translate(calc(var(--x) - 95px), calc(var(--y) - 140px)); }
}

/* Temporary date props (toilet, shower): same anchor as the bed. */
.cast-temp {
  position: absolute;
  left: 0;
  top: 0;
  width: var(--w, 90px);
  height: auto;
  transform: translate(calc(var(--x) - var(--w, 90px) / 2), calc(var(--y) - 100%));
  pointer-events: none;
  animation: prop-drop-temp 0.4s cubic-bezier(0.2, 1.4, 0.4, 1);
  z-index: 0; /* under .toon so they sit on the seat, not behind the tank */
}

.cast-temp svg { display: block; width: 100%; height: auto; }
.cast-temp.gone { animation: prop-out-temp 0.5s ease forwards; }

@keyframes prop-drop-temp {
  from { transform: translate(calc(var(--x) - var(--w, 90px) / 2), calc(var(--y) - 100% - 80px)); opacity: 0; }
}

@keyframes prop-out-temp {
  to { opacity: 0; transform: translate(calc(var(--x) - var(--w, 90px) / 2), calc(var(--y) - 100% - 40px)); }
}

.toilet-bowl { fill: #ebe6ef; stroke: #a89eb0; stroke-width: 1.5; }
.toilet-tank { fill: #e0dae8; stroke: #a89eb0; stroke-width: 1.5; }
.toilet-seat { fill: #fff; stroke: #9a90a4; stroke-width: 1.6; }
.toilet-seat-outer { fill: #f4f0f8; stroke: #a89eb0; stroke-width: 1.2; }
.toilet-seat-hole { fill: #c8c0d0; opacity: 0.55; }
.toilet-base { fill: rgba(58, 43, 51, 0.14); }
.toilet-lid { fill: #d4cedc; stroke: #a89eb0; stroke-width: 1.3; }

/* Toilets sit behind the toons; characters must appear ON the seat. */
.cast-temp { z-index: 0; }
.toon { z-index: 2; }
.cast-fx { z-index: 4; }

.shower-stall { fill: rgba(160, 200, 220, 0.35); stroke: #7aa8c0; stroke-width: 2; }
.shower-head { fill: #8a9aaa; stroke: #5a6a7a; stroke-width: 1.3; }
.shower-pipe { stroke: #8a9aaa; stroke-width: 4; fill: none; stroke-linecap: round; }
.shower-tray { fill: #c5d4de; stroke: #8aa0b0; stroke-width: 1.5; }
.shower-water {
  stroke: #7ec8e8;
  stroke-width: 1.6;
  stroke-linecap: round;
  opacity: 0.75;
  animation: shower-drip 0.55s linear infinite;
}

@keyframes shower-drip {
  0% { stroke-dashoffset: 0; opacity: 0.3; }
  50% { opacity: 0.9; }
  100% { stroke-dashoffset: 12; opacity: 0.3; }
}

/* ------------------------------------------------------------ comic fx */

.cast-fx { position: absolute; inset: 0; }

.cf {
  position: absolute;
  transform: translate(-50%, -100%);
  font-family: var(--font, sans-serif);
  font-weight: 900;
  pointer-events: none;
  white-space: nowrap;
}

.cf-hit {
  font-size: clamp(22px, 6.5vmin, 46px);
  color: #fff;
  -webkit-text-stroke: 3px var(--pink-deep, #d9407a);
  text-shadow: 0 8px 20px rgba(0, 0, 0, 0.35);
  animation: cf-pop 0.95s cubic-bezier(0.2, 1.5, 0.4, 1) both;
}

.cf-soft {
  font-size: clamp(30px, 9vmin, 62px);
  animation: cf-pop 1.1s cubic-bezier(0.2, 1.4, 0.4, 1) both;
}

.cf-bit {
  font-size: 19px;
  animation: cf-float 1.5s ease-out both;
}

@keyframes cf-pop {
  0% { opacity: 0; transform: translate(-50%, -100%) scale(0.3) rotate(-14deg); }
  30% { opacity: 1; transform: translate(-50%, -110%) scale(1.15) rotate(6deg); }
  60% { opacity: 1; transform: translate(-50%, -112%) scale(1) rotate(-2deg); }
  100% { opacity: 0; transform: translate(-50%, -140%) scale(1.05); }
}

@keyframes cf-float {
  0% { opacity: 0; transform: translate(-50%, -100%) scale(0.5); }
  20% { opacity: 1; transform: translate(-50%, -110%) scale(1); }
  100% { opacity: 0; transform: translate(calc(-50% + var(--d, 0px)), calc(-100% - var(--u, 60px))) scale(0.9); }
}

/* A hit lands on the room, not just on him. */
.cast-knock { animation: cast-knock 0.6s cubic-bezier(0.36, 0.07, 0.19, 0.97) both; }

@keyframes cast-knock {
  0%, 100% { transform: translate(0, 0) rotate(0); }
  15% { transform: translate(calc(var(--knock, 1) * -3px), calc(var(--knock, 1) * 1.5px)) rotate(calc(var(--knock, 1) * -0.14deg)); }
  35% { transform: translate(calc(var(--knock, 1) * 3px), calc(var(--knock, 1) * -1.5px)) rotate(calc(var(--knock, 1) * 0.12deg)); }
  60% { transform: translate(calc(var(--knock, 1) * -2px), calc(var(--knock, 1) * 1px)) rotate(calc(var(--knock, 1) * -0.08deg)); }
  85% { transform: translate(calc(var(--knock, 1) * 1px), 0) rotate(0); }
}

/* ------------------------------------------------------------- the room */

/* They stand in front of the messages when they are on the composer, and
   the messages go all the way down behind them: nothing is kept clear. She
   asked for exactly that on 26 Sep, with the drag in cast.js as the way to get
   one off whatever she is reading.

   Two earlier versions both kept a gap, and she disliked both. On 24 Sep the
   log ended above their heads, a hard edge that cut a half-scrolled sticker
   in two ("the cut"); a fade on it was "just blurry". Then 96px of floor under
   the newest message read as the chat starting a quarter of the way up the
   screen. The pill moves up by their real height (`--cast-floor`, from
   layoutProps() in cast.js) so it is not under their feet. */
body.cast-on:not(.cast-instage) .jump-latest { bottom: calc(var(--cast-floor, 156px) + 12px); }

/* Picked up (cast.js): arms up at whoever is holding them, legs dangling.
   `touch-action: none` or an iPad takes the drag for a scroll and cancels it. */
.toon .t-svg { touch-action: none; cursor: grab; }
.toon.t-carried .t-svg { cursor: grabbing; }
[data-pose="held"] .t-arm-f { transform: rotate(-170deg); }
[data-pose="held"] .t-arm-b { transform: rotate(168deg); }
[data-pose="held"] .t-leg-f { transform: rotate(-8deg); }
[data-pose="held"] .t-leg-b { transform: rotate(10deg); }

/* Walking, climbing and being thrown across the room is precisely what motion
   sensitivity means. The characters stay, they just stop performing: cast.js
   also skips the idle scheduler under this. */
@media (prefers-reduced-motion: reduce) {
  .t-eyes, [data-pose] .t-svg, [data-pose] .j { animation: none !important; }
  .cast-knock, .cast-bed.shaking .bed-duvet { animation: none !important; }
  .mv-flicker, .st-dot { animation: none !important; }
  .cf-bit, .cf-hit, .cf-soft { animation-duration: 0.01s; }
}

/* ------------------------------------------------------------- the cat
 *
 * Pumpkin. Her own box (90x70, feet at 45,66) on the same stage scale as the
 * two of them, and two drawings: sitting, which also walks, and a loaf for
 * sleeping. Her mood is three classes from public/cast.js, read off the streak.
 */

.toon-cat {
  width: 90px;
  height: 70px;
  transform-origin: 45px 66px;
  transform:
    translate(calc(var(--x, 0px) - 45px), calc(var(--y, 0px) - 66px))
    scale(var(--s))
    scaleX(var(--flip, 1))
    rotate(var(--lean, 0deg));

  --cat: #f4a04b;
  --cat-dk: #d9772b;
  --cat-cream: #fde8cf;
  --cat-pink: #f59ab0;
  --cat-ink: #3a2b33;
}

.c-svg {
  width: 100%;
  height: 100%;
  overflow: visible;
  pointer-events: auto;
  cursor: grab;
  /* Or an iPad takes the drag for a scroll and cancels it. */
  touch-action: none;
}

.c-fur { fill: var(--cat); stroke: var(--line); stroke-width: 1.2; }
.c-ear { fill: var(--cat); stroke: var(--line); stroke-width: 1.2; stroke-linejoin: round; }
.c-blush { fill: var(--cat-pink); opacity: 0.45; }

.toon-cat .t-say { bottom: 92%; border-color: var(--cat); }

/* The two drawings. Asleep is the loaf; everything else is sitting. */
.c-loaf { display: none; }
.toon-cat[data-pose="sleep"] .c-sit { display: none; }
.toon-cat[data-pose="sleep"] .c-loaf { display: inline; }

/* Eyes: open by default, ^^ when happy, heavy lids when sulking. */
.c-eyes-happy, .c-eyes-sulk { display: none; }
.toon-cat.c-happy .c-eyes-open { display: none; }
.toon-cat.c-happy .c-eyes-happy { display: inline; }
.toon-cat.c-sulk .c-eyes-open { display: none; }
.toon-cat.c-sulk .c-eyes-sulk { display: inline; }

/* The tail says it first. */
.c-tail { transform-box: view-box; transform-origin: 26px 60px; animation: c-swish 2.6s ease-in-out infinite; }
.toon-cat.c-happy .c-tail { animation-duration: 1.1s; }
.toon-cat.c-sulk .c-tail { animation: none; transform: rotate(-52deg); }

@keyframes c-swish {
  0%, 100% { transform: rotate(0deg); }
  50% { transform: rotate(14deg); }
}

/* Ears back when sulking. */
.c-ear-l, .c-ear-r { transform-box: fill-box; transition: transform 0.3s ease; }
.c-ear-l { transform-origin: 100% 100%; }
.c-ear-r { transform-origin: 0% 100%; }
.toon-cat.c-sulk .c-ear-l { transform: rotate(-38deg); }
.toon-cat.c-sulk .c-ear-r { transform: rotate(38deg); }

/* Head tilt while waiting for somebody. */
.c-head { transform-box: view-box; transform-origin: 57px 38px; transition: transform 0.3s ease; }
.toon-cat.c-waiting .c-head { transform: rotate(-10deg); }

/* Walking is sitting with a bob and the legs taking turns. */
.toon-cat[data-pose="walk"] .c-sit { animation: c-bob 0.36s ease-in-out infinite; }
.c-leg-f, .c-leg-b { transform-box: fill-box; transform-origin: 50% 0%; }
.toon-cat[data-pose="walk"] .c-leg-f { animation: c-step 0.36s ease-in-out infinite; }
.toon-cat[data-pose="walk"] .c-leg-b { animation: c-step 0.36s ease-in-out infinite reverse; }

@keyframes c-bob {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-2.5px); }
}

@keyframes c-step {
  0%, 100% { transform: rotate(16deg); }
  50% { transform: rotate(-16deg); }
}

/* Grooming: front paw up to the face, head down to meet it. */
.toon-cat[data-pose="groom"] .c-leg-f { animation: c-lick 0.5s ease-in-out infinite; }
.toon-cat[data-pose="groom"] .c-head { transform: rotate(12deg); }

@keyframes c-lick {
  0%, 100% { transform: rotate(-150deg) translateY(-2px); }
  50% { transform: rotate(-135deg) translateY(0); }
}

/* A hop. */
.toon-cat[data-pose="jump"] .c-svg { animation: c-hop 0.55s cubic-bezier(0.3, 1.6, 0.5, 1); }

@keyframes c-hop {
  0%, 100% { transform: translateY(0); }
  40% { transform: translateY(-18px); }
}

/* Breathing while asleep. */
.toon-cat[data-pose="sleep"] .c-loaf { transform-box: view-box; transform-origin: 45px 66px; animation: c-breathe 3s ease-in-out infinite; }

@keyframes c-breathe {
  0%, 100% { transform: scaleY(1); }
  50% { transform: scaleY(1.05); }
}

/* The rain cloud and the zzz. Counter-flipped like the speech bubble, or the
   cloud would rain backwards when she faces left. */
.c-cloud, .c-zzz {
  position: absolute;
  left: 50%;
  bottom: 88%;
  transform: translateX(-30%) scaleX(var(--flip, 1));
  font-size: 22px;
  line-height: 1;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.4s ease;
}

.toon-cat.c-sulk .c-cloud { opacity: 1; animation: c-drift 3.2s ease-in-out infinite; }
.toon-cat[data-pose="sleep"] .c-cloud { opacity: 0; }
.toon-cat[data-pose="sleep"] .c-zzz { opacity: 1; bottom: 55%; transform: translateX(20%) scaleX(var(--flip, 1)); animation: c-drift 2.6s ease-in-out infinite; }

@keyframes c-drift {
  0%, 100% { margin-bottom: 0; }
  50% { margin-bottom: 4px; }
}

@media (prefers-reduced-motion: reduce) {
  .c-tail, .toon-cat [class^="c-"], .toon-cat .c-svg { animation: none !important; }
}

/* Carried by the scruff (cast.js): legs dangling, tail hanging, a slow sway. */
.toon-cat.t-carried .c-svg { cursor: grabbing; }
.toon-cat[data-pose="held"] .c-sit { transform-box: view-box; transform-origin: 57px 12px; animation: c-dangle 1.1s ease-in-out infinite; }
.toon-cat[data-pose="held"] .c-leg-f { transform: rotate(-10deg) translateY(3px); }
.toon-cat[data-pose="held"] .c-leg-b { transform: rotate(10deg) translateY(3px); }
.toon-cat[data-pose="held"] .c-tail { animation: none; transform: rotate(-70deg); }
.toon-cat[data-pose="held"] .c-eyes-open { display: inline; }
.toon-cat[data-pose="held"] .c-eyes-happy,
.toon-cat[data-pose="held"] .c-eyes-sulk { display: none; }

@keyframes c-dangle {
  0%, 100% { transform: rotate(-5deg); }
  50% { transform: rotate(5deg); }
}

/* Pet date: eyes shut in bliss and a fast tail, whatever her mood was. */
.toon-cat.c-petted .c-eyes-open,
.toon-cat.c-petted .c-eyes-sulk { display: none; }
.toon-cat.c-petted .c-eyes-happy { display: inline; }
.toon-cat.c-petted .c-tail { animation: c-swish 0.8s ease-in-out infinite; transform: none; }
.toon-cat.c-petted .c-ear-l,
.toon-cat.c-petted .c-ear-r { transform: none; }
.toon-cat.c-petted .c-head { transform: rotate(-8deg); }

/* Kneeling beside her with one hand stroking down her back. */
[data-pose="pet"] .t-leg-f, [data-pose="pet"] .t-leg-b { transform: rotate(-78deg); }
[data-pose="pet"] .t-leg-f-2, [data-pose="pet"] .t-leg-b-2 { transform: rotate(92deg); }
[data-pose="pet"] .t-arm-b { transform: rotate(22deg); }
[data-pose="pet"] .t-arm-f { animation: t-stroke-cat 0.85s ease-in-out infinite; }
[data-pose="pet"] .t-head-j { transform: rotate(14deg); }
[data-pose="pet"] .t-svg { transform: translateY(48px); }
[data-pose="pet"] { --lean: 8deg; }

@keyframes t-stroke-cat {
  0%, 100% { transform: rotate(-74deg); }
  50% { transform: rotate(-48deg); }
}
