(function(){
/* FORGE — Act-3 Mittelstand-tenant screens (M-S15..M-S19).
   Babel-standalone in-browser React. NO ES imports / exports. Globals expected:
     React, getActiveTenant, useActiveTenant, navigate,
     registerMittelstandRoute (from mittelstand/registry-index.jsx),
     MSCard, MSPill, MSBtn (from mittelstand/cross-cutting.jsx — used
       defensively; we fall back if cross-cutting hasn't loaded yet).

   Aesthetic: chassis ink + steel + graphite ruling. Cool oklch ramp at hue 250.
   Mono is privileged — every µm tolerance, day count, € cost, and part
   number renders mono with explicit units. ISO 9001 / IATF 16949 refs.

   Tenant: Mittelstand-pilot Werkzeugbau · Stuttgart · MAHLE 24-mill MTL-220
   Rev D program. Hero ECO is ECO-MS-0017 — granite flatness ±5 µm → ±3 µm.
   Sub-suppliers: Heidenhain, GMN, Kessler, Bosch Rexroth, Siemens, DMG Mori. */

const { useState: uSM3, useMemo: uMM3, useEffect: uEM3 } = React;

// ───────────────────────────────────────────────────── ink atoms (cool / hue 250)
const M3 = {
  BG:        "oklch(0.97  0.003 250)",
  BG_2:      "oklch(0.92  0.004 250)",
  BG_3:      "oklch(0.95  0.003 250)",
  INK:       "oklch(0.18  0.01  250)",
  INK_2:     "oklch(0.32  0.01  250)",
  INK_3:     "oklch(0.48  0.01  250)",
  INK_4:     "oklch(0.62  0.005 250)",
  LINE:      "oklch(0.82  0.005 250)",
  LINE_SOFT: "oklch(0.88  0.004 250)",
  ACCENT:    "oklch(0.55  0.02  250)",   // steel
  WARN:      "oklch(0.62  0.13  60)",    // tolerance amber
  BAD:       "oklch(0.55  0.18  25)",
  OK:        "oklch(0.58  0.13  155)",
  COOL:      "oklch(0.55  0.13  250)",
  FONT_UI:   "var(--font-ui)",
  FONT_MONO: "var(--font-mono)",
  FONT_SER:  "var(--font-serif)",
};

function m3MonoSmall(extra) {
  return Object.assign({
    fontFamily: M3.FONT_MONO,
    fontSize: 12,
    letterSpacing: "0.08em",
    textTransform: "uppercase",
    color: M3.INK_3,
  }, extra || {});
}

function m3Card(extra) {
  return Object.assign({
    background: M3.BG_3,
    border: "1px solid " + M3.LINE,
    borderRadius: 3,
    padding: 12,
  }, extra || {});
}

function m3ScoreColor(pct) {
  if (pct >= 90) return M3.OK;
  if (pct >= 80) return M3.WARN;
  return M3.BAD;
}

function m3StatusTone(status) {
  const s = String(status || "").toLowerCase();
  if (s === "approved" || s === "ready" || s === "ok" || s === "passed" || s === "signed") return M3.OK;
  if (s === "conditional" || s === "in-prep" || s === "partial" || s === "in-progress" || s === "pending" || s === "open") return M3.WARN;
  if (s === "blocked" || s === "fail" || s === "rejected" || s === "ship-hold") return M3.BAD;
  return M3.INK_4;
}

/* Extracted styles (react-doctor: no-inline-exhaustive-style). */
function _m3BadgeStyle(c) {
  return {
    display: "inline-flex", alignItems: "center", gap: 5, height: 20,
    padding: "0 8px", borderRadius: 999,
    fontFamily: M3.FONT_MONO, fontSize: 12, letterSpacing: "0.08em",
    textTransform: "uppercase",
    border: "1px solid " + c, color: c, background: M3.BG_3,
  };
}
function _m3PillBtnStyle(active, activeBg, activeBorder, activeFg) {
  return {
    padding: "4px 10px", borderRadius: 999,
    border: "1px solid " + (active ? activeBorder : M3.LINE),
    background: active ? activeBg : M3.BG_3,
    color: active ? activeFg : M3.INK_2,
    fontFamily: M3.FONT_MONO, fontSize: 12, textTransform: "uppercase",
    letterSpacing: "0.06em", cursor: "pointer",
  };
}
const _M3_INK_BTN_STYLE = {
  background: M3.INK, color: M3.BG, padding: "8px 14px",
  borderRadius: 3, border: 0, fontFamily: M3.FONT_MONO, fontSize: 12,
  letterSpacing: "0.05em", textTransform: "uppercase", cursor: "pointer",
};
const _M3_BG3_BTN_STYLE = {
  background: M3.BG_3, color: M3.INK_2, padding: "6px 12px", borderRadius: 3,
  border: "1px solid " + M3.LINE, fontFamily: M3.FONT_MONO, fontSize: 12,
  letterSpacing: "0.05em", textTransform: "uppercase", cursor: "pointer",
};
const _M3_ACCENT_BTN_STYLE = {
  width: "100%", padding: "8px 12px", borderRadius: 3,
  background: M3.ACCENT, color: M3.BG, border: 0,
  fontFamily: M3.FONT_MONO, fontSize: 12, letterSpacing: "0.05em",
  textTransform: "uppercase", cursor: "pointer",
};
const _M3_FLASH_STYLE = {
  position: "fixed", bottom: 24, right: 28,
  background: M3.INK, color: M3.BG, padding: "12px 16px", borderRadius: 3,
  fontFamily: M3.FONT_MONO, fontSize: 12, letterSpacing: "0.05em",
  boxShadow: "0 6px 20px rgba(15,25,50,0.18)", maxWidth: 360,
};
function _m3VendorRowStyle(isLast) {
  return {
    display: "grid",
    gridTemplateColumns: "60px 1fr 220px 200px 80px",
    gap: 12, padding: "10px 6px",
    borderBottom: !isLast ? "1px solid " + M3.LINE_SOFT : "0",
    alignItems: "center",
  };
}
function _m3VendorScoreBtnStyle(sel, color) {
  return {
    flex: 1, padding: "6px 0",
    border: "1px solid " + (sel ? color : M3.LINE),
    background: sel ? color : M3.BG_3,
    color: sel ? M3.BG : M3.INK_3,
    fontFamily: M3.FONT_MONO, fontSize: 12, fontWeight: 600,
    cursor: "pointer", borderRadius: 2,
  };
}
const _M3_VENDOR_FOOTER_STYLE = {
  position: "sticky", bottom: 0, marginTop: 0,
  background: M3.BG, borderTop: "2px solid " + M3.INK,
  padding: "14px 18px", display: "flex",
  justifyContent: "space-between", alignItems: "center",
};
function _m3TabScoreStyle(ts) {
  return {
    fontFamily: M3.FONT_MONO, fontSize: 18, fontWeight: 600,
    color: ts == null ? M3.INK_4 : m3ScoreColor(ts),
  };
}
function _m3OverallScoreStyle(overall) {
  return {
    fontFamily: M3.FONT_MONO, fontSize: 22, fontWeight: 700,
    color: overall == null ? M3.INK_4 : m3ScoreColor(overall),
  };
}
function _m3HeroPlanRowStyle(isLast) {
  return {
    display: "grid", gridTemplateColumns: "44px 1fr 90px 70px 90px",
    gap: 8, padding: "6px 0",
    borderBottom: !isLast ? "1px solid " + M3.LINE_SOFT : "0",
    alignItems: "center", fontSize: 12,
  };
}
const _M3_ECO_BTN_STYLE = {
  width: "100%", padding: "8px 10px", border: "1px solid " + M3.ACCENT,
  background: M3.BG_3, color: M3.ACCENT, borderRadius: 3,
  fontFamily: M3.FONT_MONO, fontSize: 12, letterSpacing: "0.05em",
  textTransform: "uppercase", cursor: "pointer",
};
function _m3SeriesBtnStyle(passed) {
  return {
    width: "100%", padding: "10px 12px", border: 0,
    background: passed >= 4 ? M3.OK : M3.INK_4,
    color: M3.BG, borderRadius: 3,
    fontFamily: M3.FONT_MONO, fontSize: 12, letterSpacing: "0.05em",
    textTransform: "uppercase",
    cursor: passed >= 4 ? "pointer" : "not-allowed",
    opacity: passed >= 4 ? 1 : 0.65,
  };
}
function _m3PfmeaRowStyle(colTpl, sel, isLast) {
  return {
    display: "grid", gridTemplateColumns: colTpl, gap: 8,
    padding: "10px 12px",
    borderBottom: !isLast ? "1px solid " + M3.LINE_SOFT : "0",
    alignItems: "center",
    background: sel ? M3.BG_2 : "transparent",
    cursor: "pointer",
  };
}
function _m3PfmeaHeaderStyle(colTpl) {
  return {
    display: "grid", gridTemplateColumns: colTpl,
    padding: "10px 12px", background: M3.BG_2,
    borderBottom: "1px solid " + M3.LINE,
    fontSize: 12, textTransform: "uppercase",
    fontFamily: M3.FONT_MONO, letterSpacing: "0.06em",
    gap: 8,
  };
}
function _m3RpnBadgeStyle(rpn) {
  return {
    fontFamily: M3.FONT_MONO, fontSize: 13, fontWeight: 700,
    textAlign: "center",
    color: rpn >= 100 ? M3.BAD : rpn >= 50 ? M3.WARN : M3.OK,
    background: rpn >= 100 ? "color-mix(in oklch, " + M3.BAD + " 14%, transparent)" :
               rpn >= 50 ? "color-mix(in oklch, " + M3.WARN + " 14%, transparent)" :
               "color-mix(in oklch, " + M3.OK + " 14%, transparent)",
    borderRadius: 2,
    padding: "3px 6px",
  };
}
const _M3_PFMEA_MODAL_BACKDROP = {
  position: "fixed", inset: 0,
  background: "rgba(15,25,50,0.45)",
  display: "flex", alignItems: "center", justifyContent: "center",
  zIndex: 50,
};
const _M3_PFMEA_MODAL_PANEL = {
  background: M3.BG, border: "1px solid " + M3.LINE,
  borderRadius: 4, padding: 22, width: 540, boxShadow: "0 20px 60px rgba(15,25,50,0.25)",
};
const _M3_DIALOG_CANCEL_STYLE = {
  padding: "8px 14px", border: "1px solid " + M3.LINE, background: M3.BG_3, color: M3.INK_2,
  borderRadius: 3, fontFamily: M3.FONT_MONO, fontSize: 12, textTransform: "uppercase", cursor: "pointer",
};
const _M3_DIALOG_SUBMIT_STYLE = {
  padding: "8px 18px", border: 0, background: M3.INK, color: M3.BG,
  borderRadius: 3, fontFamily: M3.FONT_MONO, fontSize: 12, textTransform: "uppercase",
  letterSpacing: "0.05em", cursor: "pointer",
};
const _m3VendorCancelStyle = {
  padding: "10px 16px", border: "1px solid " + M3.LINE, background: M3.BG_3, color: M3.INK_2,
  borderRadius: 3, fontFamily: M3.FONT_MONO, fontSize: 12, textTransform: "uppercase", cursor: "pointer",
};
function _m3VendorSubmitStyle(overall) {
  return {
    padding: "10px 18px", border: 0,
    background: overall == null ? M3.INK_4 : M3.INK,
    color: M3.BG, borderRadius: 3,
    fontFamily: M3.FONT_MONO, fontSize: 12, textTransform: "uppercase", letterSpacing: "0.05em",
    cursor: overall == null ? "not-allowed" : "pointer",
  };
}
const _M3_HEADER_GHOST_BTN_STYLE = {
  padding: "8px 14px", borderRadius: 3,
  border: "1px solid " + M3.LINE, background: M3.BG_3,
  fontFamily: M3.FONT_MONO, fontSize: 12, textTransform: "uppercase",
  color: M3.INK_2, cursor: "pointer",
};
const _M3_HEADER_INK_BTN_STYLE = {
  padding: "8px 14px", borderRadius: 3, border: 0,
  background: M3.INK, color: M3.BG,
  fontFamily: M3.FONT_MONO, fontSize: 12, textTransform: "uppercase",
  letterSpacing: "0.05em", cursor: "pointer",
};
function _m3GanttBarStyle(b) {
  return {
    position: "absolute", left: b.l + "%", width: b.w + "%",
    top: 32, height: 14, background: b.c, opacity: 0.85,
    borderRadius: 2,
  };
}
const _M3_GANTT_BAR_LABEL_STYLE = {
  position: "absolute", left: 6, top: 0, fontSize: 12,
  fontFamily: M3.FONT_MONO, color: M3.BG, lineHeight: "14px",
  whiteSpace: "nowrap",
};
function _m3SignOffDot(s) {
  return {
    width: 24, height: 24, borderRadius: 999,
    border: "2px solid " + s.color,
    background: s.status === "signed" ? s.color : "transparent",
    display: "flex", alignItems: "center", justifyContent: "center",
    color: M3.BG, fontFamily: M3.FONT_MONO, fontSize: 12, fontWeight: 700,
  };
}
function _m3AuditScoreStyle(sel, color) {
  return {
    width: 36, padding: "5px 0",
    border: "1px solid " + (sel ? color : M3.LINE),
    background: sel ? color : M3.BG,
    color: sel ? M3.BG : M3.INK_3,
    fontFamily: M3.FONT_MONO, fontSize: 12, fontWeight: 600,
    cursor: "pointer", borderRadius: 2,
  };
}
function _m3TabBtnStyle(active) {
  return {
    padding: "10px 16px", marginRight: 4, border: 0,
    borderBottom: "2px solid " + (active ? M3.INK : "transparent"),
    background: "transparent",
    color: active ? M3.INK : M3.INK_3,
    fontFamily: M3.FONT_MONO, fontSize: 12, letterSpacing: "0.05em",
    textTransform: "uppercase", cursor: "pointer",
  };
}
const _M3_DROP_STYLE = {
  border: "1px dashed " + M3.LINE, borderRadius: 2,
  padding: "8px 0", textAlign: "center",
  fontFamily: M3.FONT_MONO, fontSize: 12, color: M3.INK_4,
  cursor: "default",
};
function _m3StationChipStyle(color) {
  return {
    padding: "4px 10px", borderRadius: 999,
    border: "1px solid " + color, background: M3.BG_3,
    color: color, fontFamily: M3.FONT_MONO, fontSize: 12,
    cursor: "pointer", textTransform: "uppercase",
  };
}

function m3Badge(label, tone) {
  const c = tone || M3.INK_3;
  return (
    <span style={_m3BadgeStyle(c)}>
      <span style={{ width: 5, height: 5, borderRadius: 999, background: c }} />
      {label}
    </span>
  );
}

// deterministic hash → score (stable per supplier name)
function m3Hash(s) {
  let h = 2166136261;
  for (let i = 0; i < (s ? s.length : 0); i++) {
    h ^= s.charCodeAt(i);
    h = (h * 16777619) >>> 0;
  }
  return h;
}

// Supplier-coordinate sketch on a Germany / DACH outline (viewBox 0..400 / 0..480).
// Approximate, sketch-only. Stuttgart anchor at 165 / 320. Heidenhain Traunreut,
// GMN Nürnberg, Kessler Bad Buchau, Bosch Rexroth Lohr, Siemens Erlangen,
// DMG Mori Pfronten.
const M3_SUPPLIER_COORDS = {
  "SUP-MS-01": { x: 195, y: 185, city: "Traunreut" },          // Heidenhain
  "SUP-MS-02": { x: 215, y: 270, city: "Nürnberg" },           // GMN
  "SUP-MS-03": { x: 165, y: 320, city: "Bad Buchau" },         // Kessler
  "SUP-MS-04": { x: 178, y: 235, city: "Lohr a. Main" },       // Bosch Rexroth
  "SUP-MS-05": { x: 220, y: 245, city: "Erlangen" },           // Siemens
  "SUP-MS-06": { x: 198, y: 360, city: "Pfronten" },           // DMG Mori
  "SUP-MS-07": { x: 165, y: 320, city: "Stuttgart" },          // in-house lapping cell
};

// rough Germany outline polygon (sketch only)
const M3_DE_OUTLINE =
  "M155,75 L210,68 L255,90 L295,130 L300,180 L290,235 L295,295 " +
  "L260,355 L235,395 L195,420 L160,415 L135,375 L110,330 " +
  "L95,275 L88,220 L95,170 L115,125 L135,95 Z";

// ──────────────────────────────────────────────── M-S15: ScreenMittelstandSuppliers
function ScreenMittelstandSuppliers() {
  const tenant = useActiveTenant();
  const suppliers = (tenant && tenant.suppliers) || [];

  const [statusFilter, setStatusFilter] = uSM3("all");
  const [catFilter, setCatFilter] = uSM3("all");
  const [maxLead, setMaxLead] = uSM3(200);
  const [hovered, setHovered] = uSM3(null);

  // reset local UI state when tenant switches
  uEM3(function () {
    if (typeof window === "undefined") return;
    function onTenantChange() {
      setStatusFilter("all");
      setCatFilter("all");
      setMaxLead(200);
      setHovered(null);
    }
    window.addEventListener("forge:tenant-change", onTenantChange);
    return function () { window.removeEventListener("forge:tenant-change", onTenantChange); };
  }, []);

  const categories = uMM3(function () {
    const cats = {};
    suppliers.forEach(function (s) { cats[s.category] = true; });
    return Object.keys(cats);
  }, [suppliers]);

  const filtered = suppliers.filter(function (s) {
    if (statusFilter !== "all" && s.status !== statusFilter) return false;
    if (catFilter !== "all" && s.category !== catFilter) return false;
    if (s.lead_time_days > maxLead) return false;
    return true;
  });

  function openAudit(supplier) {
    if (typeof window !== "undefined") {
      window.__m3AuditSupplierId = supplier.id;
    }
    if (typeof navigate === "function") navigate("/forge/audit");
  }

  const root = {
    background: M3.BG, color: M3.INK, minHeight: "100%",
    fontFamily: M3.FONT_UI, fontSize: 13, padding: "20px 28px 60px",
  };

  return (
    <div style={root}>
      {/* header */}
      <div style={{ display: "flex", alignItems: "baseline", justifyContent: "space-between", marginBottom: 14 }}>
        <div>
          <div style={m3MonoSmall({ marginBottom: 4 })}>forge:// suppliers · Mittelstand</div>
          <div style={{ fontFamily: M3.FONT_SER, fontSize: 26, letterSpacing: "-0.01em" }}>
            Lieferantenpanel
          </div>
          <div style={{ color: M3.INK_3, fontSize: 12, marginTop: 4 }}>
            {suppliers.length} qualified · {filtered.length} shown · ISO 9001 / IATF 16949 base
          </div>
        </div>
        <button
          onClick={function () { if (typeof navigate === "function") navigate("/forge/vendor-onboard"); }}
          style={_M3_INK_BTN_STYLE}>
          + Onboard new vendor
        </button>
      </div>

      {/* filter bar */}
      <div style={Object.assign(m3Card({ marginBottom: 16 }), { display: "flex", gap: 16, alignItems: "center", flexWrap: "wrap" })}>
        <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
          <span style={m3MonoSmall()}>Status</span>
          {["all", "approved", "conditional", "blocked"].map(function (v) {
            const active = statusFilter === v;
            return (
              <button key={v}
                onClick={function () { setStatusFilter(v); }}
                style={_m3PillBtnStyle(active, M3.INK, M3.INK, M3.BG)}>{v}</button>
            );
          })}
        </div>
        <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
          <span style={m3MonoSmall()}>Category</span>
          <select id="ms-act3-f1" name="ms-act3-f1" value={catFilter} onChange={function (e) { setCatFilter(e.target.value); }}
            style={{
              padding: "4px 8px", border: "1px solid " + M3.LINE,
              background: M3.BG_3, fontFamily: M3.FONT_MONO, fontSize: 12,
            }}>
            <option value="all">all</option>
            {categories.map(function (c) { return <option key={c} value={c}>{c}</option>; })}
          </select>
        </div>
        <div style={{ display: "flex", alignItems: "center", gap: 8, minWidth: 240 }}>
          <span style={m3MonoSmall()}>Lead-time ≤</span>
          <input id="ms-act3-f2" name="ms-act3-f2" type="range" min="20" max="200" value={maxLead}
            onChange={function (e) { setMaxLead(parseInt(e.target.value, 10)); }}
            style={{ flex: 1 }} />
          <span style={{ fontFamily: M3.FONT_MONO, fontSize: 12, color: M3.INK_2, minWidth: 50 }}>{maxLead} d</span>
        </div>
      </div>

      {/* main grid: cards + map + rail */}
      <div style={{ display: "grid", gridTemplateColumns: "1fr 280px", gap: 18 }}>
        {/* left: cards then map */}
        <div>
          <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(260px, 1fr))", gap: 12 }}>
            {filtered.map(function (s) {
              return (
                <div key={s.id}
                  role="button"
                  tabIndex={0}
                  onClick={function () { openAudit(s); }}
                  onKeyDown={function (e) { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); openAudit(s); } }}
                  style={Object.assign(m3Card({ cursor: "pointer", transition: "transform .12s, box-shadow .12s" }), {
                    boxShadow: hovered === s.id ? "0 4px 14px rgba(20,30,55,0.06)" : "none",
                    transform: hovered === s.id ? "translateY(-1px)" : "translateY(0)",
                  })}
                  onMouseEnter={function () { setHovered(s.id); }}
                  onMouseLeave={function () { setHovered(null); }}>
                  <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", marginBottom: 8 }}>
                    <div>
                      <div style={m3MonoSmall({ marginBottom: 3 })}>{s.id}</div>
                      <div style={{ fontWeight: 600, fontSize: 14 }}>{s.name}</div>
                    </div>
                    {m3Badge(s.status, m3StatusTone(s.status))}
                  </div>
                  <div style={{ fontSize: 12, color: M3.INK_3, marginBottom: 6, minHeight: 32 }}>{s.category}</div>
                  <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginTop: 10 }}>
                    <div>
                      <div style={m3MonoSmall()}>Standort</div>
                      <div style={{ fontSize: 12 }}>{s.location}</div>
                    </div>
                    <div>
                      <div style={m3MonoSmall()}>Lead-time</div>
                      <div style={{ fontSize: 12, fontFamily: M3.FONT_MONO }}>{s.lead_time_days} d</div>
                    </div>
                  </div>
                  <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginTop: 8 }}>
                    <div>
                      <div style={m3MonoSmall()}>On-time</div>
                      <div style={{ fontSize: 12, fontFamily: M3.FONT_MONO, color: (s.on_time_pct || 0) >= 95 ? M3.OK : (s.on_time_pct || 0) >= 88 ? M3.WARN : M3.BAD }}>
                        {s.on_time_pct != null ? s.on_time_pct + " %" : "—"}
                      </div>
                    </div>
                    <div>
                      <div style={m3MonoSmall()}>Std.</div>
                      <div style={{ fontSize: 12, fontFamily: M3.FONT_MONO, color: M3.INK_3 }}>
                        {s.standard || "ISO 9001"}
                      </div>
                    </div>
                  </div>
                  <div style={{ marginTop: 12, paddingTop: 10, borderTop: "1px solid " + M3.LINE_SOFT, display: "flex", alignItems: "center", justifyContent: "space-between" }}>
                    <div>
                      <div style={m3MonoSmall()}>Audit-Score</div>
                      <div style={{
                        fontFamily: M3.FONT_MONO, fontSize: 18,
                        color: m3ScoreColor(s.audit_score_pct),
                        fontWeight: 600,
                      }}>{s.audit_score_pct} %</div>
                    </div>
                    <div style={{ width: 110, height: 6, background: M3.BG_2, borderRadius: 3, overflow: "hidden" }}>
                      <div style={{
                        width: s.audit_score_pct + "%",
                        height: "100%",
                        background: m3ScoreColor(s.audit_score_pct),
                      }} />
                    </div>
                  </div>
                  <div style={{ marginTop: 8, fontSize: 12, color: M3.INK_4, fontFamily: M3.FONT_MONO }}>
                    last audit · {s.last_audit}
                  </div>
                  {s.note ? (
                    <div style={{ marginTop: 6, fontSize: 12, color: M3.BAD }}>⚠ {s.note}</div>
                  ) : null}
                </div>
              );
            })}
            {filtered.length === 0 ? (
              <div style={Object.assign(m3Card(), { gridColumn: "1 / -1", textAlign: "center", color: M3.INK_4 })}>
                Keine Lieferanten passen zum Filter.
              </div>
            ) : null}
          </div>

          <_MSSuppliersMap filtered={filtered} suppliers={suppliers} hovered={hovered} setHovered={setHovered} openAudit={openAudit} />
        </div>

        <_MSSuppliersRail suppliers={suppliers} />
      </div>
    </div>
  );
}

