/* The face: the shape it sits in, the canvas the engine draws it on, the
 * props its idle acts bring along, the skeleton that stands in while it
 * loads, and the "drag me" hint. Ported from rameerez.com's face.css. The
 * markup is landing/_face_card; app/javascript/face/presets.js brings it to
 * life. `.plush` is the picture's own square: give it a width and it does
 * the rest.
 *
 * The shape's geometry is defined here and in the face's manifest.json
 * (`frame`), which is what the engine clips to: the two must agree. As
 * fractions of the picture: its centre (--frame-x, --frame-y) and half its
 * width (--frame-r). data-frame picks the shape: "circle" (a disc), "square"
 * (a block as wide as the disc, running to the bottom) or "none".
 *
 * The embed (e.js) loads this same stylesheet on other people's pages, so
 * every colour has a fallback for pages without facebrrr's palette.
 */
.plush {
  --frame-x: 0.5;
  --frame-y: 0.57;
  --frame-r: 0.43;
  --frame-top: calc(var(--frame-y) - var(--frame-r));
  --frame-left: calc(var(--frame-x) - var(--frame-r));
  --frame-right: calc(1 - var(--frame-x) - var(--frame-r));
  position: relative;
  aspect-ratio: 1;
  /* Props are sized against the face (cqw), so they stay in proportion on a
   * phone and on a desk. */
  container-type: inline-size;
  /* Pressing and holding is how a finger takes hold: it mustn't select
   * anything, call up a menu, or flash the tap highlight. */
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none;
  -webkit-tap-highlight-color: transparent;
}

