// hero.amx — §03 the stack.
//
// Circles ride on circles. Each added harmonic flattens the tops and steepens
// the sides, and the equation underneath fills out term by term.
//
// The three `g*` gates are this scene's single source of truth. One `always`
// block ramps each 0 → 1 on its beat, and the epicycle chain, the plotted
// curve and the equation's reveal all read those same numbers — the wave
// cannot drift out of step with the circles because there is one schedule here,
// not three hand-copied copies of it. The chain is a loop over the harmonics
// rather than four hand-unrolled `let c2x/c2y/…` blocks, and the plot dot is
// placed with `Graph.map()` at the plot's left edge (see fourier.amx for why
// the tie-line comes out horizontal). The wave scrolls — newest sample under
// the pen, history flowing right — so unlike §02's first draft there is no
// leading edge for a marker to fall out of step with.

import "../../demos/lib/theme.amx"
import "fourier.amx" as geo

config { resolution: (1280, 720) }

# Hero
config { colorscheme: "ink", dynamic_layout: true, duration: 19.4 }

// Harmonic identity: one hue per odd k, reused by the chain, the equation row
// and the spectrum in §04 so the reader can track k=5 across three figures.
let cols = {accent.info, accent.success, accent.primary, accent.danger}

// The pen's angle, published by the always block and read by the plot closure,
// so the wave is the chain's own history rather than a second copy of the clock.
let th_now = 0.0

let g3 = 0.0
let g5 = 0.0
let g7 = 0.0

import "../../demos/lib/ambience.amx"

bg: Ambience, tint: accent.info, seed: 80

hero_hub: Ellipse, size: (9, 9), color: text.muted, at: (geo.pivot_x, geo.cy)

for k, i in {1, 3, 5, 7} {
  ring[i]: Ellipse, size: (2 * geo.R / k, 2 * geo.R / k), stroke: cols[i],
    fill_opacity: 0, stroke_width: 2, at: (geo.pivot_x, geo.cy)  // scaled per frame, see always
  arm[i]: Line, from: (geo.pivot_x, geo.cy), to: (geo.pivot_x, geo.cy),
    stroke: cols[i], stroke_width: 1.5, opacity: 0.5
}

tip: Ellipse, size: (16, 16), color: accent.primary, at: (geo.pivot_x + geo.R, geo.cy)

hero_graph: Graph, at: (geo.graph_cx, geo.cy), size: (geo.graph_w, 2 * geo.y_hi * pi * geo.R / 4),
  x_domain: (0, geo.span), y_domain: (geo.y_lo, geo.y_hi), tick_labels: "y" {
    hi: PlotCurve, kind: "cartesian", func: (x) => 1,
      stroke: stroke.default, stroke_width: 1.5, dash_pattern: {9, 7}
    lo: PlotCurve, kind: "cartesian", func: (x) => -1,
      stroke: stroke.default, stroke_width: 1.5, dash_pattern: {9, 7}
    hero_wave: PlotCurve, kind: "cartesian",
      func: (x) => (4 / pi) * (sin(th_now - x) + g3 * sin(3 * (th_now - x)) / 3 + g5 * sin(5 * (th_now - x)) / 5 + g7 * sin(7 * (th_now - x)) / 7),
      color: accent.primary, stroke_width: 3.5
}

// Pinned to the plot's left edge, where the wave's newest sample lives.
wave_dot: Ellipse, size: (14, 14), color: accent.primary,
  at: (geo.graph_cx - geo.graph_w / 2, geo.cy)

hero_link: Line, from: (geo.pivot_x + geo.R, geo.cy), to: (geo.graph_cx - geo.graph_w / 2, geo.cy),
  stroke: accent.secondary, stroke_width: 1.25, opacity: 0.26