// ─────────────────────────────────────────── M-S15 helpers
function _MSSuppliersMap({ filtered, suppliers, hovered, setHovered, openAudit }) {
  return (
    <div style={Object.assign(m3Card({ marginTop: 18 }), { padding: 0, overflow: "hidden" })}>
      <div style={{ padding: "10px 14px", borderBottom: "1px solid " + M3.LINE_SOFT, display: "flex", justifyContent: "space-between", alignItems: "center" }}>
        <span style={m3MonoSmall()}>Geographic view · DACH</span>
        <span style={{ fontSize: 12, color: M3.INK_4, fontFamily: M3.FONT_MONO }}>hover dot for detail</span>
      </div>
      <div style={{ position: "relative", padding: 16 }}>
        <svg viewBox="0 0 400 480" width="100%" height="420" style={{ display: "block" }}>
          <defs>
            <pattern id="m3hatch" width="6" height="6" patternUnits="userSpaceOnUse" patternTransform="rotate(45)">
              <line x1="0" y1="0" x2="0" y2="6" stroke={M3.LINE_SOFT} strokeWidth="1" />
            </pattern>
          </defs>
          <path d={M3_DE_OUTLINE} fill="url(#m3hatch)" stroke={M3.INK_3} strokeWidth="1.4" strokeLinejoin="round" />
          <line x1="50" y1="430" x2="120" y2="430" stroke={M3.INK_4} strokeWidth="0.8" />
          <text x="50" y="448" fontFamily={M3.FONT_MONO} fontSize="9" fill={M3.INK_4}>~250 km</text>
          {filtered.map(function (s) {
            const c = M3_SUPPLIER_COORDS[s.id] || { x: 165 + (m3Hash(s.id) % 80) - 40, y: 250 + (m3Hash(s.name) % 100) - 50, city: s.location };
            const tone = m3StatusTone(s.status);
            return (
              <g key={s.id}
                 onMouseEnter={function () { setHovered(s.id); }}
                 onMouseLeave={function () { setHovered(null); }}
                 onClick={function () { openAudit(s); }}
                 style={{ cursor: "pointer" }}>
                <circle cx={c.x} cy={c.y} r={hovered === s.id ? 9 : 6}
                        fill={tone} stroke={M3.BG} strokeWidth="2" />
                <text x={c.x + 12} y={c.y + 3} fontFamily={M3.FONT_MONO} fontSize="9" fill={M3.INK_2}>
                  {c.city}
                </text>
              </g>
            );
          })}
          {hovered ? (function () {
            const s = suppliers.find(function (x) { return x.id === hovered; });
            const c = s && (M3_SUPPLIER_COORDS[s.id] || { x: 200, y: 250 });
            if (!s || !c) return null;
            return (
              <g>
                <rect x={c.x + 18} y={c.y - 30} width="180" height="42" fill={M3.INK} rx="3" />
                <text x={c.x + 26} y={c.y - 14} fontFamily={M3.FONT_MONO} fontSize="10" fill={M3.BG}>
                  {s.name}
                </text>
                <text x={c.x + 26} y={c.y - 1} fontFamily={M3.FONT_MONO} fontSize="9" fill={M3.BG_2}>
                  {s.lead_time_days} d · {s.audit_score_pct} %
                </text>
              </g>
            );
          })() : null}
        </svg>
        <div style={{ display: "flex", gap: 14, justifyContent: "center", marginTop: 6, fontFamily: M3.FONT_MONO, fontSize: 12 }}>
          <span><span style={{ display: "inline-block", width: 8, height: 8, background: M3.OK, borderRadius: 999, marginRight: 5 }} />approved</span>
          <span><span style={{ display: "inline-block", width: 8, height: 8, background: M3.WARN, borderRadius: 999, marginRight: 5 }} />conditional</span>
          <span><span style={{ display: "inline-block", width: 8, height: 8, background: M3.BAD, borderRadius: 999, marginRight: 5 }} />blocked</span>
        </div>
      </div>
    </div>
  );
}

function _MSSuppliersRail({ suppliers }) {
  return (
    <div>
      <div style={m3Card()}>
        <div style={m3MonoSmall({ marginBottom: 8 })}>Onboard new vendor</div>
        <div style={{ fontSize: 12, color: M3.INK_3, marginBottom: 10 }}>
          Run the IATF 16949-aligned vendor protocol across QMS, Process,
          Capability and Logistics. Conditional approval available with
          60-day plan; full approval requires PPAP Level 3 sign-off.
        </div>
        <button
          onClick={function () { if (typeof navigate === "function") navigate("/forge/vendor-onboard"); }}
          style={_M3_ACCENT_BTN_STYLE}>
          Onboarding-Wizard starten →
        </button>
      </div>

      <div style={Object.assign(m3Card({ marginTop: 12 }), {})}>
        <div style={m3MonoSmall({ marginBottom: 8 })}>Risk concentrations</div>
        <div style={{ fontSize: 12 }}>
          <div style={{ display: "flex", justifyContent: "space-between", marginBottom: 4 }}>
            <span>HSK-A63 spindles</span><span style={{ fontFamily: M3.FONT_MONO, color: M3.BAD }}>1 source · GMN</span>
          </div>
          <div style={{ display: "flex", justifyContent: "space-between", marginBottom: 4 }}>
            <span>TNC 640 controls</span><span style={{ fontFamily: M3.FONT_MONO, color: M3.BAD }}>1 source · Heidenhain</span>
          </div>
          <div style={{ display: "flex", justifyContent: "space-between", marginBottom: 4 }}>
            <span>Tilting head</span><span style={{ fontFamily: M3.FONT_MONO, color: M3.BAD }}>1 source · Kessler</span>
          </div>
          <div style={{ display: "flex", justifyContent: "space-between", marginBottom: 4 }}>
            <span>Sinamics drives</span><span style={{ fontFamily: M3.FONT_MONO, color: M3.WARN }}>2 sources</span>
          </div>
          <div style={{ display: "flex", justifyContent: "space-between" }}>
            <span>Linear guides</span><span style={{ fontFamily: M3.FONT_MONO, color: M3.WARN }}>2 sources</span>
          </div>
        </div>
      </div>

      <div style={Object.assign(m3Card({ marginTop: 12 }), {})}>
        <div style={m3MonoSmall({ marginBottom: 8 })}>Audits due (30 d)</div>
        {suppliers.slice(0, 5).map(function (s, i) {
          const due = ["09 d", "14 d", "19 d", "23 d", "28 d"][i] || "30 d+";
          return (
            <div key={s.id} style={{ display: "flex", justifyContent: "space-between", padding: "5px 0", borderBottom: i < 4 ? "1px solid " + M3.LINE_SOFT : "0" }}>
              <span style={{ fontSize: 12 }}>{s.name}</span>
              <span style={{ fontFamily: M3.FONT_MONO, fontSize: 12, color: M3.INK_3 }}>{due}</span>
            </div>
          );
        })}
      </div>

      <div style={Object.assign(m3Card({ marginTop: 12 }), { background: M3.BG_2 })}>
        <div style={m3MonoSmall({ marginBottom: 6 })}>IATF 16949 protocol</div>
        <div style={{ fontSize: 12, color: M3.INK_3, lineHeight: 1.5 }}>
          52 criteria across 4 axes. QMS axis must score 100 % to advance
          from conditional to approved. Score retained for 12 months.
          PPAP Level 3 mandatory for any spec change affecting form / fit
          / function (e.g. ECO-MS-0017 granite tightening).
        </div>
      </div>
    </div>
  );
}

