/* Hero's Journey (ADR-028) — all journey + boss-vignette CSS lives here (styles.css sits at its
   line ceiling). Motion tokens extend the v2 set; every animation targets transform/opacity/filter
   only, and every keyframe's 100% is a valid resting state so the global prefers-reduced-motion
   block (styles.css) snaps each one to its OUTCOME, never a mid-motion frame. */

:root {
  --motion-step: 450ms; /* one attack lunge / guard break */
  --motion-hit: 300ms; /* hit flash */
}

/* --- The stage ---------------------------------------------------------------------------------- */

.journey-caption {
  color: var(--ink-dim);
  font-family: var(--serif);
  font-style: italic;
  margin: 0 0 var(--space-2);
}

.journey-viewport {
  position: relative;
  height: clamp(320px, 55vh, 480px);
  overflow: hidden;
  border: 1px solid rgba(201, 164, 76, 0.25);
  border-radius: var(--radius);
  background: linear-gradient(180deg, #131d33 0%, var(--bg) 78%);
}

.journey-strip {
  position: absolute;
  inset: 0 auto 0 0;
  will-change: transform;
}

/* --- Zones -------------------------------------------------------------------------------------- */

.journey-zone {
  position: absolute;
  top: 0;
  bottom: 0;
  --zone-colour: var(--gold);
  /* The biome sky: its accent settling into the shared night. */
  background: linear-gradient(
    180deg,
    color-mix(in srgb, var(--zone-colour) 24%, var(--bg)) 0%,
    color-mix(in srgb, var(--zone-colour) 8%, var(--bg)) 46%,
    transparent 78%
  );
  border-right: 1px solid rgba(201, 164, 76, 0.12);
}

.journey-zone-name {
  position: absolute;
  top: 0.75rem;
  left: 1rem;
  margin: 0;
  font-family: var(--serif);
  letter-spacing: 0.08em;
  font-variant: small-caps;
  color: color-mix(in srgb, var(--zone-colour) 55%, var(--ink));
}

.journey-zone.is-sealed {
  filter: grayscale(0.7);
  opacity: 0.55; /* the ADR-027 sealed treatment — a veil, not a wall */
}

.journey-zone-veil {
  position: absolute;
  top: 2.4rem;
  left: 1rem;
  margin: 0;
  font-size: 0.85rem;
  color: var(--ink-dim);
  border: 1px solid rgba(201, 164, 76, 0.35);
  border-radius: 999px;
  padding: 0.1rem 0.6rem;
}

/* --- The path and its waypoints ------------------------------------------------------------------ */

.journey-path {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 64px;
  height: 2px;
  background: linear-gradient(90deg, transparent, rgba(201, 164, 76, 0.55) 4%, rgba(201, 164, 76, 0.55) 96%, transparent);
}

.journey-node {
  position: absolute;
  bottom: 40px;
  transform: translateX(-50%);
  color: rgba(230, 205, 134, 0.45); /* --gold-soft, dimmed: an unmet waypoint */
  display: grid;
  justify-items: center;
}

.journey-node.is-complete {
  color: var(--gold-soft);
}

.journey-node.is-boss {
  color: color-mix(in srgb, var(--fail) 45%, var(--gold-soft));
  bottom: 48px;
}

.journey-node.is-final-boss {
  color: color-mix(in srgb, var(--fail) 65%, var(--gold-soft));
  bottom: 52px;
}

.journey-node.is-complete.is-boss,
.journey-node.is-complete.is-final-boss {
  color: var(--gold-soft); /* a defeated boss rests in gold, not menace */
  opacity: 0.8;
}

/* --- Zone backdrop art --------------------------------------------------------------------------- */

.journey-zone-horizon {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 62px;
  display: flex;
  align-items: flex-end;
  overflow: hidden;
  opacity: 0.16; /* horizons are silhouettes; the engraved detail budget lives in characters */
  color: var(--zone-colour);
}

.journey-prop {
  position: absolute;
  bottom: 64px;
  opacity: 0.4;
  color: color-mix(in srgb, var(--zone-colour) 70%, var(--ink));
}

/* --- Node figures (enemies, bosses) ---------------------------------------------------------------
   Nodes are SIBLINGS of the zones (both strip children), so the zone's custom property does not
   cascade to them; data-biome + the styles.css --biome-* tokens carry the accent instead, and the
   figure inherits `color` so the .is-boss/.is-complete state colours on the node keep working. */

.journey-node[data-biome="the-grove"], .vignette[data-biome="the-grove"] { --zone-colour: var(--biome-the-grove); }
.journey-node[data-biome="the-marble-caverns"], .vignette[data-biome="the-marble-caverns"] { --zone-colour: var(--biome-the-marble-caverns); }
.journey-node[data-biome="the-forge"], .vignette[data-biome="the-forge"] { --zone-colour: var(--biome-the-forge); }
.journey-node[data-biome="the-archive"], .vignette[data-biome="the-archive"] { --zone-colour: var(--biome-the-archive); }
.journey-node[data-biome="the-spires"], .vignette[data-biome="the-spires"] { --zone-colour: var(--biome-the-spires); }
.journey-node[data-biome="the-aether"], .vignette[data-biome="the-aether"] { --zone-colour: var(--biome-the-aether); }
.journey-node[data-biome="the-observatory"], .vignette[data-biome="the-observatory"] { --zone-colour: var(--biome-the-observatory); }
.journey-node[data-biome="the-summit"], .vignette[data-biome="the-summit"] { --zone-colour: var(--biome-the-summit); }

/* A living skirmisher wears its biome's accent; boss/complete state colours (below/above) win. */
.journey-node:not(.is-complete):not(.is-boss):not(.is-final-boss) {
  color: color-mix(in srgb, var(--zone-colour, var(--gold)) 60%, var(--ink));
}

.journey-node.is-mirrored .journey-node-figure {
  transform: scaleX(-1); /* variant: every third enemy faces the other way */
}

/* Defeated: the is-locked treatment + fallen (rotated at the ground contact). The live fall
   animates in P10-M3; a revisit renders the aftermath statically — same terminal state. */
.journey-node.is-complete .journey-node-figure {
  transform: rotate(-70deg) translateY(6px);
  transform-origin: 50% 92%;
  filter: grayscale(0.8);
  opacity: 0.45;
}

.journey-node.is-complete.is-mirrored .journey-node-figure {
  transform: scaleX(-1) rotate(-70deg) translateY(6px);
}

/* A fallen boss keeps more presence than a fallen skirmisher. */
.journey-node.is-complete.is-boss .journey-node-figure,
.journey-node.is-complete.is-final-boss .journey-node-figure {
  transform: rotate(-40deg) translateY(4px);
  opacity: 0.55;
}

.journey-node-mark {
  position: absolute;
  top: -22px;
  left: 50%;
  transform: translateX(-50%);
  color: var(--gold-soft);
}

/* Ambient life: enemies sway, bosses loom. Ornamental loops — the global reduced-motion block
   strips them, and their 0%/100% frames are the rest pose. */
.journey-node:not(.is-complete):not(.is-boss):not(.is-final-boss) .journey-node-figure {
  animation: enemy-idle 1800ms ease-in-out infinite alternate;
}

.journey-node.is-boss:not(.is-complete) .journey-node-figure,
.journey-node.is-final-boss:not(.is-complete) .journey-node-figure {
  animation: boss-menace 2600ms ease-in-out infinite alternate;
}

@keyframes enemy-idle {
  from { transform: translateY(0); }
  to { transform: translateY(-2px); }
}

.journey-node.is-mirrored:not(.is-complete) .journey-node-figure {
  animation-name: enemy-idle-mirrored;
}

@keyframes enemy-idle-mirrored {
  from { transform: scaleX(-1) translateY(0); }
  to { transform: scaleX(-1) translateY(-2px); }
}

@keyframes boss-menace {
  from { transform: scale(1) translateY(0); }
  to { transform: scale(1.03) translateY(-2px); }
}

/* --- The hero rig ---------------------------------------------------------------------------------
   Joints: transform-box view-box puts transform-origin in viewBox coordinates — each origin below
   is the documented joint of its part (see journey-hero.svg). The head never animates
   independently (its joint is reserved), so the laureate circlet safely rides part-head. */

.journey-hero {
  position: absolute;
  bottom: 58px;
  transform: translateX(-50%);
  color: var(--ink);
}

.journey-hero-figure {
  display: block;
  filter: drop-shadow(0 6px 10px rgba(0, 0, 0, 0.45));
}

/* The rig's earn layers (same grammar as the codex: hidden until .is-on). */
.journey-hero-figure .avatar-layer {
  visibility: hidden;
}

.journey-hero-figure .avatar-layer.is-on {
  visibility: visible;
}

.journey-hero-rig .hero-part {
  transform-box: view-box;
}

.journey-hero-rig #part-arm-back { transform-origin: 46px 52px; }
.journey-hero-rig #part-arm-front { transform-origin: 52px 52px; }
.journey-hero-rig #part-leg-back { transform-origin: 46px 100px; }
.journey-hero-rig #part-leg-front { transform-origin: 50px 100px; }
.journey-hero-rig #part-cloak { transform-origin: 48px 50px; }
.journey-hero-rig #part-torso { transform-origin: 48px 86px; }
.journey-hero-rig #part-head { transform-origin: 52px 44px; }

/* Rest: the second flutter frame is hidden. */
.journey-hero-rig #cloak-b { opacity: 0; }

/* WALK — set by the replay driver only (never under reduced motion, which the driver checks in JS;
   the global CSS block strips these regardless). Limbs counter-swing; the whole figure bobs; the
   cloak flutters as a 2-frame steps() swap. */
.journey-hero[data-pose="walk"] .journey-hero-figure { animation: hero-bob 900ms ease-in-out infinite; }
.journey-hero[data-pose="walk"] #part-leg-front { animation: hero-limb-swing 900ms ease-in-out infinite alternate; }
.journey-hero[data-pose="walk"] #part-leg-back { animation: hero-limb-swing 900ms ease-in-out infinite alternate-reverse; }
.journey-hero[data-pose="walk"] #part-arm-front { animation: hero-arm-swing 900ms ease-in-out infinite alternate-reverse; }
.journey-hero[data-pose="walk"] #part-arm-back { animation: hero-arm-swing 900ms ease-in-out infinite alternate; }
.journey-hero[data-pose="walk"] #cloak-a { animation: cloak-frame-a 900ms steps(2, jump-none) infinite; }
.journey-hero[data-pose="walk"] #cloak-b { animation: cloak-frame-b 900ms steps(2, jump-none) infinite; }

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

@keyframes hero-limb-swing {
  from { transform: rotate(-14deg); }
  to { transform: rotate(14deg); }
}

@keyframes hero-arm-swing {
  from { transform: rotate(-10deg); }
  to { transform: rotate(10deg); }
}

@keyframes cloak-frame-a {
  from { opacity: 1; }
  to { opacity: 0; }
}

@keyframes cloak-frame-b {
  from { opacity: 0; }
  to { opacity: 1; }
}

/* ATTACK — one lunge (fire-and-forget; 100% = returned to rest). The staff swings because it
   lives inside the front arm. */
.journey-hero[data-pose="attack"] .journey-hero-figure { animation: hero-attack var(--motion-step) var(--ease-out-back); }
.journey-hero[data-pose="attack"] #part-arm-front { animation: hero-attack-arm var(--motion-step) var(--ease-out-back); }
.journey-hero[data-pose="attack"] #part-torso { animation: hero-attack-lean var(--motion-step) var(--ease-out-back); }

@keyframes hero-attack {
  0%, 100% { transform: translateX(0); }
  40% { transform: translateX(10px); }
}

@keyframes hero-attack-arm {
  0%, 100% { transform: rotate(0deg); }
  38% { transform: rotate(-58deg); }
  70% { transform: rotate(10deg); }
}

@keyframes hero-attack-lean {
  0%, 100% { transform: rotate(0deg); }
  40% { transform: rotate(5deg); }
}

/* VICTORY — arm raised, held (forwards; 100% = the outcome pose). Echoes the boss-cleared pulse. */
.journey-hero[data-pose="victory"] .journey-hero-figure {
  animation: hero-victory var(--motion-celebrate) var(--ease-out-back) forwards;
}
.journey-hero[data-pose="victory"] #part-arm-front {
  animation: hero-victory-arm var(--motion-celebrate) var(--ease-out-back) forwards;
}

