/* global window, React */

function Topbar({ search, onSearch, theme, onTheme, onToggleTweaks, onSelectQ, selectedQ, onSelectNode, onSelectEndpoint, onApiAccess, showTweaks }) {
  const { NODES, ENDPOINTS, QUESTIONS, ENTITY_KINDS } = window.CIQ;
  const [open, setOpen] = React.useState(false);
  const wrapRef = React.useRef(null);
  const inputRef = React.useRef(null);

  const MODULE_COLORS = {
    PartnerIQ:    "#a78bfa",
    CompeteIQ:    "#f59e0b",
    WhiteSpaceIQ: "#58efe0",
  };

  // Universal search — entities, endpoints, and questions in one panel.
  const q = (search || "").trim().toLowerCase();
  const matchedNodes = q
    ? NODES.filter(n => n.name.toLowerCase().includes(q) || n.id.toLowerCase().includes(q)).slice(0, 8)
    : [];
  const matchedEndpoints = q
    ? ENDPOINTS.filter(e =>
        (e.path || "").toLowerCase().includes(q) ||
        (e.summary || "").toLowerCase().includes(q) ||
        (e.module || "").toLowerCase().includes(q)).slice(0, 6)
    : [];
  const matchedQuestions = q
    ? QUESTIONS.filter(it =>
        (it.q || "").toLowerCase().includes(q) ||
        (it.group || "").toLowerCase().includes(q) ||
        (it.module || "").toLowerCase().includes(q)).slice(0, 8)
    : QUESTIONS;
  const totalMatches = matchedNodes.length + matchedEndpoints.length + matchedQuestions.length;

  React.useEffect(() => {
    const onDoc = (e) => { if (wrapRef.current && !wrapRef.current.contains(e.target)) setOpen(false); };
    document.addEventListener("mousedown", onDoc);
    return () => document.removeEventListener("mousedown", onDoc);
  }, []);

  React.useEffect(() => {
    const onKey = (e) => {
      if ((e.metaKey || e.ctrlKey) && e.key === "k") {
        e.preventDefault();
        inputRef.current?.focus();
        setOpen(true);
      }
      if (e.key === "Escape") setOpen(false);
    };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, []);

  return React.createElement("header", { className: "topbar glass-2" },
    React.createElement("div", { className: "brand", style: { display: "flex", alignItems: "center", gap: 10 } },
      window.MarkClassic && React.createElement(window.MarkClassic, { size: 22, glow: true }),
      React.createElement("div", { className: "ciq-wm" },
        React.createElement("span", { className: "ciq-wm-collab" }, "collab"),
        React.createElement("span", { className: "ciq-wm-iq" }, "IQ")
      ),
      // Vertical divider + tagline. Hidden on mobile (<768px).
      React.createElement("div", {
        className: "topbar-tagline",
        style: {
          display: "flex", alignItems: "center", gap: 10,
        },
      },
        React.createElement("div", {
          style: {
            width: 1, height: 18,
            background: "var(--hairline-strong)",
          },
        }),
        React.createElement("div", {
          style: {
            fontSize: 11.5,
            fontWeight: 500,
            color: "var(--text-3)",
            letterSpacing: "0.005em",
            whiteSpace: "nowrap",
          },
        }, "Knowledge graph for drug discovery")
      )
    ),
    React.createElement("div", { className: "search", ref: wrapRef, style: { position: "relative" } },
      React.createElement(window.Icons.Search, { size: 13 }),
      React.createElement("input", {
        ref: inputRef,
        type: "text",
        placeholder: "Search nodes, endpoints, questions…",
        value: search,
        onChange: e => { onSearch(e.target.value); setOpen(true); },
        onFocus: () => setOpen(true),
      }),
      React.createElement("span", { className: "kbd" }, "⌘K"),

      open && React.createElement("div", {
        className: "glass-3 search-results",
        style: {
          position: "absolute", top: "calc(100% + 8px)", left: 0, right: 0,
          borderRadius: 12, padding: 8, zIndex: 60,
          maxHeight: "min(560px, 70vh)", overflow: "hidden",
          display: "flex", flexDirection: "column",
          background: "var(--glass-solid)",
          backdropFilter: "blur(40px) saturate(180%)",
          WebkitBackdropFilter: "blur(40px) saturate(180%)",
          boxShadow: "0 24px 60px rgba(0,0,0,0.55), 0 0 0 1px var(--glass-border-strong), inset 0 1px 0 var(--glass-inset-top)",
        }
      },
        React.createElement("div", { className: "scroll", style: { overflowY: "auto", flex: 1 } },
          q && totalMatches === 0 && React.createElement("div", {
            style: { padding: 20, textAlign: "center", fontSize: 12, color: "var(--text-3)" } }, "No matches."),

          // ── Nodes / kinds ──
          matchedNodes.length > 0 && React.createElement("div", { style: { padding: "4px 4px 6px" } },
            React.createElement("div", { className: "search-group-label", style: {
              fontSize: 10, fontWeight: 600, letterSpacing: "0.08em", textTransform: "uppercase",
              color: "var(--text-3)", padding: "6px 8px 4px",
            } }, "Entities · " + matchedNodes.length),
            matchedNodes.map(n => {
              const kc = ENTITY_KINDS[n.kind];
              return React.createElement("button", {
                key: n.id,
                onClick: () => { onSelectNode && onSelectNode(n.id); setOpen(false); onSearch(""); },
                style: {
                  display: "flex", alignItems: "center", gap: 10, width: "100%", textAlign: "left",
                  padding: "8px 10px", borderRadius: 8, background: "transparent",
                  border: "1px solid transparent", cursor: "pointer",
                }
              },
                React.createElement("span", { style: {
                  width: 8, height: 8, borderRadius: "50%",
                  background: kc.color, flexShrink: 0,
                } }),
                React.createElement("span", { style: { fontSize: 12.5, color: "var(--text-1)", flex: 1 } }, n.name),
                React.createElement("span", { style: {
                  fontSize: 9.5, padding: "1px 6px", borderRadius: 999,
                  color: kc.color,
                  background: `color-mix(in oklab, ${kc.color} 12%, transparent)`,
                  border: `1px solid color-mix(in oklab, ${kc.color} 28%, transparent)`,
                } }, kc.label)
              );
            })
          ),

          // ── Endpoints ──
          matchedEndpoints.length > 0 && React.createElement("div", { style: { padding: "4px 4px 6px" } },
            React.createElement("div", { style: {
              fontSize: 10, fontWeight: 600, letterSpacing: "0.08em", textTransform: "uppercase",
              color: "var(--text-3)", padding: "6px 8px 4px",
            } }, "API · " + matchedEndpoints.length),
            matchedEndpoints.map(ep => React.createElement("button", {
              key: ep.id,
              onClick: () => { onSelectEndpoint && onSelectEndpoint(ep); setOpen(false); onSearch(""); },
              style: {
                display: "flex", alignItems: "center", gap: 10, width: "100%", textAlign: "left",
                padding: "8px 10px", borderRadius: 8, background: "transparent",
                border: "1px solid transparent", cursor: "pointer",
              }
            },
              React.createElement("span", { className: `api-method ${ep.method.toLowerCase()}`, style: { flexShrink: 0 } }, ep.method),
              React.createElement("span", { className: "mono", style: { fontSize: 11.5, color: "var(--text-1)", flex: 1, fontFamily: "var(--font-mono)" } }, ep.path),
              React.createElement("span", { style: {
                fontSize: 9.5, padding: "1px 6px", borderRadius: 999,
                color: MODULE_COLORS[ep.module] || "var(--text-3)",
                background: `color-mix(in oklab, ${MODULE_COLORS[ep.module] || "var(--text-3)"} 12%, transparent)`,
                border: `1px solid color-mix(in oklab, ${MODULE_COLORS[ep.module] || "var(--text-3)"} 28%, transparent)`,
              } }, ep.module)
            ))
          ),

          // ── Questions ──
          matchedQuestions.length > 0 && React.createElement("div", { style: { padding: "4px 4px 6px" } },
            React.createElement("div", { style: {
              fontSize: 10, fontWeight: 600, letterSpacing: "0.08em", textTransform: "uppercase",
              color: "var(--text-3)", padding: "6px 8px 4px",
            } }, "Questions · " + matchedQuestions.length),
            matchedQuestions.map(it => React.createElement("button", {
              key: it.id,
              onClick: () => { onSelectQ && onSelectQ(it); setOpen(false); },
              "data-active": selectedQ === it.id ? "true" : "false",
              style: {
                display: "block", width: "100%", textAlign: "left",
                padding: "8px 10px", borderRadius: 8,
                background: selectedQ === it.id ? "var(--accent-tint)" : "transparent",
                border: "1px solid " + (selectedQ === it.id ? "var(--glass-border-strong)" : "transparent"),
                cursor: "pointer",
              }
            },
              React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 6, marginBottom: 3 } },
                React.createElement("span", { style: {
                  fontSize: 9.5, padding: "1px 6px", borderRadius: 999,
                  color: MODULE_COLORS[it.module] || "var(--text-3)",
                  background: `color-mix(in oklab, ${MODULE_COLORS[it.module] || "var(--text-3)"} 12%, transparent)`,
                  border: `1px solid color-mix(in oklab, ${MODULE_COLORS[it.module] || "var(--text-3)"} 28%, transparent)`,
                  fontWeight: 600, letterSpacing: "0.04em", textTransform: "uppercase",
                } }, it.module),
                React.createElement("span", { style: {
                  fontSize: 9.5, color: "var(--text-3)", textTransform: "uppercase", letterSpacing: "0.05em",
                } }, it.group)
              ),
              React.createElement("div", { style: { fontSize: 12.5, color: "var(--text-1)", lineHeight: 1.4 } }, it.q)
            ))
          )
        )
      )
    ),
    React.createElement("div", { className: "topbar-right" },
      React.createElement("button", {
        className: "icon-btn theme-toggle",
        onClick: onTheme,
        title: theme === "dark" ? "Switch to light mode" : "Switch to dark mode",
        "aria-label": theme === "dark" ? "Switch to light mode" : "Switch to dark mode",
        "aria-pressed": theme === "light" ? "true" : "false",
        "data-theme": theme,
      },
        React.createElement(theme === "dark" ? window.Icons.Sun : window.Icons.Moon, null)
      ),
      // Tweaks button — only visible when the URL carries ?tweaks=1.
      // Buyers don't see Glass Blur / Glass Saturation; designers do.
      showTweaks && React.createElement("button", { className: "icon-btn", onClick: onToggleTweaks, title: "Tweaks" },
        React.createElement(window.Icons.Sparkle, null)
      ),
      // ── Get API access — primary CTA, theme-aware text color ──
      React.createElement("button", {
        type: "button",
        className: "topbar-cta",
        onClick: () => {
          if (window.CIQ_CONFIG && window.CIQ_CONFIG.track) window.CIQ_CONFIG.track("api_access_clicked", { source: "topbar" });
          onApiAccess && onApiAccess();
        },
        title: "Request API credentials",
        style: {
          marginLeft: 8,
          padding: "7px 14px",
          borderRadius: 999,
          fontSize: 12,
          fontWeight: 600,
          letterSpacing: "0.005em",
          color: "var(--text-on-accent)",
          background: "var(--accent-bright)",
          border: "1px solid color-mix(in oklab, var(--accent-bright) 80%, transparent)",
          // Inset uses theme-aware --glass-inset-top so the highlight reads
          // on both bright-cyan (dark mode) and dark-teal (light mode) buttons.
          boxShadow: "0 6px 18px color-mix(in oklab, var(--accent-bright) 30%, transparent), inset 0 1px 0 var(--glass-inset-top)",
          cursor: "pointer",
          display: "inline-flex",
          alignItems: "center",
          gap: 6,
          whiteSpace: "nowrap",
        },
      },
        React.createElement(window.Icons.Code, { size: 13 }),
        React.createElement("span", { className: "topbar-cta-label" }, "Get API access")
      )
    )
  );
}