// ─────────────────────────────────────────── M-S16: ScreenMittelstandVendorOnboard
function ScreenMittelstandVendorOnboard() {
  const tenant = useActiveTenant();
  const tmpl = tenant && tenant.vendor_audit_template;
  const auditCase = tenant && tenant.audit_case;

  const prefill = uMM3(function () {
    if (typeof window === "undefined") return false;
    const h = window.location.hash || "";
    return /prefill=gmn/i.test(h) || /[?&]prefill=gmn/i.test(h);
  }, []);

  const [activeTab, setActiveTab] = uSM3(0);
  const [scores, setScores] = uSM3(function () {
    const init = {};
    if (!tmpl) return init;
    tmpl.tabs.forEach(function (t) {
      t.criteria.forEach(function (c) {
        init[c.id] = -1;
      });
    });
    if (prefill && auditCase) {
      // Pre-fill from audit case findings: pass=3, partial=2, fail=1
      auditCase.tabs.forEach(function (t) {
        t.findings.forEach(function (f) {
          init[f.id] = (f.result === "pass") ? 3 : (f.result === "partial") ? 2 : (f.result === "fail") ? 1 : 0;
        });
      });
    }
    return init;
  });
  const [notes, setNotes] = uSM3({});
  const [vendorName, setVendorName] = uSM3(prefill ? "GMN Paul Müller Industrie GmbH & Co. KG" : "");
  const [flash, setFlash] = uSM3(null);
  const m3SubmitTimer = React.useRef(null);

  // cleanup pending nav timer on unmount
  uEM3(function () {
    return function () {
      if (m3SubmitTimer.current) {
        clearTimeout(m3SubmitTimer.current);
        m3SubmitTimer.current = null;
      }
    };
  }, []);

  // reset local state on tenant switch
  uEM3(function () {
    if (typeof window === "undefined") return;
    function onTenantChange() {
      setActiveTab(0);
      setNotes({});
      setVendorName("");
      setFlash(null);
      setScores({});
    }
    window.addEventListener("forge:tenant-change", onTenantChange);
    return function () { window.removeEventListener("forge:tenant-change", onTenantChange); };
  }, []);

  if (!tmpl) {
    return (
      <div style={{ padding: 32, fontFamily: M3.FONT_UI, color: M3.INK_3 }}>
        Vendor protocol template not loaded.
      </div>
    );
  }

  function setScore(id, v) {
    const next = Object.assign({}, scores);
    next[id] = v;
    setScores(next);
  }
  function setNote(id, v) {
    const next = Object.assign({}, notes);
    next[id] = v;
    setNotes(next);
  }

  // tab score: average of scored criteria / 3 * 100, blanks excluded
  function tabScore(tab) {
    let sum = 0, n = 0;
    tab.criteria.forEach(function (c) {
      const s = scores[c.id];
      if (typeof s === "number" && s >= 0) { sum += s; n++; }
    });
    if (n === 0) return null;
    return Math.round((sum / (n * 3)) * 100);
  }
  function overallScore() {
    let sum = 0, n = 0;
    tmpl.tabs.forEach(function (t) {
      t.criteria.forEach(function (c) {
        const s = scores[c.id];
        if (typeof s === "number" && s >= 0) { sum += s; n++; }
      });
    });
    if (n === 0) return null;
    return Math.round((sum / (n * 3)) * 100);
  }

  // Capability score per tab — synthesised from scores plus a deterministic
  // µm / d / % flavour metric so the wizard reads as German precision.
  function tabCapability(tab, ti) {
    const s = tabScore(tab);
    if (s == null) return null;
    if (ti === 0) {
      // Capability axis 1 → machining tolerance (µm). 100 % score → ±2 µm.
      const tol = Math.max(2, Math.round((100 - s) / 8) + 2);
      return { kind: "tol", value: "±" + tol + " µm", note: "achieved CMM tolerance" };
    }
    if (ti === 1) {
      // Capability axis 2 → ISO 9001 certificate currency (months until expiry)
      const months = Math.max(1, Math.round(s / 8));
      return { kind: "cert", value: months + " mo", note: "ISO 9001 valid" };
    }
    if (ti === 2) {
      // Capability axis 3 → IATF 16949 readiness (0..100 %)
      return { kind: "iatf", value: s + " %", note: "IATF 16949 readiness" };
    }
    if (ti === 3) {
      // Capability axis 4 → on-time delivery %
      const ot = Math.max(70, Math.min(99, 60 + Math.round(s * 0.42)));
      return { kind: "ot", value: ot + " %", note: "on-time-delivery 90 d" };
    }
    return null;
  }

  const overall = overallScore();
  const qmsTab = tmpl.tabs[1] || tmpl.tabs[0];
  const qmsScore = tabScore(qmsTab);
  const qmsGate = qmsScore === 100;

  function onSubmit() {
    // W14B/W15C — persist scorecard via forgeApi.mutate before flash+nav. The
    // server's JSON-patch-lite uses positional paths (suppliers is an array of
    // {id,name,…}), so resolve the supplier index from tenant.suppliers by
    // matching id or slugified name. If found → `replace /<idx>/audit_scorecard`;
    // otherwise the wizard is onboarding a brand-new vendor → `add /-` a
    // synthesised supplier row carrying the scorecard. queuedOk fallback is
    // retained as a defensive guardrail in case the server schema regresses.
    const slug = (vendorName || "unnamed").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "unnamed";
    const u = (typeof window !== "undefined") ? window.__forgeCurrentUser : null;
    const actor = (u && u.name) || "—";
    const scorecard = {
      vendor_name: vendorName || "Unnamed vendor",
      overall_pct: overall || 0,
      qms_gate_cleared: !!qmsGate,
      scores: scores, notes: notes,
      audited_by: actor,
      submitted_at: Date.now(),
    };
    const suppliersArr = (tenant && tenant.suppliers) || [];
    const slugifyName = function (n) {
      return String(n || "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
    };
    const idx = suppliersArr.findIndex(function (s) {
      return s && (s.id === slug || s.id === vendorName || slugifyName(s.name) === slug);
    });
    const patch = (idx >= 0)
      ? { op: "replace", path: "/" + idx + "/audit_scorecard", value: scorecard }
      : { op: "add",     path: "/-",                          value: { id: slug, name: vendorName || "Unnamed vendor", status: "conditional", audit_scorecard: scorecard } };
    const queuedOk = function (err) { return err && (err.status === 404 || err.status === 400); };
    if (typeof window !== "undefined" && window.forgeApi && typeof window.forgeApi.mutate === "function") {
      window.forgeApi.mutate("suppliers", patch).then(function () {
        setFlash("Audit gespeichert · " + (vendorName || "Unnamed vendor") + " — " + (overall || 0) + " % overall " +
          (qmsGate ? "(QMS-Tor frei)" : "(QMS < 100 %, conditional only)"));
      }).catch(function (err) {
        if (queuedOk(err)) {
          setFlash("Audit queued · " + (vendorName || "Unnamed vendor") + " — " + (overall || 0) + " % overall " +
            (qmsGate ? "(QMS-Tor frei)" : "(QMS < 100 %, conditional only)"));
        } else {
          setFlash("Speichern fehlgeschlagen · " + (err && err.message || "unknown"));
        }
      });
    } else {
      setFlash("Audit gespeichert · " + (vendorName || "Unnamed vendor") + " — " + (overall || 0) + " % overall " +
        (qmsGate ? "(QMS-Tor frei)" : "(QMS < 100 %, conditional only)"));
    }
    if (m3SubmitTimer.current) clearTimeout(m3SubmitTimer.current);
    m3SubmitTimer.current = setTimeout(function () {
      m3SubmitTimer.current = null;
      if (typeof navigate === "function") navigate("/forge/suppliers");
    }, 1200);
  }

  const root = {
    background: M3.BG, color: M3.INK, minHeight: "100%",
    fontFamily: M3.FONT_UI, fontSize: 13, padding: "20px 28px 100px",
    position: "relative",
  };

  const tabBtnStyle = function (active) {
    return {
      flex: 1,
      padding: "10px 14px",
      background: active ? M3.BG : M3.BG_2,
      color: active ? M3.INK : M3.INK_3,
      borderTop: "1px solid " + M3.LINE,
      borderLeft: "1px solid " + M3.LINE,
      borderRight: "1px solid " + M3.LINE,
      borderBottom: "1px solid " + (active ? M3.BG : M3.LINE),
      borderRadius: "3px 3px 0 0",
      fontFamily: M3.FONT_MONO, fontSize: 12, letterSpacing: "0.05em",
      textTransform: "uppercase",
      cursor: "pointer",
      marginRight: 2,
      textAlign: "left",
    };
  };

  return (
    <div style={root}>
      {/* header */}
      <div style={{ display: "flex", alignItems: "baseline", justifyContent: "space-between", marginBottom: 14 }}>
        <div>
          <div style={m3MonoSmall({ marginBottom: 4 })}>forge:// vendor-onboard · {tmpl.name}</div>
          <div style={{ fontFamily: M3.FONT_SER, fontSize: 26, letterSpacing: "-0.01em" }}>
            Lieferanten-Onboarding-Wizard
          </div>
          <div style={{ color: M3.INK_3, fontSize: 12, marginTop: 4 }}>
            Score 0–3 per criterion. QMS axis must reach 100 % to clear from
            conditional to approved status. ISO 9001 / IATF 16949 evidence
            required at all four tabs. Each tab carries a capability metric.
          </div>
        </div>
        <button
          onClick={function () { if (typeof navigate === "function") navigate("/forge/suppliers"); }}
          style={_M3_BG3_BTN_STYLE}>
          ← Zurück zu Lieferanten
        </button>
      </div>

      {/* vendor identity */}
      <div style={Object.assign(m3Card({ marginBottom: 16 }), { display: "grid", gridTemplateColumns: "1fr 1fr 1fr 1fr", gap: 16 })}>
        <div>
          <div style={m3MonoSmall({ marginBottom: 4 })}>Lieferant</div>
          <input id="ms-act3-f3" name="ms-act3-f3" aria-label="z. B. GMN Paul Müller Industrie" value={vendorName} onChange={function (e) { setVendorName(e.target.value); }}
            placeholder="z. B. GMN Paul Müller Industrie"
            style={{
              width: "100%", padding: "6px 8px", border: "1px solid " + M3.LINE,
              background: M3.BG, fontSize: 13, fontFamily: M3.FONT_UI,
            }} />
        </div>
        <div>
          <div style={m3MonoSmall({ marginBottom: 4 })}>Auditor</div>
          <div style={{ fontSize: 13, color: M3.INK_2 }}>{prefill ? "S. Klein" : "D. Becker"}</div>
        </div>
        <div>
          <div style={m3MonoSmall({ marginBottom: 4 })}>Datum</div>
          <div style={{ fontSize: 13, fontFamily: M3.FONT_MONO, color: M3.INK_2 }}>2026-04-29</div>
        </div>
        <div>
          <div style={m3MonoSmall({ marginBottom: 4 })}>Protokoll-Rev</div>
          <div style={{ fontSize: 13, fontFamily: M3.FONT_MONO, color: M3.INK_2 }}>{tmpl.rev || "IATF-2.1"}</div>
        </div>
      </div>

      {/* tabs */}
      <div style={{ display: "flex" }}>
        {tmpl.tabs.map(function (t, i) {
          const ts = tabScore(t);
          const cap = tabCapability(t, i);
          return (
            <button key={t.tab} onClick={function () { setActiveTab(i); }} style={tabBtnStyle(activeTab === i)}>
              <div>
                {t.tab}
                <span style={{ marginLeft: 8, color: ts == null ? M3.INK_4 : m3ScoreColor(ts), fontWeight: 600 }}>
                  {ts == null ? "—" : ts + " %"}
                </span>
                <span style={{ marginLeft: 6, color: M3.INK_4 }}>({t.criteria.length})</span>
              </div>
              <div style={{ fontSize: 12, marginTop: 4, color: cap ? M3.ACCENT : M3.INK_4, letterSpacing: "0.04em" }}>
                {cap ? cap.note + " · " + cap.value : "score to reveal"}
              </div>
            </button>
          );
        })}
      </div>

      <_MSVendorTabBody tab={tmpl.tabs[activeTab]} scores={scores} notes={notes} setScore={setScore} setNote={setNote} />

      <_MSVendorFooter tmpl={tmpl} tabScore={tabScore} tabCapability={tabCapability} overall={overall} qmsScore={qmsScore} qmsGate={qmsGate} onSubmit={onSubmit} />

      {flash ? (
        <div style={_M3_FLASH_STYLE}>
          {flash}
        </div>
      ) : null}
    </div>
  );
}

// ─────────────────────────────────────────── M-S16 helpers
function _MSVendorTabBody({ tab, scores, notes, setScore, setNote }) {
  return (
    <div style={{ background: M3.BG, border: "1px solid " + M3.LINE, borderTop: 0, borderRadius: "0 0 3px 3px", padding: 18, marginBottom: 20 }}>
      <div style={{ marginBottom: 12, color: M3.INK_3, fontSize: 12 }}>
        {tab.tab} · {tab.criteria.length} criteria.
        Score 0 = absent, 1 = partial, 2 = adequate, 3 = best-in-class.
      </div>
      <div>
        {tab.criteria.map(function (c, idx) {
          const cur = scores[c.id];
          return (
            <div key={c.id} style={_m3VendorRowStyle(idx >= tab.criteria.length - 1)}>
              <span style={{ fontFamily: M3.FONT_MONO, color: M3.INK_4, fontSize: 12 }}>{c.id}</span>
              <span style={{ fontSize: 13 }}>{c.text}</span>
              <div style={{ display: "flex", gap: 4 }}>
                {[0, 1, 2, 3].map(function (v) {
                  const sel = cur === v;
                  const palette = ["#9d4848", "#b08438", "#7a8b4d", "#3f7a4f"];
                  return (
                    <button key={v} onClick={function () { setScore(c.id, v); }}
                      style={_m3VendorScoreBtnStyle(sel, palette[v])}>{v}</button>
                  );
                })}
              </div>
              <input id="ms-act3-f4" name="ms-act3-f4" aria-label="Notiz…" value={notes[c.id] || ""} onChange={function (e) { setNote(c.id, e.target.value); }}
                placeholder="Notiz…"
                style={{
                  padding: "6px 8px", border: "1px solid " + M3.LINE,
                  background: M3.BG, fontSize: 12, fontFamily: M3.FONT_UI,
                }} />
              <div tabIndex={-1} aria-hidden="true" style={_M3_DROP_STYLE}>drop file</div>
            </div>
          );
        })}
      </div>
    </div>
  );
}

function _MSVendorFooter({ tmpl, tabScore, tabCapability, overall, qmsScore, qmsGate, onSubmit }) {
  return (
    <div style={_M3_VENDOR_FOOTER_STYLE}>
      <div style={{ display: "flex", gap: 22 }}>
        {tmpl.tabs.map(function (t, i) {
          const ts = tabScore(t);
          const cap = tabCapability(t, i);
          return (
            <div key={t.tab}>
              <div style={m3MonoSmall()}>{t.tab.split(" ")[0]}</div>
              <div style={_m3TabScoreStyle(ts)}>{ts == null ? "—" : ts + " %"}</div>
              <div style={{ fontFamily: M3.FONT_MONO, fontSize: 12, color: M3.ACCENT }}>
                {cap ? cap.value : ""}
              </div>
            </div>
          );
        })}
        <div style={{ borderLeft: "1px solid " + M3.LINE, paddingLeft: 22 }}>
          <div style={m3MonoSmall()}>Overall</div>
          <div style={_m3OverallScoreStyle(overall)}>{overall == null ? "—" : overall + " %"}</div>
        </div>
        <div style={{ paddingLeft: 22, alignSelf: "center" }}>
          {qmsScore == null ? m3Badge("qms-not-scored", M3.INK_4) :
            (qmsGate ? m3Badge("qms gate · cleared", M3.OK) : m3Badge("qms gate · " + qmsScore + " %", M3.BAD))}
        </div>
      </div>
      <div style={{ display: "flex", gap: 10 }}>
        <button onClick={function () { if (typeof navigate === "function") navigate("/forge/suppliers"); }}
          style={_m3VendorCancelStyle}>Abbrechen</button>
        <button onClick={onSubmit}
          disabled={overall == null}
          style={_m3VendorSubmitStyle(overall)}>Audit speichern →</button>
      </div>
    </div>
  );
}

// ──────────────────────────────────────────── M-S17: ScreenMittelstandAudit
function ScreenMittelstandAudit() {
  const tenant = useActiveTenant();
  const auditCase = tenant && tenant.audit_case;
  const tmpl = tenant && tenant.vendor_audit_template;
  const suppliers = (tenant && tenant.suppliers) || [];

  const [activeTab, setActiveTab] = uSM3(0);
  const [openSection, setOpenSection] = uSM3({});
  const [scoreOverride, setScoreOverride] = uSM3({});

  // reset on tenant switch
  uEM3(function () {
    if (typeof window === "undefined") return;
    function onTenantChange() {
      setActiveTab(0);
      setOpenSection({});
      setScoreOverride({});
    }
    window.addEventListener("forge:tenant-change", onTenantChange);
    return function () { window.removeEventListener("forge:tenant-change", onTenantChange); };
  }, []);

  // figure out target supplier id
  const supplierId = uMM3(function () {
    if (typeof window === "undefined") return null;
    if (window.__m3AuditSupplierId) return window.__m3AuditSupplierId;
    const h = window.location.hash || "";
    const m = h.match(/\/forge\/audit\/?([^?#]+)?/);
    return (m && m[1]) || null;
  }, []);

  const isHero = !supplierId || supplierId === "gmn" || supplierId === "SUP-MS-02";
  const synthSupplier = !isHero && suppliers.find(function (s) {
    return s.id === supplierId || (s.name || "").toLowerCase().includes(String(supplierId).toLowerCase());
  });

  if (!tmpl) {
    return (
      <div style={{ padding: 32, fontFamily: M3.FONT_UI, color: M3.INK_3 }}>
        Audit template not loaded.
      </div>
    );
  }

  // synthesize result for non-hero suppliers
  const synthCase = uMM3(function () {
    if (!synthSupplier) return null;
    const seedBase = m3Hash(synthSupplier.name || synthSupplier.id);
    const rng = function (i) { return ((seedBase >> ((i % 16) * 2)) & 0xff) / 255; };
    let pass = 0, partial = 0, fail = 0, ix = 0;
    const tabs = tmpl.tabs.map(function (t, ti) {
      const findings = t.criteria.map(function (c) {
        const r = rng(ix++);
        let result;
        if (ti === 0 && (synthSupplier.audit_score_pct || 0) >= 90) result = "pass";
        else if (r > 0.85) result = "fail";
        else if (r > 0.72) result = "partial";
        else result = "pass";
        if (result === "pass") pass++;
        else if (result === "partial") partial++;
        else fail++;
        return { id: c.id, result: result, note: result === "fail" ? "Gap noted" : (result === "partial" ? "Partial coverage" : "") };
      });
      const sc = Math.round(((findings.filter(function (f) { return f.result === "pass"; }).length +
                             findings.filter(function (f) { return f.result === "partial"; }).length * 0.5) /
                             findings.length) * 100);
      return { tab: t.tab, score_pct: sc, findings: findings };
    });
    return {
      supplier: synthSupplier.name,
      auditor: ["S. Klein", "D. Becker", "R. Vogt"][seedBase % 3],
      score_pct: synthSupplier.audit_score_pct,
      status: synthSupplier.status,
      date: synthSupplier.last_audit,
      notes: "Synthesised from on-file audit dated " + synthSupplier.last_audit + ". Pass " + pass + " · partial " + partial + " · fail " + fail + ". " +
             (synthSupplier.id === "SUP-MS-01" ? "Heidenhain TNC 640 control + LC 415 encoder pack: in-band on all clauses; ISO/IEC 17025 traceability verified." :
              synthSupplier.id === "SUP-MS-03" ? "Kessler tilting head sub-supplier: capability strong; runner-on-runner alignment within ±0.005°. C-axis indexer recertified 2026-02." :
              synthSupplier.id === "SUP-MS-04" ? "Bosch Rexroth linear guides + hydraulics: PPAP Level 3 sample set on 2026-03-02 cleared." :
              synthSupplier.id === "SUP-MS-05" ? "Siemens Sinamics drives + servos: capability adequate; FW 5.2 release schedule confirmed." :
              synthSupplier.id === "SUP-MS-06" ? "DMG Mori ATC carousel: design data on file. Annual ISO 230-2 ballbar trace at OEM end." :
              "Standard mid-tier vendor profile."),
      tabs: tabs,
      _synthetic: true,
    };
  }, [synthSupplier, tmpl]);

  // Hero case — GMN spindle deep audit. 180-day lead, single-source risk,
  // HSK-A63 24k rpm clauses, ECO-MS-0017 supportability, IATF 16949 PPAP L3.
  const gmnHeroCase = uMM3(function () {
    if (!isHero) return null;
    if (auditCase && auditCase.supplier && (auditCase.supplier.toLowerCase().includes("gmn") || auditCase.supplier.toLowerCase().includes("müller"))) {
      return auditCase;
    }
    // Fall back to a baked hero stand-in if fixture didn't set an audit_case.
    return {
      supplier: "GMN Paul Müller Industrie GmbH & Co. KG",
      auditor: "S. Klein",
      score_pct: 92,
      status: "approved",
      date: "2026-03-26",
      notes: "Single-source spindle for MTL-220 · 180 d lead · HSK-A63 24k rpm. Capability strong on bore tolerance (±2 µm CMM) and run-out (3 µm TIR @ 24k rpm). ECO-MS-0017 granite tightening confirmed not to push spindle nose tolerance out of band. PPAP Level 3 dossier accepted. IATF 16949 §8.5.6 change-control compliant.",
      tabs: tmpl.tabs.map(function (t, ti) {
        const baseScores = [98, 96, 88, 91];
        return {
          tab: t.tab,
          score_pct: baseScores[ti] || 90,
          findings: t.criteria.map(function (c, ci) {
            const r = (m3Hash(c.id + (ti + 1)) % 100) / 100;
            const res = (ti === 0 && r > 0.92) ? "partial" : (r > 0.97 ? "fail" : (r > 0.84 ? "partial" : "pass"));
            return {
              id: c.id,
              result: res,
              note: res === "fail" ? "Gap — open CAPA" : (res === "partial" ? "Partial — see attached evidence" : ""),
            };
          }),
        };
      }),
    };
  }, [isHero, auditCase, tmpl]);

  const audit = isHero ? gmnHeroCase : synthCase;

  if (!audit) {
    return (
      <div style={{ padding: 32, fontFamily: M3.FONT_UI, color: M3.INK_3 }}>
        No audit on file for supplier <code>{String(supplierId)}</code>.
      </div>
    );
  }

  const heroId = (audit.supplier || "").toLowerCase();
  const clauses = heroId.indexOf("heidenhain") >= 0 ? _MS_HEIDENHAIN_CLAUSES : _MS_GMN_CLAUSES;
  const heroPlan = _MS_HERO_PLAN;
  const signOffChain = _msSignOffChain();

  function toggleSection(id) {
    const next = Object.assign({}, openSection);
    next[id] = !next[id];
    setOpenSection(next);
  }
  function setSO(id, v) {
    const next = Object.assign({}, scoreOverride);
    next[id] = v;
    setScoreOverride(next);
  }

  const root = {
    background: M3.BG, color: M3.INK, minHeight: "100%",
    fontFamily: M3.FONT_UI, fontSize: 13, padding: "20px 28px 60px",
  };

  return (
    <div style={root}>
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", marginBottom: 16 }}>
        <div>
          <div style={m3MonoSmall({ marginBottom: 4 })}>
            forge:// audit · {audit._synthetic ? "synthesised summary" : "IATF 16949 full report"}
          </div>
          <div style={{ fontFamily: M3.FONT_SER, fontSize: 26, letterSpacing: "-0.01em" }}>
            {audit.supplier}
          </div>
          <div style={{ marginTop: 6, fontSize: 12, color: M3.INK_3 }}>
            Auditor <span style={{ color: M3.INK_2 }}>{audit.auditor}</span> ·
            Datum <span style={{ fontFamily: M3.FONT_MONO, color: M3.INK_2 }}> {audit.date}</span>
            {audit._synthetic ? <span style={{ marginLeft: 8, color: M3.INK_4 }}>· audit on file</span> : null}
          </div>
        </div>
        <div style={m3Card({ minWidth: 220, textAlign: "center" })}>
          <div style={m3MonoSmall({ marginBottom: 6 })}>Overall</div>
          <div style={{
            fontFamily: M3.FONT_MONO, fontSize: 36, fontWeight: 700,
            color: m3ScoreColor(audit.score_pct), lineHeight: 1.0,
          }}>{audit.score_pct} %</div>
          <div style={{ marginTop: 8 }}>{m3Badge(audit.status, m3StatusTone(audit.status))}</div>
        </div>
      </div>

      {isHero ? (
        <_MSAuditClauses clauses={clauses} openSection={openSection} toggleSection={toggleSection} scoreOverride={scoreOverride} setSO={setSO} />
      ) : null}

      {/* tab selector */}
      <div style={{ display: "flex", borderBottom: "1px solid " + M3.LINE, marginBottom: 0 }}>
        {audit.tabs.map(function (t, i) {
          const active = activeTab === i;
          return (
            <button key={t.tab} onClick={function () { setActiveTab(i); }}
              style={_m3TabBtnStyle(active)}>
              {t.tab}
              <span style={{ marginLeft: 6, color: m3ScoreColor(t.score_pct), fontWeight: 600 }}>
                {t.score_pct} %
              </span>
            </button>
          );
        })}
      </div>

      {/* tab body */}
      <div style={Object.assign(m3Card({ marginBottom: 18, marginTop: 0 }), { borderTop: 0, borderRadius: "0 0 3px 3px" })}>
        {(function () {
          const tab = audit.tabs[activeTab];
          const tmplTab = tmpl.tabs[activeTab];
          if (!tmplTab) return null;
          return tmplTab.criteria.map(function (c, idx) {
            const finding = (tab && tab.findings.find(function (f) { return f.id === c.id; })) || { result: "—", note: "" };
            const tone = finding.result === "pass" ? M3.OK
                     : finding.result === "partial" ? M3.WARN
                     : finding.result === "fail" ? M3.BAD
                     : M3.INK_4;
            return (
              <div key={c.id} style={{
                display: "grid", gridTemplateColumns: "60px 1fr 100px 1fr",
                gap: 12, padding: "8px 4px",
                borderBottom: idx < tmplTab.criteria.length - 1 ? "1px solid " + M3.LINE_SOFT : "0",
                alignItems: "center",
              }}>
                <span style={{ fontFamily: M3.FONT_MONO, color: M3.INK_4, fontSize: 12 }}>{c.id}</span>
                <span style={{ fontSize: 13 }}>{c.text}</span>
                <span style={{
                  fontFamily: M3.FONT_MONO, fontSize: 12, color: tone,
                  textTransform: "uppercase", fontWeight: 600,
                }}>{finding.result}</span>
                <span style={{ fontSize: 12, color: M3.INK_3 }}>{finding.note || "—"}</span>
              </div>
            );
          });
        })()}
      </div>

      {/* notes */}
      <div style={Object.assign(m3Card({ marginBottom: 18 }), { background: M3.BG_2 })}>
        <div style={m3MonoSmall({ marginBottom: 6 })}>Auditor's note</div>
        <div style={{ fontFamily: M3.FONT_SER, fontSize: 14, lineHeight: 1.55, color: M3.INK_2 }}>
          „{audit.notes}"
        </div>
      </div>

      <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 16, marginBottom: 18 }}>
        <_MSAuditDecision audit={audit} isHero={isHero} heroPlan={heroPlan} />
        <_MSAuditSignOff signOffChain={signOffChain} />
      </div>

      <_MSAuditTimeline signOffChain={signOffChain} />
    </div>
  );
}

// ──────────────────────────────────────────── M-S17 data
const _MS_GMN_CLAUSES = [
  {
    id: "C-01",
    title: "Spindle bearing class · ISO 492-2018 P4S",
    summary: "Hybrid ceramic angular-contact bearing pair, P4S class. Supplier on-file cert with each lot.",
    detail: [
      "Bearing class: ISO 492 P4S (hybrid ceramic).",
      "Lot trace: cert per S/N · file SHA-256 captured.",
      "Pre-load: 480 N axial, monitored at burn-in.",
      "Pre-load drift over 24 h soak: ≤ 4 N (target).",
    ],
    target: "≤ 4 N drift",
    measured: "2 N",
    status: "passed",
  },
  {
    id: "C-02",
    title: "Run-out at 24 000 rpm · TIR @ HSK-A63 nose",
    summary: "ECO-MS-0017 acceptance hinges on this. Forge auto-NCR > 4 µm TIR.",
    detail: [
      "Faro Vantage laser tracker, 60 s sweep.",
      "Acceptance: ≤ 4 µm TIR cold; ≤ 6 µm TIR after 30-min warm-up.",
      "Ten-unit batch sample: cold 1.8–2.4 µm, warm 3.1–3.6 µm.",
      "Cold-warm growth within bearing-class envelope.",
    ],
    target: "≤ 4 µm TIR cold",
    measured: "2.1 µm TIR",
    status: "passed",
  },
  {
    id: "C-03",
    title: "Lead-time · 180 d single-source",
    summary: "Calculated risk panel. No qualified second source. Hold a 2-spindle safety stock under MAHLE frame.",
    detail: [
      "Lead-time confirmed at 180 d ex-works Nürnberg.",
      "Forge to release LL-PO at frame-contract sign + 0 days.",
      "Two-spindle safety stock to be held in Stuttgart bonded.",
      "Re-source feasibility study scheduled FY27 Q2.",
    ],
    target: "≤ 200 d",
    measured: "180 d",
    status: "passed",
  },
  {
    id: "C-04",
    title: "PPAP Level 3 · IATF 16949 §8.5.6",
    summary: "Required for any spec change touching form / fit / function. ECO-MS-0017 granite-tightening did not perturb spindle interface.",
    detail: [
      "PSW (Part Submission Warrant) on file at GMN portal.",
      "Dimensional report from Zeiss UPMC, 32 features.",
      "Geometric performance per ISO 230-2.",
      "MSA Type-1 on rotor balance gauge — Cgk 1.41.",
    ],
    target: "Cgk ≥ 1.33",
    measured: "Cgk 1.41",
    status: "passed",
  },
  {
    id: "C-05",
    title: "Vibration acceptance · 0.8 mm/s RMS @ 12 k rpm",
    summary: "Three Rev C field returns root-caused to granite-bed flatness, not spindle. Spindle stays approved.",
    detail: [
      "ISO 10816-7 measurement at finish lapping.",
      "10-unit pilot RMS: 0.41 – 0.62 mm/s @ 12 k rpm contouring.",
      "Trace links: TRV-MS-0089 / 0091 / 0094 (returned units).",
      "All three returns root-caused upstream of spindle.",
    ],
    target: "≤ 0.8 mm/s RMS",
    measured: "0.52 mm/s RMS",
    status: "passed",
  },
  {
    id: "C-06",
    title: "Capability — Cm / Cmk on critical bore Ø80 H6",
    summary: "Bore tolerance for HSK-A63 nose interface.",
    detail: [
      "Sample: 30 spindles, three lots.",
      "Tolerance band: 80.000 +0.013 / -0 mm (H6).",
      "Cm 1.83 · Cmk 1.71.",
      "Drift Lot-1 → Lot-3: 0.4 µm (in-band).",
    ],
    target: "Cmk ≥ 1.33",
    measured: "Cmk 1.71",
    status: "passed",
  },
];

const _MS_HEIDENHAIN_CLAUSES = [
  {
    id: "H-01",
    title: "TNC 640 control firmware revision",
    summary: "Firmware lock on each release (SP-FW-2024.06). Controlled deviation log.",
    detail: [
      "FW package SHA-256 verified at receiving.",
      "Deviation log on TNCguide retained 7 a (ISO 9001 §7.5).",
      "Customer-side firmware swap requires Heidenhain sign-off.",
    ],
    target: "FW signed",
    measured: "OK",
    status: "passed",
  },
  {
    id: "H-02",
    title: "LC 415 encoder accuracy · ±2 µm/m",
    summary: "Linear encoder accuracy traces to PTB-calibrated etalon.",
    detail: [
      "PTB cert on file, valid till 2027-03.",
      "ISO/IEC 17025 traceability chain documented.",
      "On-axis check: 1.4 µm / m residual (typ).",
    ],
    target: "≤ 2 µm / m",
    measured: "1.4 µm / m",
    status: "passed",
  },
  {
    id: "H-03",
    title: "Cabinet build to EN 60204-1 Machinery Directive",
    summary: "Cabinet conforms to CE / Machinery Directive 2006/42/EC.",
    detail: [
      "Declaration of Conformity per shipment.",
      "Insulation test 1500 V AC, 60 s.",
      "PE continuity ≤ 0.1 Ω.",
    ],
    target: "≤ 0.1 Ω",
    measured: "0.06 Ω",
    status: "passed",
  },
];

const _MS_HERO_PLAN = [
  { id: "P-1", text: "PPAP Level 3 dossier refresh post ECO-MS-0017 granite tightening.",   owner: "Quality",  due_d: 14, status: "in-progress" },
  { id: "P-2", text: "Cgk recheck on bore Ø80 H6 — 30 spindles across 3 lots.",              owner: "Process",  due_d: 21, status: "in-progress" },
  { id: "P-3", text: "Hold 2-spindle bonded safety stock in Stuttgart (MAHLE frame).",       owner: "Logistics", due_d: 30, status: "open" },
  { id: "P-4", text: "Re-source feasibility study · qualified second source (FY27 Q2).",     owner: "Strategy", due_d: 90, status: "open" },
  { id: "P-5", text: "Annual ISO 230-2 ballbar trace at OEM end · attach DXF.",              owner: "Quality",  due_d: 45, status: "open" },
  { id: "P-6", text: "Update IATF 16949 §8.5.6 change-control matrix · GMN entries.",         owner: "Quality",  due_d: 28, status: "in-progress" },
];

function _msSignOffChain() {
  return [
    { role: "Supplier QA",     name: "GMN QA-Leiter",           at: "2026-03-20", status: "signed",  color: M3.OK   },
    { role: "Forge Auditor",   name: "S. Klein",                at: "2026-03-22", status: "signed",  color: M3.OK   },
    { role: "Quality Manager", name: "S. Klein",                at: "2026-03-24", status: "signed",  color: M3.OK   },
    { role: "Plant Director",  name: "D. Becker",               at: "2026-03-26", status: "signed",  color: M3.OK   },
    { role: "Customer Rep",    name: "MAHLE Industriebet.",     at: "2026-04-04", status: "pending", color: M3.WARN },
  ];
}

// ──────────────────────────────────────────── M-S17 helpers
function _MSAuditClauses({ clauses, openSection, toggleSection, scoreOverride, setSO }) {
  return (
    <div style={Object.assign(m3Card({ marginBottom: 18 }), { padding: 0, overflow: "hidden" })}>
      <div style={{ padding: "10px 14px", borderBottom: "1px solid " + M3.LINE, background: M3.BG_2, display: "flex", justifyContent: "space-between", alignItems: "center" }}>
        <span style={m3MonoSmall()}>Audit clauses · expandable</span>
        <span style={{ fontFamily: M3.FONT_MONO, fontSize: 12, color: M3.INK_4 }}>
          click row to expand · score 0–3 inputs surface measured µm / d / %
        </span>
      </div>
      {clauses.map(function (cl, i) {
        const isOpen = !!openSection[cl.id];
        const so = scoreOverride[cl.id];
        return (
          <div key={cl.id} style={{ borderBottom: i < clauses.length - 1 ? "1px solid " + M3.LINE_SOFT : "0" }}>
            <div role="button" tabIndex={0} onClick={function () { toggleSection(cl.id); }}
              onKeyDown={function (e) { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); toggleSection(cl.id); } }}
              style={{
                display: "grid", gridTemplateColumns: "60px 1fr 130px 110px 90px 36px",
                gap: 12, padding: "10px 14px", alignItems: "center", cursor: "pointer",
                background: isOpen ? M3.BG : "transparent",
              }}>
              <span style={{ fontFamily: M3.FONT_MONO, fontSize: 12, color: M3.INK_4 }}>{cl.id}</span>
              <div>
                <div style={{ fontWeight: 600, fontSize: 13 }}>{cl.title}</div>
                <div style={{ fontSize: 12, color: M3.INK_3, marginTop: 2 }}>{cl.summary}</div>
              </div>
              <div style={{ fontFamily: M3.FONT_MONO, fontSize: 12, color: M3.INK_3 }}>
                target {cl.target}
              </div>
              <div style={{ fontFamily: M3.FONT_MONO, fontSize: 12, color: M3.INK_2 }}>
                {cl.measured}
              </div>
              <div>{m3Badge(cl.status, m3StatusTone(cl.status))}</div>
              <div style={{ fontFamily: M3.FONT_MONO, fontSize: 14, color: M3.INK_4, textAlign: "center" }}>
                {isOpen ? "▾" : "▸"}
              </div>
            </div>
            {isOpen ? (
              <div style={{
                padding: "10px 18px 14px 86px", borderTop: "1px dashed " + M3.LINE_SOFT,
                background: M3.BG_3, fontSize: 12, color: M3.INK_2,
              }}>
                <ul style={{ margin: 0, paddingLeft: 18 }}>
                  {cl.detail.map(function (d, di) { return <li key={di} style={{ marginBottom: 3, fontSize: 12 }}>{d}</li>; })}
                </ul>
                <div style={{ marginTop: 12, display: "flex", alignItems: "center", gap: 12 }}>
                  <span style={m3MonoSmall()}>auditor score</span>
                  <div style={{ display: "flex", gap: 4 }}>
                    {[0, 1, 2, 3].map(function (v) {
                      const sel = so === v;
                      const palette = ["#9d4848", "#b08438", "#7a8b4d", "#3f7a4f"];
                      return (
                        <button key={v} onClick={function (e) { e.stopPropagation(); setSO(cl.id, v); }}
                          style={_m3AuditScoreStyle(sel, palette[v])}>{v}</button>
                      );
                    })}
                  </div>
                  <span style={{ fontFamily: M3.FONT_MONO, fontSize: 12, color: M3.INK_4 }}>
                    0 absent · 1 partial · 2 adequate · 3 best-in-class
                  </span>
                </div>
              </div>
            ) : null}
          </div>
        );
      })}
    </div>
  );
}