@keyframes hero-victory {
  0% { transform: scale(1); }
  35% { transform: scale(1.04); }
  100% { transform: scale(1); }
}

@keyframes hero-victory-arm {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(-120deg); }
}

/* Shared hit flash (struck sprite, M3/M4). steps(2): a flicker, ending normal. */
@keyframes hit-flash {
  0%, 100% { filter: brightness(1); }
  50% { filter: brightness(1.7); }
}

/* --- Replay combat (P10-M3) -----------------------------------------------------------------------
   The flash and the shake live on the INNER svg: the figure div carries the idle/menace loops at
   higher specificity, and a different element sidesteps the animation-property collision entirely. */

.journey-node.is-hit .journey-node-figure > svg {
  animation: hit-flash var(--motion-hit) steps(2, jump-none);
}

.journey-node.is-shaking .journey-node-figure > svg {
  animation: boss-shake 600ms ease-in-out;
}

@keyframes boss-shake {
  0%, 100% { transform: translateX(0); }
  20% { transform: translateX(-4px); }
  40% { transform: translateX(4px); }
  60% { transform: translateX(-3px); }
  80% { transform: translateX(2px); }
}

/* The live fall: .is-felled arms the transition, then .is-complete's static defeated transform
   (above) becomes the transition target — the replay animates to EXACTLY the state a revisit
   renders. Never armed under reduced motion (the driver doesn't run). */
