");
+ for m in modules {
+ out.push_str(&format!(
+ "",
+ m.name, m.name
+ ));
+ }
+ out.push_str("
");
+ }
+ for m in modules {
+ out.push_str(&format!(
+ "\
+ {}\
+ {}
",
+ m.name, m.name, m.description
+ ));
+ // Statements first, each with the steps that chain onto it.
+ for statement in &m.statements {
+ out.push_str(&format!(
+ "
statement\
+ {}",
+ statement.name
+ ));
+ for step in &statement.steps {
+ out.push_str(&format!("{step}"));
+ }
+ out.push_str("
");
+ }
+ for (key, class, items) in [
+ ("conditions", "me-chip", &m.conditions),
+ ("effects", "me-chip me-chip-effect", &m.effects),
+ ("nouns", "me-chip me-chip-noun", &m.nouns),
+ ("modes", "me-chip me-chip-mode", &m.modes),
+ ("requires", "me-chip me-chip-req", &m.requires),
+ ] {
+ if items.is_empty() {
+ continue;
+ }
+ out.push_str(&format!(
+ "
{key}"
+ ));
+ for item in items.iter() {
+ out.push_str(&format!("{item}"));
+ }
+ out.push_str("
");
+ }
+ out.push_str("
");
+ }
+ out
+}
+
+/// The landing dashboard: a KPI row that is also the navigation, plus one
+/// part-to-whole bar of the mission's statements. Every tile opens its
+/// section — including Modules, the way into the module editor — so the panel
+/// can open fully collapsed and still show the shape of the mission.
+///
+/// Marks follow the viz rules: two categorical slots (blue/orange, validated
+/// against both terminals' dark surfaces), a 2px surface gap between
+/// segments, rounded ends, and identity carried by the dots — labels and
+/// values stay in text ink, never in a series color.
+fn summary(triggers: usize, spawns: usize, objectives: usize, named: usize, modules: usize) -> String {
+ let total = triggers + spawns;
+ let pct = |n: usize| if total == 0 { 0.0 } else { (n as f64) * 100.0 / (total as f64) };
+ let tile = |slot: &str, label: &str, value: usize, section: &str| {
+ let dot = if slot.is_empty() {
+ String::new()
+ } else {
+ format!("