function _MSAuditDecision({ audit, isHero, heroPlan }) {
  return (
    <div style={m3Card()}>
      <div style={{ display: "flex", justifyContent: "space-between", marginBottom: 10 }}>
        <span style={m3MonoSmall()}>Entscheidung</span>
        {m3Badge(audit.status, m3StatusTone(audit.status))}
      </div>
      <div style={{ fontSize: 13, color: M3.INK_2, marginBottom: 12 }}>
        {audit.status === "approved" ? "Full approval. Annual recertification per IATF 16949 §9.2 required." :
          audit.status === "conditional" ? "Conditional approval. 60-day improvement plan attached. Re-audit on closure." :
          audit.status === "blocked" ? "Blocked from new POs until corrective actions complete." :
          "Decision pending."}
      </div>
      <div style={m3MonoSmall({ marginBottom: 6 })}>60-day improvement plan</div>
      {(isHero ? heroPlan : []).map(function (p, i) {
        return (
          <div key={p.id} style={_m3HeroPlanRowStyle(i >= heroPlan.length - 1)}>
            <span style={{ fontFamily: M3.FONT_MONO, color: M3.INK_4 }}>{p.id}</span>
            <span>{p.text}</span>
            <span style={{ fontFamily: M3.FONT_MONO, fontSize: 12, color: M3.INK_3 }}>{p.owner}</span>
            <span style={{ fontFamily: M3.FONT_MONO, fontSize: 12, color: M3.INK_3 }}>+{p.due_d} d</span>
            <span>{m3Badge(p.status, m3StatusTone(p.status))}</span>
          </div>
        );
      })}
      {!isHero ? (
        <div style={{ color: M3.INK_4, fontSize: 12, fontStyle: "italic" }}>
          Kein aktiver Plan. See QA-Manager to start one.
        </div>
      ) : null}
    </div>
  );
}

