/*
  Player styles - this is the element we place in the center of each map's
  viewport to represent the main character that the user controls.

  The character has left, right, up and down walk animations, which are
  switched between using a technique from Bramus Van Damme that is absolutely
  _wild_.
  
  Bramus explains it much better than I can, but the gist is that you can tell
  which direction the user is scrolling in with pure CSS:
  
  - defining two @property values to represent the progress of a scroll axis.
      One is the "lead" value, the other "delayed"
  - update the "main" value using scroll-timeline (a "scroll-linked animation")
  - transition the delayed value in response to the lead value, but with a
      short delay (0.15s in our case)
  - derive the direction of scroll by comparing the two values (with some swanky
      CSS math functions clamp() and sign()) - if the delayed value is less than
      the lead value then you're scrolling down; if it's greater than you're
      scrolling up.

  Bramus also uses this technique to determine scroll speed, which we don't use
  here, but we could in future switch to a "run" spritesheet when the user
  scrolls quickly.

  His full write up is here: https://www.bram.us/2023/10/23/css-scroll-detection/
*/
@property --scroll-x {
  syntax: "<number>";
  inherits: true;
  initial-value: 0;
}
@property --scroll-x-delayed {
  syntax: "<number>";
  inherits: true;
  initial-value: 0;
}
@property --scroll-y {
  syntax: "<number>";
  inherits: true;
  initial-value: 0;
}
@property --scroll-y-delayed {
  syntax: "<number>";
  inherits: true;
  initial-value: 0;
}
@property --going-right {
  syntax: "<integer>";
  inherits: true;
  initial-value: 0;
}
@property --going-left {
  syntax: "<integer>";
  inherits: true;
  initial-value: 0;
}
@property --going-down {
  syntax: "<integer>";
  inherits: true;
  initial-value: 0;
}
@property --going-up {
  syntax: "<integer>";
  inherits: true;
  initial-value: 0;
}

.map {
  /*
    Two attempts at improving the performance of scrolling when holding down
    arrow keys (rather than scrolling using mouse/touch). BOth were suggestions
    from Copilot which do seem to help but I'd like to understand better. I've
    outlined what I understand of Copilot's reasoning below.
  */

  /*
    Adopt the scroll timelines from the child viewport so the animation
    runs on .map (outside the scroll container) rather than inside it.
    This means that even though custom properties are animated on the main
    thread, we at least keep them from running on the scroll container. The
    hunch here is that this helps with performance because it stops conflict
    with the scroll-snapping on .viewport.
  */
  timeline-scope: --map-timeline-x, --map-timeline-y;

  /*
    Define the style container here rather than on .player-sprite.
    Even though this is technically looser than what we need, it keeps style
    query evaluation outside the scroll content tree. Again, this should improve
    performance.
  */
  container-type: style;

  animation:
    set-scroll-x linear both,
    set-scroll-y linear both;
  animation-timeline: --map-timeline-x, --map-timeline-y;
}

.map .viewport {
  scroll-timeline:
    --map-timeline-x x,
    --map-timeline-y y;

  /*
    The transition mentioned above lives here; on the child of .map where
    --scroll-x is set, but not inside the scroll content, so it doesn't
    block scroll-snap settling. Again, this was Copilot's idea, and again,
    it seems to improve performance but I would like to delve into exactly why.
  */
  transition:
    --scroll-x-delayed 0.15s linear,
    --scroll-y-delayed 0.15s linear;
  --scroll-x-delayed: var(--scroll-x);
  --scroll-y-delayed: var(--scroll-y);
}

@keyframes set-scroll-x {
  from {
    --scroll-x: 0;
  }
  to {
    --scroll-x: 1;
  }
}

@keyframes set-scroll-y {
  from {
    --scroll-y: 0;
  }
  to {
    --scroll-y: 1;
  }
}

/*
  Basic player container styles
*/
.player {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate3d(-50%, -50%, 0);
  width: calc(var(--tile-width) / 2);
  height: calc(var(--tile-height) / 2);
  pointer-events: none;
  --z-index: var(--stacking-layer-action);
  display: flex;
  justify-content: center;
  align-items: center;
  color: white;

  /*
    Compute 0/1 boolean per direction via clamp+sign.
     --scroll-x / --scroll-x-delayed are inherited from .map and .map .viewport
  */
  --going-right: clamp(
    0,
    sign(calc(var(--scroll-x) - var(--scroll-x-delayed))),
    1
  );
  --going-left: clamp(
    0,
    sign(calc(var(--scroll-x-delayed) - var(--scroll-x))),
    1
  );
  --going-down: clamp(
    0,
    sign(calc(var(--scroll-y) - var(--scroll-y-delayed))),
    1
  );
  --going-up: clamp(
    0,
    sign(calc(var(--scroll-y-delayed) - var(--scroll-y))),
    1
  );
}

/*
  Player sprite styles. This uses the --going-* variables defined on its parent
  to pick which spritesheet to show, so that the character appears to face the 
  direction that the user is making them walk.
  
  When not scrolling, we don't have a direction, so we use the background-image
  property itself as the "memory" of last direction. How? An all-but infinite
  999999s transition-delay prevents us reverting to the initial spritesheet
  after walking stops.

  When the user resumes scrolling and the character resumes walking in a
  particular direction, the container queries below set transition-delay to 0
  so that the directional background-image swaps in immediately.
*/
.player-sprite::before {
  content: "";
  position: absolute;
  inset: 0;
  background-image: url("../assets/player/player-idle.png");
  background-size: auto 100%;
  background-repeat: no-repeat;
  animation: walk 0.6s steps(3) infinite paused;
  background-position: calc(-3 * var(--tile-width) / 2);
  transition: background-image 0s 999999s allow-discrete;
  will-change: transform; /* Performance boost - tell the compositor to separate the player div from map */
}

@container style(--going-down: 1) {
  .player-sprite::before {
    background-image: url("../assets/player/player-walk-down.png");
    animation-play-state: running;
    transition-delay: 0s;
  }
}
@container style(--going-up: 1) {
  .player-sprite::before {
    background-image: url("../assets/player/player-walk-up.png");
    animation-play-state: running;
    transition-delay: 0s;
  }
}
@container style(--going-right: 1) {
  .player-sprite::before {
    background-image: url("../assets/player/player-walk-right.png");
    animation-play-state: running;
    transition-delay: 0s;
  }
}
@container style(--going-left: 1) {
  .player-sprite::before {
    background-image: url("../assets/player/player-walk-left.png");
    animation-play-state: running;
    transition-delay: 0s;
  }
}

@keyframes walk {
  from {
    background-position-x: 0;
  }
  to {
    background-position-x: calc(-3 * var(--tile-width) / 2);
  }
}

/*
  Position the player's speech bubble - currently only used in endgame cut-scene (see endgame.css)
*/
.player .speech-bubble {
  top: -2rem;
  translate: 0 2rem;
}