function Footer({ stats }) {
  return React.createElement("footer", { className: "footer glass-2" },
    // Footer brand block — small mark + wordmark using the .ciq-wm
    // class so the IQ picks up the brand accent (teal) in both themes
    // instead of rendering black-on-white in light mode.
    React.createElement("div", {
      style: { display: "flex", alignItems: "center", gap: 8, paddingRight: 6 },
    },
      window.MarkClassic && React.createElement(window.MarkClassic, { size: 16, glow: false }),
      React.createElement("div", { className: "ciq-wm", style: { fontSize: 13 } },
        React.createElement("span", { className: "ciq-wm-collab" }, "collab"),
        React.createElement("span", { className: "ciq-wm-iq" }, "IQ")
      )
    ),
    React.createElement("span", {
      style: { color: "var(--text-4)", margin: "0 4px" },
    }, "·"),
    React.createElement("div", { className: "footer-stat footer-mobile-hide" },
      React.createElement("span", { className: "value live" }, "live")
    ),
    React.createElement("div", { className: "footer-stat" },
      React.createElement("span", { className: "label" }, "Nodes"),
      React.createElement("span", { className: "value" }, stats.nodes)
    ),
    React.createElement("div", { className: "footer-stat" },
      React.createElement("span", { className: "label" }, "Edges"),
      React.createElement("span", { className: "value" }, stats.edges)
    ),
    React.createElement("div", { className: "footer-stat" },
      React.createElement("span", { className: "label" }, "Sources"),
      React.createElement("span", { className: "value" }, "25+")
    ),
    React.createElement("div", { className: "footer-right" },
      React.createElement("a", {
        className: "footer-link footer-mobile-hide",
        href: (window.CIQ_CONFIG && window.CIQ_CONFIG.docsUrl) || "#",
        target: "_blank", rel: "noopener noreferrer",
        onClick: () => window.CIQ_CONFIG && window.CIQ_CONFIG.track && window.CIQ_CONFIG.track("footer_docs_clicked"),
      }, "Docs"),
      React.createElement("a", {
        className: "footer-link footer-mobile-hide",
        href: (window.CIQ_CONFIG && window.CIQ_CONFIG.statusUrl) || "#",
        target: "_blank", rel: "noopener noreferrer",
        onClick: () => window.CIQ_CONFIG && window.CIQ_CONFIG.track && window.CIQ_CONFIG.track("footer_status_clicked"),
      }, "Status"),
      React.createElement("span", { className: "footer-divider footer-mobile-hide", style: { color: "var(--text-4)", margin: "0 6px" } }, "·"),
      React.createElement("a", {
        className: "footer-link",
        href: (window.CIQ_CONFIG && window.CIQ_CONFIG.dashboardUrl) || "#",
        target: "_blank", rel: "noopener noreferrer",
        title: "Sign in to the CollabIQ dashboard",
        style: { color: "var(--text-2)", whiteSpace: "nowrap" },
        onClick: () => window.CIQ_CONFIG && window.CIQ_CONFIG.track && window.CIQ_CONFIG.track("footer_dashboard_clicked"),
      }, React.createElement("span", { className: "footer-mobile-hide" }, "Already a customer? "), "Dashboard →")
    )
  );
}