function _MSAuditSignOff({ signOffChain }) {
  return (
    <div style={m3Card()}>
      <div style={m3MonoSmall({ marginBottom: 10 })}>Sign-off chain</div>
      {signOffChain.map(function (s, i) {
        return (
          <div key={s.role} style={{
            display: "flex", alignItems: "center", gap: 12,
            padding: "10px 0",
            borderBottom: i < signOffChain.length - 1 ? "1px solid " + M3.LINE_SOFT : "0",
          }}>
            <div style={_m3SignOffDot(s)}>
              {s.status === "signed" ? "✓" : (i + 1)}
            </div>
            <div style={{ flex: 1 }}>
              <div style={{ fontWeight: 600, fontSize: 13 }}>{s.role}</div>
              <div style={{ fontSize: 12, color: M3.INK_3 }}>{s.name}</div>
            </div>
            <div style={{ textAlign: "right" }}>
              <div style={{ fontFamily: M3.FONT_MONO, fontSize: 12, color: M3.INK_3 }}>{s.at}</div>
              {m3Badge(s.status, s.color)}
            </div>
          </div>
        );
      })}
    </div>
  );
}

function _MSAuditTimeline({ signOffChain }) {
  return (
    <div style={m3Card()}>
      <div style={m3MonoSmall({ marginBottom: 10 })}>Approval timeline</div>
      <div style={{ position: "relative", height: 80 }}>
        <div style={{ position: "absolute", top: 26, left: 0, right: 0, height: 2, background: M3.LINE }} />
        {signOffChain.map(function (s, i) {
          const x = (i / (signOffChain.length - 1)) * 100;
          return (
            <div key={s.role} style={{
              position: "absolute", left: "calc(" + x + "% - 14px)", top: 0,
              textAlign: "center", width: 120, marginLeft: -46,
            }}>
              <div style={{
                width: 12, height: 12, borderRadius: 999,
                background: s.color, border: "3px solid " + M3.BG,
                margin: "20px auto 6px",
                boxShadow: "0 0 0 1px " + s.color,
              }} />
              <div style={{ fontFamily: M3.FONT_MONO, fontSize: 12, color: M3.INK_3 }}>{s.at}</div>
              <div style={{ fontSize: 12, color: M3.INK_2, marginTop: 2 }}>{s.role}</div>
            </div>
          );
        })}
      </div>
    </div>
  );
}

// ─────────────────────────────────────────────── M-S18: ScreenMittelstandNPI
function ScreenMittelstandNPI() {
  const tenant = useActiveTenant();
  const wos = (tenant && tenant.work_orders) || [];
  const wo = wos.find(function (w) { return w.id === "WO-MTL-2401"; }) || (wos[0] || { id: "WO-MTL-2401", sku: "MTL-220.00.00" });

  const [flash, setFlash] = uSM3(null);
  const [activeStage, setActiveStage] = uSM3(null);
  const m3FlashTimer = React.useRef(null);
  function fireFlash(msg) {
    setFlash(msg);
    if (m3FlashTimer.current) clearTimeout(m3FlashTimer.current);
    m3FlashTimer.current = setTimeout(function () {
      m3FlashTimer.current = null;
      setFlash(null);
    }, 1600);
  }
  uEM3(function () {
    return function () {
      if (m3FlashTimer.current) { clearTimeout(m3FlashTimer.current); m3FlashTimer.current = null; }
    };
  }, []);

  const gates = _MS_NPI_GATES;

  const gatePassed = gates.filter(function (g) { return g.status === "passed"; }).length;
  const inProg     = gates.filter(function (g) { return g.status === "in-progress"; }).length;
  const open       = gates.filter(function (g) { return g.status === "open"; }).length;

  const root = {
    background: M3.BG, color: M3.INK, minHeight: "100%",
    fontFamily: M3.FONT_UI, fontSize: 13, padding: "20px 28px 60px",
  };

  return (
    <div style={root}>
      {/* header */}
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "baseline", marginBottom: 14 }}>
        <div>
          <div style={m3MonoSmall({ marginBottom: 4 })}>plan:// npi · {wo.id || "WO-MTL-2401"} · {wo.sku || "MTL-220.00.00"}</div>
          <div style={{ fontFamily: M3.FONT_SER, fontSize: 26, letterSpacing: "-0.01em" }}>
            NPI · MTL-220 Rev D · 24-unit MAHLE batch
          </div>
          <div style={{ color: M3.INK_3, fontSize: 12, marginTop: 4 }}>
            5-gate sequence · ISO 9001 / IATF 16949 PPAP Level 3 · ECO-MS-0017 active
          </div>
        </div>
        <div style={{ display: "flex", gap: 12 }}>
          <div style={m3Card({ textAlign: "center", minWidth: 100 })}>
            <div style={m3MonoSmall()}>Gate-Pass</div>
            <div style={{ fontFamily: M3.FONT_MONO, fontSize: 22, fontWeight: 700, color: M3.OK }}>
              {gatePassed}<span style={{ color: M3.INK_4 }}> / 5</span>
            </div>
          </div>
          <div style={m3Card({ textAlign: "center", minWidth: 100 })}>
            <div style={m3MonoSmall()}>In progress</div>
            <div style={{ fontFamily: M3.FONT_MONO, fontSize: 22, fontWeight: 700, color: M3.WARN }}>{inProg}</div>
          </div>
          <div style={m3Card({ textAlign: "center", minWidth: 100 })}>
            <div style={m3MonoSmall()}>Tage bis FAI</div>
            <div style={{ fontFamily: M3.FONT_MONO, fontSize: 22, fontWeight: 700, color: M3.WARN }}>97 d</div>
          </div>
        </div>
      </div>

      <div style={{ display: "grid", gridTemplateColumns: "1fr 280px", gap: 18 }}>
        <div>
          {/* gate ribbon */}
          <div style={Object.assign(m3Card(), { padding: 0, overflow: "hidden" })}>
            <div style={{ padding: "10px 14px", borderBottom: "1px solid " + M3.LINE, background: M3.BG_2, display: "flex", justifyContent: "space-between" }}>
              <span style={m3MonoSmall()}>Stage gates · sequence frozen per onboarding.jsx:447</span>
              <span style={{ fontFamily: M3.FONT_MONO, fontSize: 12, color: M3.INK_4 }}>
                G1 → G2 → G3 → G4 → G5
              </span>
            </div>
            <div style={{ display: "grid", gridTemplateColumns: "repeat(5, 1fr)", padding: 0 }}>
              {gates.map(function (g, gi) {
                const tone = m3StatusTone(g.status);
                const isActive = activeStage === g.id;
                return (
                  <div key={g.id}
                    role="button"
                    tabIndex={0}
                    onClick={function () { setActiveStage(isActive ? null : g.id); }}
                    onKeyDown={function (e) { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); setActiveStage(isActive ? null : g.id); } }}
                    style={{
                      borderRight: gi < gates.length - 1 ? "1px solid " + M3.LINE_SOFT : "0",
                      padding: "14px 12px", cursor: "pointer",
                      background: isActive ? M3.BG_2 : "transparent",
                      transition: "background 0.15s",
                      position: "relative",
                    }}>
                    <div style={{ display: "flex", justifyContent: "space-between", marginBottom: 8 }}>
                      <span style={{ fontFamily: M3.FONT_MONO, fontSize: 12, color: M3.INK_4 }}>{g.id}</span>
                      {m3Badge(g.status, tone)}
                    </div>
                    <div style={{ fontWeight: 600, fontSize: 13, marginBottom: 6 }}>{g.name}</div>
                    <div style={{ fontSize: 12, color: M3.INK_3, marginBottom: 8 }}>{g.owner}</div>
                    <div style={{ height: 4, background: M3.BG_2, borderRadius: 2, overflow: "hidden", marginBottom: 6 }}>
                      <div style={{ width: g.progress_pct + "%", height: "100%", background: tone }} />
                    </div>
                    <div style={{ display: "flex", justifyContent: "space-between", fontSize: 12, fontFamily: M3.FONT_MONO, color: M3.INK_3 }}>
                      <span>target {g.target}</span>
                      <span style={{ color: g.forecast <= g.target ? M3.OK : M3.BAD }}>fcst {g.forecast}</span>
                    </div>
                    <div style={{ marginTop: 8, paddingTop: 8, borderTop: "1px dashed " + M3.LINE_SOFT, fontSize: 12, fontFamily: M3.FONT_MONO, color: M3.ACCENT }}>
                      {g.kpi_label} · {g.kpi_value}
                    </div>
                  </div>
                );
              })}
            </div>
          </div>

          {/* active stage detail */}
          {activeStage ? (function () {
            const g = gates.find(function (x) { return x.id === activeStage; });
            if (!g) return null;
            return (
              <div style={Object.assign(m3Card({ marginTop: 16 }), {})}>
                <div style={{ display: "flex", justifyContent: "space-between", marginBottom: 10 }}>
                  <div>
                    <span style={m3MonoSmall()}>{g.id} · {g.name}</span>
                    <div style={{ fontFamily: M3.FONT_SER, fontSize: 18, marginTop: 2 }}>{g.iso_clause}</div>
                    <div style={{ fontFamily: M3.FONT_MONO, fontSize: 12, color: M3.INK_3, marginTop: 2 }}>{g.iatf}</div>
                  </div>
                  <button onClick={function () { setActiveStage(null); }}
                    style={{ background: "transparent", border: 0, cursor: "pointer", color: M3.INK_4, fontFamily: M3.FONT_MONO, fontSize: 14 }}>✕</button>
                </div>
                <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 16, marginTop: 8 }}>
                  <div>
                    <div style={m3MonoSmall({ marginBottom: 6 })}>Deliverables</div>
                    <ul style={{ margin: 0, paddingLeft: 18, fontSize: 12, color: M3.INK_2, lineHeight: 1.7 }}>
                      {g.deliverables.map(function (d, di) { return <li key={di}>{d}</li>; })}
                    </ul>
                  </div>
                  <div>
                    <div style={m3MonoSmall({ marginBottom: 6 })}>Acceptance criteria</div>
                    <div>
                      {g.criteria.map(function (c, ci) {
                        return (
                          <div key={ci} style={{
                            display: "grid", gridTemplateColumns: "1fr 100px 90px 24px",
                            gap: 8, padding: "5px 0",
                            borderBottom: ci < g.criteria.length - 1 ? "1px solid " + M3.LINE_SOFT : "0",
                            alignItems: "center", fontSize: 12,
                          }}>
                            <span style={{ color: M3.INK_2 }}>{c.label}</span>
                            <span style={{ fontFamily: M3.FONT_MONO, fontSize: 12, color: M3.INK_3 }}>{c.target}</span>
                            <span style={{ fontFamily: M3.FONT_MONO, fontSize: 12, color: c.ok ? M3.OK : M3.BAD }}>{c.actual}</span>
                            <span style={{ textAlign: "right", color: c.ok ? M3.OK : M3.BAD, fontFamily: M3.FONT_MONO, fontSize: 12, fontWeight: 700 }}>
                              {c.ok ? "✓" : "✗"}
                            </span>
                          </div>
                        );
                      })}
                    </div>
                  </div>
                </div>
              </div>
            );
          })() : null}

          <_MSNpiGantt gates={gates} />

          {/* deliverable links */}
          <div style={Object.assign(m3Card({ marginTop: 18 }), {})}>
            <div style={m3MonoSmall({ marginBottom: 10 })}>NPI dossier links</div>
            <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 8 }}>
              {gates.map(function (g) {
                return (
                  <div key={g.id}
                    role="button"
                    tabIndex={0}
                    onClick={function () { fireFlash("NPI-MTL-220-D-" + g.id + " gate detail queued — v2"); }}
                    onKeyDown={function (e) { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); fireFlash("NPI-MTL-220-D-" + g.id + " gate detail queued — v2"); } }}
                    style={{
                      display: "flex", justifyContent: "space-between",
                      padding: "8px 10px", border: "1px dashed " + M3.LINE,
                      borderRadius: 2, background: M3.BG_3, cursor: "pointer",
                    }}>
                    <div>
                      <div style={{ fontFamily: M3.FONT_MONO, fontSize: 12, color: M3.INK_4 }}>NPI-MTL-220-D-{g.id}</div>
                      <div style={{ fontSize: 12, color: M3.INK_2 }}>{g.name}</div>
                    </div>
                    <span style={{ fontFamily: M3.FONT_MONO, fontSize: 12, color: M3.COOL, alignSelf: "center" }}>open ↗</span>
                  </div>
                );
              })}
            </div>
          </div>
        </div>

        <_MSNpiRail wo={wo} gatePassed={gatePassed} fireFlash={fireFlash} />
      </div>
      {flash ? (
        <div style={_M3_FLASH_STYLE}>{flash}</div>
      ) : null}
    </div>
  );
}