.journey-node.is-felled .journey-node-figure {
  transition: transform 640ms ease-in, filter 640ms ease, opacity 640ms ease;
}

/* Walking back happens: a later module can complete while an earlier one is still open, and the
   hero must return to the frontier. The mirror rides the positioning container — the figure's own
   transform is busy with the bob keyframes. */
.journey-hero.is-facing-left {
  transform: translateX(-50%) scaleX(-1);
}

/* The Skip affordance sits on the stage (outside the aria-hidden viewport), over the top-right. */
#journey-stage {
  position: relative;
}

.journey-skip {
  position: absolute;
  top: 0.75rem;
  right: 0.75rem;
  z-index: 2;
}

/* --- The live boss vignette (P10-M4) ---------------------------------------------------------------
   Hero left, boss right, the guard between them. The hero reuses the .journey-hero rig + pose CSS
   verbatim (attack/victory), just positioned statically inside the arena. */

.vignette {
  --zone-colour: var(--gold);
  margin: 0 0 var(--space-2);
  border: 1px solid rgba(201, 164, 76, 0.25);
  border-radius: var(--radius);
  background: linear-gradient(
    180deg,
    color-mix(in srgb, var(--zone-colour) 14%, var(--bg)) 0%,
    var(--bg) 88%
  );
  padding: 0.75rem 1rem;
}