function FilterBar({
  activePhase, onPhase,
  hasFilterDeviation, onResetFilters,
}) {
  const { PIPELINE_STAGES } = window.CIQ;
  // Top of canvas now hosts ONLY the pipeline strip + (conditional) reset.
  // Edge tier toggles and confidence slider moved to the bottom of the
  // canvas as floating OG-style pills (see CanvasFilters component).
  return React.createElement("div", { className: "filter-bar glass-3", role: "region", "aria-label": "Pipeline stage filter" },
    React.createElement("h2", { className: "filter-bar-title" }, "Pipeline stages"),

    React.createElement("div", { className: "filter-pipeline" },
      React.createElement("span", { className: "evidence-segment-label" }, "Pipeline"),
      PIPELINE_STAGES.map(p => React.createElement("button", {
        key: p.id,
        type: "button",
        className: "filter-pipeline-stage",
        "aria-pressed": activePhase === p.id ? "true" : "false",
        "data-active": activePhase === p.id ? "true" : "false",
        onClick: () => onPhase && onPhase(activePhase === p.id ? null : p.id),
        style: { "--phase-color": p.color },
        title: `${p.label} · ${p.years}`,
      }, p.label))
    ),

    hasFilterDeviation && React.createElement(React.Fragment, null,
      React.createElement("div", { className: "filter-divider" }),
      React.createElement("button", {
        type: "button",
        onClick: onResetFilters,
        title: "Reset all filters",
        style: {
          display: "inline-flex",
          alignItems: "center",
          gap: 5,
          padding: "5px 10px",
          fontSize: 10.5,
          fontWeight: 600,
          letterSpacing: "0.04em",
          textTransform: "uppercase",
          color: "var(--text-2)",
          background: "transparent",
          border: "1px solid var(--hairline)",
          borderRadius: 999,
          cursor: "pointer",
          whiteSpace: "nowrap",
        },
      },
        React.createElement(window.Icons.Close, { size: 10 }),
        "Reset"
      )
    )
  );
}