// ─────────────────────────────────────────── M-S18 data
const _MS_NPI_GATES = [
  {
    id: "G1", name: "Design Review",
    owner: "R. Vogt (Eng)",
    target: "2025-11-18",
    forecast: "2025-11-15",
    status: "passed",
    progress_pct: 100,
    iso_clause: "ISO 9001 §8.3.4 · Design verification",
    iatf: "IATF 16949 §8.3.4 · Cross-functional approach",
    deliverables: [
      "Rev D drawing pack — granite ±3 µm clauses",
      "Tolerance stack-up vs. Rev C",
      "ECO-MS-0017 raised + impact analysis",
      "DFMEA refresh on granite-bed substructure",
    ],
    kpi_label: "Toleranz-Drift",
    kpi_value: "±3 µm",
    criteria: [
      { label: "DFMEA RPN reduction", target: "≥ 20 %", actual: "27 %", ok: true },
      { label: "GD&T review sign-offs", target: "3 disciplines", actual: "3 / 3", ok: true },
      { label: "Cost delta vs. Rev C", target: "< €1 000 / unit", actual: "+" + ((typeof window !== "undefined" && window.forgeI18n && window.forgeI18n.formatMoneyMinor && window.forgeI18n.formatMoneyMinor("82000", "EUR")) || "€820"), ok: true },
      { label: "Lead-time delta", target: "≤ 30 d", actual: "+11 d", ok: true },
    ],
  },
  {
    id: "G2", name: "Prototype Approval",
    owner: "D. Becker (Mfg)",
    target: "2026-04-30",
    forecast: "2026-04-22",
    status: "passed",
    progress_pct: 100,
    iso_clause: "ISO 9001 §8.3.5 · Design and development outputs",
    iatf: "IATF 16949 §8.3.5.1 · Manufacturing process design output",
    deliverables: [
      "Pilot granite-bed unit machined to Rev D",
      "Lapping cell re-routed with re-grind operation",
      "Two-pass lapping recipe locked",
      "Process sheet PR-MTL-220-D-rev01 issued",
    ],
    kpi_label: "Re-Grind Cycle",
    kpi_value: "+11 d",
    criteria: [
      { label: "Pilot flatness · base 10.01", target: "≤ ±3 µm", actual: "1.8 µm RMS", ok: true },
      { label: "Pilot flatness · cross-beam 10.02", target: "≤ ±3 µm", actual: "2.1 µm RMS", ok: true },
      { label: "Lapping cycle time", target: "≤ 96 h", actual: "92 h", ok: true },
      { label: "Re-grind operator-cert", target: "≥ 2 trained", actual: "3 / 3 trained", ok: true },
    ],
  },
  {
    id: "G3", name: "Validation Test",
    owner: "S. Klein (Quality)",
    target: "2026-07-10",
    forecast: "2026-07-08",
    status: "in-progress",
    progress_pct: 72,
    iso_clause: "ISO 9001 §8.3.6 · Design and development changes · validation",
    iatf: "IATF 16949 §9.1.1 · Monitoring and measurement of processes",
    deliverables: [
      "ISO 230-1 / 230-2 geometric performance test",
      "Vibration acceptance test 0.8 mm/s RMS @ 12 k rpm",
      "Ballbar trace at finish lapping",
      "CMM gauge R&R per AIAG-MSA",
    ],
    kpi_label: "Vibration",
    kpi_value: "0.52 mm/s",
    criteria: [
      { label: "ISO 230-2 axial straightness", target: "≤ 4 µm / 1 m", actual: "2.4 µm", ok: true },
      { label: "Ballbar circularity", target: "≤ 6 µm", actual: "4.1 µm", ok: true },
      { label: "Vibration @ 12 k rpm contouring", target: "≤ 0.8 mm/s", actual: "0.52 mm/s", ok: true },
      { label: "CMM gauge R&R %TV", target: "≤ 10 %", actual: "12 %", ok: false },
    ],
  },
  {
    id: "G4", name: "FAI",
    owner: "S. Klein (Quality)",
    target: "2026-08-14",
    forecast: "2026-08-14",
    status: "open",
    progress_pct: 18,
    iso_clause: "ISO 9001 §8.5.1 · Control of production · first-piece",
    iatf: "IATF 16949 PPAP Level 3 · re-submission for ECO-MS-0017",
    deliverables: [
      "AS9102 form set adapted to IATF template",
      "Dimensional report from Zeiss UPMC CMM",
      "Geometric performance report per ISO 230-2",
      "Control-system FAT log (Heidenhain TNC 640)",
      "Signed traveler TRV-MS-0244",
    ],
    kpi_label: "FAI-Termin",
    kpi_value: "2026-08-14",
    criteria: [
      { label: "AS9102 forms 1 / 2 / 3 complete", target: "all 3", actual: "1 / 3", ok: false },
      { label: "Dimensional report features", target: "all CTQ", actual: "in-prep", ok: false },
      { label: "PPAP Level 3 dossier", target: "Tier-1 packet", actual: "in-prep", ok: false },
      { label: "MAHLE witness slot", target: "scheduled", actual: "scheduled 2026-08-14", ok: true },
    ],
  },
  {
    id: "G5", name: "Production Release",
    owner: "D. Becker (Mfg)",
    target: "2026-09-02",
    forecast: "2026-09-05",
    status: "open",
    progress_pct: 0,
    iso_clause: "ISO 9001 §8.5.6 · Control of changes",
    iatf: "IATF 16949 §8.5.6.1 · Production release authority",
    deliverables: [
      "Series production order for 24-unit batch released",
      "Supplier release notes to GMN, Heidenhain, Kessler",
      "MES line balancing for granite cell with re-grind op",
      "ACCEPTANCE_SIGNED ledger event after first 12-unit batch",
    ],
    kpi_label: "Stückzahl",
    kpi_value: "24 Stk.",
    criteria: [
      { label: "Frame contract sign-off", target: "MAHLE", actual: "signed 2025-09", ok: true },
      { label: "Long-lead PO release", target: "T-180 d", actual: "released 2026-01", ok: true },
      { label: "Granite cell capacity", target: "≥ 4 units / month", actual: "4.2 / month", ok: true },
      { label: "All G1–G4 cleared", target: "yes", actual: "G3 in-prog · G4 open", ok: false },
    ],
  },
];

function _MSNpiRail({ wo, gatePassed, fireFlash }) {
  return (
    <div>
      <div style={m3Card()}>
        <div style={m3MonoSmall({ marginBottom: 8 })}>Pilot status</div>
        <div style={{ fontSize: 12, marginBottom: 8 }}>
          <div style={{ display: "flex", justifyContent: "space-between", marginBottom: 4 }}>
            <span>SKU</span><span style={{ fontFamily: M3.FONT_MONO }}>{wo.sku || "MTL-220.00.00"}</span>
          </div>
          <div style={{ display: "flex", justifyContent: "space-between", marginBottom: 4 }}>
            <span>Batch</span><span style={{ fontFamily: M3.FONT_MONO }}>24 Stk. (2 × 12)</span>
          </div>
          <div style={{ display: "flex", justifyContent: "space-between", marginBottom: 4 }}>
            <span>Customer</span><span style={{ fontFamily: M3.FONT_MONO, fontSize: 12 }}>MAHLE Industriebet.</span>
          </div>
          <div style={{ display: "flex", justifyContent: "space-between", marginBottom: 4 }}>
            <span>Started</span><span style={{ fontFamily: M3.FONT_MONO, fontSize: 12 }}>2025-09</span>
          </div>
          <div style={{ display: "flex", justifyContent: "space-between" }}>
            <span>Target close</span><span style={{ fontFamily: M3.FONT_MONO, fontSize: 12 }}>{(window.forgeI18n && window.forgeI18n.formatDate && window.forgeI18n.formatDate("2026-08-14")) || "2026-08-14"}</span>
          </div>
        </div>
      </div>

      <div style={Object.assign(m3Card({ marginTop: 12 }), {})}>
        <div style={m3MonoSmall({ marginBottom: 8 })}>ECO-MS-0017 · live</div>
        <div style={{ fontSize: 12, color: M3.INK_3, marginBottom: 8, lineHeight: 1.5 }}>
          Granite-bed flatness tightened from <span style={{ fontFamily: M3.FONT_MONO, color: M3.INK_2 }}>±5 µm</span> to <span style={{ fontFamily: M3.FONT_MONO, color: M3.INK_2 }}>±3 µm</span>. Cost +<span style={{ fontFamily: M3.FONT_MONO, color: M3.INK_2 }}>{(window.forgeI18n && window.forgeI18n.formatMoneyMinor && window.forgeI18n.formatMoneyMinor("82000", "EUR")) || "€820"}</span> / unit. Lead +<span style={{ fontFamily: M3.FONT_MONO, color: M3.INK_2 }}>11 d</span> on granite path.
        </div>
        <button onClick={function () { if (typeof navigate === "function") navigate("/build/eco"); }}
          style={_M3_ECO_BTN_STYLE}>ECO-Detail öffnen →</button>
      </div>

      <div style={Object.assign(m3Card({ marginTop: 12 }), {})}>
        <div style={m3MonoSmall({ marginBottom: 8 })}>Convert pilot to series</div>
        <div style={{ fontSize: 12, color: M3.INK_3, marginBottom: 10 }}>
          Once G1–G4 clear and PPAP Level 3 lands at MAHLE, this WO can be promoted to a 24-unit series production order with cloned routing &amp; gates.
        </div>
        <button
          disabled={gatePassed < 4}
          onClick={gatePassed >= 4 ? function () {
            var _q = function (err) { return err && (err.status === 404 || err.status === 400); };
            var woId = (wo && wo.id) || "";
            if (window.forgeApi && typeof window.forgeApi.mutate === "function") {
              window.forgeApi.mutate("work_orders", { op: "replace", path: "/" + encodeURIComponent(woId) + "/status", value: "pilot-passed" })
                .then(function () { fireFlash("Pilot promoted · " + woId + " → series"); })
                .catch(function (err) { fireFlash(_q(err) ? "Pilot promoted · " + woId + " queued" : "Promote failed · " + (err && err.message || "unknown")); });
            } else {
              fireFlash("Pilot promoted · " + woId + " queued");
            }
          } : undefined}
          style={_m3SeriesBtnStyle(gatePassed)}>
          Convert to series →
        </button>
        {gatePassed < 4 ? (
          <div style={{ marginTop: 8, fontSize: 12, color: M3.INK_4, lineHeight: 1.4 }}>
            Disabled · {gatePassed} / 4 gates passed before FAI window.
          </div>
        ) : null}
      </div>

      <div style={Object.assign(m3Card({ marginTop: 12 }), { background: M3.BG_2 })}>
        <div style={m3MonoSmall({ marginBottom: 6 })}>Risk register</div>
        <div style={{ fontSize: 12, color: M3.INK_3, lineHeight: 1.55 }}>
          <div>• GMN spindle <span style={{ fontFamily: M3.FONT_MONO }}>180 d</span>: single source.</div>
          <div>• CMM gauge R&amp;R <span style={{ fontFamily: M3.FONT_MONO, color: M3.WARN }}>%TV 12 %</span> on G3.</div>
          <div>• Heidenhain TNC 640 FW 5.2: slot in pre-FAT.</div>
          <div>• Granite re-grind operator-cert at 3 / 3.</div>
        </div>
      </div>
    </div>
  );
}

function _MSNpiGantt({ gates }) {
  return (
    <div style={Object.assign(m3Card({ marginTop: 18 }), {})}>
      <div style={m3MonoSmall({ marginBottom: 10 })}>Programm-Gantt · 12-Monat MTL-220 Rev D</div>
      <div style={{ position: "relative", height: 90, padding: "10px 0" }}>
        {(["09", "10", "11", "12", "01", "02", "03", "04", "05", "06", "07", "08"]).map(function (m, i) {
          const x = (i / 11) * 100;
          return (
            <div key={m} style={{
              position: "absolute", left: x + "%", top: 0,
              fontFamily: M3.FONT_MONO, fontSize: 12, color: M3.INK_4,
            }}>{m}</div>
          );
        })}
        <div style={{ position: "absolute", left: 0, right: 0, top: 22, height: 1, background: M3.LINE_SOFT }} />

        {[
          { l: 0,   w: 8,  c: M3.OK,    n: "Frame · MAHLE" },
          { l: 14,  w: 12, c: M3.WARN,  n: "Eng Rev D + ECO-MS-0017" },
          { l: 32,  w: 18, c: M3.ACCENT,n: "Long-lead · spindle 180 d" },
          { l: 48,  w: 16, c: M3.WARN,  n: "Granite re-grind qual" },
          { l: 70,  w: 14, c: M3.COOL,  n: "First article assy" },
          { l: 82,  w: 8,  c: M3.OK,    n: "FAI / PPAP L3" },
        ].map(function (b, bi) {
          return (
            <div key={bi} style={_m3GanttBarStyle(b)}>
              <span style={_M3_GANTT_BAR_LABEL_STYLE}>{b.n}</span>
            </div>
          );
        })}

        <div style={{
          position: "absolute", left: "85%", top: 30, height: 18,
          width: 2, background: M3.BAD,
        }} />
        <div style={{
          position: "absolute", left: "85%", top: 54, fontSize: 12,
          fontFamily: M3.FONT_MONO, color: M3.BAD, transform: "translateX(-32px)",
        }}>FAI · 2026-08-14</div>

        {gates.map(function (g, gi) {
          const x = [10, 24, 60, 85, 92][gi];
          return (
            <div key={g.id} style={{
              position: "absolute", left: x + "%", top: 70, transform: "translateX(-50%)",
              textAlign: "center",
            }}>
              <div style={{
                width: 10, height: 10, borderRadius: 999,
                background: m3StatusTone(g.status), border: "2px solid " + M3.BG,
                margin: "0 auto",
              }} />
              <div style={{ fontFamily: M3.FONT_MONO, fontSize: 12, color: M3.INK_3, marginTop: 2 }}>{g.id}</div>
            </div>
          );
        })}
      </div>
    </div>
  );
}

