/* SVG visual system — temporal charts and flow diagrams.
 * Charts are inline <svg class="viz"> sharing one coordinate space.
 * Strokes use vector-effect so widths stay crisp regardless of scaling. */

.reveal svg.viz {
  display: block;
  margin: 0.4em auto 0;
  width: 64%;
  /* Absolute cap so charts don't grow with the wider slide box. Sized so the
     tallest charts (640×400) leave room for a heading and caption lines
     inside the 700px slide height. */
  max-width: 560px;
  height: auto;
  overflow: visible;
}

.viz .stroke,
.viz .axis,
.viz .locked,
.viz .spot,
.viz .strike,
.viz .outcome,
.viz .flow {
  fill: none;
  stroke-linecap: round;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}

/* Axes — taught once on the forward, then reused. */
.viz .axis {
  stroke: var(--line);
  stroke-width: 1.5;
}
.viz .axis-label {
  fill: var(--muted);
  font-size: 18px;
  font-family: inherit;
}
.viz .tick-label {
  fill: var(--muted);
  font-size: 15px;
}

/* ---- Temporal chart lines (X = time, Y = price) ----
 * The one chart type for the whole farmer staircase. */

/* The locked price: a flat, solid, highlighted horizontal line — the
 * forward/strike. The certainty the instrument buys. */
.viz .locked {
  stroke: var(--accent);
  stroke-width: 3;
}

/* The spot price: a dashed line wandering over the season. */
.viz .spot {
  stroke: var(--line);
  stroke-width: 2.5;
  stroke-dasharray: 5 6;
}

/* The strike: a flat, soft reference line — the price the option lets the farmer sell
 * at. Subordinate to the outcome line so the lift-off reads as the action. */
.viz .strike {
  stroke: var(--accent-soft);
  stroke-width: 2.5;
}

/* The option's outcome line: what the farmer actually walks away with. The
 * star of the option slides (the deck's active accent) — it lifts off the flat
 * at the strike-crossing. */
.viz .outcome {
  stroke: var(--accent);
  stroke-width: 3.5;
}

/* The floor continued: where a forward would have kept them. The outcome line
 * peels up and away from this ghost at the strike-crossing. */
.viz .outcome.ghost {
  stroke: var(--line);
  stroke-width: 2.5;
  stroke-dasharray: 2 6;
  opacity: 0.7;
}

/* Multi-world reveals: a dot on each line at harvest, and a coloured gap
 * showing what the instrument was worth that season. */
.viz .dot {
  stroke: var(--bg);
  stroke-width: 2;
}
.viz .dot.lock {
  fill: var(--accent);
}
.viz .dot.market {
  fill: var(--muted);
}
.viz .gap {
  fill: none;
  stroke-width: 4;
  stroke-linecap: round;
}
.viz .gap.good {
  stroke: var(--good);
}
.viz .gap.bad {
  stroke: var(--bad);
}
.viz .gap.soft {
  stroke: var(--muted);
}

/* ---- Flow grammar (bridge, swap, CDS): payments as arrows over a timeline.
 * Axes retire here; motion and direction carry the meaning. ---- */
.viz .flow {
  stroke: var(--accent);
  stroke-width: 2.5;
}
.viz .flow.muted {
  stroke: var(--muted);
}
.viz .flow.calm {
  stroke: var(--good);
}

/* Payments as bars marching along a lane. Each amount is a bar's height, so a
 * floating (variable) stream is jagged and a fixed stream is even. The bar-set
 * is duplicated one period over and the group is translated by exactly one
 * period, looping seamlessly — the motion is the message: a stream of payments.
 *
 * Bars are drawn via <use> clones; CSS class selectors don't cross the <use>
 * shadow boundary, so stroke is set here on the wrapper (an inherited property)
 * and flows into the cloned <line>s. (vector-effect doesn't inherit, so bar
 * strokes scale with the chart — fine, and arguably correct for data bars.) */
.viz .stream-anim {
  fill: none;
  stroke: var(--accent);
  stroke-width: 5;
  stroke-linecap: round;
}
.viz .stream-anim.calm {
  stroke: var(--good);
}
.viz .stream-anim.soft {
  stroke: var(--accent-soft);
}

/* CDS capstone: the one big contingent payback (many small premiums, then this).
 * Drawn thick and drawn-on so it visibly shoots across to make the fund whole. */
.viz .payback {
  fill: none;
  stroke: var(--good);
  stroke-width: 6;
  stroke-linecap: round;
}