// Each term carries its harmonic's hue and appears on the beat its circle is
// added. An opacity-0 child still holds its layout slot, so the row is spaced
// for the finished equation from the start and never jumps as it fills out.
eq_row: Row, anchor: scene.bottom, offset: (0, -64), gap: 12, align: "center" {
  eq_1: Text, text: "y(x) = sin x", font_size: 25, color: accent.info
  for k, i in {3, 5, 7} {
    eqt[i]: Text, text: format("+ sin {:.0}x/{:.0}", k, k), font_size: 25,
      color: cols[i + 1], opacity: 0
  }
}

#0.15s
fade-in hero_graph [550ms, ease: expo-out]
fade-in ring__0, arm__0, hero_hub [450ms, ease: expo-out]

#0.95s
stagger [80ms] {
  fade-in tip [400ms, ease: expo-out]
  fade-in wave_dot [400ms, ease: expo-out]
  fade-in hero_link [400ms, ease: expo-out]
  fade-in eq_1 [400ms, ease: expo-out]
}

#1.4s
fade-in hi, lo [400ms, ease: expo-out]
fade-in hero_wave [300ms, ease: expo-out]

// The beats here and the `g*` ramps in the always block are the same three
// numbers on purpose; a new harmonic means one line in each place, not four.
#4s
fade-in ring__1, arm__1, eqt__0 [450ms, ease: expo-out]

#9s
fade-in ring__2, arm__2, eqt__1 [450ms, ease: expo-out]

#14s
fade-in ring__3, arm__3, eqt__2 [450ms, ease: expo-out]

always {
  let s3 = clamp((t - 4.0) / 1.5, 0.0, 1.0)
  let s5 = clamp((t - 9.0) / 1.5, 0.0, 1.0)
  let s7 = clamp((t - 14.0) / 1.5, 0.0, 1.0)
  // Smoothstep, not the raw ramp. A linear clamp has a velocity kink at both
  // ends, and the wave morphs at exactly that rate, so each gate started and
  // stopped its reshape with a visible twitch.
  g3 = s3 * s3 * (3 - 2 * s3)
  g5 = s5 * s5 * (3 - 2 * s5)
  g7 = s7 * s7 * (3 - 2 * s7)

  let u = clamp((t - 1.4) / 16.0, 0.0, 1.0)
  th_now = geo.span * u
  let th = th_now
  let wts = {1.0, g3, g5, g7}
  let rad = {geo.R, geo.R / 3, geo.R / 5, geo.R / 7}

  // Walk the chain: circle i is centred where harmonic i-1's vector ended, and
  // its own weighted radius reaches the next centre. `sum` is the same partial
  // sum the plotted function evaluates at x = th.
  let cx = geo.pivot_x
  let cy = geo.cy
  let sum = 0.0
  for k, i in {1, 3, 5, 7} {
    ring[i].at = (cx, cy)
    arm[i].from = (cx, cy)
    let reach = wts[i] * rad[i]
    let nx = cx + reach * cos(k * th)
    let ny = cy - reach * sin(k * th)
    arm[i].to = (nx, ny)
    // The circle is scaled to its own weight, so its rim passes through the
    // joint the arm reaches: radius (R/k)·wts[i] = reach, exactly. Declaring a
    // constant radius while the arm ramped up from zero left the joint sitting
    // at a fraction of the way to a full-size circle for the whole 1.5s.
    // A multiplier says "this circle, shrunk by its weight" without restating
    // the radius the declaration above already carries.
    ring[i].scale = wts[i]
    let sum = sum + wts[i] * sin(k * th) / k
    let cx = nx
    let cy = ny
  }
  tip.at = (cx, cy)

  // Bound to names because a path-call (`hero_graph.map(…)`) rejects binary
  // operators in its argument list — see docs/roadmap.md.
  let dx = 0.0
  let dy = (4 / pi) * sum
  let dp = hero_graph.map(dx, dy)
  wave_dot.at = dp
  hero_link.from = (cx, cy)
  hero_link.to = dp
}


