/* Angel and Demon standing beside the video.

   The layer is absolutely positioned over the DOCUMENT and sized to the video
   container by npc-scene-layer.js, so the characters sit beside whatever width
   the player currently is. Nothing here uses fixed page coordinates.

   Deliberately NOT over the video: the characters overhang the player's left
   and right edges and are pushed outward by their own width, so the picture
   stays uncovered. */

.npc-layer {
  position: absolute;
  z-index: 60;
  /* The layer itself is inert — only the characters and the dismiss button
     take clicks, so the video's own controls stay reachable underneath. */
  pointer-events: none;
}

.npc-char {
  position: absolute;
  /* The platform is part of the art, so this sits it on the video's baseline. */
  bottom: 2%;
  width: 140px;
  pointer-events: auto;
}
/* Outside the frame on both sides. The extra 12px is breathing room so a
   speech bubble never touches the video's edge.

   Positioned with left/right and NOT with a transform, deliberately: the
   entrance and exit animations below animate `transform` with fill-mode both,
   so a placement transform gets overwritten the moment a character enters and
   the demon lands inside the picture. Offsets and animation must not share a
   property. */
.npc-char--left { right: calc(100% + 12px); left: auto; }
.npc-char--right { left: calc(100% + 12px); right: auto; }

.npc-char__body {
  position: relative;
  display: flex;
  justify-content: center;
  /* The gentle bob that makes them feel alive rather than pasted on. */
  animation: npc-bob 3.2s ease-in-out infinite;
}
.npc-char__art {
  width: 100%;
  height: auto;
  image-rendering: pixelated;
  filter: drop-shadow(0 6px 12px rgba(0, 0, 0, .45));
  user-select: none;
  -webkit-user-drag: none;
}

/* A talking character bobs a little harder and lifts slightly. */
.npc-char.is-talking .npc-char__body { animation-duration: 1.4s; }
.npc-char[data-mood="laugh"] .npc-char__body { animation: npc-laugh .5s ease-in-out infinite; }
.npc-char[data-mood="annoyed"] .npc-char__body { animation: npc-annoyed .9s ease-in-out infinite; }
.npc-char[data-mood="smug"] .npc-char__body { animation-duration: 4.5s; }

/* Not enough room outside the frame on that side, so the character tucks into
   the corner instead of standing in space that is off-screen. Set per side by
   npc-scene-layer.js from a real measurement, so a wide window keeps both of
   them beside the video and a narrow one only moves the side that had to. */
.npc-layer--tuck-left .npc-char--left {
  left: 8px;
  right: auto;
  width: 92px;
}
.npc-layer--tuck-right .npc-char--right {
  right: 8px;
  left: auto;
  width: 92px;
}
/* When tucked there is no room OUTWARD — that is what tucking means — so the
   bubble has to open inward instead, back across the frame.
   Both left AND right are set explicitly: the outward rules further down set
   the opposite property, and a rule that only sets one leaves the other
   applied, which put the bubble half off-screen. */
.npc-layer--tuck-left .npc-char--left .npc-bubble {
  left: 0;
  right: auto;
  transform: none;
}
.npc-layer--tuck-left .npc-char--left .npc-bubble::after {
  left: 28px;
  right: auto;
  transform: none;
}
.npc-layer--tuck-right .npc-char--right .npc-bubble {
  left: auto;
  right: 0;
  transform: none;
}
.npc-layer--tuck-right .npc-char--right .npc-bubble::after {
  left: auto;
  right: 28px;
  transform: none;
}

/* ---- Speech bubble ------------------------------------------------------- */
/* MapleStory-shaped: small, light, hard border, pointer down at the character.
   Never a chat panel. */

.npc-bubble {
  position: absolute;
  bottom: calc(100% + 8px);
  left: 50%;
  transform: translateX(-50%);
  width: max-content;
  min-width: 108px;
  /* Narrow on purpose, and narrower than the text strictly needs. Lines are
     four to nine words, so this wraps them into the two or three short rows the
     original design has — a tall little bubble reads as speech, a wide one
     reads as a caption. It also keeps the bubble clear of the player. */
  max-width: 16ch;
  padding: 9px 12px;
  background: rgba(255, 255, 255, .97);
  border: 2px solid #12161c;
  border-radius: 18px;
  box-shadow: 0 10px 24px rgba(0, 0, 0, .3);
  animation: npc-bubble-pop .18s ease-out;
}
.npc-bubble[hidden] { display: none; }
.npc-bubble::after {
  content: '';
  position: absolute;
  top: 100%;
  left: 50%;
  transform: translateX(-50%);
  border: 8px solid transparent;
  border-top-color: #12161c;
}

/* Anchored to the OUTER edge of each character so the bubble opens outward,
   away from the player. Centring it meant half its width grew toward the video
   and a long line laid itself over the picture. The tail stays above the
   character, which is what keeps it reading as speech. */