// ─────────────────────────────────────────── M-S19: ScreenMittelstandPFMEA
function ScreenMittelstandPFMEA() {
  const tenant = useActiveTenant();
  const tenantPfmea = (tenant && tenant.pfmea) || [];
  const stations = (tenant && tenant.routing) || [];

  // Granite-flatness failure tree — anchors the whole panel. Pulls from
  // tenant.pfmea if Mittelstand fixtures provide one; otherwise we fall back
  // to a baked-in granite-flatness tree pointing at ECO-MS-0017.
  const pfmea = uMM3(function () {
    if (tenantPfmea && tenantPfmea.length) return tenantPfmea;
    return [
      { mode: "Granite base flatness drift > ±3 µm",       effect: "Vibration > 0.8 mm/s RMS @ 12 k rpm contouring",        cause: "Single-pass lapping, cross-beam thermal cycle",           S: 9, O: 4, D: 4, RPN: 144, current_control: "QG-3 lapping flatness check (3 µm probe)",   recommended_action: "Two-pass lap + re-grind between rough and finish (ECO-MS-0017)" },
      { mode: "Cross-beam flatness drift > ±3 µm",          effect: "Y-axis bridge geometry off · ISO 230-2 fail",            cause: "Cross-beam casting porosity, asymmetric clamp force",     S: 9, O: 3, D: 4, RPN: 108, current_control: "QG-3 surface plate + dye penetrant",          recommended_action: "Tighten clamp torque envelope; add CMM scan post-rough" },
      { mode: "Spindle nose run-out > 4 µm TIR @ 24 k rpm", effect: "Auto-NCR at SUBASSY · re-mount spindle module",           cause: "Bearing pre-load drift after thermal soak",               S: 8, O: 2, D: 3, RPN: 48,  current_control: "Faro Vantage tracker @ QG-4",                 recommended_action: "Soak 24 h before TIR check; PPAP-controlled lot trace" },
      { mode: "C-axis indexer · alignment drift > 0.005°",  effect: "Five-axis kinematic loss · contour surface band",         cause: "Kessler tilting head re-installation drift after service", S: 7, O: 2, D: 4, RPN: 56,  current_control: "Laser tracker check at every service",        recommended_action: "Re-cert C-axis after every spindle swap + monthly" },
      { mode: "Linear encoder LC 415 · drift > 2 µm / m",   effect: "Position loop error · Cmk drop on bore Ø80 H6",           cause: "Encoder thermal drift after long warm-up",                S: 7, O: 3, D: 5, RPN: 105, current_control: "PTB-traceable cert · QG-2",                   recommended_action: "30-min warm-up cycle locked; thermal log" },
      { mode: "Hydrostatic guide pressure loss > 0.5 bar",   effect: "Y / Z carriage friction → axis rub → wear",              cause: "Bosch Rexroth pressure regulator deviation",              S: 6, O: 3, D: 4, RPN: 72,  current_control: "Pressure switch + alarm @ QG-2",              recommended_action: "Quarterly regulator service; redundant sensor" },
      { mode: "TNC 640 FW mismatch · feed-forward gain",     effect: "Contour band on high-feed contouring",                   cause: "Heidenhain firmware swap without parameter capture",       S: 7, O: 2, D: 6, RPN: 84,  current_control: "FW SHA-256 verified at receiving",            recommended_action: "Lock parameter set; FAT before swap" },
      { mode: "ATC carousel mis-pocket-pickup",              effect: "Tool collision · loss of part",                           cause: "DMG Mori ATC sensor drift, dirt in pocket",                S: 9, O: 2, D: 3, RPN: 54,  current_control: "Dual-sensor + visual at tool-change",         recommended_action: "Add laser pocket-confirm; PM cycle 1 / 2 wk" },
      { mode: "Coolant flow drop on finish lapping",         effect: "Granite surface micro-burn, flatness bust",               cause: "Filter clogging, pump cavitation",                         S: 8, O: 4, D: 3, RPN: 96,  current_control: "Flow-meter alarm at QG-3",                    recommended_action: "Schedule filter swap weekly during ECO-MS-0017 window" },
      { mode: "Machine enclosure · door interlock fail",     effect: "CE / Machinery Directive non-compliant ship-hold",        cause: "Operator wear on interlock latch",                         S: 6, O: 2, D: 2, RPN: 24,  current_control: "Daily visual + pre-power BIST",               recommended_action: "Replacement schedule every 18 mo" },
      { mode: "Sinamics drive · torque ripple > 3 %",        effect: "Surface roughness Ra drift on Y-axis carriage",           cause: "Siemens drive parameter set out of date",                  S: 6, O: 3, D: 4, RPN: 72,  current_control: "Servo trace at FAT @ QG-4",                   recommended_action: "Annual parameter review with Siemens FW release" },
      { mode: "Granite blank COC missing flatness cert",     effect: "RECEIVING_LOGGED held · CAPA opened",                     cause: "Foundry shipped without cert page 2",                      S: 5, O: 3, D: 1, RPN: 15,  current_control: "Receiving operator check @ QG-1",             recommended_action: "Foundry portal upload + checksum" },
    ];
  }, [tenantPfmea]);

  const [sortKey, setSortKey] = uSM3("RPN");
  const [sortDir, setSortDir] = uSM3("desc");
  const [rpnFilter, setRpnFilter] = uSM3("all");        // all | crit (>=100) | mid (50-99) | low (<50)
  const [stationFilter, setStationFilter] = uSM3("all"); // station id or "all"
  const [sevFilter, setSevFilter] = uSM3(false);         // S>=7
  const [active, setActive] = uSM3(null);
  const [addOpen, setAddOpen] = uSM3(false);
  const [draftMode, setDraftMode] = uSM3({ mode: "", effect: "", cause: "", S: 5, O: 3, D: 4 });
  const [extraModes, setExtraModes] = uSM3([]);
  const [flash, setFlash] = uSM3(null);
  const m3FlashTimer = React.useRef(null);
  function fireFlash(msg) {
    setFlash(msg);
    if (m3FlashTimer.current) clearTimeout(m3FlashTimer.current);
    m3FlashTimer.current = setTimeout(function () {
      m3FlashTimer.current = null;
      setFlash(null);
    }, 1600);
  }
  uEM3(function () {
    return function () {
      if (m3FlashTimer.current) { clearTimeout(m3FlashTimer.current); m3FlashTimer.current = null; }
    };
  }, []);

  // reset local UI state when tenant switches
  uEM3(function () {
    if (typeof window === "undefined") return;
    function onTenantChange() {
      setSortKey("RPN");
      setSortDir("desc");
      setRpnFilter("all");
      setStationFilter("all");
      setSevFilter(false);
      setActive(null);
      setAddOpen(false);
      setExtraModes([]);
      setFlash(null);
    }
    window.addEventListener("forge:tenant-change", onTenantChange);
    return function () { window.removeEventListener("forge:tenant-change", onTenantChange); };
  }, []);

  // map mode → stations (heuristic by keywords for the mittelstand cell)
  function modeStations(mode) {
    const txt = (mode.mode + " " + mode.cause + " " + (mode.current_control || "")).toLowerCase();
    const out = [];
    if (/granite|lapping|flatness|cross-beam|re-grind/.test(txt)) out.push("s2");
    if (/spindle|run-out|hsk|bearing|tir/.test(txt)) out.push("s3");
    if (/encoder|tnc|firmware|sinamics|drive|servo|atc|c-axis|kinematic/.test(txt)) out.push("s4");
    if (/coolant|filter|enclosure|interlock|machinery directive/.test(txt)) out.push("s4");
    if (/cmm|gauge|ballbar|iso 230|fai|witness/.test(txt)) out.push("s4");
    if (/receiving|coc|cert|incoming/.test(txt)) out.push("s1");
    if (out.length === 0) out.push("s2");
    return Array.from(new Set(out));
  }

  // map mode → quality gates (heuristic by current control text)
  function modeGates(mode) {
    const txt = (mode.current_control || "").toUpperCase();
    const re = /QG-\d+/g;
    return txt.match(re) || [];
  }

  // map mode → ECO link — anchor everything granite to ECO-MS-0017
  function modeEcos(mode) {
    const txt = (mode.mode + " " + mode.cause + " " + (mode.recommended_action || "")).toLowerCase();
    const out = [];
    if (/granite|cross-beam|flatness|lapping|re-grind|coolant.*lapping|finish lap/.test(txt)) out.push("ECO-MS-0017");
    if (/eco-ms-/.test(txt)) {
      const m = txt.match(/eco-ms-\d+/g);
      if (m) m.forEach(function (e) { if (out.indexOf(e.toUpperCase()) < 0) out.push(e.toUpperCase()); });
    }
    return out;
  }

  const allModes = uMM3(function () { return pfmea.concat(extraModes); }, [pfmea, extraModes]);

  const filtered = uMM3(function () {
    let arr = allModes.slice().map(function (m, i) {
      return Object.assign({}, m, {
        _ix: i,
        _stations: modeStations(m),
        _gates: modeGates(m),
        _ecos: modeEcos(m),
      });
    });
    if (rpnFilter === "crit") arr = arr.filter(function (m) { return m.RPN >= 100; });
    else if (rpnFilter === "mid") arr = arr.filter(function (m) { return m.RPN >= 50 && m.RPN < 100; });
    else if (rpnFilter === "low") arr = arr.filter(function (m) { return m.RPN < 50; });
    if (stationFilter !== "all") arr = arr.filter(function (m) { return m._stations.indexOf(stationFilter) >= 0; });
    if (sevFilter) arr = arr.filter(function (m) { return m.S >= 7; });
    arr.sort(function (a, b) {
      const av = a[sortKey], bv = b[sortKey];
      if (typeof av === "number") return sortDir === "asc" ? av - bv : bv - av;
      return sortDir === "asc" ? String(av).localeCompare(String(bv)) : String(bv).localeCompare(String(av));
    });
    return arr;
  }, [allModes, rpnFilter, stationFilter, sevFilter, sortKey, sortDir]);

  function rpnColor(rpn) {
    if (rpn >= 100) return M3.BAD;
    if (rpn >= 50) return M3.WARN;
    return M3.OK;
  }

  function toggleSort(k) {
    if (sortKey === k) setSortDir(sortDir === "asc" ? "desc" : "asc");
    else { setSortKey(k); setSortDir(k === "mode" ? "asc" : "desc"); }
  }

  function stationName(id) {
    const st = stations.find(function (s) { return s.id === id; });
    return st ? st.name : id;
  }

  function commitDraft() {
    const S = parseInt(draftMode.S, 10) || 0;
    const O = parseInt(draftMode.O, 10) || 0;
    const D = parseInt(draftMode.D, 10) || 0;
    if (!draftMode.mode || !draftMode.effect) {
      fireFlash("Mode + effect required");
      return;
    }
    const next = {
      mode: draftMode.mode, effect: draftMode.effect, cause: draftMode.cause,
      S: S, O: O, D: D, RPN: S * O * D,
      current_control: "—",
      recommended_action: "—",
    };
    setExtraModes(extraModes.concat([next]));
    setDraftMode({ mode: "", effect: "", cause: "", S: 5, O: 3, D: 4 });
    setAddOpen(false);
    fireFlash("Mode added · RPN " + next.RPN);
  }

  const root = {
    background: M3.BG, color: M3.INK, minHeight: "100%",
    fontFamily: M3.FONT_UI, fontSize: 13, padding: "20px 28px 60px",
  };

  const colTpl = "1.6fr 1.6fr 1.4fr 36px 36px 36px 60px 1.3fr 1.3fr 110px";

  const sortHead = function (k, label) {
    const isActive = sortKey === k;
    return (
      <div role="button" tabIndex={0} onClick={function () { toggleSort(k); }} onKeyDown={function (e) { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); toggleSort(k); } }} style={{
        cursor: "pointer", display: "flex", alignItems: "center", gap: 4,
        color: isActive ? M3.INK : M3.INK_3,
      }}>
        {label}
        {isActive ? <span style={{ fontSize: 12 }}>{sortDir === "asc" ? "▲" : "▼"}</span> : null}
      </div>
    );
  };

  // failure tree visualisation — granite as the trunk, branches at ECO-MS-0017
  const granitePrimary = filtered.filter(function (m) { return /granite|flatness|lapping|cross-beam|re-grind|coolant.*lap/i.test(m.mode + " " + m.cause); }).slice(0, 6);

  return (
    <div style={root}>
      {/* header */}
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "baseline", marginBottom: 14 }}>
        <div>
          <div style={m3MonoSmall({ marginBottom: 4 })}>build:// pfmea · MTL-220 Rev D</div>
          <div style={{ fontFamily: M3.FONT_SER, fontSize: 26, letterSpacing: "-0.01em" }}>
            Process FMEA · {allModes.length} Modi
          </div>
          <div style={{ color: M3.INK_3, fontSize: 12, marginTop: 4 }}>
            S × O × D → RPN · Modes ≥ 100 require live containment · root cluster anchors to ECO-MS-0017
          </div>
        </div>
        <div style={{ display: "flex", gap: 8 }}>
          <button onClick={function () {
            if (window.forgeApi && typeof window.forgeApi.exportAuditUrl === "function") {
              window.location.href = window.forgeApi.exportAuditUrl("csv");
              fireFlash("PFMEA export started · audit CSV");
            } else {
              fireFlash("Export PFMEA queued — v2");
            }
          }} style={_M3_HEADER_GHOST_BTN_STYLE}>Export PFMEA</button>
          <button onClick={function () { setAddOpen(true); }} style={_M3_HEADER_INK_BTN_STYLE}>+ Modus hinzufügen</button>
        </div>
      </div>

      <_MSPfmeaTree granitePrimary={granitePrimary} rpnColor={rpnColor} />

      <_MSPfmeaFilterChips rpnFilter={rpnFilter} setRpnFilter={setRpnFilter} sortKey={sortKey} sortDir={sortDir} toggleSort={toggleSort} stations={stations} stationFilter={stationFilter} setStationFilter={setStationFilter} sevFilter={sevFilter} setSevFilter={setSevFilter} filtered={filtered} allModes={allModes} />

      <div style={{ display: "grid", gridTemplateColumns: active ? "1fr 360px" : "1fr", gap: 18 }}>
        {/* table */}
        <div style={Object.assign(m3Card(), { padding: 0, overflow: "hidden" })}>
          <div style={_m3PfmeaHeaderStyle(colTpl)}>
            {sortHead("mode", "Mode")}
            {sortHead("effect", "Effect")}
            {sortHead("cause", "Cause")}
            {sortHead("S", "S")}
            {sortHead("O", "O")}
            {sortHead("D", "D")}
            {sortHead("RPN", "RPN")}
            <div>Current control</div>
            <div>Recommended action</div>
            <div>ECO link</div>
          </div>
          {filtered.map(function (m, i) {
            const sel = active && active._ix === m._ix;
            return (
              <div key={m._ix}
                role="button"
                tabIndex={0}
                onClick={function () { setActive(m); }}
                onKeyDown={function (e) { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); setActive(m); } }}
                style={_m3PfmeaRowStyle(colTpl, sel, i >= filtered.length - 1)}>
                <div style={{ fontSize: 12, fontWeight: 600, color: M3.INK }}>{m.mode}</div>
                <div style={{ fontSize: 12, color: M3.INK_2 }}>{m.effect}</div>
                <div style={{ fontSize: 12, color: M3.INK_3 }}>{m.cause}</div>
                <div style={{ fontFamily: M3.FONT_MONO, fontSize: 12, textAlign: "center", color: m.S >= 7 ? M3.BAD : M3.INK_2 }}>{m.S}</div>
                <div style={{ fontFamily: M3.FONT_MONO, fontSize: 12, textAlign: "center", color: M3.INK_2 }}>{m.O}</div>
                <div style={{ fontFamily: M3.FONT_MONO, fontSize: 12, textAlign: "center", color: M3.INK_2 }}>{m.D}</div>
                <div style={_m3RpnBadgeStyle(m.RPN)}>{m.RPN}</div>
                <div style={{ fontSize: 12, color: M3.INK_3 }}>{m.current_control}</div>
                <div style={{ fontSize: 12, color: M3.INK_3 }}>{m.recommended_action}</div>
                <div style={{ display: "flex", flexWrap: "wrap", gap: 3 }}>
                  {(m._ecos || []).length ? m._ecos.map(function (e) {
                    return (
                      <span key={e} style={{
                        fontFamily: M3.FONT_MONO, fontSize: 12, padding: "2px 6px",
                        borderRadius: 2, color: M3.ACCENT, background: M3.BG_3,
                        border: "1px solid " + M3.ACCENT,
                      }}>{e}</span>
                    );
                  }) : <span style={{ fontFamily: M3.FONT_MONO, fontSize: 12, color: M3.INK_4 }}>-</span>}
                </div>
              </div>
            );
          })}
          {filtered.length === 0 ? (
            <div style={{ padding: 24, textAlign: "center", color: M3.INK_4 }}>
              No PFMEA entries match the current filter.
            </div>
          ) : null}
        </div>

        {active ? <_MSPfmeaDetailPanel active={active} setActive={setActive} rpnColor={rpnColor} stationName={stationName} /> : null}
      </div>

      <_MSPfmeaFooterSummary allModes={allModes} modeEcos={modeEcos} />

      {addOpen ? <_MSPfmeaAddModal draftMode={draftMode} setDraftMode={setDraftMode} setAddOpen={setAddOpen} commitDraft={commitDraft} rpnColor={rpnColor} /> : null}

      {flash ? (
        <div style={_M3_FLASH_STYLE}>{flash}</div>
      ) : null}
    </div>
  );
}

// ─────────────────────────────────────────── M-S19 helpers
function _MSPfmeaFilterChips({ rpnFilter, setRpnFilter, sortKey, sortDir, toggleSort, stations, stationFilter, setStationFilter, sevFilter, setSevFilter, filtered, allModes }) {
  return (
    <div style={Object.assign(m3Card({ marginBottom: 16 }), { display: "flex", gap: 14, flexWrap: "wrap", alignItems: "center" })}>
      <div style={{ display: "flex", gap: 6, alignItems: "center" }}>
        <span style={m3MonoSmall()}>RPN</span>
        {[["all", "all"], ["crit", "≥ 100"], ["mid", "50–99"], ["low", "< 50"]].map(function (pair) {
          const k = pair[0], l = pair[1], active = rpnFilter === k;
          const tone = k === "crit" ? M3.BAD : k === "mid" ? M3.WARN : k === "low" ? M3.OK : M3.INK_2;
          return (
            <button key={k} onClick={function () { setRpnFilter(k); }} style={_m3PillBtnStyle(active, tone, tone, M3.BG)}>{l}</button>
          );
        })}
      </div>
      <div style={{ display: "flex", gap: 6, alignItems: "center" }}>
        <span style={m3MonoSmall()}>Sort</span>
        {[["RPN", "RPN"], ["S", "Severity"], ["O", "Occurrence"], ["D", "Detection"], ["mode", "Mode"]].map(function (pair) {
          const k = pair[0], l = pair[1], active = sortKey === k;
          return (
            <button key={k} onClick={function () { toggleSort(k); }} style={_m3PillBtnStyle(active, M3.INK, M3.INK, M3.BG)}>{l} {active ? (sortDir === "asc" ? "▲" : "▼") : ""}</button>
          );
        })}
      </div>
      <div style={{ display: "flex", gap: 6, alignItems: "center" }}>
        <span style={m3MonoSmall()}>Station</span>
        <select id="ms-act3-f5" name="ms-act3-f5" value={stationFilter} onChange={function (e) { setStationFilter(e.target.value); }}
          style={{
            padding: "4px 8px", border: "1px solid " + M3.LINE,
            background: M3.BG_3, fontFamily: M3.FONT_MONO, fontSize: 12,
          }}>
          <option value="all">all</option>
          {stations.map(function (s) { return <option key={s.id} value={s.id}>{s.id} · {s.name}</option>; })}
          {stations.length === 0 ? (
            <React.Fragment>
              <option value="s1">s1 · Receiving</option>
              <option value="s2">s2 · Granite cell</option>
              <option value="s3">s3 · Subassy</option>
              <option value="s4">s4 · FAI</option>
            </React.Fragment>
          ) : null}
        </select>
      </div>
      <label style={{ display: "flex", gap: 6, alignItems: "center", cursor: "pointer" }}>
        <input id="ms-act3-f6" name="ms-act3-f6" type="checkbox" checked={sevFilter} onChange={function (e) { setSevFilter(e.target.checked); }} />
        <span style={m3MonoSmall()}>severity ≥ 7</span>
      </label>
      <div style={{ flex: 1 }} />
      <span style={{ fontFamily: M3.FONT_MONO, fontSize: 12, color: M3.INK_3 }}>
        {filtered.length} of {allModes.length} modes
      </span>
    </div>
  );
}