.vignette-arena {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  min-height: 160px;
  padding: 0 8%;
}

.vignette .journey-hero {
  position: static;
  transform: none;
}

.vignette-boss {
  color: color-mix(in srgb, var(--fail) 45%, var(--gold-soft));
}

.vignette-boss-figure {
  animation: boss-menace 2600ms ease-in-out infinite alternate;
}

/* The strike's answer: the boss shudders. On the INNER svg — the figure div owns the menace loop. */
.vignette-boss.is-guard-break .vignette-boss-figure > svg {
  animation: boss-shake 500ms ease-in-out;
}

/* Defeat: the fall animates once (ease-in, forwards)… */
.vignette-boss.is-defeated .vignette-boss-figure {
  animation: vignette-boss-fall 900ms ease-in forwards;
}

@keyframes vignette-boss-fall {
  from { transform: rotate(0deg) translateY(0); filter: grayscale(0); opacity: 1; }
  to { transform: rotate(-40deg) translateY(10px); filter: grayscale(0.8); opacity: 0.6; }
}

/* …and a revisited cleared boss rests in the SAME terminal state, statically. */
.vignette-boss.is-aftermath .vignette-boss-figure {
  animation: none;
  transform: rotate(-40deg) translateY(10px);
  filter: grayscale(0.8);
  opacity: 0.6;
}

/* The guard meter: engraved ward lozenges — biome-accent cores in gold rings; broken = hollow. */
.vignette-guard {
  display: flex;
  gap: 0.45rem;
  justify-content: center;
  margin-top: 0.5rem;
}

.vignette-pip {
  width: 22px;
  height: 30px;
}

.vignette-pip-ring {
  fill: none;
  stroke: var(--gold-soft);
  stroke-width: 2;
}

.vignette-pip-core {
  fill: color-mix(in srgb, var(--zone-colour, var(--gold)) 75%, var(--ink));
  transition: opacity 300ms ease;
}

.vignette-pip.is-broken .vignette-pip-ring {
  stroke: rgba(201, 164, 76, 0.25);
}

.vignette-pip.is-broken .vignette-pip-core {
  opacity: 0;
}

.vignette-caption {
  margin: 0.4rem 0 0;
  text-align: center;
  font-family: var(--serif);
  font-style: italic;
  color: var(--ink-dim);
}

.vignette-caption:empty {
  display: none;
}
