/* The vertical trail (task.md sections 9.4 and 9.5). */

/* The trail is edge to edge: the photo fills the screen under the header, with no
   side indents, so the map covers the full width and the discs keep the exact spots
   the owner picked on /dev/coords. The bottom padding is scroll room, so the last
   disc can be brought up to the middle of the screen. */
.path {
  position: relative;
  margin: 0;
  padding: 0 0 calc(var(--space-7) + 40vh);
  padding-bottom: calc(var(--space-7) + 40vh + env(safe-area-inset-bottom));
}

.path__svg {
  display: block;
  width: 100%;
  height: auto;
  overflow: visible;
}

/* The SVG, the discs, the vehicle and the photo must share one box, or the ribbon
   (drawn in the SVG's 680x1470 view box), the discs (positioned in %) and the
   landmarks in the picture drift apart. The stage is exactly the SVG's size; the
   disc layer covers it.

   The background from media/background/, named in quest.yaml, is painted here on
   the stage - not on the viewport - so it scrolls with the discs instead of
   staying put behind them. The crop ("cover", aligned top centre) is the one
   /dev/coords draws its grid over, so a point the owner picked lands on the same
   landmark at any stage width. A missing file just leaves the paper colour; the
   photo stays untouched, with no paper veil over it. */
.path__stage {
  position: relative;
  /* A stacking context of its own, so the snow's negative z-index below means
     "above the photo, under the ribbon" instead of reaching for an ancestor and
     possibly hiding behind its background. */
  isolation: isolate;
  background: var(--paper) url("/media/background") center top / cover no-repeat;
}

/* --- falling snow -------------------------------------------------------- */

/* Snow over one landmark on the photo. It is decoration: `pointer-events: none`
   here and on every flake is the whole point - the easter egg for the same
   landmark stands under it, and the snow must not be able to swallow a tap. */