/* The default event: the borrower collapses. Red, with a one-shot shake. */
.viz .cds-collapse {
  fill: none;
  stroke-width: 2.5;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.viz .fragment.visible .cds-collapse {
  animation: cds-shake 0.5s ease-in-out 1 both;
}
@keyframes cds-shake {
  0%,
  100% {
    transform: translateX(0);
  }
  20% {
    transform: translateX(-5px);
  }
  40% {
    transform: translateX(5px);
  }
  60% {
    transform: translateX(-3px);
  }
  80% {
    transform: translateX(3px);
  }
}
/* Bridge: on the final beat ("a stream to manage") the static monthly arrows
 * give way to a live marching stream — introducing the swap's animation. The
 * `.streaming` class is toggled on the svg via data-anim on the last caption. */
.viz .monthly,
.viz .month-stream {
  transition: opacity var(--t-fade) var(--ease);
}
.viz .month-stream {
  opacity: 0;
}
.viz.streaming .monthly {
  opacity: 0;
}
.viz.streaming .month-stream {
  opacity: 1;
}

.viz .stream-anim.rightward {
  animation: stream-right var(--t-flow) linear infinite;
}
.viz .stream-anim.leftward {
  animation: stream-left var(--t-flow) linear infinite;
}
@keyframes stream-right {
  from {
    transform: translateX(-200px);
  }
  to {
    transform: translateX(0);
  }
}
@keyframes stream-left {
  from {
    transform: translateX(0);
  }
  to {
    transform: translateX(-200px);
  }
}

/* Trade-staging (future slide): two strangers + a clearinghouse referee. */
.viz .person {
  fill: none;
  stroke: var(--ink);
  stroke-width: 2.5;
  stroke-linecap: round;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}
.viz .link {
  fill: none;
  stroke: var(--muted);
  stroke-width: 2;
  vector-effect: non-scaling-stroke;
}
.viz .box {
  fill: none;
  stroke: var(--accent);
  stroke-width: 2.5;
  vector-effect: non-scaling-stroke;
}
.viz .glyph-label {
  fill: var(--ink);
  font-size: 16px;
  font-family: inherit;
  text-anchor: middle;
}
.viz .glyph-label.muted {
  fill: var(--muted);
}

/* Annotation callouts inside a chart. */
.viz .note {
  fill: var(--ink);
  font-size: 16px;
  font-family: inherit;
}
.viz .note.good {
  fill: var(--good);
}
.viz .note.bad {
  fill: var(--bad);
}
.viz .note.muted {
  fill: var(--muted);
}

/* A field of wheat — abundance. Line-art ears in the grain tone. */
.stalk {
  fill: none;
  stroke: var(--grain);
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}

/* Needs the .reveal svg prefix to out-rank the base svg.viz sizing. Kept small:
 * two of these stack vertically, so the pair (plus titles, text, and divider)
 * must stay inside the 700px slide box. */
.reveal svg.viz.payoff {
  width: 92%;
  max-width: 340px;
  margin: 0.1em 0 0;
}
.viz .payoff-line {
  stroke: var(--accent);
  stroke-width: 2;
  fill: none;
  stroke-linecap: round;
  stroke-linejoin: round;
  /* NB: deliberately NO vector-effect: non-scaling-stroke. */
}
.viz .payoff-line.drawable {
  stroke-dasharray: 1.05;
  stroke-dashoffset: 1.05;
}
/* the strike: a soft dashed vertical reference where the buyer's line kinks. */
.viz .ref {
  stroke: var(--line);
  stroke-width: 2;
  stroke-dasharray: 3 5;
  vector-effect: non-scaling-stroke;
  opacity: 0.8;
}
/* this chart's viewBox is half the width of the temporal ones, so dial text
 * down to keep labels visually the same size. */
.viz.payoff .tick-label,
.viz.payoff .axis-label,
.viz.payoff .note {
  font-size: 12px;
}

/* ---- Animation primitives ----
 * Two triggers, same end state:
 *   1. CSS-only: give the SVG element class="fragment" so reveal.js toggles
 *      `.visible` on a click. Preferred — no JS, ordered by reveal naturally.
 *   2. JS-driven: visuals.js toggles `.draw` / `.show` (used where a click on
 *      one element must animate a different one, e.g. swap/CDS sequencing).
 *
 * Draw-on needs no path measurement: set pathLength="1" on the path, so a
 * dasharray/offset of 1 == the whole line. */

/* Multi-world reveals (e.g. forward-worlds): each world's group fades in/out as
 * one. Override reveal's brisk default with the deck's fade timing/easing so
 * every world — not just the first — visibly fades its line + text in and out.
 * Keep `all` (not just opacity) so `visibility` still flips with the fade. */
.viz .fragment.fade-in-then-out {
  transition: all var(--t-fade) var(--ease);
}

/* The option's lift-off — the focal beat. Like .drawable but slower and eased,
 * so the outcome line visibly peels off the flat and rides the spot upward.
 * Same pathLength="1" trick: dashoffset 1 → 0 draws the line on from its start. */
.viz .liftoff {
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
}
.viz .liftoff.fragment.visible {
  transition: stroke-dashoffset var(--t-liftoff) var(--ease);
  stroke-dashoffset: 0;
}
.viz .liftoff.fragment {
  opacity: 1;
  visibility: visible;
}

/* The strike-crossing marker: a dot with a ring that pulses once as the outcome
 * lifts off — drawing the eye to the exact moment the cage opens. */
.viz .crossing-dot {
  fill: var(--accent);
  stroke: var(--bg);
  stroke-width: 2;
}
.viz .crossing-ring {
  fill: none;
  stroke: var(--accent);
  stroke-width: 2;
  transform-box: fill-box;
  transform-origin: center;
  opacity: 0;
}
.viz .fragment.visible .crossing-ring {
  animation: liftoff-pulse 1s var(--ease) 0.25s 1 both;
}
@keyframes liftoff-pulse {
  0% {
    transform: scale(1);
    opacity: 0.9;
  }
  100% {
    transform: scale(3);
    opacity: 0;
  }
}

.viz .drawable {
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
}
.viz .drawable.draw,
.viz .drawable.fragment.visible {
  transition: stroke-dashoffset var(--t-draw) var(--ease);
  stroke-dashoffset: 0;
}

/* reveal.js hides un-shown fragments via opacity; neutralize that for SVG
 * paths we draw ourselves so the draw (not a fade) is what the eye reads. */
.viz .drawable.fragment {
  opacity: 1;
  visibility: visible;
}