// ── CanvasFilters — bottom-of-canvas floating filter island ──
// Restored to the OG_schema.html pattern: edge tier on/off toggles
// (left) and confidence scrubber (center). Anchored INSIDE the
// canvas so it moves with the graph as the viewport resizes.
function CanvasFilters({
  visibleTiers, onToggleTier,
  confidence, onConfidence,
  visibleEdgeCount, totalEdgeCount,
}) {
  const TIERS = [
    {
      k: "direct", label: "Direct", color: "var(--edge-direct)",
      explain: "Observed in the source data — gold-tier evidence (DrugBank, ClinVar, UBERON, ChEMBL, etc.). High-confidence relationships that already exist in the graph.",
    },
    {
      k: "inferred", label: "Inferred", color: "var(--edge-inferred)",
      explain: "Derived from NLP, weighted scoring, or cross-source aggregation. Lower-confidence than direct, but still observed evidence.",
    },
    {
      k: "predicted", label: "Predicted", color: "var(--edge-predicted)",
      explain: "Link-prediction surface — every relationship the ML model could surface, not just what's currently observed. The differentiator: every kind-pair has a prediction score.",
    },
  ];

  return React.createElement("div", { className: "canvas-filters", role: "region", "aria-label": "Edge tier and confidence filters" },
    // Edge tier toggles — iOS-style on/off switches
    React.createElement("div", { className: "cf-toggles" },
      TIERS.map(row => {
        const on = !visibleTiers || visibleTiers[row.k] !== false;
        const action = on ? `Click to hide ${row.label.toLowerCase()} edges.` : `Click to show ${row.label.toLowerCase()} edges.`;
        return React.createElement("button", {
          key: row.k,
          type: "button",
          className: "cf-toggle-row",
          "data-on": on ? "true" : "false",
          onClick: () => onToggleTier && onToggleTier(row.k),
          title: row.explain + "\n\n" + action,
          "aria-label": `${row.label}: ${row.explain}`,
        },
          React.createElement("span", { className: "cf-toggle-switch", "data-on": on ? "true" : "false" }),
          React.createElement("span", { className: "cf-toggle-label" },
            React.createElement("span", { className: "cf-toggle-dot", style: { background: row.color } }),
            row.label
          )
        );
      })
    ),

    React.createElement("div", { className: "cf-divider" }),

    // Confidence slider with live edge count
    React.createElement("div", { className: "cf-confidence" },
      React.createElement("div", { className: "cf-confidence-head" },
        React.createElement("span", { className: "cf-confidence-label" }, "Confidence"),
        React.createElement("span", { className: "cf-confidence-count" },
          (visibleEdgeCount != null ? visibleEdgeCount : 0).toLocaleString(),
          " / ",
          (totalEdgeCount != null ? totalEdgeCount : 0).toLocaleString(),
          " edges"
        )
      ),
      React.createElement("div", { className: "cf-confidence-row" },
        React.createElement("input", {
          type: "range",
          min: 0, max: 100, step: 1,
          value: Math.round((confidence || 0) * 100),
          onChange: (ev) => onConfidence && onConfidence(+ev.target.value / 100),
          className: "confidence-slider cf-slider",
          "aria-label": "Confidence threshold",
        }),
        React.createElement("span", { className: "cf-confidence-value" }, (confidence || 0).toFixed(2))
      )
    )
  );
}