/* What the face sits in: a disc, or a block that runs to the bottom. */
.plush-frame {
  position: absolute;
  left: calc(var(--frame-left) * 100%); top: calc(var(--frame-top) * 100%);
  width: calc(var(--frame-r) * 200%); height: calc(var(--frame-r) * 200%);
  border-radius: 50%;
  background: var(--color-brrr, #df430d);
}
.plush[data-frame="square"] .plush-frame { height: auto; bottom: 0; border-radius: 0; }
.plush[data-frame="none"] .plush-frame { display: none; }

/* Where the face can be touched (a button with nothing of a button's
 * looks): the disc and the head above it, so a finger in the empty corners
 * is the page's. On the face, swipes up and down still scroll the page and
 * pinches still zoom it; sideways swipes, and a finger that rests on it,
 * take hold (the engine sees to that). */
.plush-hit {
  position: absolute; inset: 0; width: 100%; height: 100%;
  z-index: 3; display: block; margin: 0; padding: 0; border: 0; border-radius: 0;
  appearance: none; -webkit-appearance: none; background: none; color: inherit; font: inherit;
  touch-action: pan-y pinch-zoom; outline: 0;
  clip-path: inset(
    0 calc(var(--frame-right) * 100%) calc((1 - var(--frame-y) - var(--frame-r)) * 100%) calc(var(--frame-left) * 100%)
    round 0 0 calc(var(--frame-r) * 100%) calc(var(--frame-r) * 100%)
  );
}
.plush[data-frame="square"] > .plush-hit { clip-path: inset(0 calc(var(--frame-right) * 100%) 0 calc(var(--frame-left) * 100%)); }
.plush[data-frame="none"] > .plush-hit { clip-path: none; }
/* While the skin is well out of place it can be touched wherever it has got
 * to; the engine works out whether a touch is on the face. */
.plush.is-stretched > .plush-hit { clip-path: none; }
/* Reached with the keyboard, the disc shows it: a ring of two colours. */
.plush:has(.plush-hit:focus-visible) .plush-frame { outline: 3px solid var(--color-ink, #16120f); outline-offset: 3px; box-shadow: 0 0 0 9px #fff; }

/* The canvas is bigger than the picture by the engine's `pad` (--plush-pad,
 * which the engine sets on it) on every side, so stretched skin has
 * somewhere to go. It never takes the pointer, and it fades in once the
 * engine has drawn its first frame. */
.plush canvas {
  position: absolute; z-index: 2;
  left: calc(var(--plush-pad) * -100%); top: calc(var(--plush-pad) * -100%);
  width: calc(100% + var(--plush-pad) * 200%); height: calc(100% + var(--plush-pad) * 200%);
  pointer-events: none;
  opacity: 0; transition: opacity .35s ease-out;
}
.plush.is-ready canvas { opacity: 1; }

/* --- Loading ------------------------------------------------------------- */

/* A head-and-shoulders shape, breathing, on the disc: it says a face is on
 * its way. Gone once the face has drawn itself (or turned out not to be
 * able to). */
.plush-skeleton {
  position: absolute; inset: 0; z-index: 1;
  pointer-events: none;
  transition: opacity .35s ease-out;
  animation: plush-breathe 1.4s ease-in-out infinite;
}
.plush-skeleton::before,
.plush-skeleton::after {
  content: ""; position: absolute; left: 50%; translate: -50% 0;
  background: color-mix(in srgb, var(--color-paper, #fbf6ef) 30%, transparent);
}
/* The head. */
.plush-skeleton::before { top: 10%; width: 32%; height: 42%; border-radius: 50%; }
/* The shoulders, kept inside the disc. */
.plush-skeleton::after { top: 64%; width: 56%; height: 33%; border-radius: 50% 50% 50% 50% / 70% 70% 30% 30%; }
.plush.is-ready .plush-skeleton,
.plush.is-still .plush-skeleton { opacity: 0; animation: none; }
@keyframes plush-breathe { 50% { opacity: .55; } }

/* No WebGL here: the resting picture, still, clipped like the live face. */
.plush-still { display: none; }
.plush.is-still .plush-still { display: block; }
.plush-still img {
  position: absolute; inset: 0; z-index: 2; width: 100%; height: 100%;
  clip-path: inset(
    0 calc(var(--frame-right) * 100%) calc((1 - var(--frame-y) - var(--frame-r)) * 100%) calc(var(--frame-left) * 100%)
    round 0 0 calc(var(--frame-r) * 100%) calc(var(--frame-r) * 100%)
  );
}

/* --- "drag me" ------------------------------------------------------------ */

/* Bobs beside the face, pointing at it, until the first grab. */
.plush-hint {
  position: absolute; z-index: 5; left: 0; top: 34%;
  pointer-events: none;
  opacity: 0; transition: opacity .3s;
  animation: plush-hint 1.6s ease-in-out infinite;
}
.plush.is-ready .plush-hint { opacity: 1; }
@keyframes plush-hint {
  0%, 100% { transform: translate(-30%, 0) rotate(-6deg); }
  50% { transform: translate(calc(-30% + .5rem), .25rem) rotate(-2deg); }
}

/* --- Props ---------------------------------------------------------------- */

/* Sizes are fractions of the face's width (cqw), with a fixed size ahead of
 * each for browsers that don't know the unit. */

/* The phantom hand that comes to tug the cheek: a cursor where there's a
 * mouse, a fingertip on a touch screen. */
.plush-ghost {
  position: absolute; left: 0; top: 0; z-index: 5;
  width: 1.75rem; height: 1.75rem;
  width: clamp(1.2rem, 4.4cqw, 2.25rem); height: clamp(1.2rem, 4.4cqw, 2.25rem);
  pointer-events: none; opacity: 0; transition: opacity .18s;
  filter: drop-shadow(0 2px 3px rgba(0, 0, 0, .35));
  will-change: transform;
}
.plush-ghost path { fill: #fff; stroke: var(--color-ink, #16120f); stroke-width: 1.6; stroke-linejoin: round; }
.plush-ghost.is-here { opacity: 1; }
.plush-ghost .grab,
.plush-ghost .tip,
.plush-ghost.is-gripping .open { display: none; }
.plush-ghost.is-gripping .grab { display: block; }
/* `.is-touch` (from presets.js): the visitor is using fingers. */
.plush.is-touch .plush-ghost { width: 2.6rem; height: 2.6rem; width: clamp(2.2rem, 12cqw, 3.4rem); height: clamp(2.2rem, 12cqw, 3.4rem); filter: none; }
.plush.is-touch .plush-ghost path { display: none; }
.plush.is-touch .plush-ghost .tip {
  display: block;
  fill: rgba(255, 255, 255, .45); stroke: rgba(255, 255, 255, .95); stroke-width: 2;
  paint-order: stroke; filter: drop-shadow(0 0 1.5px rgba(0, 0, 0, .6));
  transform-box: fill-box; transform-origin: center; transition: transform .12s ease-out, fill .12s;
}
.plush.is-touch .plush-ghost.is-gripping .tip { transform: scale(.78); fill: rgba(255, 255, 255, .8); }

/* The fly. Its wings beat about their hinges in flight and fold back along
 * the body once it lands. */
.plush-fly {
  position: absolute; left: 0; top: 0; z-index: 5;
  width: 1.9rem; height: 1.9rem;
  width: clamp(1.25rem, 4.75cqw, 2.4rem); height: clamp(1.25rem, 4.75cqw, 2.4rem);
  pointer-events: none; opacity: 0;
  will-change: transform;
}
.plush-fly.is-here { opacity: 1; }
.plush-fly .body { fill: var(--color-ink, #16120f); stroke: rgba(255, 255, 255, .7); stroke-width: .7; paint-order: stroke; }
.plush-fly .eye { fill: var(--color-brrr, #df430d); }
.plush-fly .wing { fill: rgba(255, 255, 255, .85); stroke: var(--color-ink, #16120f); stroke-width: 1; transform-box: view-box; }
.plush-fly .wing.left { transform-origin: 15px 15px; }
.plush-fly .wing.right { transform-origin: 17px 15px; }
.plush-fly.is-here .wing.left { animation: plush-wing-left 40ms linear infinite alternate; }
.plush-fly.is-here .wing.right { animation: plush-wing-right 40ms linear infinite alternate; }
.plush-fly.is-landed .wing.left { animation: none; transform: rotate(-62deg); }
.plush-fly.is-landed .wing.right { animation: none; transform: rotate(62deg); }
@keyframes plush-wing-left { from { transform: rotate(24deg); } to { transform: rotate(-20deg); } }
@keyframes plush-wing-right { from { transform: rotate(-24deg); } to { transform: rotate(20deg); } }

/* The puff of breath that sees the fly off, at the mouth. */
.plush-puff {
  position: absolute; z-index: 4;
  width: 4rem; height: 4rem;
  width: 10cqw; height: 10cqw;
  pointer-events: none; opacity: 0;
  animation: plush-puff 380ms ease-out forwards;
}
.plush-puff path { fill: none; stroke-linecap: round; }
.plush-puff .under { stroke: var(--color-ink, #16120f); stroke-width: 5.5; }
.plush-puff .over { stroke: #fff; stroke-width: 2.6; }
@keyframes plush-puff {
  0% { opacity: 0; transform: translate(-50%, -80%) scale(.45); }
  20% { opacity: 1; }
  100% { opacity: 0; transform: translate(-50%, -135%) scale(1.15); }
}

/* Notes drifting off the lips while it whistles. How far each drifts is its
 * own (--drift, 0–1, from presets.js). */
.plush-note {
  --far: 12rem;
  --up: 6rem;
  position: absolute; z-index: 4;
  font: 400 2.6rem/1 "Apple Symbols", "Segoe UI Symbol", "Noto Sans Symbols 2", sans-serif;
  color: #fff;
  text-shadow: 0 0 2px var(--color-ink, #16120f), 1px 1px 0 var(--color-ink, #16120f), -1px 1px 0 var(--color-ink, #16120f), 1px -1px 0 var(--color-ink, #16120f), -1px -1px 0 var(--color-ink, #16120f);
  pointer-events: none; opacity: 0;
  animation: plush-note 1.9s ease-out forwards;
}
@supports (width: 1cqw) {
  .plush-note { --far: 30cqw; --up: 15cqw; font-size: clamp(1.4rem, 6.5cqw, 3rem); }
}
@keyframes plush-note {
  0% { opacity: 0; transform: translate(0, 0) scale(.5) rotate(-8deg); }
  15% { opacity: 1; }
  100% { opacity: 0; transform: translate(calc(var(--far) * var(--drift, 1)), calc(var(--up) * -1)) scale(1.15) rotate(14deg); }
}

/* --- Embed --------------------------------------------------------------- */

/* The "made with facebrrr" link e.js puts in the corner of an embedded face. */
.plush-badge {
  position: absolute; right: 0; bottom: 0; z-index: 6;
  padding: .2rem .5rem; border-radius: 999px;
  font: 600 11px/1.4 ui-sans-serif, system-ui, sans-serif; letter-spacing: 0;
  color: var(--color-paper, #fbf6ef); background: color-mix(in srgb, var(--color-ink, #16120f) 80%, transparent);
  text-decoration: none;
}
.plush-badge:hover { background: var(--color-brrr, #df430d); }

@media (prefers-reduced-motion: reduce) {
  .plush-fly.is-here .wing,
  .plush-skeleton,
  .plush-hint { animation: none; }
}