function _MSPfmeaFooterSummary({ allModes, modeEcos }) {
  return (
    <div style={Object.assign(m3Card({ marginTop: 18 }), {
      background: M3.BG_2, display: "grid", gridTemplateColumns: "repeat(5, 1fr)", gap: 18, textAlign: "center",
    })}>
      <div>
        <div style={m3MonoSmall()}>Modes ≥ 100 RPN</div>
        <div style={{ fontFamily: M3.FONT_MONO, fontSize: 22, fontWeight: 700, color: M3.BAD }}>
          {allModes.filter(function (m) { return m.RPN >= 100; }).length}
        </div>
      </div>
      <div>
        <div style={m3MonoSmall()}>Modes 50–99</div>
        <div style={{ fontFamily: M3.FONT_MONO, fontSize: 22, fontWeight: 700, color: M3.WARN }}>
          {allModes.filter(function (m) { return m.RPN >= 50 && m.RPN < 100; }).length}
        </div>
      </div>
      <div>
        <div style={m3MonoSmall()}>Severity ≥ 7</div>
        <div style={{ fontFamily: M3.FONT_MONO, fontSize: 22, fontWeight: 700, color: M3.BAD }}>
          {allModes.filter(function (m) { return m.S >= 7; }).length}
        </div>
      </div>
      <div>
        <div style={m3MonoSmall()}>Anchored to ECO-MS-0017</div>
        <div style={{ fontFamily: M3.FONT_MONO, fontSize: 22, fontWeight: 700, color: M3.ACCENT }}>
          {allModes.filter(function (m) { return modeEcos(m).indexOf("ECO-MS-0017") >= 0; }).length}
        </div>
      </div>
      <div>
        <div style={m3MonoSmall()}>Avg RPN</div>
        <div style={{ fontFamily: M3.FONT_MONO, fontSize: 22, fontWeight: 700, color: M3.INK_2 }}>
          {allModes.length ? Math.round(allModes.reduce(function (a, b) { return a + b.RPN; }, 0) / allModes.length) : 0}
        </div>
      </div>
    </div>
  );
}

function _MSPfmeaTree({ granitePrimary, rpnColor }) {
  return (
    <div style={Object.assign(m3Card({ marginBottom: 16 }), { padding: 0, overflow: "hidden" })}>
      <div style={{ padding: "10px 14px", borderBottom: "1px solid " + M3.LINE_SOFT, background: M3.BG_2, display: "flex", justifyContent: "space-between", alignItems: "center" }}>
        <span style={m3MonoSmall()}>Granite-flatness failure tree · ECO-MS-0017 anchor</span>
        <button onClick={function () { if (typeof navigate === "function") navigate("/build/eco"); }}
          style={_m3StationChipStyle(M3.ACCENT)}>ECO-MS-0017 ↗</button>
      </div>
      <div style={{ padding: 16 }}>
        <svg viewBox="0 0 900 200" width="100%" height="200" style={{ display: "block" }}>
          <line x1="60" y1="100" x2="290" y2="100" stroke={M3.INK_2} strokeWidth="2" />
          <rect x="60" y="80" width="220" height="40" fill={M3.BG_3} stroke={M3.BAD} strokeWidth="1.4" rx="3" />
          <text x="170" y="98" textAnchor="middle" fontFamily={M3.FONT_SER} fontSize="13" fill={M3.INK} fontWeight="600">Granite-bed flatness drift</text>
          <text x="170" y="112" textAnchor="middle" fontFamily={M3.FONT_MONO} fontSize="10" fill={M3.INK_3}>±5 µm → ±3 µm · Rev C → Rev D</text>
          <line x1="290" y1="100" x2="370" y2="100" stroke={M3.ACCENT} strokeWidth="2" strokeDasharray="4 3" />
          <rect x="370" y="84" width="140" height="32" fill={M3.ACCENT} rx="3" />
          <text x="440" y="98" textAnchor="middle" fontFamily={M3.FONT_MONO} fontSize="11" fill={M3.BG} fontWeight="600">ECO-MS-0017</text>
          <text x="440" y="110" textAnchor="middle" fontFamily={M3.FONT_MONO} fontSize="9" fill={M3.BG_2}>{"+" + ((window.forgeI18n && window.forgeI18n.formatMoneyMinor && window.forgeI18n.formatMoneyMinor("82000", "EUR")) || "€820") + " / unit · +11 d"}</text>
          {granitePrimary.map(function (m, i) {
            const yTop = 30 + i * 26;
            const tone = rpnColor(m.RPN);
            return (
              <g key={m.mode}>
                <line x1="510" y1="100" x2="600" y2={yTop} stroke={M3.LINE} strokeWidth="1" />
                <rect x="600" y={yTop - 10} width="270" height="20" fill={M3.BG_3} stroke={tone} strokeWidth="1" rx="2" />
                <circle cx="610" cy={yTop} r="4" fill={tone} />
                <text x="620" y={yTop + 3} fontFamily={M3.FONT_UI} fontSize="11" fill={M3.INK_2}>
                  {(m.mode || "").substring(0, 32)}{(m.mode || "").length > 32 ? "…" : ""}
                </text>
                <text x="860" y={yTop + 3} textAnchor="end" fontFamily={M3.FONT_MONO} fontSize="10" fill={tone} fontWeight="600">
                  RPN {m.RPN}
                </text>
              </g>
            );
          })}
          <line x1="510" y1="100" x2="600" y2="180" stroke={M3.BAD} strokeWidth="1.4" strokeDasharray="3 3" />
          <rect x="600" y="170" width="270" height="20" fill={M3.BG_3} stroke={M3.BAD} strokeWidth="1" rx="2" />
          <text x="620" y="183" fontFamily={M3.FONT_UI} fontSize="11" fill={M3.BAD} fontWeight="600">
            Effect · Vibration &gt; 0.8 mm/s RMS @ 12 k rpm
          </text>
        </svg>
        <div style={{ marginTop: 8, fontSize: 12, color: M3.INK_3, fontFamily: M3.FONT_MONO, lineHeight: 1.55 }}>
          Granite drift was the upstream root cause for 3 / 24 Rev C field returns.
          ECO-MS-0017 retires the single-pass lap; the new two-pass-with-re-grind
          sequence holds <span style={{ color: M3.INK_2 }}>±3 µm</span> across both <span style={{ color: M3.INK_2 }}>10.01</span> and <span style={{ color: M3.INK_2 }}>10.02</span>.
          All branches above retain mitigation owners; downstream effect monitored at FAI gate G4.
        </div>
      </div>
    </div>
  );
}

function _MSPfmeaDetailPanel({ active, setActive, rpnColor, stationName }) {
  return (
    <div style={m3Card()}>
      <div style={{ display: "flex", justifyContent: "space-between", marginBottom: 10 }}>
        <span style={m3MonoSmall()}>Mode detail</span>
        <button onClick={function () { setActive(null); }} style={{
          background: "transparent", border: 0, cursor: "pointer",
          color: M3.INK_4, fontFamily: M3.FONT_MONO, fontSize: 12,
        }}>✕</button>
      </div>
      <div style={{ fontFamily: M3.FONT_SER, fontSize: 18, lineHeight: 1.3, marginBottom: 10 }}>
        {active.mode}
      </div>
      <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: 8, marginBottom: 14 }}>
        <div style={{ textAlign: "center", padding: "8px 4px", background: M3.BG_2, borderRadius: 2 }}>
          <div style={m3MonoSmall()}>Severity</div>
          <div style={{ fontFamily: M3.FONT_MONO, fontSize: 22, fontWeight: 700, color: active.S >= 7 ? M3.BAD : M3.INK_2 }}>{active.S}</div>
        </div>
        <div style={{ textAlign: "center", padding: "8px 4px", background: M3.BG_2, borderRadius: 2 }}>
          <div style={m3MonoSmall()}>Occurrence</div>
          <div style={{ fontFamily: M3.FONT_MONO, fontSize: 22, fontWeight: 700, color: M3.INK_2 }}>{active.O}</div>
        </div>
        <div style={{ textAlign: "center", padding: "8px 4px", background: M3.BG_2, borderRadius: 2 }}>
          <div style={m3MonoSmall()}>Detection</div>
          <div style={{ fontFamily: M3.FONT_MONO, fontSize: 22, fontWeight: 700, color: M3.INK_2 }}>{active.D}</div>
        </div>
      </div>
      <div style={{ marginBottom: 12 }}>
        <div style={m3MonoSmall({ marginBottom: 4 })}>RPN</div>
        <div style={{ fontFamily: M3.FONT_MONO, fontSize: 30, fontWeight: 700, color: rpnColor(active.RPN) }}>
          {active.RPN}
        </div>
      </div>
      <div style={{ marginBottom: 12 }}>
        <div style={m3MonoSmall({ marginBottom: 4 })}>Effect</div>
        <div style={{ fontSize: 13, color: M3.INK_2 }}>{active.effect}</div>
      </div>
      <div style={{ marginBottom: 12 }}>
        <div style={m3MonoSmall({ marginBottom: 4 })}>Cause</div>
        <div style={{ fontSize: 13, color: M3.INK_2 }}>{active.cause}</div>
      </div>
      <div style={{ marginBottom: 12 }}>
        <div style={m3MonoSmall({ marginBottom: 4 })}>Current control</div>
        <div style={{ fontSize: 12, color: M3.INK_3 }}>{active.current_control}</div>
      </div>
      <div style={{ marginBottom: 14 }}>
        <div style={m3MonoSmall({ marginBottom: 4 })}>Recommended action</div>
        <div style={{ fontSize: 12, color: M3.INK_3 }}>{active.recommended_action}</div>
      </div>

      <div style={{ marginBottom: 14 }}>
        <div style={m3MonoSmall({ marginBottom: 6 })}>Stationen betroffen</div>
        <div style={{ display: "flex", gap: 6, flexWrap: "wrap" }}>
          {active._stations.map(function (sid) {
            return (
              <button key={sid}
                onClick={function () { if (typeof navigate === "function") navigate("/build/routing"); }}
                style={_m3StationChipStyle(M3.COOL)}>{sid} · {stationName(sid)} ↗</button>
            );
          })}
        </div>
      </div>

      <div style={{ marginBottom: 14 }}>
        <div style={m3MonoSmall({ marginBottom: 6 })}>Quality gates</div>
        <div style={{ display: "flex", gap: 6, flexWrap: "wrap" }}>
          {active._gates.length ? active._gates.map(function (g) {
            return (
              <button key={g}
                onClick={function () { if (typeof navigate === "function") navigate("/build/gates"); }}
                style={_m3StationChipStyle(M3.ACCENT)}>{g} ↗</button>
            );
          }) : <span style={{ fontSize: 12, color: M3.INK_4 }}>No gate currently mapped; recommend adding one.</span>}
        </div>
      </div>

      <div>
        <div style={m3MonoSmall({ marginBottom: 6 })}>ECO link</div>
        <div style={{ display: "flex", gap: 6, flexWrap: "wrap" }}>
          {(active._ecos || []).length ? active._ecos.map(function (e) {
            return (
              <button key={e}
                onClick={function () { if (typeof navigate === "function") navigate("/build/eco"); }}
                style={_m3StationChipStyle(M3.BAD)}>{e} ↗</button>
            );
          }) : <span style={{ fontSize: 12, color: M3.INK_4 }}>No ECO link.</span>}
        </div>
      </div>

      <div style={{ marginTop: 16, paddingTop: 12, borderTop: "1px solid " + M3.LINE_SOFT, fontSize: 12, color: M3.INK_4, lineHeight: 1.45 }}>
        ISO 9001 §8.5.1 control of production · IATF 16949 §10.2.4 corrective
        action. Modes ≥ 100 RPN inherit auto-CAPA per <span style={{ fontFamily: M3.FONT_MONO }}>WI-MS-Q-0034</span>.
      </div>
    </div>
  );
}

function _MSPfmeaAddModal({ draftMode, setDraftMode, setAddOpen, commitDraft, rpnColor }) {
  return (
    <div role="button" tabIndex={0} style={_M3_PFMEA_MODAL_BACKDROP}
      onClick={function (e) { if (e.target === e.currentTarget) setAddOpen(false); }}
      onKeyDown={function (e) { if (e.key === "Escape" || e.key === "Enter" || e.key === " ") { e.preventDefault(); setAddOpen(false); } }}>
      <div style={_M3_PFMEA_MODAL_PANEL}>
        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "baseline", marginBottom: 14 }}>
          <div style={{ fontFamily: M3.FONT_SER, fontSize: 18 }}>Neuen Modus erfassen</div>
          <button onClick={function () { setAddOpen(false); }}
            style={{ background: "transparent", border: 0, cursor: "pointer", color: M3.INK_4, fontFamily: M3.FONT_MONO, fontSize: 14 }}>✕</button>
        </div>
        <div style={{ display: "grid", gap: 10 }}>
          <div>
            <div style={m3MonoSmall({ marginBottom: 3 })}>Mode</div>
            <input id="ms-act3-f7" name="ms-act3-f7" aria-label="z. B. Granit-Querbalken Risse" value={draftMode.mode} onChange={function (e) { setDraftMode(Object.assign({}, draftMode, { mode: e.target.value })); }}
              placeholder="z. B. Granit-Querbalken Risse"
              style={{ width: "100%", padding: "6px 8px", border: "1px solid " + M3.LINE, fontSize: 13, background: M3.BG }} />
          </div>
          <div>
            <div style={m3MonoSmall({ marginBottom: 3 })}>Effect</div>
            <input id="ms-act3-f8" name="ms-act3-f8" aria-label="z. B. Y-Achsen-Brücke kippt" value={draftMode.effect} onChange={function (e) { setDraftMode(Object.assign({}, draftMode, { effect: e.target.value })); }}
              placeholder="z. B. Y-Achsen-Brücke kippt"
              style={{ width: "100%", padding: "6px 8px", border: "1px solid " + M3.LINE, fontSize: 13, background: M3.BG }} />
          </div>
          <div>
            <div style={m3MonoSmall({ marginBottom: 3 })}>Cause</div>
            <input id="ms-act3-f9" name="ms-act3-f9" aria-label="z. B. Gussporosität, asymmetrische Klemmkraft" value={draftMode.cause} onChange={function (e) { setDraftMode(Object.assign({}, draftMode, { cause: e.target.value })); }}
              placeholder="z. B. Gussporosität, asymmetrische Klemmkraft"
              style={{ width: "100%", padding: "6px 8px", border: "1px solid " + M3.LINE, fontSize: 13, background: M3.BG }} />
          </div>
          <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr 1fr", gap: 10 }}>
            {[["S", "Severity"], ["O", "Occurrence"], ["D", "Detection"]].map(function (pair) {
              const k = pair[0], l = pair[1];
              return (
                <div key={k}>
                  <div style={m3MonoSmall({ marginBottom: 3 })}>{l}</div>
                  <input id="ms-act3-f10" name="ms-act3-f10" type="number" min="1" max="10" value={draftMode[k]}
                    onChange={function (e) { const v = e.target.value; const n = Object.assign({}, draftMode); n[k] = v; setDraftMode(n); }}
                    style={{ width: "100%", padding: "6px 8px", border: "1px solid " + M3.LINE, fontSize: 13, background: M3.BG, fontFamily: M3.FONT_MONO }} />
                </div>
              );
            })}
            <div>
              <div style={m3MonoSmall({ marginBottom: 3 })}>RPN</div>
              <div style={{
                padding: "6px 8px", fontFamily: M3.FONT_MONO, fontSize: 16, fontWeight: 700,
                color: rpnColor((parseInt(draftMode.S, 10) || 0) * (parseInt(draftMode.O, 10) || 0) * (parseInt(draftMode.D, 10) || 0)),
              }}>
                {(parseInt(draftMode.S, 10) || 0) * (parseInt(draftMode.O, 10) || 0) * (parseInt(draftMode.D, 10) || 0)}
              </div>
            </div>
          </div>
        </div>
        <div style={{ marginTop: 18, display: "flex", justifyContent: "flex-end", gap: 8 }}>
          <button onClick={function () { setAddOpen(false); }} style={_M3_DIALOG_CANCEL_STYLE}>Abbrechen</button>
          <button onClick={commitDraft} style={_M3_DIALOG_SUBMIT_STYLE}>Hinzufügen →</button>
        </div>
      </div>
    </div>
  );
}

// ────────────────────────────────────────────────────── route registration
if (typeof registerMittelstandRoute === "function") {
  registerMittelstandRoute({ path: "/forge/suppliers",      mode: "forge", title: "Suppliers · Mittelstand",         renderer: function () { return React.createElement(ScreenMittelstandSuppliers); } });
  registerMittelstandRoute({ path: "/forge/vendor-onboard", mode: "forge", title: "Vendor onboarding",               renderer: function () { return React.createElement(ScreenMittelstandVendorOnboard); } });
  registerMittelstandRoute({ path: "/forge/audit",          mode: "forge", title: "Lieferanten-Audit",               renderer: function () { return React.createElement(ScreenMittelstandAudit); } });
  registerMittelstandRoute({ path: "/forge/npi",            mode: "forge", title: "NPI · Rev D plan",                renderer: function () { return React.createElement(ScreenMittelstandNPI); } });
  registerMittelstandRoute({ path: "/build/pfmea",          mode: "build", title: "PFMEA · granite-flatness tree",   renderer: function () { return React.createElement(ScreenMittelstandPFMEA); } });
}

if (typeof window !== "undefined") {
  window.ScreenMittelstandSuppliers     = ScreenMittelstandSuppliers;
  window.ScreenMittelstandVendorOnboard = ScreenMittelstandVendorOnboard;
  window.ScreenMittelstandAudit         = ScreenMittelstandAudit;
  window.ScreenMittelstandNPI           = ScreenMittelstandNPI;
  window.ScreenMittelstandPFMEA         = ScreenMittelstandPFMEA;
}
})();