function CanvasLegend({ selectedKind, onSelectKind, visibleTiers, onToggleTier }) {
  const { ENTITY_KINDS } = window.CIQ;
  const [collapsed, setCollapsed] = React.useState(false);

  const rowBaseStyle = {
    width: "100%",
    background: "transparent",
    border: "1px solid transparent",
    borderRadius: 6,
    padding: "3px 6px",
    margin: "1px 0",
    cursor: "pointer",
    color: "inherit",
    font: "inherit",
    textAlign: "left",
    transition: "opacity 120ms, background 120ms, border-color 120ms",
    outline: "none",
  };
  const activeRowStyle = {
    background: "var(--accent-tint)",
    border: "1px solid var(--glass-border-strong)",
  };

  const EDGE_TIERS = [
    { k: "direct",    label: "Direct",    cls: "" },
    { k: "inferred",  label: "Inferred",  cls: "dashed" },
    { k: "predicted", label: "Predicted", cls: "dotted" },
  ];

  return React.createElement("div", { className: "legend glass-2" },
    React.createElement("h3", null,
      "Legend",
      React.createElement("button", {
        className: "legend-toggle",
        onClick: () => setCollapsed(!collapsed),
        title: collapsed ? "Expand" : "Collapse",
      }, collapsed ? "+" : "−")
    ),
    !collapsed && React.createElement(React.Fragment, null,
      React.createElement("div", { className: "legend-group" },
        React.createElement("div", { className: "legend-group-title" }, "Entities · click to filter"),
        Object.entries(ENTITY_KINDS).map(([k, v]) => {
          const active = selectedKind === k;
          return React.createElement("button", {
            key: k,
            type: "button",
            className: "legend-row legend-row-interactive",
            "data-active": active ? "true" : "false",
            onClick: () => onSelectKind && onSelectKind(active ? null : k),
            title: active ? `Clear ${v.label} filter` : `Filter to ${v.label}`,
            style: { ...rowBaseStyle, ...(active ? activeRowStyle : {}), display: "flex", alignItems: "center", gap: 8 },
          },
            React.createElement("div", { className: "legend-swatch dot", style: { background: v.color } }),
            React.createElement("span", null, v.label)
          );
        })
      ),
      // Edge tiers — interactive toggles. Click to hide/show that tier.
      // Inactive rows dim to ~40% opacity; the line preview itself ghosts.
      React.createElement("div", { className: "legend-group" },
        React.createElement("div", { className: "legend-group-title" }, "Edges · click to toggle"),
        EDGE_TIERS.map(row => {
          const on = !visibleTiers || visibleTiers[row.k] !== false;
          return React.createElement("button", {
            key: row.k,
            type: "button",
            className: "legend-row legend-row-interactive",
            "data-active": on ? "true" : "false",
            onClick: () => onToggleTier && onToggleTier(row.k),
            title: on ? `Hide ${row.label.toLowerCase()} edges` : `Show ${row.label.toLowerCase()} edges`,
            style: {
              ...rowBaseStyle,
              display: "flex", alignItems: "center", gap: 8,
              opacity: on ? 1 : 0.4,
            },
          },
            React.createElement("div", {
              className: "legend-edge" + (row.cls ? " " + row.cls : ""),
              style: row.k === "direct" ? { background: "var(--edge-direct)" } : {},
            }),
            React.createElement("span", { style: { fontSize: 11 } }, row.label)
          );
        }),
        // Recovery hint when the user has toggled all three tiers off and
        // the canvas is rendering zero edges.
        visibleTiers &&
        !visibleTiers.direct && !visibleTiers.inferred && !visibleTiers.predicted &&
        React.createElement("div", {
          style: {
            marginTop: 8,
            padding: "7px 9px",
            borderRadius: 6,
            background: "color-mix(in oklab, var(--warning, #f59e0b) 14%, transparent)",
            border: "1px solid color-mix(in oklab, var(--warning, #f59e0b) 30%, transparent)",
            fontSize: 10.5,
            color: "var(--text-2)",
            lineHeight: 1.4,
          },
        }, "All edge tiers hidden. Toggle one back on to see the graph.")
      )
    )
  );
}

function PipelineStrip({ activePhase, onPhase }) {
  const { PIPELINE_STAGES } = window.CIQ;
  return React.createElement("div", { className: "pipeline glass-2" },
    React.createElement("div", { className: "pipeline-label" }, "Drug pipeline"),
    React.createElement("div", { className: "pipeline-track" },
      PIPELINE_STAGES.map(p => React.createElement("button", {
        key: p.id,
        className: "pipeline-stage",
        "data-active": activePhase === p.id ? "true" : "false",
        onClick: () => onPhase(activePhase === p.id ? null : p.id),
        style: { "--phase-color": p.color },
      },
        React.createElement("div", { className: "pipeline-stage-name" }, p.label)
      ))
    )
  );
}

window.Topbar = Topbar;
window.Footer = Footer;
window.FilterBar = FilterBar;
window.CanvasFilters = CanvasFilters;
window.CanvasLegend = CanvasLegend;
window.PipelineStrip = PipelineStrip;