.path__snow {
  position: absolute;
  overflow: hidden;
  pointer-events: none;
  /* A positioned element paints above a static one no matter where it sits in the
     tree, so the snow would land on top of the ribbon; a negative z-index puts it
     back under the SVG (see .path__stage above) while still over the photo. */
  z-index: -1;
  /* Each flake falls in from above the patch and out below it, so without a fade
     it would pop into existence on the top edge and vanish on the bottom one. */
  -webkit-mask-image: linear-gradient(180deg, transparent 0%, #000 18%, #000 82%, transparent 100%);
  mask-image: linear-gradient(180deg, transparent 0%, #000 18%, #000 82%, transparent 100%);
}

.snow__flake {
  position: absolute;
  left: var(--snow-x);
  top: var(--snow-rest);
  width: var(--snow-size);
  height: var(--snow-size);
  border-radius: 50%;
  /* Snow is white in both themes: it falls on the photo, which does not change
     with the theme, so it needs no token and must not be given one. */
  background: rgb(255 255 255 / var(--snow-alpha));
  box-shadow: 0 0 3px rgb(255 255 255 / calc(var(--snow-alpha) * 0.6));
  pointer-events: none;
}

/* The fall animates `top` rather than a transform on purpose: a percentage in
   translateY resolves against the flake's own size, not against the patch, so
   there is no unit that says "the height of the box" without measuring it in JS
   and re-measuring on every resize. Thirty tiny leaves are cheap to lay out. */
@media (prefers-reduced-motion: no-preference) {
  .snow__flake {
    animation:
      snow-fall var(--snow-fall) linear infinite var(--snow-delay),
      snow-sway var(--snow-sway) ease-in-out infinite alternate;
  }
}

@keyframes snow-fall {
  from {
    top: -12%;
  }
  to {
    top: 112%;
  }
}

@keyframes snow-sway {
  from {
    transform: translateX(calc(var(--snow-drift) * -1));
  }
  to {
    transform: translateX(var(--snow-drift));
  }
}

/* With reduced motion the animation is never applied at all, so every flake
   simply stays where --snow-rest put it: a static sprinkle through the patch
   rather than thirty dots piled on its top edge. */

/* The discs need this layer to be the whole stage so they can be placed in %, but
   the layer itself must not be a target: it covers every pixel of the photo and
   sits above the easter eggs, so with the default pointer-events it swallowed
   every tap on a landmark. Only the discs themselves take a tap - see
   `pointer-events: auto` on .path__node. */
.path__nodes {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

/* --- the hidden easter eggs --------------------------------------------- */

/* Invisible buttons over landmarks on the photo: same position layer as the
   nodes, same on-screen size, but no disc and no paint, so only the owner knows
   they are there. opacity (not visibility) is dropped so the button still
   receives taps; the layer sits under the discs so a node always wins. Like the
   node layer it is a positioning layer, not a target: only the button itself
   takes a tap. */
.path__eggs {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

.path__egg {
  position: absolute;
  pointer-events: auto;
  width: calc(var(--node-radius) * 2);
  height: calc(var(--node-radius) * 2);
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: none;
  opacity: 0;
  cursor: pointer;
  transform: translate(-50%, -50%);
  -webkit-tap-highlight-color: transparent;
}

/* --- the trail itself --------------------------------------------------- */

.path__trail {
  fill: none;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-width: 14;
  /* A soft paper halo under the stroke, so the gold "done" ribbon keeps its
     separation on the bare photo (the owner asked for the photo untouched, with no
     paper veil over it). The faint locked dashes stay faint by design. */
  filter: drop-shadow(0 0 2px var(--paper)) drop-shadow(0 0 1px var(--paper));
}

.path__trail--done {
  stroke: var(--gold);
}

.path__trail--locked {
  stroke: var(--lock);
  stroke-dasharray: 2 22;
  stroke-linecap: round;
}

.path__trail--current {
  stroke: color-mix(in srgb, var(--violet) 40%, var(--lock));
  stroke-dasharray: 2 22;
}

/* --- nodes -------------------------------------------------------------- */

.path__node {
  position: absolute;
  /* The discs are the only thing in their layer a tap should reach; see
     .path__nodes above. */
  pointer-events: auto;
  width: calc(var(--node-radius) * 2);
  height: calc(var(--node-radius) * 2);
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: none;
  cursor: pointer;
  transform: translate(-50%, -50%);
  -webkit-tap-highlight-color: transparent;
  /* Breathing room when a disc is scrolled into view, so it never lands flush
     against the top or bottom edge of the screen. */
  scroll-margin-top: 96px;
  scroll-margin-bottom: 96px;
}

.path__disc {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: var(--lock);
  box-shadow: 0 var(--node-edge) 0 color-mix(in srgb, var(--lock) 60%, var(--ink));
  transition: background var(--dur-base) var(--ease-out);
}

.path__glyph {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  font-family: var(--font-display);
  font-size: var(--text-md);
  color: var(--lock-ink);
}

.path__node[data-status="current"] .path__disc {
  background: var(--btn-bg);
  box-shadow: 0 var(--node-edge) 0 var(--violet-deep);
}

.path__node[data-status="current"] .path__glyph {
  color: #fff;
}

.path__node[data-status="done"] .path__disc {
  background: linear-gradient(160deg, var(--gold) 0%, var(--gold-deep) 100%);
  box-shadow: 0 var(--node-edge) 0 var(--gold-deep);
}

.path__node[data-status="done"] .path__glyph {
  color: #3a2400;
  font-size: var(--text-lg);
  font-weight: 900;
}

.path__node[disabled] {
  cursor: default;
}

/* The current node breathes, the way Duolingo does. */
@media (prefers-reduced-motion: no-preference) {
  .path__node[data-status="current"] {
    animation: node-pulse 2s var(--ease-out) infinite;
  }
}

@keyframes node-pulse {
  0%,
  100% {
    transform: translate(-50%, -50%) scale(1);
  }
  50% {
    transform: translate(-50%, -50%) scale(1.06);
  }
}

.path__node--shake {
  animation: node-shake 400ms var(--ease-out);
}

@keyframes node-shake {
  0%,
  100% {
    transform: translate(-50%, -50%) rotate(0deg);
  }
  25% {
    transform: translate(-50%, -50%) rotate(-7deg);
  }
  75% {
    transform: translate(-50%, -50%) rotate(7deg);
  }
}

/* --- The little vehicle that marks the current node ---------------------- */

.path__car {
  position: absolute;
  width: 46px;
  height: 46px;
  object-fit: contain;
  pointer-events: none;
  z-index: 3;
  filter: drop-shadow(0 4px 6px rgb(0 0 0 / 25%));
  /* The car is upright and its position is animated in JS (path.js, animateCar),
     so no CSS transition on left/top - it would fight the per-frame updates. */
  transform: translate(-50%, -50%);
}

/* --- stars on the last node --------------------------------------------- */

.path__node--home .path__disc {
  background: linear-gradient(160deg, var(--rose) 0%, var(--rose-deep) 100%);
  box-shadow: 0 var(--node-edge) 0 var(--rose-deep);
}

.path__node--home .path__glyph {
  color: #fff;
}