.npc-char--left .npc-bubble {
  left: auto;
  right: 0;
  transform: none;
}
.npc-char--left .npc-bubble::after {
  left: auto;
  right: 46px;
  transform: none;
}
.npc-char--right .npc-bubble {
  left: 0;
  right: auto;
  transform: none;
}
.npc-char--right .npc-bubble::after {
  left: 46px;
  transform: none;
}
.npc-bubble__text {
  margin: 0;
  font-size: 13.5px;
  font-weight: 800;
  line-height: 1.35;
  /* Red, the way the game's own NPC bubbles read (npc-runtime.js). The two
     surfaces should look like the same characters. */
  color: #e11d1d;
  text-align: center;
  overflow-wrap: anywhere;
}

.npc-dismiss {
  position: absolute;
  top: 8px;
  right: 8px;
  width: 24px;
  height: 24px;
  padding: 0;
  font: inherit;
  font-size: 11px;
  color: #fff;
  background: rgba(0, 0, 0, .5);
  border: 0;
  border-radius: 50%;
  cursor: pointer;
  opacity: .55;
  pointer-events: auto;
}
.npc-dismiss:hover { opacity: 1; }

/* ---- Entrances and exits ------------------------------------------------- */
/* Names come from a server-side allowlist, so a class here always exists for
   whatever the scene asks for. */

.npc-anim--float-in { animation: npc-float-in .7s ease-out both; }
.npc-anim--slide-in { animation: npc-slide-in .55s cubic-bezier(.2, .9, .3, 1.2) both; }
.npc-anim--fade-in  { animation: npc-fade-in .7s ease-out both; }
.npc-anim--drift-in { animation: npc-drift-in .9s ease-out both; }

.npc-anim--float-out { animation: npc-float-out .6s ease-in both; }
.npc-anim--slide-out { animation: npc-slide-out .5s ease-in both; }
.npc-anim--fade-out  { animation: npc-fade-out .5s ease-in both; }
.npc-anim--drift-out { animation: npc-drift-out .8s ease-in both; }

@keyframes npc-bob { 50% { transform: translateY(-6px); } }
@keyframes npc-laugh { 50% { transform: translateY(-4px) rotate(-3deg); } }
@keyframes npc-annoyed { 25% { transform: translateX(-3px); } 75% { transform: translateX(3px); } }
/* Opacity and scale only — NO translate.
   This used to be `translateX(-50%) scale(.9)`, from when the bubble was
   centred by transform. Positioning moved to left/right, and the leftover
   translate in this keyframe dragged the bubble half its own width sideways
   and off the screen. Second time transform-as-positioning has bitten this
   file; the rule is that offsets and animation never share a property. */
@keyframes npc-bubble-pop { from { opacity: 0; transform: scale(.94); } }

@keyframes npc-float-in  { from { opacity: 0; transform: translateY(26px); } }
@keyframes npc-slide-in  { from { opacity: 0; transform: translateY(0) scale(.85); } }
@keyframes npc-fade-in   { from { opacity: 0; } }
@keyframes npc-drift-in  { from { opacity: 0; transform: translateY(-22px); } }

@keyframes npc-float-out { to { opacity: 0; transform: translateY(-26px); } }
@keyframes npc-slide-out { to { opacity: 0; transform: translateY(18px) scale(.9); } }
@keyframes npc-fade-out  { to { opacity: 0; } }
@keyframes npc-drift-out { to { opacity: 0; transform: translateY(24px); } }

/* Somebody who asked for less motion still gets the scene, just without the
   drifting and bobbing. */
@media (prefers-reduced-motion: reduce) {
  .npc-char__body,
  .npc-char.is-talking .npc-char__body,
  .npc-char[data-mood] .npc-char__body { animation: none; }
  .npc-layer [class*="npc-anim--"] { animation-duration: .01ms; }
}

/* ---- Narrow ------------------------------------------------------------- */
/* There is no room beside the player on a phone, so the characters come inside
   the bottom corners at a smaller size rather than overhanging into nothing or
   covering the up-next rail. The bubble opens inward so it stays on screen. */

@media (max-width: 900px) {
  .npc-char { width: 84px; bottom: 2%; }
  .npc-char--left { left: 6px; right: auto; }
  .npc-char--right { right: 6px; left: auto; }
  .npc-bubble { min-width: 108px; max-width: 26ch; padding: 9px 12px; }
  .npc-bubble__text { font-size: 12px; }
  /* Both characters are inside the frame on a phone, so both bubbles open
     inward. Left and right both set, for the same reason as the tuck rules. */
  .npc-char--left .npc-bubble { left: 0; right: auto; transform: none; }
  .npc-char--left .npc-bubble::after { left: 22px; right: auto; transform: none; }
  .npc-char--right .npc-bubble { left: auto; right: 0; transform: none; }
  .npc-char--right .npc-bubble::after { left: auto; right: 22px; transform: none; }
}
