@memberjunction/ng-dashboards 5.46.0 → 5.47.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/dist/PredictiveStudio/at-risk.view-models.d.ts +33 -0
  2. package/dist/PredictiveStudio/at-risk.view-models.d.ts.map +1 -1
  3. package/dist/PredictiveStudio/at-risk.view-models.js +69 -2
  4. package/dist/PredictiveStudio/at-risk.view-models.js.map +1 -1
  5. package/dist/PredictiveStudio/components/ps-catalog.component.js +2 -2
  6. package/dist/PredictiveStudio/components/ps-catalog.component.js.map +1 -1
  7. package/dist/PredictiveStudio/components/ps-compare.component.js +2 -2
  8. package/dist/PredictiveStudio/components/ps-compare.component.js.map +1 -1
  9. package/dist/PredictiveStudio/components/ps-confirm-modal.component.js +2 -2
  10. package/dist/PredictiveStudio/components/ps-confirm-modal.component.js.map +1 -1
  11. package/dist/PredictiveStudio/components/ps-experiments.component.js +2 -2
  12. package/dist/PredictiveStudio/components/ps-experiments.component.js.map +1 -1
  13. package/dist/PredictiveStudio/components/ps-home.component.js +2 -2
  14. package/dist/PredictiveStudio/components/ps-home.component.js.map +1 -1
  15. package/dist/PredictiveStudio/components/ps-operate-dialog.component.js +2 -2
  16. package/dist/PredictiveStudio/components/ps-operate-dialog.component.js.map +1 -1
  17. package/dist/PredictiveStudio/components/ps-pipelines.component.d.ts.map +1 -1
  18. package/dist/PredictiveStudio/components/ps-pipelines.component.js +306 -297
  19. package/dist/PredictiveStudio/components/ps-pipelines.component.js.map +1 -1
  20. package/dist/PredictiveStudio/components/ps-production.component.js +2 -2
  21. package/dist/PredictiveStudio/components/ps-production.component.js.map +1 -1
  22. package/dist/PredictiveStudio/components/ps-registry.component.d.ts +1 -1
  23. package/dist/PredictiveStudio/components/ps-registry.component.d.ts.map +1 -1
  24. package/dist/PredictiveStudio/components/ps-registry.component.js +15 -9
  25. package/dist/PredictiveStudio/components/ps-registry.component.js.map +1 -1
  26. package/dist/PredictiveStudio/predictive-studio-agent-context.d.ts +115 -0
  27. package/dist/PredictiveStudio/predictive-studio-agent-context.d.ts.map +1 -0
  28. package/dist/PredictiveStudio/predictive-studio-agent-context.js +119 -0
  29. package/dist/PredictiveStudio/predictive-studio-agent-context.js.map +1 -0
  30. package/dist/PredictiveStudio/predictive-studio-copilot.view-models.d.ts +36 -0
  31. package/dist/PredictiveStudio/predictive-studio-copilot.view-models.d.ts.map +1 -0
  32. package/dist/PredictiveStudio/predictive-studio-copilot.view-models.js +52 -0
  33. package/dist/PredictiveStudio/predictive-studio-copilot.view-models.js.map +1 -0
  34. package/dist/PredictiveStudio/resources/ps-models-resource.component.d.ts +9 -1
  35. package/dist/PredictiveStudio/resources/ps-models-resource.component.d.ts.map +1 -1
  36. package/dist/PredictiveStudio/resources/ps-models-resource.component.js +133 -56
  37. package/dist/PredictiveStudio/resources/ps-models-resource.component.js.map +1 -1
  38. package/dist/PredictiveStudio/resources/ps-predictions-resource.component.d.ts +50 -1
  39. package/dist/PredictiveStudio/resources/ps-predictions-resource.component.d.ts.map +1 -1
  40. package/dist/PredictiveStudio/resources/ps-predictions-resource.component.js +591 -165
  41. package/dist/PredictiveStudio/resources/ps-predictions-resource.component.js.map +1 -1
  42. package/dist/PredictiveStudio/resources/ps-resource-base.d.ts +17 -0
  43. package/dist/PredictiveStudio/resources/ps-resource-base.d.ts.map +1 -1
  44. package/dist/PredictiveStudio/resources/ps-resource-base.js +27 -1
  45. package/dist/PredictiveStudio/resources/ps-resource-base.js.map +1 -1
  46. package/dist/PredictiveStudio/resources/ps-studio-resource.component.d.ts +11 -1
  47. package/dist/PredictiveStudio/resources/ps-studio-resource.component.d.ts.map +1 -1
  48. package/dist/PredictiveStudio/resources/ps-studio-resource.component.js +199 -99
  49. package/dist/PredictiveStudio/resources/ps-studio-resource.component.js.map +1 -1
  50. package/package.json +59 -59
@@ -213,7 +213,7 @@ export class PSCompareComponent {
213
213
  } if (rf & 2) {
214
214
  i0.ɵɵadvance();
215
215
  i0.ɵɵconditional(ctx.sessionOptions.length === 0 ? 1 : 2);
216
- } }, dependencies: [CommonModule, FormsModule, i1.NgSelectOption, i1.ɵNgSelectMultipleOption, i1.SelectControlValueAccessor, i1.NgControlStatus, i1.NgModel], styles: ["/* ============================================================================\n Predictive Studio \u2014 shared panel styles (design-token compliant)\n Maps the mockup design system onto MemberJunction semantic --mj-* tokens.\n Loaded by every PS panel component (styleUrls) under ViewEncapsulation.None,\n so the class names are global within the PS dashboard subtree.\n No hardcoded colors except the intentional categorical node/algorithm palette\n (declared once as --ps-* custom props below) which must stay distinct across\n light/dark to remain readable as diagram accents.\n ============================================================================ */\n\n/* Categorical accents for DAG node types + algorithm chips. These are\n data-visualization colors that must remain distinct regardless of theme\n (allowed exception #3 in the design-token rules). */\n.ps-panel {\n --ps-node-source: #6b7280;\n --ps-node-feature: #2050c8;\n --ps-node-embedding: #7b3ff0;\n --ps-node-target: #1f9d57;\n --ps-node-algorithm: #d98213;\n /* budget-gauge categorical accents (reuse the node palette so light/dark stay distinct) */\n --ps-gauge-cost: var(--ps-node-algorithm);\n --ps-gauge-iterations: var(--ps-node-feature);\n}\n\n.ps-panel {\n display: block;\n height: 100%;\n}\n\n/* ---- generic layout helpers ---- */\n.ps-row { display: flex; gap: 16px; }\n.ps-col { display: flex; flex-direction: column; gap: 16px; }\n.ps-wrap { flex-wrap: wrap; }\n.ps-spacer { flex: 1; }\n.ps-muted { color: var(--mj-text-muted); }\n.ps-small { font-size: 12.5px; }\n.ps-mono { font-family: ui-monospace, 'SF Mono', Menlo, Consolas, monospace; font-size: 12.5px; }\n.ps-section-title {\n font-size: 12px; text-transform: uppercase; letter-spacing: .06em;\n color: var(--mj-text-muted); margin: 4px 0 2px; font-weight: 700;\n}\n\n/* ---- cards ---- */\n.ps-card {\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 10px;\n box-shadow: var(--mj-shadow-sm);\n overflow: hidden;\n}\n.ps-card-head {\n display: flex; align-items: center; gap: 10px;\n padding: 14px 16px; border-bottom: 1px solid var(--mj-border-subtle);\n}\n.ps-card-head h3 { font-size: 14.5px; margin: 0; font-weight: 650; color: var(--mj-text-primary); }\n.ps-card-body { padding: 16px; }\n\n/* ---- KPI tiles ---- */\n.ps-kpi {\n background: var(--mj-bg-surface); border: 1px solid var(--mj-border-default);\n border-radius: 10px; padding: 16px 18px; box-shadow: var(--mj-shadow-sm);\n}\n.ps-kpi .label { font-size: 12px; color: var(--mj-text-muted); font-weight: 600; }\n.ps-kpi .val { font-size: 26px; font-weight: 720; letter-spacing: -.02em; margin-top: 2px; color: var(--mj-text-primary); }\n\n/* ---- badges ---- */\n.ps-badge {\n display: inline-flex; align-items: center; gap: 5px;\n padding: 2px 9px; border-radius: 999px; font-size: 11.5px; font-weight: 650; line-height: 1.7;\n}\n.ps-badge.gray { background: var(--mj-bg-surface-sunken); color: var(--mj-text-secondary); }\n.ps-badge.blue { background: var(--mj-status-info-bg); color: var(--mj-status-info-text); }\n.ps-badge.green { background: var(--mj-status-success-bg); color: var(--mj-status-success-text); }\n.ps-badge.amber { background: var(--mj-status-warning-bg); color: var(--mj-status-warning-text); }\n.ps-badge.red { background: var(--mj-status-error-bg); color: var(--mj-status-error-text); }\n.ps-dot { width: 7px; height: 7px; border-radius: 50%; display: inline-block; }\n\n/* recommendation-level chips (the ranking matrix payoff) */\n.ps-lvl {\n display: inline-flex; align-items: center; justify-content: center;\n min-width: 92px; padding: 4px 10px; border-radius: 7px;\n font-weight: 650; font-size: 11.5px; color: var(--mj-text-inverse);\n}\n.ps-lvl-Primary { background: var(--mj-status-success); }\n.ps-lvl-Strong { background: var(--mj-status-info); }\n.ps-lvl-Viable { background: var(--mj-status-warning); }\n.ps-lvl-Weak { background: var(--mj-text-disabled); }\n.ps-lvl-NotRecommended { background: var(--mj-bg-surface-sunken); color: var(--mj-text-muted); }\n\n/* ---- tags ---- */\n.ps-tag {\n display: inline-block; padding: 2px 8px; border-radius: 6px;\n background: var(--mj-bg-surface-sunken); color: var(--mj-text-secondary);\n font-size: 11.5px; font-weight: 600;\n}\n.ps-tag.muted { opacity: .6; }\n.ps-divider { height: 1px; background: var(--mj-border-subtle); margin: 8px 0; }\n\n/* ---- tables ---- */\n.ps-table { width: 100%; border-collapse: collapse; font-size: 13px; }\n.ps-table th {\n text-align: left; font-size: 11.5px; text-transform: uppercase; letter-spacing: .04em;\n color: var(--mj-text-muted); font-weight: 700; padding: 9px 12px;\n border-bottom: 1px solid var(--mj-border-default);\n}\n.ps-table td { padding: 11px 12px; border-bottom: 1px solid var(--mj-border-subtle); color: var(--mj-text-primary); }\n.ps-table tr:hover td { background: var(--mj-bg-surface-hover); }\n.ps-table .num { text-align: right; font-variant-numeric: tabular-nums; }\n\n.ps-rank {\n width: 22px; height: 22px; border-radius: 6px; background: var(--mj-bg-surface-sunken);\n display: inline-grid; place-items: center; font-weight: 700; font-size: 11.5px; color: var(--mj-text-secondary);\n}\n.ps-rank.gold { background: var(--mj-status-warning-bg); color: var(--mj-status-warning-text); }\n\n/* ---- progress bars ---- */\n.ps-bar { height: 8px; border-radius: 999px; background: var(--mj-bg-surface-sunken); overflow: hidden; }\n.ps-bar > span { display: block; height: 100%; border-radius: 999px; background: var(--mj-brand-primary); }\n.ps-bar.warn > span { background: var(--mj-status-warning); }\n.ps-bar.run > span { background: var(--mj-brand-primary); }\n\n.ps-fbar { display: grid; grid-template-columns: 150px 1fr 48px; align-items: center; gap: 10px; padding: 5px 0; }\n.ps-fbar .track { height: 14px; border-radius: 5px; background: var(--mj-bg-surface-sunken); overflow: hidden; }\n.ps-fbar .track > span { display: block; height: 100%; background: var(--mj-brand-primary); }\n.ps-fbar .track > span.warn { background: var(--mj-status-warning); }\n.ps-fbar .name { color: var(--mj-text-secondary); }\n\n/* gauge */\n.ps-gauge {\n position: relative; width: 64px; height: 64px; border-radius: 50%; flex: 0 0 auto;\n background: conic-gradient(var(--ps-gauge-color, var(--mj-brand-primary)) calc(var(--ps-p, 40) * 1%), var(--mj-bg-surface-sunken) 0);\n display: grid; place-items: center;\n}\n.ps-gauge::after { content: ''; position: absolute; inset: 9px; background: var(--mj-bg-surface); border-radius: 50%; }\n.ps-gauge .g-val { position: relative; font-weight: 720; font-size: 14px; color: var(--mj-text-primary); }\n\n/* warning / info callouts */\n.ps-callout { display: flex; gap: 11px; padding: 12px 14px; border-radius: 10px; border: 1px solid; }\n.ps-callout.warn { background: var(--mj-status-warning-bg); border-color: var(--mj-status-warning-border); color: var(--mj-status-warning-text); }\n.ps-callout.info { background: var(--mj-status-info-bg); border-color: var(--mj-status-info-border); color: var(--mj-status-info-text); }\n.ps-callout.success { background: var(--mj-status-success-bg); border-color: var(--mj-status-success-border); color: var(--mj-status-success-text); }\n.ps-callout i { font-size: 16px; margin-top: 1px; }\n\n/* lifecycle stepper */\n.ps-stepper { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }\n.ps-step { display: flex; align-items: center; gap: 6px; font-size: 12px; font-weight: 600; color: var(--mj-text-muted); }\n.ps-step .pip {\n width: 22px; height: 22px; border-radius: 50%; border: 2px solid var(--mj-border-strong);\n display: grid; place-items: center; font-size: 11px;\n}\n.ps-step.done { color: var(--mj-status-success-text); }\n.ps-step.done .pip { background: var(--mj-status-success); border-color: var(--mj-status-success); color: var(--mj-text-inverse); }\n.ps-step.curr { color: var(--mj-brand-primary); }\n.ps-step.curr .pip { border-color: var(--mj-brand-primary); color: var(--mj-brand-primary); }\n.ps-step .ln { width: 26px; height: 2px; background: var(--mj-border-strong); }\n\n/* ---- DAG flow canvas ---- */\n.ps-flow {\n position: relative;\n background:\n linear-gradient(var(--mj-border-subtle) 1px, transparent 1px) 0 0 / 22px 22px,\n linear-gradient(90deg, var(--mj-border-subtle) 1px, transparent 1px) 0 0 / 22px 22px,\n var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-default); border-radius: 10px;\n min-height: 620px; overflow: hidden;\n}\n.ps-node {\n position: absolute; width: 190px; background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-strong); border-left-width: 3px; border-radius: 10px;\n box-shadow: var(--mj-shadow-sm); cursor: pointer;\n}\n.ps-node .nh {\n display: flex; align-items: center; gap: 8px; padding: 9px 11px;\n border-bottom: 1px solid var(--mj-border-subtle); font-weight: 650; font-size: 12.5px; color: var(--mj-text-primary);\n}\n.ps-node .nh i.tile {\n width: 24px; height: 24px; border-radius: 6px; display: grid; place-items: center;\n color: var(--mj-text-inverse); font-size: 12px; flex: 0 0 auto;\n}\n.ps-node .nh .ps-tag { margin-left: auto; }\n.ps-node .nb { padding: 9px 11px; font-size: 12px; color: var(--mj-text-secondary); }\n.ps-node .nb-row { display: flex; justify-content: space-between; gap: 8px; padding: 2px 0; }\n.ps-node .nb-row .k { color: var(--mj-text-muted); }\n.ps-node.src { border-left-color: var(--ps-node-source); }\n.ps-node.src .nh i.tile { background: var(--ps-node-source); }\n.ps-node.feat { border-left-color: var(--ps-node-feature); }\n.ps-node.feat .nh i.tile { background: var(--ps-node-feature); }\n.ps-node.emb { border-left-color: var(--ps-node-embedding); }\n.ps-node.emb .nh i.tile { background: var(--ps-node-embedding); }\n.ps-node.target { border-left-color: var(--ps-node-target); }\n.ps-node.target .nh i.tile { background: var(--ps-node-target); }\n.ps-node.algo { border-left-color: var(--ps-node-algorithm); }\n.ps-node.algo .nh i.tile { background: var(--ps-node-algorithm); }\n.ps-node.output { border-left-color: var(--mj-status-success); }\n.ps-node.output .nh i.tile { background: var(--mj-status-success); }\n.ps-node.selected {\n border-color: var(--mj-brand-primary);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 18%, transparent);\n}\n.ps-node .port {\n position: absolute; width: 11px; height: 11px; border-radius: 50%;\n background: var(--mj-bg-surface); border: 2px solid var(--mj-brand-primary);\n top: 50%; transform: translateY(-50%);\n}\n.ps-node .port.in { left: -6px; }\n.ps-node .port.out { right: -6px; }\nsvg.ps-edges { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }\nsvg.ps-edges path { stroke: color-mix(in srgb, var(--mj-brand-primary) 45%, transparent); stroke-width: 2; fill: none; }\n\n/* palette chips */\n.ps-pchip {\n display: inline-flex; align-items: center; gap: 7px; padding: 6px 11px; border-radius: 8px;\n border: 1px dashed var(--mj-border-strong); background: var(--mj-bg-surface);\n font-size: 12.5px; font-weight: 600; color: var(--mj-text-secondary); cursor: grab;\n}\n.ps-pchip i { font-size: 12px; color: var(--mj-text-inverse); width: 20px; height: 20px; border-radius: 5px; display: grid; place-items: center; }\n.ps-pchip.s i { background: var(--ps-node-source); }\n.ps-pchip.f i { background: var(--ps-node-feature); }\n.ps-pchip.e i { background: var(--ps-node-embedding); }\n.ps-pchip.t i { background: var(--ps-node-target); }\n.ps-pchip.a i { background: var(--ps-node-algorithm); }\n\n/* segmented control */\n.ps-seg { display: inline-flex; border: 1px solid var(--mj-border-default); border-radius: 8px; overflow: hidden; }\n.ps-seg button {\n border: none; background: var(--mj-bg-surface); color: var(--mj-text-secondary);\n padding: 6px 12px; font-size: 12.5px; font-weight: 600; cursor: pointer;\n border-right: 1px solid var(--mj-border-default);\n}\n.ps-seg button:last-child { border-right: none; }\n.ps-seg button.on { background: var(--mj-brand-primary); color: var(--mj-text-inverse); }\n\n/* field control */\n.ps-field { margin-bottom: 14px; }\n.ps-field > label {\n display: block; font-size: 11px; text-transform: uppercase; letter-spacing: .04em;\n color: var(--mj-text-muted); font-weight: 700; margin-bottom: 6px;\n}\n.ps-input {\n width: 100%; padding: 8px 10px; border: 1px solid var(--mj-border-default);\n border-radius: 6px; font-size: 13px; background: var(--mj-bg-surface); color: var(--mj-text-primary);\n box-sizing: border-box;\n}\n.ps-input:focus { outline: none; border-color: var(--mj-brand-primary); box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 12%, transparent); }\n\n/* loading container */\n.ps-loading { display: flex; align-items: center; justify-content: center; padding: 60px 0; }\n\n/* ---- empty / prompt states ---- */\n.ps-empty {\n display: flex; flex-direction: column; align-items: center; justify-content: center;\n text-align: center; gap: 10px; padding: 56px 24px; color: var(--mj-text-muted);\n}\n.ps-empty .ps-empty-ico {\n width: 60px; height: 60px; border-radius: 16px; display: grid; place-items: center;\n background: var(--mj-bg-surface-sunken); color: var(--mj-text-disabled); font-size: 24px;\n}\n.ps-empty h3 { margin: 4px 0 0; font-size: 16px; font-weight: 650; color: var(--mj-text-secondary); }\n.ps-empty p { margin: 0; max-width: 420px; font-size: 13px; line-height: 1.5; }\n\n/* ---- confirm modal ---- */\n.ps-modal-backdrop {\n position: fixed; inset: 0; z-index: 1000;\n background: var(--mj-bg-overlay);\n display: flex; align-items: center; justify-content: center; padding: 24px;\n}\n.ps-modal {\n width: 100%; max-width: 480px; background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default); border-radius: 14px;\n box-shadow: var(--mj-shadow-lg, var(--mj-shadow-sm)); overflow: hidden;\n}\n.ps-modal-head {\n display: flex; align-items: center; gap: 12px; padding: 18px 20px;\n border-bottom: 1px solid var(--mj-border-subtle);\n}\n.ps-modal-head .ico {\n width: 38px; height: 38px; border-radius: 10px; display: grid; place-items: center;\n font-size: 16px; flex: 0 0 auto;\n background: var(--mj-status-info-bg); color: var(--mj-status-info-text);\n}\n.ps-modal-head.warn .ico { background: var(--mj-status-warning-bg); color: var(--mj-status-warning-text); }\n.ps-modal-head.danger .ico { background: var(--mj-status-error-bg); color: var(--mj-status-error-text); }\n.ps-modal-head h3 { margin: 0; font-size: 15.5px; font-weight: 650; color: var(--mj-text-primary); }\n.ps-modal-body { padding: 18px 20px; font-size: 13.5px; line-height: 1.55; color: var(--mj-text-secondary); }\n.ps-modal-body .ps-field { margin-top: 14px; margin-bottom: 0; }\n.ps-modal-foot {\n display: flex; align-items: center; gap: 10px; padding: 14px 20px;\n border-top: 1px solid var(--mj-border-subtle); background: var(--mj-bg-surface-card);\n}\n.ps-modal-foot .ps-spacer { flex: 1; }\n", ".ps-compare { display: flex; flex-direction: column; gap: 16px; }\n.ps-compare .cmp-toolbar { display: flex; align-items: center; gap: 12px; }\n\n/* side-by-side grid */\n.ps-compare .cmp-grid { display: grid; grid-template-columns: 200px repeat(3, 1fr); gap: 0; min-width: 720px; }\n.ps-compare .cmp-grid .stub { padding: 14px 12px; font-weight: 700; color: var(--mj-text-muted); font-size: 12px; text-transform: uppercase; letter-spacing: .04em; border-bottom: 1px solid var(--mj-border-default); }\n.ps-compare .colhead { padding: 12px; border-top: 3px solid var(--mj-brand-primary); border-bottom: 1px solid var(--mj-border-default); border-left: 1px solid var(--mj-border-subtle); }\n.ps-compare .colhead .rn { font-weight: 700; color: var(--mj-text-primary); }\n.ps-compare .rowhdr { padding: 11px 12px; border-bottom: 1px solid var(--mj-border-subtle); display: flex; flex-direction: column; }\n.ps-compare .rowhdr > span:first-child { color: var(--mj-text-primary); font-weight: 600; font-size: 13px; }\n.ps-compare .cell {\n padding: 11px 12px; border-bottom: 1px solid var(--mj-border-subtle); border-left: 1px solid var(--mj-border-subtle);\n display: flex; align-items: center; gap: 8px;\n}\n.ps-compare .cell .v { color: var(--mj-text-primary); font-weight: 600; font-variant-numeric: tabular-nums; }\n.ps-compare .cell.best { background: var(--mj-status-success-bg); }\n.ps-compare .cell.best .v { color: var(--mj-status-success-text); }\n.ps-compare .cell.warn .v { color: var(--mj-status-warning-text); }\n.ps-compare .best-pill { font-size: 9.5px; font-weight: 800; letter-spacing: .05em; color: var(--mj-status-success-text); background: color-mix(in srgb, var(--mj-status-success) 18%, transparent); padding: 1px 6px; border-radius: 999px; }\n\n/* overlay */\n.ps-compare .legend-row { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }\n.ps-compare .legend-row .li { display: inline-flex; align-items: center; gap: 7px; font-size: 12.5px; color: var(--mj-text-secondary); }\n.ps-compare .sw { width: 11px; height: 11px; border-radius: 3px; display: inline-block; }\n.ps-compare .chart-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; align-items: start; }\n@media (max-width: 980px) { .ps-compare .chart-grid { grid-template-columns: 1fr; } }\n.ps-compare .chart-grid .span2 { grid-column: 1 / span 2; }\n@media (max-width: 980px) { .ps-compare .chart-grid .span2 { grid-column: auto; } }\n.ps-compare .chart { width: 100%; height: auto; }\n.ps-compare .chart .axis { stroke: var(--mj-border-strong); stroke-width: 1; }\n.ps-compare .chart .chance { stroke: var(--mj-border-strong); stroke-width: 1; stroke-dasharray: 4 4; }\n.ps-compare .chart .threshold { stroke: var(--mj-status-warning); stroke-width: 1.5; stroke-dasharray: 5 4; }\n.ps-compare .chart .axis-lbl { font-size: 10px; fill: var(--mj-text-muted); }\n.ps-compare .chart .warn-lbl { fill: var(--mj-status-warning-text); font-weight: 700; }\n.ps-compare .roc-legend { display: flex; gap: 14px; margin-top: 8px; }\n.ps-compare .roc-legend span { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; color: var(--mj-text-secondary); font-variant-numeric: tabular-nums; }\n\n/* champion / challenger */\n.ps-compare .reco { align-items: center; }\n.ps-compare .vs-head { display: grid; grid-template-columns: 1fr 64px 1fr; gap: 0; align-items: stretch; }\n@media (max-width: 820px) { .ps-compare .vs-head { grid-template-columns: 1fr; } }\n.ps-compare .vs-card { border: 1px solid var(--mj-border-default); border-radius: 10px; padding: 16px; background: var(--mj-bg-surface); }\n.ps-compare .vs-card.champ { border-top: 4px solid var(--mj-status-success); }\n.ps-compare .vs-card.chall { border-top: 4px solid var(--ps-node-embedding); }\n.ps-compare .vs-card .role { display: inline-flex; align-items: center; gap: 7px; font-size: 12px; font-weight: 700; }\n.ps-compare .vs-card.champ .role { color: var(--mj-status-success-text); }\n.ps-compare .vs-card.chall .role { color: var(--ps-node-embedding); }\n.ps-compare .vs-card .title { font-size: 16px; font-weight: 700; color: var(--mj-text-primary); margin: 8px 0 2px; }\n.ps-compare .vs-stats { display: flex; gap: 18px; margin-top: 14px; }\n.ps-compare .vs-stats strong { font-size: 20px; color: var(--mj-text-primary); }\n.ps-compare .vs-stats .warn-txt { color: var(--mj-status-warning-text); }\n.ps-compare .vs-stats .err-txt { color: var(--mj-status-error-text); }\n.ps-compare .vs-mid { display: grid; place-items: center; font-weight: 800; color: var(--mj-text-muted); background: var(--mj-bg-surface-sunken); border-top: 1px solid var(--mj-border-default); border-bottom: 1px solid var(--mj-border-default); }\n@media (max-width: 820px) { .ps-compare .vs-mid { padding: 8px; } }\n\n.ps-compare .delta-row { display: grid; grid-template-columns: repeat(4, 1fr); gap: 14px; }\n@media (max-width: 880px) { .ps-compare .delta-row { grid-template-columns: 1fr 1fr; } }\n.ps-compare .delta { border: 1px solid var(--mj-border-default); border-radius: 10px; padding: 14px; }\n.ps-compare .delta .lab { font-size: 12px; color: var(--mj-text-muted); font-weight: 600; }\n.ps-compare .delta .big { font-size: 20px; font-weight: 720; margin: 4px 0; display: flex; align-items: center; gap: 7px; }\n.ps-compare .delta.up .big { color: var(--mj-status-success-text); }\n.ps-compare .delta.down .big { color: var(--mj-status-error-text); }\n"], encapsulation: 2 });
216
+ } }, dependencies: [CommonModule, FormsModule, i1.NgSelectOption, i1.ɵNgSelectMultipleOption, i1.SelectControlValueAccessor, i1.NgControlStatus, i1.NgModel], styles: ["/* ============================================================================\n Predictive Studio \u2014 shared panel styles (design-token compliant)\n Maps the mockup design system onto MemberJunction semantic --mj-* tokens.\n Loaded by every PS panel component (styleUrls) under ViewEncapsulation.None,\n so the class names are global within the PS dashboard subtree.\n No hardcoded colors except the intentional categorical node/algorithm palette\n (declared once as --ps-* custom props below) which must stay distinct across\n light/dark to remain readable as diagram accents.\n ============================================================================ */\n\n/* Categorical accents for DAG node types + algorithm chips. These are\n data-visualization colors that must remain distinct regardless of theme\n (allowed exception #3 in the design-token rules). */\n.ps-panel {\n --ps-node-source: #6b7280;\n --ps-node-feature: #2050c8;\n --ps-node-embedding: #7b3ff0;\n --ps-node-target: #1f9d57;\n --ps-node-algorithm: #d98213;\n /* budget-gauge categorical accents (reuse the node palette so light/dark stay distinct) */\n --ps-gauge-cost: var(--ps-node-algorithm);\n --ps-gauge-iterations: var(--ps-node-feature);\n}\n\n.ps-panel {\n display: block;\n height: 100%;\n}\n\n/* ---- generic layout helpers ---- */\n.ps-row { display: flex; gap: 16px; }\n.ps-col { display: flex; flex-direction: column; gap: 16px; }\n.ps-wrap { flex-wrap: wrap; }\n.ps-spacer { flex: 1; }\n.ps-muted { color: var(--mj-text-muted); }\n.ps-small { font-size: 12.5px; }\n.ps-mono { font-family: ui-monospace, 'SF Mono', Menlo, Consolas, monospace; font-size: 12.5px; }\n.ps-section-title {\n font-size: 12px; text-transform: uppercase; letter-spacing: .06em;\n color: var(--mj-text-muted); margin: 4px 0 2px; font-weight: 700;\n}\n\n/* ---- cards ---- */\n.ps-card {\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n overflow: hidden;\n}\n.ps-card-head {\n display: flex; align-items: center; gap: 10px;\n padding: 14px 16px; border-bottom: 1px solid var(--mj-border-subtle);\n}\n.ps-card-head h3 { font-size: 14.5px; margin: 0; font-weight: 650; color: var(--mj-text-primary); }\n.ps-card-body { padding: 16px; }\n\n/* ---- KPI tiles ---- */\n.ps-kpi {\n background: var(--mj-bg-surface); border: 1px solid var(--mj-border-default);\n border-radius: 8px; padding: 16px 18px;\n}\n.ps-kpi .label { font-size: 12px; color: var(--mj-text-muted); font-weight: 600; }\n.ps-kpi .val { font-size: 26px; font-weight: 720; letter-spacing: -.02em; margin-top: 2px; color: var(--mj-text-primary); }\n\n/* ---- badges ---- */\n.ps-badge {\n display: inline-flex; align-items: center; gap: 5px;\n padding: 2px 9px; border-radius: 999px; font-size: 11.5px; font-weight: 650; line-height: 1.7;\n}\n.ps-badge.gray { background: var(--mj-bg-surface-sunken); color: var(--mj-text-secondary); }\n.ps-badge.blue { background: var(--mj-status-info-bg); color: var(--mj-status-info-text); }\n.ps-badge.green { background: var(--mj-status-success-bg); color: var(--mj-status-success-text); }\n.ps-badge.amber { background: var(--mj-status-warning-bg); color: var(--mj-status-warning-text); }\n.ps-badge.red { background: var(--mj-status-error-bg); color: var(--mj-status-error-text); }\n.ps-dot { width: 7px; height: 7px; border-radius: 50%; display: inline-block; }\n\n/* recommendation-level chips (the ranking matrix payoff) */\n.ps-lvl {\n display: inline-flex; align-items: center; justify-content: center;\n min-width: 92px; padding: 4px 10px; border-radius: 7px;\n font-weight: 650; font-size: 11.5px; color: var(--mj-text-inverse);\n}\n.ps-lvl-Primary { background: var(--mj-status-success); }\n.ps-lvl-Strong { background: var(--mj-status-info); }\n.ps-lvl-Viable { background: var(--mj-status-warning); }\n.ps-lvl-Weak { background: var(--mj-text-disabled); }\n.ps-lvl-NotRecommended { background: var(--mj-bg-surface-sunken); color: var(--mj-text-muted); }\n\n/* ---- tags ---- */\n.ps-tag {\n display: inline-block; padding: 2px 8px; border-radius: 6px;\n background: var(--mj-bg-surface-sunken); color: var(--mj-text-secondary);\n font-size: 11.5px; font-weight: 600;\n}\n.ps-tag.muted { opacity: .6; }\n.ps-divider { height: 1px; background: var(--mj-border-subtle); margin: 8px 0; }\n\n/* ---- tables ---- */\n.ps-table { width: 100%; border-collapse: collapse; font-size: 13px; }\n.ps-table th {\n text-align: left; font-size: 11.5px; text-transform: uppercase; letter-spacing: .04em;\n color: var(--mj-text-muted); font-weight: 700; padding: 9px 12px;\n border-bottom: 1px solid var(--mj-border-default);\n}\n.ps-table td { padding: 11px 12px; border-bottom: 1px solid var(--mj-border-subtle); color: var(--mj-text-primary); }\n.ps-table tr:hover td { background: var(--mj-bg-surface-hover); }\n.ps-table .num { text-align: right; font-variant-numeric: tabular-nums; }\n\n.ps-rank {\n width: 22px; height: 22px; border-radius: 6px; background: var(--mj-bg-surface-sunken);\n display: inline-grid; place-items: center; font-weight: 700; font-size: 11.5px; color: var(--mj-text-secondary);\n}\n.ps-rank.gold { background: var(--mj-status-warning-bg); color: var(--mj-status-warning-text); }\n\n/* ---- progress bars ---- */\n.ps-bar { height: 8px; border-radius: 999px; background: var(--mj-bg-surface-sunken); overflow: hidden; }\n.ps-bar > span { display: block; height: 100%; border-radius: 999px; background: var(--mj-brand-primary); }\n.ps-bar.warn > span { background: var(--mj-status-warning); }\n.ps-bar.run > span { background: var(--mj-brand-primary); }\n\n.ps-fbar { display: grid; grid-template-columns: minmax(150px, 1.3fr) 2fr 56px; align-items: center; gap: 14px; padding: 8px 0; }\n.ps-fbar + .ps-fbar { border-top: 1px solid var(--mj-border-subtle); }\n.ps-fbar .track { height: 10px; border-radius: var(--mj-radius-full); background: var(--mj-bg-surface-sunken); overflow: hidden; }\n.ps-fbar .track > span { display: block; height: 100%; border-radius: var(--mj-radius-full); background: var(--mj-brand-primary); }\n.ps-fbar .track > span.warn { background: var(--mj-status-warning); }\n.ps-fbar .name { color: var(--mj-text-primary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.ps-fbar > .ps-mono { text-align: right; color: var(--mj-text-secondary); font-variant-numeric: tabular-nums; }\n\n/* gauge */\n.ps-gauge {\n position: relative; width: 64px; height: 64px; border-radius: 50%; flex: 0 0 auto;\n background: conic-gradient(var(--ps-gauge-color, var(--mj-brand-primary)) calc(var(--ps-p, 40) * 1%), var(--mj-bg-surface-sunken) 0);\n display: grid; place-items: center;\n}\n.ps-gauge::after { content: ''; position: absolute; inset: 9px; background: var(--mj-bg-surface); border-radius: 50%; }\n.ps-gauge .g-val { position: relative; font-weight: 720; font-size: 14px; color: var(--mj-text-primary); }\n\n/* warning / info callouts */\n.ps-callout { display: flex; gap: 11px; padding: 12px 14px; border-radius: 10px; border: 1px solid; }\n.ps-callout.warn { background: var(--mj-status-warning-bg); border-color: var(--mj-status-warning-border); color: var(--mj-status-warning-text); }\n.ps-callout.info { background: var(--mj-status-info-bg); border-color: var(--mj-status-info-border); color: var(--mj-status-info-text); }\n.ps-callout.success { background: var(--mj-status-success-bg); border-color: var(--mj-status-success-border); color: var(--mj-status-success-text); }\n.ps-callout i { font-size: 16px; margin-top: 1px; }\n\n/* lifecycle stepper */\n.ps-stepper { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }\n.ps-step { display: flex; align-items: center; gap: 6px; font-size: 12px; font-weight: 600; color: var(--mj-text-muted); }\n.ps-step .pip {\n width: 22px; height: 22px; border-radius: 50%; border: 2px solid var(--mj-border-strong);\n display: grid; place-items: center; font-size: 11px;\n}\n.ps-step.done { color: var(--mj-status-success-text); }\n.ps-step.done .pip { background: var(--mj-status-success); border-color: var(--mj-status-success); color: var(--mj-text-inverse); }\n.ps-step.curr { color: var(--mj-brand-primary); }\n.ps-step.curr .pip { border-color: var(--mj-brand-primary); color: var(--mj-brand-primary); }\n.ps-step .ln { width: 26px; height: 2px; background: var(--mj-border-strong); }\n\n/* ---- DAG flow canvas ---- */\n.ps-flow {\n position: relative;\n background:\n linear-gradient(var(--mj-border-subtle) 1px, transparent 1px) 0 0 / 22px 22px,\n linear-gradient(90deg, var(--mj-border-subtle) 1px, transparent 1px) 0 0 / 22px 22px,\n var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-default); border-radius: 10px;\n min-height: 620px; overflow: hidden;\n}\n.ps-node {\n position: absolute; width: 190px; background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-strong); border-left-width: 3px; border-radius: 8px;\n cursor: pointer;\n}\n.ps-node .nh {\n display: flex; align-items: center; gap: 8px; padding: 9px 11px;\n border-bottom: 1px solid var(--mj-border-subtle); font-weight: 650; font-size: 12.5px; color: var(--mj-text-primary);\n}\n.ps-node .nh i.tile {\n width: 24px; height: 24px; border-radius: 6px; display: grid; place-items: center;\n color: var(--mj-text-inverse); font-size: 12px; flex: 0 0 auto;\n}\n.ps-node .nh .ps-tag { margin-left: auto; }\n.ps-node .nb { padding: 9px 11px; font-size: 12px; color: var(--mj-text-secondary); }\n.ps-node .nb-row { display: flex; justify-content: space-between; gap: 8px; padding: 2px 0; }\n.ps-node .nb-row .k { color: var(--mj-text-muted); }\n.ps-node.src { border-left-color: var(--ps-node-source); }\n.ps-node.src .nh i.tile { background: var(--ps-node-source); }\n.ps-node.feat { border-left-color: var(--ps-node-feature); }\n.ps-node.feat .nh i.tile { background: var(--ps-node-feature); }\n.ps-node.emb { border-left-color: var(--ps-node-embedding); }\n.ps-node.emb .nh i.tile { background: var(--ps-node-embedding); }\n.ps-node.target { border-left-color: var(--ps-node-target); }\n.ps-node.target .nh i.tile { background: var(--ps-node-target); }\n.ps-node.algo { border-left-color: var(--ps-node-algorithm); }\n.ps-node.algo .nh i.tile { background: var(--ps-node-algorithm); }\n.ps-node.output { border-left-color: var(--mj-status-success); }\n.ps-node.output .nh i.tile { background: var(--mj-status-success); }\n.ps-node.selected {\n border-color: var(--mj-brand-primary);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 18%, transparent);\n}\n.ps-node .port {\n position: absolute; width: 11px; height: 11px; border-radius: 50%;\n background: var(--mj-bg-surface); border: 2px solid var(--mj-brand-primary);\n top: 50%; transform: translateY(-50%);\n}\n.ps-node .port.in { left: -6px; }\n.ps-node .port.out { right: -6px; }\nsvg.ps-edges { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }\nsvg.ps-edges path { stroke: color-mix(in srgb, var(--mj-brand-primary) 45%, transparent); stroke-width: 2; fill: none; }\n\n/* palette chips */\n.ps-pchip {\n display: inline-flex; align-items: center; gap: 7px; padding: 6px 11px; border-radius: 8px;\n border: 1px dashed var(--mj-border-strong); background: var(--mj-bg-surface);\n font-size: 12.5px; font-weight: 600; color: var(--mj-text-secondary); cursor: grab;\n}\n.ps-pchip i { font-size: 12px; color: var(--mj-text-inverse); width: 20px; height: 20px; border-radius: 5px; display: grid; place-items: center; }\n.ps-pchip.s i { background: var(--ps-node-source); }\n.ps-pchip.f i { background: var(--ps-node-feature); }\n.ps-pchip.e i { background: var(--ps-node-embedding); }\n.ps-pchip.t i { background: var(--ps-node-target); }\n.ps-pchip.a i { background: var(--ps-node-algorithm); }\n\n/* segmented control */\n.ps-seg { display: inline-flex; border: 1px solid var(--mj-border-default); border-radius: 8px; overflow: hidden; }\n.ps-seg button {\n border: none; background: var(--mj-bg-surface); color: var(--mj-text-secondary);\n padding: 6px 12px; font-size: 12.5px; font-weight: 600; cursor: pointer;\n border-right: 1px solid var(--mj-border-default);\n}\n.ps-seg button:last-child { border-right: none; }\n.ps-seg button.on { background: var(--mj-brand-primary); color: var(--mj-text-inverse); }\n\n/* field control */\n.ps-field { margin-bottom: 14px; }\n.ps-field > label {\n display: block; font-size: 11px; text-transform: uppercase; letter-spacing: .04em;\n color: var(--mj-text-muted); font-weight: 700; margin-bottom: 6px;\n}\n.ps-input {\n width: 100%; padding: 8px 10px; border: 1px solid var(--mj-border-default);\n border-radius: 6px; font-size: 13px; background: var(--mj-bg-surface); color: var(--mj-text-primary);\n box-sizing: border-box;\n}\n.ps-input:focus { outline: none; border-color: var(--mj-brand-primary); box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 12%, transparent); }\n\n/* loading container */\n.ps-loading { display: flex; align-items: center; justify-content: center; padding: 60px 0; }\n\n/* ---- empty / prompt states ---- */\n.ps-empty {\n display: flex; flex-direction: column; align-items: center; justify-content: center;\n text-align: center; gap: 10px; padding: 56px 24px; color: var(--mj-text-muted);\n}\n.ps-empty .ps-empty-ico {\n width: 60px; height: 60px; border-radius: 16px; display: grid; place-items: center;\n background: var(--mj-bg-surface-sunken); color: var(--mj-text-disabled); font-size: 24px;\n}\n.ps-empty h3 { margin: 4px 0 0; font-size: 16px; font-weight: 650; color: var(--mj-text-secondary); }\n.ps-empty p { margin: 0; max-width: 420px; font-size: 13px; line-height: 1.5; }\n\n/* ---- confirm modal ---- */\n.ps-modal-backdrop {\n position: fixed; inset: 0; z-index: 1000;\n background: var(--mj-bg-overlay);\n display: flex; align-items: center; justify-content: center; padding: 24px;\n}\n.ps-modal {\n width: 100%; max-width: 480px; background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default); border-radius: 14px;\n box-shadow: var(--mj-shadow-lg, var(--mj-shadow-sm)); overflow: hidden;\n}\n.ps-modal-head {\n display: flex; align-items: center; gap: 12px; padding: 18px 20px;\n border-bottom: 1px solid var(--mj-border-subtle);\n}\n.ps-modal-head .ico {\n width: 38px; height: 38px; border-radius: 10px; display: grid; place-items: center;\n font-size: 16px; flex: 0 0 auto;\n background: var(--mj-status-info-bg); color: var(--mj-status-info-text);\n}\n.ps-modal-head.warn .ico { background: var(--mj-status-warning-bg); color: var(--mj-status-warning-text); }\n.ps-modal-head.danger .ico { background: var(--mj-status-error-bg); color: var(--mj-status-error-text); }\n.ps-modal-head h3 { margin: 0; font-size: 15.5px; font-weight: 650; color: var(--mj-text-primary); }\n.ps-modal-body { padding: 18px 20px; font-size: 13.5px; line-height: 1.55; color: var(--mj-text-secondary); }\n.ps-modal-body .ps-field { margin-top: 14px; margin-bottom: 0; }\n.ps-modal-foot {\n display: flex; align-items: center; gap: 10px; padding: 14px 20px;\n border-top: 1px solid var(--mj-border-subtle); background: var(--mj-bg-surface-card);\n}\n.ps-modal-foot .ps-spacer { flex: 1; }\n", ".ps-compare { display: flex; flex-direction: column; gap: 16px; }\n.ps-compare .cmp-toolbar { display: flex; align-items: center; gap: 12px; }\n\n/* side-by-side grid */\n.ps-compare .cmp-grid { display: grid; grid-template-columns: 200px repeat(3, 1fr); gap: 0; min-width: 720px; }\n.ps-compare .cmp-grid .stub { padding: 14px 12px; font-weight: 700; color: var(--mj-text-muted); font-size: 12px; text-transform: uppercase; letter-spacing: .04em; border-bottom: 1px solid var(--mj-border-default); }\n.ps-compare .colhead { padding: 12px; border-top: 3px solid var(--mj-brand-primary); border-bottom: 1px solid var(--mj-border-default); border-left: 1px solid var(--mj-border-subtle); }\n.ps-compare .colhead .rn { font-weight: 700; color: var(--mj-text-primary); }\n.ps-compare .rowhdr { padding: 11px 12px; border-bottom: 1px solid var(--mj-border-subtle); display: flex; flex-direction: column; }\n.ps-compare .rowhdr > span:first-child { color: var(--mj-text-primary); font-weight: 600; font-size: 13px; }\n.ps-compare .cell {\n padding: 11px 12px; border-bottom: 1px solid var(--mj-border-subtle); border-left: 1px solid var(--mj-border-subtle);\n display: flex; align-items: center; gap: 8px;\n}\n.ps-compare .cell .v { color: var(--mj-text-primary); font-weight: 600; font-variant-numeric: tabular-nums; }\n.ps-compare .cell.best { background: var(--mj-status-success-bg); }\n.ps-compare .cell.best .v { color: var(--mj-status-success-text); }\n.ps-compare .cell.warn .v { color: var(--mj-status-warning-text); }\n.ps-compare .best-pill { font-size: 9.5px; font-weight: 800; letter-spacing: .05em; color: var(--mj-status-success-text); background: color-mix(in srgb, var(--mj-status-success) 18%, transparent); padding: 1px 6px; border-radius: 999px; }\n\n/* overlay */\n.ps-compare .legend-row { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }\n.ps-compare .legend-row .li { display: inline-flex; align-items: center; gap: 7px; font-size: 12.5px; color: var(--mj-text-secondary); }\n.ps-compare .sw { width: 11px; height: 11px; border-radius: 3px; display: inline-block; }\n.ps-compare .chart-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; align-items: start; }\n@media (max-width: 980px) { .ps-compare .chart-grid { grid-template-columns: 1fr; } }\n.ps-compare .chart-grid .span2 { grid-column: 1 / span 2; }\n@media (max-width: 980px) { .ps-compare .chart-grid .span2 { grid-column: auto; } }\n.ps-compare .chart { width: 100%; height: auto; }\n.ps-compare .chart .axis { stroke: var(--mj-border-strong); stroke-width: 1; }\n.ps-compare .chart .chance { stroke: var(--mj-border-strong); stroke-width: 1; stroke-dasharray: 4 4; }\n.ps-compare .chart .threshold { stroke: var(--mj-status-warning); stroke-width: 1.5; stroke-dasharray: 5 4; }\n.ps-compare .chart .axis-lbl { font-size: 10px; fill: var(--mj-text-muted); }\n.ps-compare .chart .warn-lbl { fill: var(--mj-status-warning-text); font-weight: 700; }\n.ps-compare .roc-legend { display: flex; gap: 14px; margin-top: 8px; }\n.ps-compare .roc-legend span { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; color: var(--mj-text-secondary); font-variant-numeric: tabular-nums; }\n\n/* champion / challenger */\n.ps-compare .reco { align-items: center; }\n.ps-compare .vs-head { display: grid; grid-template-columns: 1fr 64px 1fr; gap: 0; align-items: stretch; }\n@media (max-width: 820px) { .ps-compare .vs-head { grid-template-columns: 1fr; } }\n.ps-compare .vs-card { border: 1px solid var(--mj-border-default); border-radius: 10px; padding: 16px; background: var(--mj-bg-surface); }\n.ps-compare .vs-card.champ { border-top: 4px solid var(--mj-status-success); }\n.ps-compare .vs-card.chall { border-top: 4px solid var(--ps-node-embedding); }\n.ps-compare .vs-card .role { display: inline-flex; align-items: center; gap: 7px; font-size: 12px; font-weight: 700; }\n.ps-compare .vs-card.champ .role { color: var(--mj-status-success-text); }\n.ps-compare .vs-card.chall .role { color: var(--ps-node-embedding); }\n.ps-compare .vs-card .title { font-size: 16px; font-weight: 700; color: var(--mj-text-primary); margin: 8px 0 2px; }\n.ps-compare .vs-stats { display: flex; gap: 18px; margin-top: 14px; }\n.ps-compare .vs-stats strong { font-size: 20px; color: var(--mj-text-primary); }\n.ps-compare .vs-stats .warn-txt { color: var(--mj-status-warning-text); }\n.ps-compare .vs-stats .err-txt { color: var(--mj-status-error-text); }\n.ps-compare .vs-mid { display: grid; place-items: center; font-weight: 800; color: var(--mj-text-muted); background: var(--mj-bg-surface-sunken); border-top: 1px solid var(--mj-border-default); border-bottom: 1px solid var(--mj-border-default); }\n@media (max-width: 820px) { .ps-compare .vs-mid { padding: 8px; } }\n\n.ps-compare .delta-row { display: grid; grid-template-columns: repeat(4, 1fr); gap: 14px; }\n@media (max-width: 880px) { .ps-compare .delta-row { grid-template-columns: 1fr 1fr; } }\n.ps-compare .delta { border: 1px solid var(--mj-border-default); border-radius: 10px; padding: 14px; }\n.ps-compare .delta .lab { font-size: 12px; color: var(--mj-text-muted); font-weight: 600; }\n.ps-compare .delta .big { font-size: 20px; font-weight: 720; margin: 4px 0; display: flex; align-items: center; gap: 7px; }\n.ps-compare .delta.up .big { color: var(--mj-status-success-text); }\n.ps-compare .delta.down .big { color: var(--mj-status-error-text); }\n"], encapsulation: 2 });
217
217
  }
218
218
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(PSCompareComponent, [{
219
219
  type: Component,
@@ -279,7 +279,7 @@ export class PSCompareComponent {
279
279
  }
280
280
  }
281
281
  </div>
282
- `, styles: ["/* ============================================================================\n Predictive Studio \u2014 shared panel styles (design-token compliant)\n Maps the mockup design system onto MemberJunction semantic --mj-* tokens.\n Loaded by every PS panel component (styleUrls) under ViewEncapsulation.None,\n so the class names are global within the PS dashboard subtree.\n No hardcoded colors except the intentional categorical node/algorithm palette\n (declared once as --ps-* custom props below) which must stay distinct across\n light/dark to remain readable as diagram accents.\n ============================================================================ */\n\n/* Categorical accents for DAG node types + algorithm chips. These are\n data-visualization colors that must remain distinct regardless of theme\n (allowed exception #3 in the design-token rules). */\n.ps-panel {\n --ps-node-source: #6b7280;\n --ps-node-feature: #2050c8;\n --ps-node-embedding: #7b3ff0;\n --ps-node-target: #1f9d57;\n --ps-node-algorithm: #d98213;\n /* budget-gauge categorical accents (reuse the node palette so light/dark stay distinct) */\n --ps-gauge-cost: var(--ps-node-algorithm);\n --ps-gauge-iterations: var(--ps-node-feature);\n}\n\n.ps-panel {\n display: block;\n height: 100%;\n}\n\n/* ---- generic layout helpers ---- */\n.ps-row { display: flex; gap: 16px; }\n.ps-col { display: flex; flex-direction: column; gap: 16px; }\n.ps-wrap { flex-wrap: wrap; }\n.ps-spacer { flex: 1; }\n.ps-muted { color: var(--mj-text-muted); }\n.ps-small { font-size: 12.5px; }\n.ps-mono { font-family: ui-monospace, 'SF Mono', Menlo, Consolas, monospace; font-size: 12.5px; }\n.ps-section-title {\n font-size: 12px; text-transform: uppercase; letter-spacing: .06em;\n color: var(--mj-text-muted); margin: 4px 0 2px; font-weight: 700;\n}\n\n/* ---- cards ---- */\n.ps-card {\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 10px;\n box-shadow: var(--mj-shadow-sm);\n overflow: hidden;\n}\n.ps-card-head {\n display: flex; align-items: center; gap: 10px;\n padding: 14px 16px; border-bottom: 1px solid var(--mj-border-subtle);\n}\n.ps-card-head h3 { font-size: 14.5px; margin: 0; font-weight: 650; color: var(--mj-text-primary); }\n.ps-card-body { padding: 16px; }\n\n/* ---- KPI tiles ---- */\n.ps-kpi {\n background: var(--mj-bg-surface); border: 1px solid var(--mj-border-default);\n border-radius: 10px; padding: 16px 18px; box-shadow: var(--mj-shadow-sm);\n}\n.ps-kpi .label { font-size: 12px; color: var(--mj-text-muted); font-weight: 600; }\n.ps-kpi .val { font-size: 26px; font-weight: 720; letter-spacing: -.02em; margin-top: 2px; color: var(--mj-text-primary); }\n\n/* ---- badges ---- */\n.ps-badge {\n display: inline-flex; align-items: center; gap: 5px;\n padding: 2px 9px; border-radius: 999px; font-size: 11.5px; font-weight: 650; line-height: 1.7;\n}\n.ps-badge.gray { background: var(--mj-bg-surface-sunken); color: var(--mj-text-secondary); }\n.ps-badge.blue { background: var(--mj-status-info-bg); color: var(--mj-status-info-text); }\n.ps-badge.green { background: var(--mj-status-success-bg); color: var(--mj-status-success-text); }\n.ps-badge.amber { background: var(--mj-status-warning-bg); color: var(--mj-status-warning-text); }\n.ps-badge.red { background: var(--mj-status-error-bg); color: var(--mj-status-error-text); }\n.ps-dot { width: 7px; height: 7px; border-radius: 50%; display: inline-block; }\n\n/* recommendation-level chips (the ranking matrix payoff) */\n.ps-lvl {\n display: inline-flex; align-items: center; justify-content: center;\n min-width: 92px; padding: 4px 10px; border-radius: 7px;\n font-weight: 650; font-size: 11.5px; color: var(--mj-text-inverse);\n}\n.ps-lvl-Primary { background: var(--mj-status-success); }\n.ps-lvl-Strong { background: var(--mj-status-info); }\n.ps-lvl-Viable { background: var(--mj-status-warning); }\n.ps-lvl-Weak { background: var(--mj-text-disabled); }\n.ps-lvl-NotRecommended { background: var(--mj-bg-surface-sunken); color: var(--mj-text-muted); }\n\n/* ---- tags ---- */\n.ps-tag {\n display: inline-block; padding: 2px 8px; border-radius: 6px;\n background: var(--mj-bg-surface-sunken); color: var(--mj-text-secondary);\n font-size: 11.5px; font-weight: 600;\n}\n.ps-tag.muted { opacity: .6; }\n.ps-divider { height: 1px; background: var(--mj-border-subtle); margin: 8px 0; }\n\n/* ---- tables ---- */\n.ps-table { width: 100%; border-collapse: collapse; font-size: 13px; }\n.ps-table th {\n text-align: left; font-size: 11.5px; text-transform: uppercase; letter-spacing: .04em;\n color: var(--mj-text-muted); font-weight: 700; padding: 9px 12px;\n border-bottom: 1px solid var(--mj-border-default);\n}\n.ps-table td { padding: 11px 12px; border-bottom: 1px solid var(--mj-border-subtle); color: var(--mj-text-primary); }\n.ps-table tr:hover td { background: var(--mj-bg-surface-hover); }\n.ps-table .num { text-align: right; font-variant-numeric: tabular-nums; }\n\n.ps-rank {\n width: 22px; height: 22px; border-radius: 6px; background: var(--mj-bg-surface-sunken);\n display: inline-grid; place-items: center; font-weight: 700; font-size: 11.5px; color: var(--mj-text-secondary);\n}\n.ps-rank.gold { background: var(--mj-status-warning-bg); color: var(--mj-status-warning-text); }\n\n/* ---- progress bars ---- */\n.ps-bar { height: 8px; border-radius: 999px; background: var(--mj-bg-surface-sunken); overflow: hidden; }\n.ps-bar > span { display: block; height: 100%; border-radius: 999px; background: var(--mj-brand-primary); }\n.ps-bar.warn > span { background: var(--mj-status-warning); }\n.ps-bar.run > span { background: var(--mj-brand-primary); }\n\n.ps-fbar { display: grid; grid-template-columns: 150px 1fr 48px; align-items: center; gap: 10px; padding: 5px 0; }\n.ps-fbar .track { height: 14px; border-radius: 5px; background: var(--mj-bg-surface-sunken); overflow: hidden; }\n.ps-fbar .track > span { display: block; height: 100%; background: var(--mj-brand-primary); }\n.ps-fbar .track > span.warn { background: var(--mj-status-warning); }\n.ps-fbar .name { color: var(--mj-text-secondary); }\n\n/* gauge */\n.ps-gauge {\n position: relative; width: 64px; height: 64px; border-radius: 50%; flex: 0 0 auto;\n background: conic-gradient(var(--ps-gauge-color, var(--mj-brand-primary)) calc(var(--ps-p, 40) * 1%), var(--mj-bg-surface-sunken) 0);\n display: grid; place-items: center;\n}\n.ps-gauge::after { content: ''; position: absolute; inset: 9px; background: var(--mj-bg-surface); border-radius: 50%; }\n.ps-gauge .g-val { position: relative; font-weight: 720; font-size: 14px; color: var(--mj-text-primary); }\n\n/* warning / info callouts */\n.ps-callout { display: flex; gap: 11px; padding: 12px 14px; border-radius: 10px; border: 1px solid; }\n.ps-callout.warn { background: var(--mj-status-warning-bg); border-color: var(--mj-status-warning-border); color: var(--mj-status-warning-text); }\n.ps-callout.info { background: var(--mj-status-info-bg); border-color: var(--mj-status-info-border); color: var(--mj-status-info-text); }\n.ps-callout.success { background: var(--mj-status-success-bg); border-color: var(--mj-status-success-border); color: var(--mj-status-success-text); }\n.ps-callout i { font-size: 16px; margin-top: 1px; }\n\n/* lifecycle stepper */\n.ps-stepper { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }\n.ps-step { display: flex; align-items: center; gap: 6px; font-size: 12px; font-weight: 600; color: var(--mj-text-muted); }\n.ps-step .pip {\n width: 22px; height: 22px; border-radius: 50%; border: 2px solid var(--mj-border-strong);\n display: grid; place-items: center; font-size: 11px;\n}\n.ps-step.done { color: var(--mj-status-success-text); }\n.ps-step.done .pip { background: var(--mj-status-success); border-color: var(--mj-status-success); color: var(--mj-text-inverse); }\n.ps-step.curr { color: var(--mj-brand-primary); }\n.ps-step.curr .pip { border-color: var(--mj-brand-primary); color: var(--mj-brand-primary); }\n.ps-step .ln { width: 26px; height: 2px; background: var(--mj-border-strong); }\n\n/* ---- DAG flow canvas ---- */\n.ps-flow {\n position: relative;\n background:\n linear-gradient(var(--mj-border-subtle) 1px, transparent 1px) 0 0 / 22px 22px,\n linear-gradient(90deg, var(--mj-border-subtle) 1px, transparent 1px) 0 0 / 22px 22px,\n var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-default); border-radius: 10px;\n min-height: 620px; overflow: hidden;\n}\n.ps-node {\n position: absolute; width: 190px; background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-strong); border-left-width: 3px; border-radius: 10px;\n box-shadow: var(--mj-shadow-sm); cursor: pointer;\n}\n.ps-node .nh {\n display: flex; align-items: center; gap: 8px; padding: 9px 11px;\n border-bottom: 1px solid var(--mj-border-subtle); font-weight: 650; font-size: 12.5px; color: var(--mj-text-primary);\n}\n.ps-node .nh i.tile {\n width: 24px; height: 24px; border-radius: 6px; display: grid; place-items: center;\n color: var(--mj-text-inverse); font-size: 12px; flex: 0 0 auto;\n}\n.ps-node .nh .ps-tag { margin-left: auto; }\n.ps-node .nb { padding: 9px 11px; font-size: 12px; color: var(--mj-text-secondary); }\n.ps-node .nb-row { display: flex; justify-content: space-between; gap: 8px; padding: 2px 0; }\n.ps-node .nb-row .k { color: var(--mj-text-muted); }\n.ps-node.src { border-left-color: var(--ps-node-source); }\n.ps-node.src .nh i.tile { background: var(--ps-node-source); }\n.ps-node.feat { border-left-color: var(--ps-node-feature); }\n.ps-node.feat .nh i.tile { background: var(--ps-node-feature); }\n.ps-node.emb { border-left-color: var(--ps-node-embedding); }\n.ps-node.emb .nh i.tile { background: var(--ps-node-embedding); }\n.ps-node.target { border-left-color: var(--ps-node-target); }\n.ps-node.target .nh i.tile { background: var(--ps-node-target); }\n.ps-node.algo { border-left-color: var(--ps-node-algorithm); }\n.ps-node.algo .nh i.tile { background: var(--ps-node-algorithm); }\n.ps-node.output { border-left-color: var(--mj-status-success); }\n.ps-node.output .nh i.tile { background: var(--mj-status-success); }\n.ps-node.selected {\n border-color: var(--mj-brand-primary);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 18%, transparent);\n}\n.ps-node .port {\n position: absolute; width: 11px; height: 11px; border-radius: 50%;\n background: var(--mj-bg-surface); border: 2px solid var(--mj-brand-primary);\n top: 50%; transform: translateY(-50%);\n}\n.ps-node .port.in { left: -6px; }\n.ps-node .port.out { right: -6px; }\nsvg.ps-edges { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }\nsvg.ps-edges path { stroke: color-mix(in srgb, var(--mj-brand-primary) 45%, transparent); stroke-width: 2; fill: none; }\n\n/* palette chips */\n.ps-pchip {\n display: inline-flex; align-items: center; gap: 7px; padding: 6px 11px; border-radius: 8px;\n border: 1px dashed var(--mj-border-strong); background: var(--mj-bg-surface);\n font-size: 12.5px; font-weight: 600; color: var(--mj-text-secondary); cursor: grab;\n}\n.ps-pchip i { font-size: 12px; color: var(--mj-text-inverse); width: 20px; height: 20px; border-radius: 5px; display: grid; place-items: center; }\n.ps-pchip.s i { background: var(--ps-node-source); }\n.ps-pchip.f i { background: var(--ps-node-feature); }\n.ps-pchip.e i { background: var(--ps-node-embedding); }\n.ps-pchip.t i { background: var(--ps-node-target); }\n.ps-pchip.a i { background: var(--ps-node-algorithm); }\n\n/* segmented control */\n.ps-seg { display: inline-flex; border: 1px solid var(--mj-border-default); border-radius: 8px; overflow: hidden; }\n.ps-seg button {\n border: none; background: var(--mj-bg-surface); color: var(--mj-text-secondary);\n padding: 6px 12px; font-size: 12.5px; font-weight: 600; cursor: pointer;\n border-right: 1px solid var(--mj-border-default);\n}\n.ps-seg button:last-child { border-right: none; }\n.ps-seg button.on { background: var(--mj-brand-primary); color: var(--mj-text-inverse); }\n\n/* field control */\n.ps-field { margin-bottom: 14px; }\n.ps-field > label {\n display: block; font-size: 11px; text-transform: uppercase; letter-spacing: .04em;\n color: var(--mj-text-muted); font-weight: 700; margin-bottom: 6px;\n}\n.ps-input {\n width: 100%; padding: 8px 10px; border: 1px solid var(--mj-border-default);\n border-radius: 6px; font-size: 13px; background: var(--mj-bg-surface); color: var(--mj-text-primary);\n box-sizing: border-box;\n}\n.ps-input:focus { outline: none; border-color: var(--mj-brand-primary); box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 12%, transparent); }\n\n/* loading container */\n.ps-loading { display: flex; align-items: center; justify-content: center; padding: 60px 0; }\n\n/* ---- empty / prompt states ---- */\n.ps-empty {\n display: flex; flex-direction: column; align-items: center; justify-content: center;\n text-align: center; gap: 10px; padding: 56px 24px; color: var(--mj-text-muted);\n}\n.ps-empty .ps-empty-ico {\n width: 60px; height: 60px; border-radius: 16px; display: grid; place-items: center;\n background: var(--mj-bg-surface-sunken); color: var(--mj-text-disabled); font-size: 24px;\n}\n.ps-empty h3 { margin: 4px 0 0; font-size: 16px; font-weight: 650; color: var(--mj-text-secondary); }\n.ps-empty p { margin: 0; max-width: 420px; font-size: 13px; line-height: 1.5; }\n\n/* ---- confirm modal ---- */\n.ps-modal-backdrop {\n position: fixed; inset: 0; z-index: 1000;\n background: var(--mj-bg-overlay);\n display: flex; align-items: center; justify-content: center; padding: 24px;\n}\n.ps-modal {\n width: 100%; max-width: 480px; background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default); border-radius: 14px;\n box-shadow: var(--mj-shadow-lg, var(--mj-shadow-sm)); overflow: hidden;\n}\n.ps-modal-head {\n display: flex; align-items: center; gap: 12px; padding: 18px 20px;\n border-bottom: 1px solid var(--mj-border-subtle);\n}\n.ps-modal-head .ico {\n width: 38px; height: 38px; border-radius: 10px; display: grid; place-items: center;\n font-size: 16px; flex: 0 0 auto;\n background: var(--mj-status-info-bg); color: var(--mj-status-info-text);\n}\n.ps-modal-head.warn .ico { background: var(--mj-status-warning-bg); color: var(--mj-status-warning-text); }\n.ps-modal-head.danger .ico { background: var(--mj-status-error-bg); color: var(--mj-status-error-text); }\n.ps-modal-head h3 { margin: 0; font-size: 15.5px; font-weight: 650; color: var(--mj-text-primary); }\n.ps-modal-body { padding: 18px 20px; font-size: 13.5px; line-height: 1.55; color: var(--mj-text-secondary); }\n.ps-modal-body .ps-field { margin-top: 14px; margin-bottom: 0; }\n.ps-modal-foot {\n display: flex; align-items: center; gap: 10px; padding: 14px 20px;\n border-top: 1px solid var(--mj-border-subtle); background: var(--mj-bg-surface-card);\n}\n.ps-modal-foot .ps-spacer { flex: 1; }\n", ".ps-compare { display: flex; flex-direction: column; gap: 16px; }\n.ps-compare .cmp-toolbar { display: flex; align-items: center; gap: 12px; }\n\n/* side-by-side grid */\n.ps-compare .cmp-grid { display: grid; grid-template-columns: 200px repeat(3, 1fr); gap: 0; min-width: 720px; }\n.ps-compare .cmp-grid .stub { padding: 14px 12px; font-weight: 700; color: var(--mj-text-muted); font-size: 12px; text-transform: uppercase; letter-spacing: .04em; border-bottom: 1px solid var(--mj-border-default); }\n.ps-compare .colhead { padding: 12px; border-top: 3px solid var(--mj-brand-primary); border-bottom: 1px solid var(--mj-border-default); border-left: 1px solid var(--mj-border-subtle); }\n.ps-compare .colhead .rn { font-weight: 700; color: var(--mj-text-primary); }\n.ps-compare .rowhdr { padding: 11px 12px; border-bottom: 1px solid var(--mj-border-subtle); display: flex; flex-direction: column; }\n.ps-compare .rowhdr > span:first-child { color: var(--mj-text-primary); font-weight: 600; font-size: 13px; }\n.ps-compare .cell {\n padding: 11px 12px; border-bottom: 1px solid var(--mj-border-subtle); border-left: 1px solid var(--mj-border-subtle);\n display: flex; align-items: center; gap: 8px;\n}\n.ps-compare .cell .v { color: var(--mj-text-primary); font-weight: 600; font-variant-numeric: tabular-nums; }\n.ps-compare .cell.best { background: var(--mj-status-success-bg); }\n.ps-compare .cell.best .v { color: var(--mj-status-success-text); }\n.ps-compare .cell.warn .v { color: var(--mj-status-warning-text); }\n.ps-compare .best-pill { font-size: 9.5px; font-weight: 800; letter-spacing: .05em; color: var(--mj-status-success-text); background: color-mix(in srgb, var(--mj-status-success) 18%, transparent); padding: 1px 6px; border-radius: 999px; }\n\n/* overlay */\n.ps-compare .legend-row { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }\n.ps-compare .legend-row .li { display: inline-flex; align-items: center; gap: 7px; font-size: 12.5px; color: var(--mj-text-secondary); }\n.ps-compare .sw { width: 11px; height: 11px; border-radius: 3px; display: inline-block; }\n.ps-compare .chart-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; align-items: start; }\n@media (max-width: 980px) { .ps-compare .chart-grid { grid-template-columns: 1fr; } }\n.ps-compare .chart-grid .span2 { grid-column: 1 / span 2; }\n@media (max-width: 980px) { .ps-compare .chart-grid .span2 { grid-column: auto; } }\n.ps-compare .chart { width: 100%; height: auto; }\n.ps-compare .chart .axis { stroke: var(--mj-border-strong); stroke-width: 1; }\n.ps-compare .chart .chance { stroke: var(--mj-border-strong); stroke-width: 1; stroke-dasharray: 4 4; }\n.ps-compare .chart .threshold { stroke: var(--mj-status-warning); stroke-width: 1.5; stroke-dasharray: 5 4; }\n.ps-compare .chart .axis-lbl { font-size: 10px; fill: var(--mj-text-muted); }\n.ps-compare .chart .warn-lbl { fill: var(--mj-status-warning-text); font-weight: 700; }\n.ps-compare .roc-legend { display: flex; gap: 14px; margin-top: 8px; }\n.ps-compare .roc-legend span { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; color: var(--mj-text-secondary); font-variant-numeric: tabular-nums; }\n\n/* champion / challenger */\n.ps-compare .reco { align-items: center; }\n.ps-compare .vs-head { display: grid; grid-template-columns: 1fr 64px 1fr; gap: 0; align-items: stretch; }\n@media (max-width: 820px) { .ps-compare .vs-head { grid-template-columns: 1fr; } }\n.ps-compare .vs-card { border: 1px solid var(--mj-border-default); border-radius: 10px; padding: 16px; background: var(--mj-bg-surface); }\n.ps-compare .vs-card.champ { border-top: 4px solid var(--mj-status-success); }\n.ps-compare .vs-card.chall { border-top: 4px solid var(--ps-node-embedding); }\n.ps-compare .vs-card .role { display: inline-flex; align-items: center; gap: 7px; font-size: 12px; font-weight: 700; }\n.ps-compare .vs-card.champ .role { color: var(--mj-status-success-text); }\n.ps-compare .vs-card.chall .role { color: var(--ps-node-embedding); }\n.ps-compare .vs-card .title { font-size: 16px; font-weight: 700; color: var(--mj-text-primary); margin: 8px 0 2px; }\n.ps-compare .vs-stats { display: flex; gap: 18px; margin-top: 14px; }\n.ps-compare .vs-stats strong { font-size: 20px; color: var(--mj-text-primary); }\n.ps-compare .vs-stats .warn-txt { color: var(--mj-status-warning-text); }\n.ps-compare .vs-stats .err-txt { color: var(--mj-status-error-text); }\n.ps-compare .vs-mid { display: grid; place-items: center; font-weight: 800; color: var(--mj-text-muted); background: var(--mj-bg-surface-sunken); border-top: 1px solid var(--mj-border-default); border-bottom: 1px solid var(--mj-border-default); }\n@media (max-width: 820px) { .ps-compare .vs-mid { padding: 8px; } }\n\n.ps-compare .delta-row { display: grid; grid-template-columns: repeat(4, 1fr); gap: 14px; }\n@media (max-width: 880px) { .ps-compare .delta-row { grid-template-columns: 1fr 1fr; } }\n.ps-compare .delta { border: 1px solid var(--mj-border-default); border-radius: 10px; padding: 14px; }\n.ps-compare .delta .lab { font-size: 12px; color: var(--mj-text-muted); font-weight: 600; }\n.ps-compare .delta .big { font-size: 20px; font-weight: 720; margin: 4px 0; display: flex; align-items: center; gap: 7px; }\n.ps-compare .delta.up .big { color: var(--mj-status-success-text); }\n.ps-compare .delta.down .big { color: var(--mj-status-error-text); }\n"] }]
282
+ `, styles: ["/* ============================================================================\n Predictive Studio \u2014 shared panel styles (design-token compliant)\n Maps the mockup design system onto MemberJunction semantic --mj-* tokens.\n Loaded by every PS panel component (styleUrls) under ViewEncapsulation.None,\n so the class names are global within the PS dashboard subtree.\n No hardcoded colors except the intentional categorical node/algorithm palette\n (declared once as --ps-* custom props below) which must stay distinct across\n light/dark to remain readable as diagram accents.\n ============================================================================ */\n\n/* Categorical accents for DAG node types + algorithm chips. These are\n data-visualization colors that must remain distinct regardless of theme\n (allowed exception #3 in the design-token rules). */\n.ps-panel {\n --ps-node-source: #6b7280;\n --ps-node-feature: #2050c8;\n --ps-node-embedding: #7b3ff0;\n --ps-node-target: #1f9d57;\n --ps-node-algorithm: #d98213;\n /* budget-gauge categorical accents (reuse the node palette so light/dark stay distinct) */\n --ps-gauge-cost: var(--ps-node-algorithm);\n --ps-gauge-iterations: var(--ps-node-feature);\n}\n\n.ps-panel {\n display: block;\n height: 100%;\n}\n\n/* ---- generic layout helpers ---- */\n.ps-row { display: flex; gap: 16px; }\n.ps-col { display: flex; flex-direction: column; gap: 16px; }\n.ps-wrap { flex-wrap: wrap; }\n.ps-spacer { flex: 1; }\n.ps-muted { color: var(--mj-text-muted); }\n.ps-small { font-size: 12.5px; }\n.ps-mono { font-family: ui-monospace, 'SF Mono', Menlo, Consolas, monospace; font-size: 12.5px; }\n.ps-section-title {\n font-size: 12px; text-transform: uppercase; letter-spacing: .06em;\n color: var(--mj-text-muted); margin: 4px 0 2px; font-weight: 700;\n}\n\n/* ---- cards ---- */\n.ps-card {\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n overflow: hidden;\n}\n.ps-card-head {\n display: flex; align-items: center; gap: 10px;\n padding: 14px 16px; border-bottom: 1px solid var(--mj-border-subtle);\n}\n.ps-card-head h3 { font-size: 14.5px; margin: 0; font-weight: 650; color: var(--mj-text-primary); }\n.ps-card-body { padding: 16px; }\n\n/* ---- KPI tiles ---- */\n.ps-kpi {\n background: var(--mj-bg-surface); border: 1px solid var(--mj-border-default);\n border-radius: 8px; padding: 16px 18px;\n}\n.ps-kpi .label { font-size: 12px; color: var(--mj-text-muted); font-weight: 600; }\n.ps-kpi .val { font-size: 26px; font-weight: 720; letter-spacing: -.02em; margin-top: 2px; color: var(--mj-text-primary); }\n\n/* ---- badges ---- */\n.ps-badge {\n display: inline-flex; align-items: center; gap: 5px;\n padding: 2px 9px; border-radius: 999px; font-size: 11.5px; font-weight: 650; line-height: 1.7;\n}\n.ps-badge.gray { background: var(--mj-bg-surface-sunken); color: var(--mj-text-secondary); }\n.ps-badge.blue { background: var(--mj-status-info-bg); color: var(--mj-status-info-text); }\n.ps-badge.green { background: var(--mj-status-success-bg); color: var(--mj-status-success-text); }\n.ps-badge.amber { background: var(--mj-status-warning-bg); color: var(--mj-status-warning-text); }\n.ps-badge.red { background: var(--mj-status-error-bg); color: var(--mj-status-error-text); }\n.ps-dot { width: 7px; height: 7px; border-radius: 50%; display: inline-block; }\n\n/* recommendation-level chips (the ranking matrix payoff) */\n.ps-lvl {\n display: inline-flex; align-items: center; justify-content: center;\n min-width: 92px; padding: 4px 10px; border-radius: 7px;\n font-weight: 650; font-size: 11.5px; color: var(--mj-text-inverse);\n}\n.ps-lvl-Primary { background: var(--mj-status-success); }\n.ps-lvl-Strong { background: var(--mj-status-info); }\n.ps-lvl-Viable { background: var(--mj-status-warning); }\n.ps-lvl-Weak { background: var(--mj-text-disabled); }\n.ps-lvl-NotRecommended { background: var(--mj-bg-surface-sunken); color: var(--mj-text-muted); }\n\n/* ---- tags ---- */\n.ps-tag {\n display: inline-block; padding: 2px 8px; border-radius: 6px;\n background: var(--mj-bg-surface-sunken); color: var(--mj-text-secondary);\n font-size: 11.5px; font-weight: 600;\n}\n.ps-tag.muted { opacity: .6; }\n.ps-divider { height: 1px; background: var(--mj-border-subtle); margin: 8px 0; }\n\n/* ---- tables ---- */\n.ps-table { width: 100%; border-collapse: collapse; font-size: 13px; }\n.ps-table th {\n text-align: left; font-size: 11.5px; text-transform: uppercase; letter-spacing: .04em;\n color: var(--mj-text-muted); font-weight: 700; padding: 9px 12px;\n border-bottom: 1px solid var(--mj-border-default);\n}\n.ps-table td { padding: 11px 12px; border-bottom: 1px solid var(--mj-border-subtle); color: var(--mj-text-primary); }\n.ps-table tr:hover td { background: var(--mj-bg-surface-hover); }\n.ps-table .num { text-align: right; font-variant-numeric: tabular-nums; }\n\n.ps-rank {\n width: 22px; height: 22px; border-radius: 6px; background: var(--mj-bg-surface-sunken);\n display: inline-grid; place-items: center; font-weight: 700; font-size: 11.5px; color: var(--mj-text-secondary);\n}\n.ps-rank.gold { background: var(--mj-status-warning-bg); color: var(--mj-status-warning-text); }\n\n/* ---- progress bars ---- */\n.ps-bar { height: 8px; border-radius: 999px; background: var(--mj-bg-surface-sunken); overflow: hidden; }\n.ps-bar > span { display: block; height: 100%; border-radius: 999px; background: var(--mj-brand-primary); }\n.ps-bar.warn > span { background: var(--mj-status-warning); }\n.ps-bar.run > span { background: var(--mj-brand-primary); }\n\n.ps-fbar { display: grid; grid-template-columns: minmax(150px, 1.3fr) 2fr 56px; align-items: center; gap: 14px; padding: 8px 0; }\n.ps-fbar + .ps-fbar { border-top: 1px solid var(--mj-border-subtle); }\n.ps-fbar .track { height: 10px; border-radius: var(--mj-radius-full); background: var(--mj-bg-surface-sunken); overflow: hidden; }\n.ps-fbar .track > span { display: block; height: 100%; border-radius: var(--mj-radius-full); background: var(--mj-brand-primary); }\n.ps-fbar .track > span.warn { background: var(--mj-status-warning); }\n.ps-fbar .name { color: var(--mj-text-primary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.ps-fbar > .ps-mono { text-align: right; color: var(--mj-text-secondary); font-variant-numeric: tabular-nums; }\n\n/* gauge */\n.ps-gauge {\n position: relative; width: 64px; height: 64px; border-radius: 50%; flex: 0 0 auto;\n background: conic-gradient(var(--ps-gauge-color, var(--mj-brand-primary)) calc(var(--ps-p, 40) * 1%), var(--mj-bg-surface-sunken) 0);\n display: grid; place-items: center;\n}\n.ps-gauge::after { content: ''; position: absolute; inset: 9px; background: var(--mj-bg-surface); border-radius: 50%; }\n.ps-gauge .g-val { position: relative; font-weight: 720; font-size: 14px; color: var(--mj-text-primary); }\n\n/* warning / info callouts */\n.ps-callout { display: flex; gap: 11px; padding: 12px 14px; border-radius: 10px; border: 1px solid; }\n.ps-callout.warn { background: var(--mj-status-warning-bg); border-color: var(--mj-status-warning-border); color: var(--mj-status-warning-text); }\n.ps-callout.info { background: var(--mj-status-info-bg); border-color: var(--mj-status-info-border); color: var(--mj-status-info-text); }\n.ps-callout.success { background: var(--mj-status-success-bg); border-color: var(--mj-status-success-border); color: var(--mj-status-success-text); }\n.ps-callout i { font-size: 16px; margin-top: 1px; }\n\n/* lifecycle stepper */\n.ps-stepper { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }\n.ps-step { display: flex; align-items: center; gap: 6px; font-size: 12px; font-weight: 600; color: var(--mj-text-muted); }\n.ps-step .pip {\n width: 22px; height: 22px; border-radius: 50%; border: 2px solid var(--mj-border-strong);\n display: grid; place-items: center; font-size: 11px;\n}\n.ps-step.done { color: var(--mj-status-success-text); }\n.ps-step.done .pip { background: var(--mj-status-success); border-color: var(--mj-status-success); color: var(--mj-text-inverse); }\n.ps-step.curr { color: var(--mj-brand-primary); }\n.ps-step.curr .pip { border-color: var(--mj-brand-primary); color: var(--mj-brand-primary); }\n.ps-step .ln { width: 26px; height: 2px; background: var(--mj-border-strong); }\n\n/* ---- DAG flow canvas ---- */\n.ps-flow {\n position: relative;\n background:\n linear-gradient(var(--mj-border-subtle) 1px, transparent 1px) 0 0 / 22px 22px,\n linear-gradient(90deg, var(--mj-border-subtle) 1px, transparent 1px) 0 0 / 22px 22px,\n var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-default); border-radius: 10px;\n min-height: 620px; overflow: hidden;\n}\n.ps-node {\n position: absolute; width: 190px; background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-strong); border-left-width: 3px; border-radius: 8px;\n cursor: pointer;\n}\n.ps-node .nh {\n display: flex; align-items: center; gap: 8px; padding: 9px 11px;\n border-bottom: 1px solid var(--mj-border-subtle); font-weight: 650; font-size: 12.5px; color: var(--mj-text-primary);\n}\n.ps-node .nh i.tile {\n width: 24px; height: 24px; border-radius: 6px; display: grid; place-items: center;\n color: var(--mj-text-inverse); font-size: 12px; flex: 0 0 auto;\n}\n.ps-node .nh .ps-tag { margin-left: auto; }\n.ps-node .nb { padding: 9px 11px; font-size: 12px; color: var(--mj-text-secondary); }\n.ps-node .nb-row { display: flex; justify-content: space-between; gap: 8px; padding: 2px 0; }\n.ps-node .nb-row .k { color: var(--mj-text-muted); }\n.ps-node.src { border-left-color: var(--ps-node-source); }\n.ps-node.src .nh i.tile { background: var(--ps-node-source); }\n.ps-node.feat { border-left-color: var(--ps-node-feature); }\n.ps-node.feat .nh i.tile { background: var(--ps-node-feature); }\n.ps-node.emb { border-left-color: var(--ps-node-embedding); }\n.ps-node.emb .nh i.tile { background: var(--ps-node-embedding); }\n.ps-node.target { border-left-color: var(--ps-node-target); }\n.ps-node.target .nh i.tile { background: var(--ps-node-target); }\n.ps-node.algo { border-left-color: var(--ps-node-algorithm); }\n.ps-node.algo .nh i.tile { background: var(--ps-node-algorithm); }\n.ps-node.output { border-left-color: var(--mj-status-success); }\n.ps-node.output .nh i.tile { background: var(--mj-status-success); }\n.ps-node.selected {\n border-color: var(--mj-brand-primary);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 18%, transparent);\n}\n.ps-node .port {\n position: absolute; width: 11px; height: 11px; border-radius: 50%;\n background: var(--mj-bg-surface); border: 2px solid var(--mj-brand-primary);\n top: 50%; transform: translateY(-50%);\n}\n.ps-node .port.in { left: -6px; }\n.ps-node .port.out { right: -6px; }\nsvg.ps-edges { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }\nsvg.ps-edges path { stroke: color-mix(in srgb, var(--mj-brand-primary) 45%, transparent); stroke-width: 2; fill: none; }\n\n/* palette chips */\n.ps-pchip {\n display: inline-flex; align-items: center; gap: 7px; padding: 6px 11px; border-radius: 8px;\n border: 1px dashed var(--mj-border-strong); background: var(--mj-bg-surface);\n font-size: 12.5px; font-weight: 600; color: var(--mj-text-secondary); cursor: grab;\n}\n.ps-pchip i { font-size: 12px; color: var(--mj-text-inverse); width: 20px; height: 20px; border-radius: 5px; display: grid; place-items: center; }\n.ps-pchip.s i { background: var(--ps-node-source); }\n.ps-pchip.f i { background: var(--ps-node-feature); }\n.ps-pchip.e i { background: var(--ps-node-embedding); }\n.ps-pchip.t i { background: var(--ps-node-target); }\n.ps-pchip.a i { background: var(--ps-node-algorithm); }\n\n/* segmented control */\n.ps-seg { display: inline-flex; border: 1px solid var(--mj-border-default); border-radius: 8px; overflow: hidden; }\n.ps-seg button {\n border: none; background: var(--mj-bg-surface); color: var(--mj-text-secondary);\n padding: 6px 12px; font-size: 12.5px; font-weight: 600; cursor: pointer;\n border-right: 1px solid var(--mj-border-default);\n}\n.ps-seg button:last-child { border-right: none; }\n.ps-seg button.on { background: var(--mj-brand-primary); color: var(--mj-text-inverse); }\n\n/* field control */\n.ps-field { margin-bottom: 14px; }\n.ps-field > label {\n display: block; font-size: 11px; text-transform: uppercase; letter-spacing: .04em;\n color: var(--mj-text-muted); font-weight: 700; margin-bottom: 6px;\n}\n.ps-input {\n width: 100%; padding: 8px 10px; border: 1px solid var(--mj-border-default);\n border-radius: 6px; font-size: 13px; background: var(--mj-bg-surface); color: var(--mj-text-primary);\n box-sizing: border-box;\n}\n.ps-input:focus { outline: none; border-color: var(--mj-brand-primary); box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 12%, transparent); }\n\n/* loading container */\n.ps-loading { display: flex; align-items: center; justify-content: center; padding: 60px 0; }\n\n/* ---- empty / prompt states ---- */\n.ps-empty {\n display: flex; flex-direction: column; align-items: center; justify-content: center;\n text-align: center; gap: 10px; padding: 56px 24px; color: var(--mj-text-muted);\n}\n.ps-empty .ps-empty-ico {\n width: 60px; height: 60px; border-radius: 16px; display: grid; place-items: center;\n background: var(--mj-bg-surface-sunken); color: var(--mj-text-disabled); font-size: 24px;\n}\n.ps-empty h3 { margin: 4px 0 0; font-size: 16px; font-weight: 650; color: var(--mj-text-secondary); }\n.ps-empty p { margin: 0; max-width: 420px; font-size: 13px; line-height: 1.5; }\n\n/* ---- confirm modal ---- */\n.ps-modal-backdrop {\n position: fixed; inset: 0; z-index: 1000;\n background: var(--mj-bg-overlay);\n display: flex; align-items: center; justify-content: center; padding: 24px;\n}\n.ps-modal {\n width: 100%; max-width: 480px; background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default); border-radius: 14px;\n box-shadow: var(--mj-shadow-lg, var(--mj-shadow-sm)); overflow: hidden;\n}\n.ps-modal-head {\n display: flex; align-items: center; gap: 12px; padding: 18px 20px;\n border-bottom: 1px solid var(--mj-border-subtle);\n}\n.ps-modal-head .ico {\n width: 38px; height: 38px; border-radius: 10px; display: grid; place-items: center;\n font-size: 16px; flex: 0 0 auto;\n background: var(--mj-status-info-bg); color: var(--mj-status-info-text);\n}\n.ps-modal-head.warn .ico { background: var(--mj-status-warning-bg); color: var(--mj-status-warning-text); }\n.ps-modal-head.danger .ico { background: var(--mj-status-error-bg); color: var(--mj-status-error-text); }\n.ps-modal-head h3 { margin: 0; font-size: 15.5px; font-weight: 650; color: var(--mj-text-primary); }\n.ps-modal-body { padding: 18px 20px; font-size: 13.5px; line-height: 1.55; color: var(--mj-text-secondary); }\n.ps-modal-body .ps-field { margin-top: 14px; margin-bottom: 0; }\n.ps-modal-foot {\n display: flex; align-items: center; gap: 10px; padding: 14px 20px;\n border-top: 1px solid var(--mj-border-subtle); background: var(--mj-bg-surface-card);\n}\n.ps-modal-foot .ps-spacer { flex: 1; }\n", ".ps-compare { display: flex; flex-direction: column; gap: 16px; }\n.ps-compare .cmp-toolbar { display: flex; align-items: center; gap: 12px; }\n\n/* side-by-side grid */\n.ps-compare .cmp-grid { display: grid; grid-template-columns: 200px repeat(3, 1fr); gap: 0; min-width: 720px; }\n.ps-compare .cmp-grid .stub { padding: 14px 12px; font-weight: 700; color: var(--mj-text-muted); font-size: 12px; text-transform: uppercase; letter-spacing: .04em; border-bottom: 1px solid var(--mj-border-default); }\n.ps-compare .colhead { padding: 12px; border-top: 3px solid var(--mj-brand-primary); border-bottom: 1px solid var(--mj-border-default); border-left: 1px solid var(--mj-border-subtle); }\n.ps-compare .colhead .rn { font-weight: 700; color: var(--mj-text-primary); }\n.ps-compare .rowhdr { padding: 11px 12px; border-bottom: 1px solid var(--mj-border-subtle); display: flex; flex-direction: column; }\n.ps-compare .rowhdr > span:first-child { color: var(--mj-text-primary); font-weight: 600; font-size: 13px; }\n.ps-compare .cell {\n padding: 11px 12px; border-bottom: 1px solid var(--mj-border-subtle); border-left: 1px solid var(--mj-border-subtle);\n display: flex; align-items: center; gap: 8px;\n}\n.ps-compare .cell .v { color: var(--mj-text-primary); font-weight: 600; font-variant-numeric: tabular-nums; }\n.ps-compare .cell.best { background: var(--mj-status-success-bg); }\n.ps-compare .cell.best .v { color: var(--mj-status-success-text); }\n.ps-compare .cell.warn .v { color: var(--mj-status-warning-text); }\n.ps-compare .best-pill { font-size: 9.5px; font-weight: 800; letter-spacing: .05em; color: var(--mj-status-success-text); background: color-mix(in srgb, var(--mj-status-success) 18%, transparent); padding: 1px 6px; border-radius: 999px; }\n\n/* overlay */\n.ps-compare .legend-row { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }\n.ps-compare .legend-row .li { display: inline-flex; align-items: center; gap: 7px; font-size: 12.5px; color: var(--mj-text-secondary); }\n.ps-compare .sw { width: 11px; height: 11px; border-radius: 3px; display: inline-block; }\n.ps-compare .chart-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; align-items: start; }\n@media (max-width: 980px) { .ps-compare .chart-grid { grid-template-columns: 1fr; } }\n.ps-compare .chart-grid .span2 { grid-column: 1 / span 2; }\n@media (max-width: 980px) { .ps-compare .chart-grid .span2 { grid-column: auto; } }\n.ps-compare .chart { width: 100%; height: auto; }\n.ps-compare .chart .axis { stroke: var(--mj-border-strong); stroke-width: 1; }\n.ps-compare .chart .chance { stroke: var(--mj-border-strong); stroke-width: 1; stroke-dasharray: 4 4; }\n.ps-compare .chart .threshold { stroke: var(--mj-status-warning); stroke-width: 1.5; stroke-dasharray: 5 4; }\n.ps-compare .chart .axis-lbl { font-size: 10px; fill: var(--mj-text-muted); }\n.ps-compare .chart .warn-lbl { fill: var(--mj-status-warning-text); font-weight: 700; }\n.ps-compare .roc-legend { display: flex; gap: 14px; margin-top: 8px; }\n.ps-compare .roc-legend span { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; color: var(--mj-text-secondary); font-variant-numeric: tabular-nums; }\n\n/* champion / challenger */\n.ps-compare .reco { align-items: center; }\n.ps-compare .vs-head { display: grid; grid-template-columns: 1fr 64px 1fr; gap: 0; align-items: stretch; }\n@media (max-width: 820px) { .ps-compare .vs-head { grid-template-columns: 1fr; } }\n.ps-compare .vs-card { border: 1px solid var(--mj-border-default); border-radius: 10px; padding: 16px; background: var(--mj-bg-surface); }\n.ps-compare .vs-card.champ { border-top: 4px solid var(--mj-status-success); }\n.ps-compare .vs-card.chall { border-top: 4px solid var(--ps-node-embedding); }\n.ps-compare .vs-card .role { display: inline-flex; align-items: center; gap: 7px; font-size: 12px; font-weight: 700; }\n.ps-compare .vs-card.champ .role { color: var(--mj-status-success-text); }\n.ps-compare .vs-card.chall .role { color: var(--ps-node-embedding); }\n.ps-compare .vs-card .title { font-size: 16px; font-weight: 700; color: var(--mj-text-primary); margin: 8px 0 2px; }\n.ps-compare .vs-stats { display: flex; gap: 18px; margin-top: 14px; }\n.ps-compare .vs-stats strong { font-size: 20px; color: var(--mj-text-primary); }\n.ps-compare .vs-stats .warn-txt { color: var(--mj-status-warning-text); }\n.ps-compare .vs-stats .err-txt { color: var(--mj-status-error-text); }\n.ps-compare .vs-mid { display: grid; place-items: center; font-weight: 800; color: var(--mj-text-muted); background: var(--mj-bg-surface-sunken); border-top: 1px solid var(--mj-border-default); border-bottom: 1px solid var(--mj-border-default); }\n@media (max-width: 820px) { .ps-compare .vs-mid { padding: 8px; } }\n\n.ps-compare .delta-row { display: grid; grid-template-columns: repeat(4, 1fr); gap: 14px; }\n@media (max-width: 880px) { .ps-compare .delta-row { grid-template-columns: 1fr 1fr; } }\n.ps-compare .delta { border: 1px solid var(--mj-border-default); border-radius: 10px; padding: 14px; }\n.ps-compare .delta .lab { font-size: 12px; color: var(--mj-text-muted); font-weight: 600; }\n.ps-compare .delta .big { font-size: 20px; font-weight: 720; margin: 4px 0; display: flex; align-items: center; gap: 7px; }\n.ps-compare .delta.up .big { color: var(--mj-status-success-text); }\n.ps-compare .delta.down .big { color: var(--mj-status-error-text); }\n"] }]
283
283
  }], null, { engine: [{
284
284
  type: Input
285
285
  }] }); })();
@@ -1 +1 @@
1
- {"version":3,"file":"ps-compare.component.js","sourceRoot":"","sources":["../../../src/PredictiveStudio/components/ps-compare.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,KAAK,EAAU,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAC5E,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAE7C,OAAO,EAGL,sBAAsB,EACtB,oBAAoB,GACrB,MAAM,kCAAkC,CAAC;;;;;;;IA8BhC,AADF,8BAAqD,cACxB;IAAA,uBAAwC;IAAA,iBAAO;IAC1E,0BAAI;IAAA,sCAAsB;IAAA,iBAAK;IAC/B,yBAAG;IAAA,qJAAqI;IAC1I,AAD0I,iBAAI,EACxI;;;IAQE,iCAAuB;IAAA,YAAyC;IAAA,iBAAS;;;IAAjE,+BAAc;IAAC,cAAyC;IAAzC,6EAAyC;;;IAStE,+BAA6B;IAC3B,wBAAuC;IACvC,+BAAsB;IAAA,yGAAoF;IAC5G,AAD4G,iBAAM,EAC5G;;;IASI,AADF,+BAAwD,cACtC;IAAA,YAAa;IAAA,iBAAM;IACnC,+BAA+B;IAAA,YAAsC;IAAA,iBAAM;IAC3E,gCAA6D;IAAA,YAAwC;IACvG,AADuG,iBAAO,EACxG;;;IAJe,8CAAkC;IACrC,eAAa;IAAb,gCAAa;IACE,eAAsC;IAAtC,sEAAsC;IAC9C,cAAqC;IAArC,6CAAqC;IAAC,cAAwC;IAAxC,8DAAwC;;;IAQlE,gCAAwB;IAAA,oBAAI;IAAA,iBAAO;;;IADpE,AADF,+BAA0D,eACxC;IAAA,YAAO;IAAA,iBAAO;IAC9B,gIAAgC;IAClC,iBAAM;;;;;IAHY,sDAAuC;IACvC,eAAO;IAAP,0BAAO;IACvB,cAAsE;IAAtE,yDAAsE;;;IAJtD,AAApB,+BAAoB,WAAM;IAAA,YAAe;IAAA,iBAAO;IAAA,gCAAkC;IAAA,YAAmB;IAAO,AAAP,iBAAO,EAAM;IAClH,yIAKC;;;IANyB,eAAe;IAAf,kCAAe;IAAyC,eAAmB;IAAnB,sCAAmB;IACrG,cAKC;IALD,4BAKC;;;IAlBmB,AAA1B,AADF,+BAA0D,cAC9B,SAAI;IAAA,uCAAuB;IAAA,iBAAK;IAAA,gCAAgC;IAAA,kDAAkC;IAAO,AAAP,iBAAO,EAAM;IAGrI,AADF,AADF,+BAAkD,cACmB,cAC/C;IAAA,4BAAY;IAAA,iBAAM;IACpC,qHAMC;IACD,sHAQC;IAGP,AADE,AADE,iBAAM,EACF,EACF;IACN,gCAA6B;IAC3B,yBAAuC;IAErC,AADF,gCAAsB,cACZ;IAAA,aAAa;IAEzB,AADE,AADuB,iBAAS,EAC1B,EACF;;;IA1BoB,eAA4C;IAA5C,4DAA4C;IAEhE,eAMC;IAND,0BAMC;IACD,eAQC;IARD,gCAQC;IAOK,eAAa;IAAb,oCAAa;;;;IA5CvB,AADF,AADF,8BAAyB,aACgC,YAC9C;IAAA,kCAAkB;IAAA,iBAAQ;IACjC,iCAAsH;IAA5D,6TAA+B;IAAC,sMAAiB,gBAAS,KAAC;IACnH,sGAEC;IAEL,AADE,iBAAS,EACL;IACN,0BAA+B;IAC/B,+BAAgC;IAAA,YAA4C;IAC9E,AAD8E,iBAAO,EAC/E;IAOJ,AALF,qGAAuB,qEAKd;;;IAfqD,eAA+B;IAA/B,wDAA+B;IACvF,cAEC;IAFD,oCAEC;IAI2B,eAA4C;IAA5C,iFAA4C;IAG9E,cAoCC;IApCD,kDAoCC;;AA5ET;;;;;;;;;;GAUG;AAuEH,MAAM,OAAO,kBAAkB;IACpB,MAAM,CAA0B;IAEzC,+DAA+D;IACxD,cAAc,GAAoB,EAAE,CAAC;IACrC,iBAAiB,GAAG,EAAE,CAAC;IACvB,IAAI,GAAsB,EAAE,CAAC;IAC7B,UAAU,GAAyB,EAAE,CAAC;IAE7C,QAAQ;QACN,IAAI,CAAC,mBAAmB,EAAE,CAAC;QAC3B,IAAI,CAAC,iBAAiB,GAAG,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC;QAC1D,IAAI,CAAC,OAAO,EAAE,CAAC;IACjB,CAAC;IAED,8FAA8F;IACtF,mBAAmB;QACzB,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,EAAE,QAAQ,IAAI,EAAE,CAAC;QAC7C,IAAI,CAAC,cAAc,GAAG,QAAQ;aAC3B,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;YACT,MAAM,WAAW,GAAG,IAAI,CAAC,MAAM,CAAC,uBAAuB,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC,MAAM,CAAC;YACtG,OAAO,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,WAAW,EAAE,CAAC;QACjD,CAAC,CAAC;aACD,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,IAAI,CAAC,CAAC,CAAC;IACvC,CAAC;IAED,yEAAyE;IAClE,OAAO;QACZ,IAAI,CAAC,IAAI,CAAC,iBAAiB,EAAE,CAAC;YAC5B,IAAI,CAAC,IAAI,GAAG,EAAE,CAAC;YACf,IAAI,CAAC,UAAU,GAAG,EAAE,CAAC;YACrB,OAAO;QACT,CAAC;QACD,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,uBAAuB,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;QACzE,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;QAC1C,IAAI,CAAC,UAAU,GAAG,sBAAsB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACtD,CAAC;IAED,qEAAqE;IACrE,IAAW,YAAY;QACrB,OAAO,gBAAgB,IAAI,CAAC,IAAI,CAAC,MAAM,QAAQ,CAAC;IAClD,CAAC;IAED,qEAAqE;IACrE,IAAW,OAAO;QAChB,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;QAC7C,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,UAAU,IAAI,IAAI;YAAE,OAAO,gFAAgF,CAAC;QAC9H,OAAO,GAAG,IAAI,CAAC,KAAK,KAAK,IAAI,CAAC,SAAS,mCAAmC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,oEAAoE,CAAC;IAC3K,CAAC;4GAhDU,kBAAkB;6DAAlB,kBAAkB;YA/D3B,8BAAgE;YAO5D,AANF,oFAAmC,qDAM1B;YAqDX,iBAAM;;YA3DJ,cA0DC;YA1DD,yDA0DC;4BA/DK,YAAY,EAAE,WAAW;;iFAmExB,kBAAkB;cAtE9B,SAAS;6BACI,IAAI,YACN,YAAY,WACb,CAAC,YAAY,EAAE,WAAW,CAAC,iBACrB,iBAAiB,CAAC,IAAI,YAE3B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8DT;;kBAGA,KAAK;;kFADK,kBAAkB","sourcesContent":["import { Component, Input, OnInit, ViewEncapsulation } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { FormsModule } from '@angular/forms';\nimport { PredictiveStudioEngine } from '../engine/predictive-studio.engine';\nimport {\n PSCompareColumn,\n PSCompareMetricRow,\n buildCompareMetricRows,\n deriveCompareColumns,\n} from '../predictive-studio.view-models';\n\n/** A selectable session option for the compare picker. */\ninterface SessionOption {\n id: string;\n name: string;\n scoredCount: number;\n}\n\n/**\n * Compare Runs panel — a live side-by-side comparison of the top scored iterations (training runs)\n * within a selected experiment session. Fully live:\n *\n * - A session picker lists every session that has ≥2 scored iterations (only those are comparable).\n * - The top runs of the selected session are derived via {@link deriveCompareColumns} and rendered as\n * side-by-side columns; the real metric rows (holdout score, compute cost) come from\n * {@link buildCompareMetricRows}, with the best column per row highlighted.\n *\n * Empty/prompt state when nothing is comparable. 100% entity-agnostic.\n */\n@Component({\n standalone: true,\n selector: 'ps-compare',\n imports: [CommonModule, FormsModule],\n encapsulation: ViewEncapsulation.None,\n styleUrls: ['../predictive-studio.shared.scss', './ps-compare.component.scss'],\n template: `\n <div class=\"ps-panel ps-compare\" data-testid=\"ps-compare-panel\">\n @if (sessionOptions.length === 0) {\n <div class=\"ps-empty\" data-testid=\"ps-compare-empty\">\n <span class=\"ps-empty-ico\"><i class=\"fa-solid fa-chart-column\"></i></span>\n <h3>Nothing to compare yet</h3>\n <p>Run an experiment with at least two scored iterations and you'll be able to line up their metrics side-by-side here to pick a winner.</p>\n </div>\n } @else {\n <!-- toolbar: session picker -->\n <div class=\"cmp-toolbar\">\n <div class=\"ps-field\" style=\"margin:0;min-width:280px\">\n <label>Session to compare</label>\n <select class=\"ps-input\" data-testid=\"ps-compare-session\" [(ngModel)]=\"selectedSessionId\" (ngModelChange)=\"rebuild()\">\n @for (s of sessionOptions; track s.id) {\n <option [value]=\"s.id\">{{ s.name }} · {{ s.scoredCount }} scored</option>\n }\n </select>\n </div>\n <span class=\"ps-spacer\"></span>\n <span class=\"ps-small ps-muted\">{{ runs.length }} top runs · ranked by score</span>\n </div>\n\n @if (runs.length < 2) {\n <div class=\"ps-callout info\">\n <i class=\"fa-solid fa-circle-info\"></i>\n <div class=\"ps-small\">This session has fewer than two scored iterations — pick another session to compare.</div>\n </div>\n } @else {\n <div class=\"ps-card\" data-testid=\"ps-compare-layout-side\">\n <div class=\"ps-card-head\"><h3>Side-by-side comparison</h3><span class=\"ps-muted ps-small\">Best value in each row highlighted</span></div>\n <div class=\"ps-card-body\" style=\"overflow-x:auto\">\n <div class=\"cmp-grid\" [style.grid-template-columns]=\"gridTemplate\">\n <div class=\"stub\">Run / Metric</div>\n @for (r of runs; track r.key) {\n <div class=\"colhead\" [style.border-top-color]=\"r.color\">\n <div class=\"rn\">{{ r.label }}</div>\n <div class=\"ps-muted ps-small\">{{ r.algorithm }} · {{ r.descriptor }}</div>\n <span class=\"ps-badge\" [class]=\"r.isBest ? 'green' : 'gray'\">{{ r.isBest ? 'Top scorer' : r.status }}</span>\n </div>\n }\n @for (row of metricRows; track row.label) {\n <div class=\"rowhdr\"><span>{{ row.label }}</span><span class=\"h ps-muted ps-small\">{{ row.qualifier }}</span></div>\n @for (v of row.values; track $index) {\n <div class=\"cell\" [class.best]=\"$index === row.bestIndex\">\n <span class=\"v\">{{ v }}</span>\n @if ($index === row.bestIndex) { <span class=\"best-pill\">BEST</span> }\n </div>\n }\n }\n </div>\n </div>\n </div>\n <div class=\"ps-callout info\">\n <i class=\"fa-solid fa-circle-info\"></i>\n <div class=\"ps-small\">\n <strong>{{ verdict }}</strong>\n </div>\n </div>\n }\n }\n </div>\n `,\n})\nexport class PSCompareComponent implements OnInit {\n @Input() engine!: PredictiveStudioEngine;\n\n /** Sessions with ≥2 scored iterations — the comparable set. */\n public sessionOptions: SessionOption[] = [];\n public selectedSessionId = '';\n public runs: PSCompareColumn[] = [];\n public metricRows: PSCompareMetricRow[] = [];\n\n ngOnInit(): void {\n this.buildSessionOptions();\n this.selectedSessionId = this.sessionOptions[0]?.id ?? '';\n this.rebuild();\n }\n\n /** List sessions that have at least two scored iterations (the only ones worth comparing). */\n private buildSessionOptions(): void {\n const sessions = this.engine?.Sessions ?? [];\n this.sessionOptions = sessions\n .map((s) => {\n const scoredCount = this.engine.IterationRowsForSession(s.ID).filter((it) => it.Score != null).length;\n return { id: s.ID, name: s.Name, scoredCount };\n })\n .filter((o) => o.scoredCount >= 2);\n }\n\n /** Derive the compare columns + metric rows for the selected session. */\n public rebuild(): void {\n if (!this.selectedSessionId) {\n this.runs = [];\n this.metricRows = [];\n return;\n }\n const rows = this.engine.IterationRowsForSession(this.selectedSessionId);\n this.runs = deriveCompareColumns(rows, 3);\n this.metricRows = buildCompareMetricRows(this.runs);\n }\n\n /** CSS grid template — stub column + one fraction per run column. */\n public get gridTemplate(): string {\n return `200px repeat(${this.runs.length}, 1fr)`;\n }\n\n /** A plain-language verdict naming the best run by holdout score. */\n public get verdict(): string {\n const best = this.runs.find((r) => r.isBest);\n if (!best || best.holdoutAuc == null) return 'Pick the run that wins on the holdout score — the honest out-of-sample number.';\n return `${best.label} (${best.algorithm}) leads on the holdout score at ${best.holdoutAuc.toFixed(3)} — prefer the holdout number over training metrics when promoting.`;\n }\n}\n"]}
1
+ {"version":3,"file":"ps-compare.component.js","sourceRoot":"","sources":["../../../src/PredictiveStudio/components/ps-compare.component.ts"],"names":[],"mappings":"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","sourcesContent":["import { Component, Input, OnInit, ViewEncapsulation } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { FormsModule } from '@angular/forms';\nimport { PredictiveStudioEngine } from '../engine/predictive-studio.engine';\nimport {\n PSCompareColumn,\n PSCompareMetricRow,\n buildCompareMetricRows,\n deriveCompareColumns,\n} from '../predictive-studio.view-models';\n\n/** A selectable session option for the compare picker. */\ninterface SessionOption {\n id: string;\n name: string;\n scoredCount: number;\n}\n\n/**\n * Compare Runs panel — a live side-by-side comparison of the top scored iterations (training runs)\n * within a selected experiment session. Fully live:\n *\n * - A session picker lists every session that has ≥2 scored iterations (only those are comparable).\n * - The top runs of the selected session are derived via {@link deriveCompareColumns} and rendered as\n * side-by-side columns; the real metric rows (holdout score, compute cost) come from\n * {@link buildCompareMetricRows}, with the best column per row highlighted.\n *\n * Empty/prompt state when nothing is comparable. 100% entity-agnostic.\n */\n@Component({\n standalone: true,\n selector: 'ps-compare',\n imports: [CommonModule, FormsModule],\n encapsulation: ViewEncapsulation.None,\n styleUrls: ['../predictive-studio.shared.css', './ps-compare.component.css'],\n template: `\n <div class=\"ps-panel ps-compare\" data-testid=\"ps-compare-panel\">\n @if (sessionOptions.length === 0) {\n <div class=\"ps-empty\" data-testid=\"ps-compare-empty\">\n <span class=\"ps-empty-ico\"><i class=\"fa-solid fa-chart-column\"></i></span>\n <h3>Nothing to compare yet</h3>\n <p>Run an experiment with at least two scored iterations and you'll be able to line up their metrics side-by-side here to pick a winner.</p>\n </div>\n } @else {\n <!-- toolbar: session picker -->\n <div class=\"cmp-toolbar\">\n <div class=\"ps-field\" style=\"margin:0;min-width:280px\">\n <label>Session to compare</label>\n <select class=\"ps-input\" data-testid=\"ps-compare-session\" [(ngModel)]=\"selectedSessionId\" (ngModelChange)=\"rebuild()\">\n @for (s of sessionOptions; track s.id) {\n <option [value]=\"s.id\">{{ s.name }} · {{ s.scoredCount }} scored</option>\n }\n </select>\n </div>\n <span class=\"ps-spacer\"></span>\n <span class=\"ps-small ps-muted\">{{ runs.length }} top runs · ranked by score</span>\n </div>\n\n @if (runs.length < 2) {\n <div class=\"ps-callout info\">\n <i class=\"fa-solid fa-circle-info\"></i>\n <div class=\"ps-small\">This session has fewer than two scored iterations — pick another session to compare.</div>\n </div>\n } @else {\n <div class=\"ps-card\" data-testid=\"ps-compare-layout-side\">\n <div class=\"ps-card-head\"><h3>Side-by-side comparison</h3><span class=\"ps-muted ps-small\">Best value in each row highlighted</span></div>\n <div class=\"ps-card-body\" style=\"overflow-x:auto\">\n <div class=\"cmp-grid\" [style.grid-template-columns]=\"gridTemplate\">\n <div class=\"stub\">Run / Metric</div>\n @for (r of runs; track r.key) {\n <div class=\"colhead\" [style.border-top-color]=\"r.color\">\n <div class=\"rn\">{{ r.label }}</div>\n <div class=\"ps-muted ps-small\">{{ r.algorithm }} · {{ r.descriptor }}</div>\n <span class=\"ps-badge\" [class]=\"r.isBest ? 'green' : 'gray'\">{{ r.isBest ? 'Top scorer' : r.status }}</span>\n </div>\n }\n @for (row of metricRows; track row.label) {\n <div class=\"rowhdr\"><span>{{ row.label }}</span><span class=\"h ps-muted ps-small\">{{ row.qualifier }}</span></div>\n @for (v of row.values; track $index) {\n <div class=\"cell\" [class.best]=\"$index === row.bestIndex\">\n <span class=\"v\">{{ v }}</span>\n @if ($index === row.bestIndex) { <span class=\"best-pill\">BEST</span> }\n </div>\n }\n }\n </div>\n </div>\n </div>\n <div class=\"ps-callout info\">\n <i class=\"fa-solid fa-circle-info\"></i>\n <div class=\"ps-small\">\n <strong>{{ verdict }}</strong>\n </div>\n </div>\n }\n }\n </div>\n `,\n})\nexport class PSCompareComponent implements OnInit {\n @Input() engine!: PredictiveStudioEngine;\n\n /** Sessions with ≥2 scored iterations — the comparable set. */\n public sessionOptions: SessionOption[] = [];\n public selectedSessionId = '';\n public runs: PSCompareColumn[] = [];\n public metricRows: PSCompareMetricRow[] = [];\n\n ngOnInit(): void {\n this.buildSessionOptions();\n this.selectedSessionId = this.sessionOptions[0]?.id ?? '';\n this.rebuild();\n }\n\n /** List sessions that have at least two scored iterations (the only ones worth comparing). */\n private buildSessionOptions(): void {\n const sessions = this.engine?.Sessions ?? [];\n this.sessionOptions = sessions\n .map((s) => {\n const scoredCount = this.engine.IterationRowsForSession(s.ID).filter((it) => it.Score != null).length;\n return { id: s.ID, name: s.Name, scoredCount };\n })\n .filter((o) => o.scoredCount >= 2);\n }\n\n /** Derive the compare columns + metric rows for the selected session. */\n public rebuild(): void {\n if (!this.selectedSessionId) {\n this.runs = [];\n this.metricRows = [];\n return;\n }\n const rows = this.engine.IterationRowsForSession(this.selectedSessionId);\n this.runs = deriveCompareColumns(rows, 3);\n this.metricRows = buildCompareMetricRows(this.runs);\n }\n\n /** CSS grid template — stub column + one fraction per run column. */\n public get gridTemplate(): string {\n return `200px repeat(${this.runs.length}, 1fr)`;\n }\n\n /** A plain-language verdict naming the best run by holdout score. */\n public get verdict(): string {\n const best = this.runs.find((r) => r.isBest);\n if (!best || best.holdoutAuc == null) return 'Pick the run that wins on the holdout score — the honest out-of-sample number.';\n return `${best.label} (${best.algorithm}) leads on the holdout score at ${best.holdoutAuc.toFixed(3)} — prefer the holdout number over training metrics when promoting.`;\n }\n}\n"]}
@@ -117,7 +117,7 @@ export class PSConfirmModalComponent {
117
117
  i0.ɵɵtextInterpolate1(" ", ctx.confirmLabel, " ");
118
118
  i0.ɵɵadvance(2);
119
119
  i0.ɵɵproperty("disabled", ctx.busy);
120
- } }, dependencies: [CommonModule, FormsModule, i1.DefaultValueAccessor, i1.NgControlStatus, i1.NgModel, MJButtonDirective], styles: ["/* ============================================================================\n Predictive Studio \u2014 shared panel styles (design-token compliant)\n Maps the mockup design system onto MemberJunction semantic --mj-* tokens.\n Loaded by every PS panel component (styleUrls) under ViewEncapsulation.None,\n so the class names are global within the PS dashboard subtree.\n No hardcoded colors except the intentional categorical node/algorithm palette\n (declared once as --ps-* custom props below) which must stay distinct across\n light/dark to remain readable as diagram accents.\n ============================================================================ */\n\n/* Categorical accents for DAG node types + algorithm chips. These are\n data-visualization colors that must remain distinct regardless of theme\n (allowed exception #3 in the design-token rules). */\n.ps-panel {\n --ps-node-source: #6b7280;\n --ps-node-feature: #2050c8;\n --ps-node-embedding: #7b3ff0;\n --ps-node-target: #1f9d57;\n --ps-node-algorithm: #d98213;\n /* budget-gauge categorical accents (reuse the node palette so light/dark stay distinct) */\n --ps-gauge-cost: var(--ps-node-algorithm);\n --ps-gauge-iterations: var(--ps-node-feature);\n}\n\n.ps-panel {\n display: block;\n height: 100%;\n}\n\n/* ---- generic layout helpers ---- */\n.ps-row { display: flex; gap: 16px; }\n.ps-col { display: flex; flex-direction: column; gap: 16px; }\n.ps-wrap { flex-wrap: wrap; }\n.ps-spacer { flex: 1; }\n.ps-muted { color: var(--mj-text-muted); }\n.ps-small { font-size: 12.5px; }\n.ps-mono { font-family: ui-monospace, 'SF Mono', Menlo, Consolas, monospace; font-size: 12.5px; }\n.ps-section-title {\n font-size: 12px; text-transform: uppercase; letter-spacing: .06em;\n color: var(--mj-text-muted); margin: 4px 0 2px; font-weight: 700;\n}\n\n/* ---- cards ---- */\n.ps-card {\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 10px;\n box-shadow: var(--mj-shadow-sm);\n overflow: hidden;\n}\n.ps-card-head {\n display: flex; align-items: center; gap: 10px;\n padding: 14px 16px; border-bottom: 1px solid var(--mj-border-subtle);\n}\n.ps-card-head h3 { font-size: 14.5px; margin: 0; font-weight: 650; color: var(--mj-text-primary); }\n.ps-card-body { padding: 16px; }\n\n/* ---- KPI tiles ---- */\n.ps-kpi {\n background: var(--mj-bg-surface); border: 1px solid var(--mj-border-default);\n border-radius: 10px; padding: 16px 18px; box-shadow: var(--mj-shadow-sm);\n}\n.ps-kpi .label { font-size: 12px; color: var(--mj-text-muted); font-weight: 600; }\n.ps-kpi .val { font-size: 26px; font-weight: 720; letter-spacing: -.02em; margin-top: 2px; color: var(--mj-text-primary); }\n\n/* ---- badges ---- */\n.ps-badge {\n display: inline-flex; align-items: center; gap: 5px;\n padding: 2px 9px; border-radius: 999px; font-size: 11.5px; font-weight: 650; line-height: 1.7;\n}\n.ps-badge.gray { background: var(--mj-bg-surface-sunken); color: var(--mj-text-secondary); }\n.ps-badge.blue { background: var(--mj-status-info-bg); color: var(--mj-status-info-text); }\n.ps-badge.green { background: var(--mj-status-success-bg); color: var(--mj-status-success-text); }\n.ps-badge.amber { background: var(--mj-status-warning-bg); color: var(--mj-status-warning-text); }\n.ps-badge.red { background: var(--mj-status-error-bg); color: var(--mj-status-error-text); }\n.ps-dot { width: 7px; height: 7px; border-radius: 50%; display: inline-block; }\n\n/* recommendation-level chips (the ranking matrix payoff) */\n.ps-lvl {\n display: inline-flex; align-items: center; justify-content: center;\n min-width: 92px; padding: 4px 10px; border-radius: 7px;\n font-weight: 650; font-size: 11.5px; color: var(--mj-text-inverse);\n}\n.ps-lvl-Primary { background: var(--mj-status-success); }\n.ps-lvl-Strong { background: var(--mj-status-info); }\n.ps-lvl-Viable { background: var(--mj-status-warning); }\n.ps-lvl-Weak { background: var(--mj-text-disabled); }\n.ps-lvl-NotRecommended { background: var(--mj-bg-surface-sunken); color: var(--mj-text-muted); }\n\n/* ---- tags ---- */\n.ps-tag {\n display: inline-block; padding: 2px 8px; border-radius: 6px;\n background: var(--mj-bg-surface-sunken); color: var(--mj-text-secondary);\n font-size: 11.5px; font-weight: 600;\n}\n.ps-tag.muted { opacity: .6; }\n.ps-divider { height: 1px; background: var(--mj-border-subtle); margin: 8px 0; }\n\n/* ---- tables ---- */\n.ps-table { width: 100%; border-collapse: collapse; font-size: 13px; }\n.ps-table th {\n text-align: left; font-size: 11.5px; text-transform: uppercase; letter-spacing: .04em;\n color: var(--mj-text-muted); font-weight: 700; padding: 9px 12px;\n border-bottom: 1px solid var(--mj-border-default);\n}\n.ps-table td { padding: 11px 12px; border-bottom: 1px solid var(--mj-border-subtle); color: var(--mj-text-primary); }\n.ps-table tr:hover td { background: var(--mj-bg-surface-hover); }\n.ps-table .num { text-align: right; font-variant-numeric: tabular-nums; }\n\n.ps-rank {\n width: 22px; height: 22px; border-radius: 6px; background: var(--mj-bg-surface-sunken);\n display: inline-grid; place-items: center; font-weight: 700; font-size: 11.5px; color: var(--mj-text-secondary);\n}\n.ps-rank.gold { background: var(--mj-status-warning-bg); color: var(--mj-status-warning-text); }\n\n/* ---- progress bars ---- */\n.ps-bar { height: 8px; border-radius: 999px; background: var(--mj-bg-surface-sunken); overflow: hidden; }\n.ps-bar > span { display: block; height: 100%; border-radius: 999px; background: var(--mj-brand-primary); }\n.ps-bar.warn > span { background: var(--mj-status-warning); }\n.ps-bar.run > span { background: var(--mj-brand-primary); }\n\n.ps-fbar { display: grid; grid-template-columns: 150px 1fr 48px; align-items: center; gap: 10px; padding: 5px 0; }\n.ps-fbar .track { height: 14px; border-radius: 5px; background: var(--mj-bg-surface-sunken); overflow: hidden; }\n.ps-fbar .track > span { display: block; height: 100%; background: var(--mj-brand-primary); }\n.ps-fbar .track > span.warn { background: var(--mj-status-warning); }\n.ps-fbar .name { color: var(--mj-text-secondary); }\n\n/* gauge */\n.ps-gauge {\n position: relative; width: 64px; height: 64px; border-radius: 50%; flex: 0 0 auto;\n background: conic-gradient(var(--ps-gauge-color, var(--mj-brand-primary)) calc(var(--ps-p, 40) * 1%), var(--mj-bg-surface-sunken) 0);\n display: grid; place-items: center;\n}\n.ps-gauge::after { content: ''; position: absolute; inset: 9px; background: var(--mj-bg-surface); border-radius: 50%; }\n.ps-gauge .g-val { position: relative; font-weight: 720; font-size: 14px; color: var(--mj-text-primary); }\n\n/* warning / info callouts */\n.ps-callout { display: flex; gap: 11px; padding: 12px 14px; border-radius: 10px; border: 1px solid; }\n.ps-callout.warn { background: var(--mj-status-warning-bg); border-color: var(--mj-status-warning-border); color: var(--mj-status-warning-text); }\n.ps-callout.info { background: var(--mj-status-info-bg); border-color: var(--mj-status-info-border); color: var(--mj-status-info-text); }\n.ps-callout.success { background: var(--mj-status-success-bg); border-color: var(--mj-status-success-border); color: var(--mj-status-success-text); }\n.ps-callout i { font-size: 16px; margin-top: 1px; }\n\n/* lifecycle stepper */\n.ps-stepper { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }\n.ps-step { display: flex; align-items: center; gap: 6px; font-size: 12px; font-weight: 600; color: var(--mj-text-muted); }\n.ps-step .pip {\n width: 22px; height: 22px; border-radius: 50%; border: 2px solid var(--mj-border-strong);\n display: grid; place-items: center; font-size: 11px;\n}\n.ps-step.done { color: var(--mj-status-success-text); }\n.ps-step.done .pip { background: var(--mj-status-success); border-color: var(--mj-status-success); color: var(--mj-text-inverse); }\n.ps-step.curr { color: var(--mj-brand-primary); }\n.ps-step.curr .pip { border-color: var(--mj-brand-primary); color: var(--mj-brand-primary); }\n.ps-step .ln { width: 26px; height: 2px; background: var(--mj-border-strong); }\n\n/* ---- DAG flow canvas ---- */\n.ps-flow {\n position: relative;\n background:\n linear-gradient(var(--mj-border-subtle) 1px, transparent 1px) 0 0 / 22px 22px,\n linear-gradient(90deg, var(--mj-border-subtle) 1px, transparent 1px) 0 0 / 22px 22px,\n var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-default); border-radius: 10px;\n min-height: 620px; overflow: hidden;\n}\n.ps-node {\n position: absolute; width: 190px; background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-strong); border-left-width: 3px; border-radius: 10px;\n box-shadow: var(--mj-shadow-sm); cursor: pointer;\n}\n.ps-node .nh {\n display: flex; align-items: center; gap: 8px; padding: 9px 11px;\n border-bottom: 1px solid var(--mj-border-subtle); font-weight: 650; font-size: 12.5px; color: var(--mj-text-primary);\n}\n.ps-node .nh i.tile {\n width: 24px; height: 24px; border-radius: 6px; display: grid; place-items: center;\n color: var(--mj-text-inverse); font-size: 12px; flex: 0 0 auto;\n}\n.ps-node .nh .ps-tag { margin-left: auto; }\n.ps-node .nb { padding: 9px 11px; font-size: 12px; color: var(--mj-text-secondary); }\n.ps-node .nb-row { display: flex; justify-content: space-between; gap: 8px; padding: 2px 0; }\n.ps-node .nb-row .k { color: var(--mj-text-muted); }\n.ps-node.src { border-left-color: var(--ps-node-source); }\n.ps-node.src .nh i.tile { background: var(--ps-node-source); }\n.ps-node.feat { border-left-color: var(--ps-node-feature); }\n.ps-node.feat .nh i.tile { background: var(--ps-node-feature); }\n.ps-node.emb { border-left-color: var(--ps-node-embedding); }\n.ps-node.emb .nh i.tile { background: var(--ps-node-embedding); }\n.ps-node.target { border-left-color: var(--ps-node-target); }\n.ps-node.target .nh i.tile { background: var(--ps-node-target); }\n.ps-node.algo { border-left-color: var(--ps-node-algorithm); }\n.ps-node.algo .nh i.tile { background: var(--ps-node-algorithm); }\n.ps-node.output { border-left-color: var(--mj-status-success); }\n.ps-node.output .nh i.tile { background: var(--mj-status-success); }\n.ps-node.selected {\n border-color: var(--mj-brand-primary);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 18%, transparent);\n}\n.ps-node .port {\n position: absolute; width: 11px; height: 11px; border-radius: 50%;\n background: var(--mj-bg-surface); border: 2px solid var(--mj-brand-primary);\n top: 50%; transform: translateY(-50%);\n}\n.ps-node .port.in { left: -6px; }\n.ps-node .port.out { right: -6px; }\nsvg.ps-edges { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }\nsvg.ps-edges path { stroke: color-mix(in srgb, var(--mj-brand-primary) 45%, transparent); stroke-width: 2; fill: none; }\n\n/* palette chips */\n.ps-pchip {\n display: inline-flex; align-items: center; gap: 7px; padding: 6px 11px; border-radius: 8px;\n border: 1px dashed var(--mj-border-strong); background: var(--mj-bg-surface);\n font-size: 12.5px; font-weight: 600; color: var(--mj-text-secondary); cursor: grab;\n}\n.ps-pchip i { font-size: 12px; color: var(--mj-text-inverse); width: 20px; height: 20px; border-radius: 5px; display: grid; place-items: center; }\n.ps-pchip.s i { background: var(--ps-node-source); }\n.ps-pchip.f i { background: var(--ps-node-feature); }\n.ps-pchip.e i { background: var(--ps-node-embedding); }\n.ps-pchip.t i { background: var(--ps-node-target); }\n.ps-pchip.a i { background: var(--ps-node-algorithm); }\n\n/* segmented control */\n.ps-seg { display: inline-flex; border: 1px solid var(--mj-border-default); border-radius: 8px; overflow: hidden; }\n.ps-seg button {\n border: none; background: var(--mj-bg-surface); color: var(--mj-text-secondary);\n padding: 6px 12px; font-size: 12.5px; font-weight: 600; cursor: pointer;\n border-right: 1px solid var(--mj-border-default);\n}\n.ps-seg button:last-child { border-right: none; }\n.ps-seg button.on { background: var(--mj-brand-primary); color: var(--mj-text-inverse); }\n\n/* field control */\n.ps-field { margin-bottom: 14px; }\n.ps-field > label {\n display: block; font-size: 11px; text-transform: uppercase; letter-spacing: .04em;\n color: var(--mj-text-muted); font-weight: 700; margin-bottom: 6px;\n}\n.ps-input {\n width: 100%; padding: 8px 10px; border: 1px solid var(--mj-border-default);\n border-radius: 6px; font-size: 13px; background: var(--mj-bg-surface); color: var(--mj-text-primary);\n box-sizing: border-box;\n}\n.ps-input:focus { outline: none; border-color: var(--mj-brand-primary); box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 12%, transparent); }\n\n/* loading container */\n.ps-loading { display: flex; align-items: center; justify-content: center; padding: 60px 0; }\n\n/* ---- empty / prompt states ---- */\n.ps-empty {\n display: flex; flex-direction: column; align-items: center; justify-content: center;\n text-align: center; gap: 10px; padding: 56px 24px; color: var(--mj-text-muted);\n}\n.ps-empty .ps-empty-ico {\n width: 60px; height: 60px; border-radius: 16px; display: grid; place-items: center;\n background: var(--mj-bg-surface-sunken); color: var(--mj-text-disabled); font-size: 24px;\n}\n.ps-empty h3 { margin: 4px 0 0; font-size: 16px; font-weight: 650; color: var(--mj-text-secondary); }\n.ps-empty p { margin: 0; max-width: 420px; font-size: 13px; line-height: 1.5; }\n\n/* ---- confirm modal ---- */\n.ps-modal-backdrop {\n position: fixed; inset: 0; z-index: 1000;\n background: var(--mj-bg-overlay);\n display: flex; align-items: center; justify-content: center; padding: 24px;\n}\n.ps-modal {\n width: 100%; max-width: 480px; background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default); border-radius: 14px;\n box-shadow: var(--mj-shadow-lg, var(--mj-shadow-sm)); overflow: hidden;\n}\n.ps-modal-head {\n display: flex; align-items: center; gap: 12px; padding: 18px 20px;\n border-bottom: 1px solid var(--mj-border-subtle);\n}\n.ps-modal-head .ico {\n width: 38px; height: 38px; border-radius: 10px; display: grid; place-items: center;\n font-size: 16px; flex: 0 0 auto;\n background: var(--mj-status-info-bg); color: var(--mj-status-info-text);\n}\n.ps-modal-head.warn .ico { background: var(--mj-status-warning-bg); color: var(--mj-status-warning-text); }\n.ps-modal-head.danger .ico { background: var(--mj-status-error-bg); color: var(--mj-status-error-text); }\n.ps-modal-head h3 { margin: 0; font-size: 15.5px; font-weight: 650; color: var(--mj-text-primary); }\n.ps-modal-body { padding: 18px 20px; font-size: 13.5px; line-height: 1.55; color: var(--mj-text-secondary); }\n.ps-modal-body .ps-field { margin-top: 14px; margin-bottom: 0; }\n.ps-modal-foot {\n display: flex; align-items: center; gap: 10px; padding: 14px 20px;\n border-top: 1px solid var(--mj-border-subtle); background: var(--mj-bg-surface-card);\n}\n.ps-modal-foot .ps-spacer { flex: 1; }\n"], encapsulation: 2 });
120
+ } }, dependencies: [CommonModule, FormsModule, i1.DefaultValueAccessor, i1.NgControlStatus, i1.NgModel, MJButtonDirective], styles: ["/* ============================================================================\n Predictive Studio \u2014 shared panel styles (design-token compliant)\n Maps the mockup design system onto MemberJunction semantic --mj-* tokens.\n Loaded by every PS panel component (styleUrls) under ViewEncapsulation.None,\n so the class names are global within the PS dashboard subtree.\n No hardcoded colors except the intentional categorical node/algorithm palette\n (declared once as --ps-* custom props below) which must stay distinct across\n light/dark to remain readable as diagram accents.\n ============================================================================ */\n\n/* Categorical accents for DAG node types + algorithm chips. These are\n data-visualization colors that must remain distinct regardless of theme\n (allowed exception #3 in the design-token rules). */\n.ps-panel {\n --ps-node-source: #6b7280;\n --ps-node-feature: #2050c8;\n --ps-node-embedding: #7b3ff0;\n --ps-node-target: #1f9d57;\n --ps-node-algorithm: #d98213;\n /* budget-gauge categorical accents (reuse the node palette so light/dark stay distinct) */\n --ps-gauge-cost: var(--ps-node-algorithm);\n --ps-gauge-iterations: var(--ps-node-feature);\n}\n\n.ps-panel {\n display: block;\n height: 100%;\n}\n\n/* ---- generic layout helpers ---- */\n.ps-row { display: flex; gap: 16px; }\n.ps-col { display: flex; flex-direction: column; gap: 16px; }\n.ps-wrap { flex-wrap: wrap; }\n.ps-spacer { flex: 1; }\n.ps-muted { color: var(--mj-text-muted); }\n.ps-small { font-size: 12.5px; }\n.ps-mono { font-family: ui-monospace, 'SF Mono', Menlo, Consolas, monospace; font-size: 12.5px; }\n.ps-section-title {\n font-size: 12px; text-transform: uppercase; letter-spacing: .06em;\n color: var(--mj-text-muted); margin: 4px 0 2px; font-weight: 700;\n}\n\n/* ---- cards ---- */\n.ps-card {\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n overflow: hidden;\n}\n.ps-card-head {\n display: flex; align-items: center; gap: 10px;\n padding: 14px 16px; border-bottom: 1px solid var(--mj-border-subtle);\n}\n.ps-card-head h3 { font-size: 14.5px; margin: 0; font-weight: 650; color: var(--mj-text-primary); }\n.ps-card-body { padding: 16px; }\n\n/* ---- KPI tiles ---- */\n.ps-kpi {\n background: var(--mj-bg-surface); border: 1px solid var(--mj-border-default);\n border-radius: 8px; padding: 16px 18px;\n}\n.ps-kpi .label { font-size: 12px; color: var(--mj-text-muted); font-weight: 600; }\n.ps-kpi .val { font-size: 26px; font-weight: 720; letter-spacing: -.02em; margin-top: 2px; color: var(--mj-text-primary); }\n\n/* ---- badges ---- */\n.ps-badge {\n display: inline-flex; align-items: center; gap: 5px;\n padding: 2px 9px; border-radius: 999px; font-size: 11.5px; font-weight: 650; line-height: 1.7;\n}\n.ps-badge.gray { background: var(--mj-bg-surface-sunken); color: var(--mj-text-secondary); }\n.ps-badge.blue { background: var(--mj-status-info-bg); color: var(--mj-status-info-text); }\n.ps-badge.green { background: var(--mj-status-success-bg); color: var(--mj-status-success-text); }\n.ps-badge.amber { background: var(--mj-status-warning-bg); color: var(--mj-status-warning-text); }\n.ps-badge.red { background: var(--mj-status-error-bg); color: var(--mj-status-error-text); }\n.ps-dot { width: 7px; height: 7px; border-radius: 50%; display: inline-block; }\n\n/* recommendation-level chips (the ranking matrix payoff) */\n.ps-lvl {\n display: inline-flex; align-items: center; justify-content: center;\n min-width: 92px; padding: 4px 10px; border-radius: 7px;\n font-weight: 650; font-size: 11.5px; color: var(--mj-text-inverse);\n}\n.ps-lvl-Primary { background: var(--mj-status-success); }\n.ps-lvl-Strong { background: var(--mj-status-info); }\n.ps-lvl-Viable { background: var(--mj-status-warning); }\n.ps-lvl-Weak { background: var(--mj-text-disabled); }\n.ps-lvl-NotRecommended { background: var(--mj-bg-surface-sunken); color: var(--mj-text-muted); }\n\n/* ---- tags ---- */\n.ps-tag {\n display: inline-block; padding: 2px 8px; border-radius: 6px;\n background: var(--mj-bg-surface-sunken); color: var(--mj-text-secondary);\n font-size: 11.5px; font-weight: 600;\n}\n.ps-tag.muted { opacity: .6; }\n.ps-divider { height: 1px; background: var(--mj-border-subtle); margin: 8px 0; }\n\n/* ---- tables ---- */\n.ps-table { width: 100%; border-collapse: collapse; font-size: 13px; }\n.ps-table th {\n text-align: left; font-size: 11.5px; text-transform: uppercase; letter-spacing: .04em;\n color: var(--mj-text-muted); font-weight: 700; padding: 9px 12px;\n border-bottom: 1px solid var(--mj-border-default);\n}\n.ps-table td { padding: 11px 12px; border-bottom: 1px solid var(--mj-border-subtle); color: var(--mj-text-primary); }\n.ps-table tr:hover td { background: var(--mj-bg-surface-hover); }\n.ps-table .num { text-align: right; font-variant-numeric: tabular-nums; }\n\n.ps-rank {\n width: 22px; height: 22px; border-radius: 6px; background: var(--mj-bg-surface-sunken);\n display: inline-grid; place-items: center; font-weight: 700; font-size: 11.5px; color: var(--mj-text-secondary);\n}\n.ps-rank.gold { background: var(--mj-status-warning-bg); color: var(--mj-status-warning-text); }\n\n/* ---- progress bars ---- */\n.ps-bar { height: 8px; border-radius: 999px; background: var(--mj-bg-surface-sunken); overflow: hidden; }\n.ps-bar > span { display: block; height: 100%; border-radius: 999px; background: var(--mj-brand-primary); }\n.ps-bar.warn > span { background: var(--mj-status-warning); }\n.ps-bar.run > span { background: var(--mj-brand-primary); }\n\n.ps-fbar { display: grid; grid-template-columns: minmax(150px, 1.3fr) 2fr 56px; align-items: center; gap: 14px; padding: 8px 0; }\n.ps-fbar + .ps-fbar { border-top: 1px solid var(--mj-border-subtle); }\n.ps-fbar .track { height: 10px; border-radius: var(--mj-radius-full); background: var(--mj-bg-surface-sunken); overflow: hidden; }\n.ps-fbar .track > span { display: block; height: 100%; border-radius: var(--mj-radius-full); background: var(--mj-brand-primary); }\n.ps-fbar .track > span.warn { background: var(--mj-status-warning); }\n.ps-fbar .name { color: var(--mj-text-primary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.ps-fbar > .ps-mono { text-align: right; color: var(--mj-text-secondary); font-variant-numeric: tabular-nums; }\n\n/* gauge */\n.ps-gauge {\n position: relative; width: 64px; height: 64px; border-radius: 50%; flex: 0 0 auto;\n background: conic-gradient(var(--ps-gauge-color, var(--mj-brand-primary)) calc(var(--ps-p, 40) * 1%), var(--mj-bg-surface-sunken) 0);\n display: grid; place-items: center;\n}\n.ps-gauge::after { content: ''; position: absolute; inset: 9px; background: var(--mj-bg-surface); border-radius: 50%; }\n.ps-gauge .g-val { position: relative; font-weight: 720; font-size: 14px; color: var(--mj-text-primary); }\n\n/* warning / info callouts */\n.ps-callout { display: flex; gap: 11px; padding: 12px 14px; border-radius: 10px; border: 1px solid; }\n.ps-callout.warn { background: var(--mj-status-warning-bg); border-color: var(--mj-status-warning-border); color: var(--mj-status-warning-text); }\n.ps-callout.info { background: var(--mj-status-info-bg); border-color: var(--mj-status-info-border); color: var(--mj-status-info-text); }\n.ps-callout.success { background: var(--mj-status-success-bg); border-color: var(--mj-status-success-border); color: var(--mj-status-success-text); }\n.ps-callout i { font-size: 16px; margin-top: 1px; }\n\n/* lifecycle stepper */\n.ps-stepper { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }\n.ps-step { display: flex; align-items: center; gap: 6px; font-size: 12px; font-weight: 600; color: var(--mj-text-muted); }\n.ps-step .pip {\n width: 22px; height: 22px; border-radius: 50%; border: 2px solid var(--mj-border-strong);\n display: grid; place-items: center; font-size: 11px;\n}\n.ps-step.done { color: var(--mj-status-success-text); }\n.ps-step.done .pip { background: var(--mj-status-success); border-color: var(--mj-status-success); color: var(--mj-text-inverse); }\n.ps-step.curr { color: var(--mj-brand-primary); }\n.ps-step.curr .pip { border-color: var(--mj-brand-primary); color: var(--mj-brand-primary); }\n.ps-step .ln { width: 26px; height: 2px; background: var(--mj-border-strong); }\n\n/* ---- DAG flow canvas ---- */\n.ps-flow {\n position: relative;\n background:\n linear-gradient(var(--mj-border-subtle) 1px, transparent 1px) 0 0 / 22px 22px,\n linear-gradient(90deg, var(--mj-border-subtle) 1px, transparent 1px) 0 0 / 22px 22px,\n var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-default); border-radius: 10px;\n min-height: 620px; overflow: hidden;\n}\n.ps-node {\n position: absolute; width: 190px; background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-strong); border-left-width: 3px; border-radius: 8px;\n cursor: pointer;\n}\n.ps-node .nh {\n display: flex; align-items: center; gap: 8px; padding: 9px 11px;\n border-bottom: 1px solid var(--mj-border-subtle); font-weight: 650; font-size: 12.5px; color: var(--mj-text-primary);\n}\n.ps-node .nh i.tile {\n width: 24px; height: 24px; border-radius: 6px; display: grid; place-items: center;\n color: var(--mj-text-inverse); font-size: 12px; flex: 0 0 auto;\n}\n.ps-node .nh .ps-tag { margin-left: auto; }\n.ps-node .nb { padding: 9px 11px; font-size: 12px; color: var(--mj-text-secondary); }\n.ps-node .nb-row { display: flex; justify-content: space-between; gap: 8px; padding: 2px 0; }\n.ps-node .nb-row .k { color: var(--mj-text-muted); }\n.ps-node.src { border-left-color: var(--ps-node-source); }\n.ps-node.src .nh i.tile { background: var(--ps-node-source); }\n.ps-node.feat { border-left-color: var(--ps-node-feature); }\n.ps-node.feat .nh i.tile { background: var(--ps-node-feature); }\n.ps-node.emb { border-left-color: var(--ps-node-embedding); }\n.ps-node.emb .nh i.tile { background: var(--ps-node-embedding); }\n.ps-node.target { border-left-color: var(--ps-node-target); }\n.ps-node.target .nh i.tile { background: var(--ps-node-target); }\n.ps-node.algo { border-left-color: var(--ps-node-algorithm); }\n.ps-node.algo .nh i.tile { background: var(--ps-node-algorithm); }\n.ps-node.output { border-left-color: var(--mj-status-success); }\n.ps-node.output .nh i.tile { background: var(--mj-status-success); }\n.ps-node.selected {\n border-color: var(--mj-brand-primary);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 18%, transparent);\n}\n.ps-node .port {\n position: absolute; width: 11px; height: 11px; border-radius: 50%;\n background: var(--mj-bg-surface); border: 2px solid var(--mj-brand-primary);\n top: 50%; transform: translateY(-50%);\n}\n.ps-node .port.in { left: -6px; }\n.ps-node .port.out { right: -6px; }\nsvg.ps-edges { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }\nsvg.ps-edges path { stroke: color-mix(in srgb, var(--mj-brand-primary) 45%, transparent); stroke-width: 2; fill: none; }\n\n/* palette chips */\n.ps-pchip {\n display: inline-flex; align-items: center; gap: 7px; padding: 6px 11px; border-radius: 8px;\n border: 1px dashed var(--mj-border-strong); background: var(--mj-bg-surface);\n font-size: 12.5px; font-weight: 600; color: var(--mj-text-secondary); cursor: grab;\n}\n.ps-pchip i { font-size: 12px; color: var(--mj-text-inverse); width: 20px; height: 20px; border-radius: 5px; display: grid; place-items: center; }\n.ps-pchip.s i { background: var(--ps-node-source); }\n.ps-pchip.f i { background: var(--ps-node-feature); }\n.ps-pchip.e i { background: var(--ps-node-embedding); }\n.ps-pchip.t i { background: var(--ps-node-target); }\n.ps-pchip.a i { background: var(--ps-node-algorithm); }\n\n/* segmented control */\n.ps-seg { display: inline-flex; border: 1px solid var(--mj-border-default); border-radius: 8px; overflow: hidden; }\n.ps-seg button {\n border: none; background: var(--mj-bg-surface); color: var(--mj-text-secondary);\n padding: 6px 12px; font-size: 12.5px; font-weight: 600; cursor: pointer;\n border-right: 1px solid var(--mj-border-default);\n}\n.ps-seg button:last-child { border-right: none; }\n.ps-seg button.on { background: var(--mj-brand-primary); color: var(--mj-text-inverse); }\n\n/* field control */\n.ps-field { margin-bottom: 14px; }\n.ps-field > label {\n display: block; font-size: 11px; text-transform: uppercase; letter-spacing: .04em;\n color: var(--mj-text-muted); font-weight: 700; margin-bottom: 6px;\n}\n.ps-input {\n width: 100%; padding: 8px 10px; border: 1px solid var(--mj-border-default);\n border-radius: 6px; font-size: 13px; background: var(--mj-bg-surface); color: var(--mj-text-primary);\n box-sizing: border-box;\n}\n.ps-input:focus { outline: none; border-color: var(--mj-brand-primary); box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 12%, transparent); }\n\n/* loading container */\n.ps-loading { display: flex; align-items: center; justify-content: center; padding: 60px 0; }\n\n/* ---- empty / prompt states ---- */\n.ps-empty {\n display: flex; flex-direction: column; align-items: center; justify-content: center;\n text-align: center; gap: 10px; padding: 56px 24px; color: var(--mj-text-muted);\n}\n.ps-empty .ps-empty-ico {\n width: 60px; height: 60px; border-radius: 16px; display: grid; place-items: center;\n background: var(--mj-bg-surface-sunken); color: var(--mj-text-disabled); font-size: 24px;\n}\n.ps-empty h3 { margin: 4px 0 0; font-size: 16px; font-weight: 650; color: var(--mj-text-secondary); }\n.ps-empty p { margin: 0; max-width: 420px; font-size: 13px; line-height: 1.5; }\n\n/* ---- confirm modal ---- */\n.ps-modal-backdrop {\n position: fixed; inset: 0; z-index: 1000;\n background: var(--mj-bg-overlay);\n display: flex; align-items: center; justify-content: center; padding: 24px;\n}\n.ps-modal {\n width: 100%; max-width: 480px; background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default); border-radius: 14px;\n box-shadow: var(--mj-shadow-lg, var(--mj-shadow-sm)); overflow: hidden;\n}\n.ps-modal-head {\n display: flex; align-items: center; gap: 12px; padding: 18px 20px;\n border-bottom: 1px solid var(--mj-border-subtle);\n}\n.ps-modal-head .ico {\n width: 38px; height: 38px; border-radius: 10px; display: grid; place-items: center;\n font-size: 16px; flex: 0 0 auto;\n background: var(--mj-status-info-bg); color: var(--mj-status-info-text);\n}\n.ps-modal-head.warn .ico { background: var(--mj-status-warning-bg); color: var(--mj-status-warning-text); }\n.ps-modal-head.danger .ico { background: var(--mj-status-error-bg); color: var(--mj-status-error-text); }\n.ps-modal-head h3 { margin: 0; font-size: 15.5px; font-weight: 650; color: var(--mj-text-primary); }\n.ps-modal-body { padding: 18px 20px; font-size: 13.5px; line-height: 1.55; color: var(--mj-text-secondary); }\n.ps-modal-body .ps-field { margin-top: 14px; margin-bottom: 0; }\n.ps-modal-foot {\n display: flex; align-items: center; gap: 10px; padding: 14px 20px;\n border-top: 1px solid var(--mj-border-subtle); background: var(--mj-bg-surface-card);\n}\n.ps-modal-foot .ps-spacer { flex: 1; }\n"], encapsulation: 2 });
121
121
  }
122
122
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(PSConfirmModalComponent, [{
123
123
  type: Component,
@@ -161,7 +161,7 @@ export class PSConfirmModalComponent {
161
161
  </div>
162
162
  </div>
163
163
  </div>
164
- `, styles: ["/* ============================================================================\n Predictive Studio \u2014 shared panel styles (design-token compliant)\n Maps the mockup design system onto MemberJunction semantic --mj-* tokens.\n Loaded by every PS panel component (styleUrls) under ViewEncapsulation.None,\n so the class names are global within the PS dashboard subtree.\n No hardcoded colors except the intentional categorical node/algorithm palette\n (declared once as --ps-* custom props below) which must stay distinct across\n light/dark to remain readable as diagram accents.\n ============================================================================ */\n\n/* Categorical accents for DAG node types + algorithm chips. These are\n data-visualization colors that must remain distinct regardless of theme\n (allowed exception #3 in the design-token rules). */\n.ps-panel {\n --ps-node-source: #6b7280;\n --ps-node-feature: #2050c8;\n --ps-node-embedding: #7b3ff0;\n --ps-node-target: #1f9d57;\n --ps-node-algorithm: #d98213;\n /* budget-gauge categorical accents (reuse the node palette so light/dark stay distinct) */\n --ps-gauge-cost: var(--ps-node-algorithm);\n --ps-gauge-iterations: var(--ps-node-feature);\n}\n\n.ps-panel {\n display: block;\n height: 100%;\n}\n\n/* ---- generic layout helpers ---- */\n.ps-row { display: flex; gap: 16px; }\n.ps-col { display: flex; flex-direction: column; gap: 16px; }\n.ps-wrap { flex-wrap: wrap; }\n.ps-spacer { flex: 1; }\n.ps-muted { color: var(--mj-text-muted); }\n.ps-small { font-size: 12.5px; }\n.ps-mono { font-family: ui-monospace, 'SF Mono', Menlo, Consolas, monospace; font-size: 12.5px; }\n.ps-section-title {\n font-size: 12px; text-transform: uppercase; letter-spacing: .06em;\n color: var(--mj-text-muted); margin: 4px 0 2px; font-weight: 700;\n}\n\n/* ---- cards ---- */\n.ps-card {\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 10px;\n box-shadow: var(--mj-shadow-sm);\n overflow: hidden;\n}\n.ps-card-head {\n display: flex; align-items: center; gap: 10px;\n padding: 14px 16px; border-bottom: 1px solid var(--mj-border-subtle);\n}\n.ps-card-head h3 { font-size: 14.5px; margin: 0; font-weight: 650; color: var(--mj-text-primary); }\n.ps-card-body { padding: 16px; }\n\n/* ---- KPI tiles ---- */\n.ps-kpi {\n background: var(--mj-bg-surface); border: 1px solid var(--mj-border-default);\n border-radius: 10px; padding: 16px 18px; box-shadow: var(--mj-shadow-sm);\n}\n.ps-kpi .label { font-size: 12px; color: var(--mj-text-muted); font-weight: 600; }\n.ps-kpi .val { font-size: 26px; font-weight: 720; letter-spacing: -.02em; margin-top: 2px; color: var(--mj-text-primary); }\n\n/* ---- badges ---- */\n.ps-badge {\n display: inline-flex; align-items: center; gap: 5px;\n padding: 2px 9px; border-radius: 999px; font-size: 11.5px; font-weight: 650; line-height: 1.7;\n}\n.ps-badge.gray { background: var(--mj-bg-surface-sunken); color: var(--mj-text-secondary); }\n.ps-badge.blue { background: var(--mj-status-info-bg); color: var(--mj-status-info-text); }\n.ps-badge.green { background: var(--mj-status-success-bg); color: var(--mj-status-success-text); }\n.ps-badge.amber { background: var(--mj-status-warning-bg); color: var(--mj-status-warning-text); }\n.ps-badge.red { background: var(--mj-status-error-bg); color: var(--mj-status-error-text); }\n.ps-dot { width: 7px; height: 7px; border-radius: 50%; display: inline-block; }\n\n/* recommendation-level chips (the ranking matrix payoff) */\n.ps-lvl {\n display: inline-flex; align-items: center; justify-content: center;\n min-width: 92px; padding: 4px 10px; border-radius: 7px;\n font-weight: 650; font-size: 11.5px; color: var(--mj-text-inverse);\n}\n.ps-lvl-Primary { background: var(--mj-status-success); }\n.ps-lvl-Strong { background: var(--mj-status-info); }\n.ps-lvl-Viable { background: var(--mj-status-warning); }\n.ps-lvl-Weak { background: var(--mj-text-disabled); }\n.ps-lvl-NotRecommended { background: var(--mj-bg-surface-sunken); color: var(--mj-text-muted); }\n\n/* ---- tags ---- */\n.ps-tag {\n display: inline-block; padding: 2px 8px; border-radius: 6px;\n background: var(--mj-bg-surface-sunken); color: var(--mj-text-secondary);\n font-size: 11.5px; font-weight: 600;\n}\n.ps-tag.muted { opacity: .6; }\n.ps-divider { height: 1px; background: var(--mj-border-subtle); margin: 8px 0; }\n\n/* ---- tables ---- */\n.ps-table { width: 100%; border-collapse: collapse; font-size: 13px; }\n.ps-table th {\n text-align: left; font-size: 11.5px; text-transform: uppercase; letter-spacing: .04em;\n color: var(--mj-text-muted); font-weight: 700; padding: 9px 12px;\n border-bottom: 1px solid var(--mj-border-default);\n}\n.ps-table td { padding: 11px 12px; border-bottom: 1px solid var(--mj-border-subtle); color: var(--mj-text-primary); }\n.ps-table tr:hover td { background: var(--mj-bg-surface-hover); }\n.ps-table .num { text-align: right; font-variant-numeric: tabular-nums; }\n\n.ps-rank {\n width: 22px; height: 22px; border-radius: 6px; background: var(--mj-bg-surface-sunken);\n display: inline-grid; place-items: center; font-weight: 700; font-size: 11.5px; color: var(--mj-text-secondary);\n}\n.ps-rank.gold { background: var(--mj-status-warning-bg); color: var(--mj-status-warning-text); }\n\n/* ---- progress bars ---- */\n.ps-bar { height: 8px; border-radius: 999px; background: var(--mj-bg-surface-sunken); overflow: hidden; }\n.ps-bar > span { display: block; height: 100%; border-radius: 999px; background: var(--mj-brand-primary); }\n.ps-bar.warn > span { background: var(--mj-status-warning); }\n.ps-bar.run > span { background: var(--mj-brand-primary); }\n\n.ps-fbar { display: grid; grid-template-columns: 150px 1fr 48px; align-items: center; gap: 10px; padding: 5px 0; }\n.ps-fbar .track { height: 14px; border-radius: 5px; background: var(--mj-bg-surface-sunken); overflow: hidden; }\n.ps-fbar .track > span { display: block; height: 100%; background: var(--mj-brand-primary); }\n.ps-fbar .track > span.warn { background: var(--mj-status-warning); }\n.ps-fbar .name { color: var(--mj-text-secondary); }\n\n/* gauge */\n.ps-gauge {\n position: relative; width: 64px; height: 64px; border-radius: 50%; flex: 0 0 auto;\n background: conic-gradient(var(--ps-gauge-color, var(--mj-brand-primary)) calc(var(--ps-p, 40) * 1%), var(--mj-bg-surface-sunken) 0);\n display: grid; place-items: center;\n}\n.ps-gauge::after { content: ''; position: absolute; inset: 9px; background: var(--mj-bg-surface); border-radius: 50%; }\n.ps-gauge .g-val { position: relative; font-weight: 720; font-size: 14px; color: var(--mj-text-primary); }\n\n/* warning / info callouts */\n.ps-callout { display: flex; gap: 11px; padding: 12px 14px; border-radius: 10px; border: 1px solid; }\n.ps-callout.warn { background: var(--mj-status-warning-bg); border-color: var(--mj-status-warning-border); color: var(--mj-status-warning-text); }\n.ps-callout.info { background: var(--mj-status-info-bg); border-color: var(--mj-status-info-border); color: var(--mj-status-info-text); }\n.ps-callout.success { background: var(--mj-status-success-bg); border-color: var(--mj-status-success-border); color: var(--mj-status-success-text); }\n.ps-callout i { font-size: 16px; margin-top: 1px; }\n\n/* lifecycle stepper */\n.ps-stepper { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }\n.ps-step { display: flex; align-items: center; gap: 6px; font-size: 12px; font-weight: 600; color: var(--mj-text-muted); }\n.ps-step .pip {\n width: 22px; height: 22px; border-radius: 50%; border: 2px solid var(--mj-border-strong);\n display: grid; place-items: center; font-size: 11px;\n}\n.ps-step.done { color: var(--mj-status-success-text); }\n.ps-step.done .pip { background: var(--mj-status-success); border-color: var(--mj-status-success); color: var(--mj-text-inverse); }\n.ps-step.curr { color: var(--mj-brand-primary); }\n.ps-step.curr .pip { border-color: var(--mj-brand-primary); color: var(--mj-brand-primary); }\n.ps-step .ln { width: 26px; height: 2px; background: var(--mj-border-strong); }\n\n/* ---- DAG flow canvas ---- */\n.ps-flow {\n position: relative;\n background:\n linear-gradient(var(--mj-border-subtle) 1px, transparent 1px) 0 0 / 22px 22px,\n linear-gradient(90deg, var(--mj-border-subtle) 1px, transparent 1px) 0 0 / 22px 22px,\n var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-default); border-radius: 10px;\n min-height: 620px; overflow: hidden;\n}\n.ps-node {\n position: absolute; width: 190px; background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-strong); border-left-width: 3px; border-radius: 10px;\n box-shadow: var(--mj-shadow-sm); cursor: pointer;\n}\n.ps-node .nh {\n display: flex; align-items: center; gap: 8px; padding: 9px 11px;\n border-bottom: 1px solid var(--mj-border-subtle); font-weight: 650; font-size: 12.5px; color: var(--mj-text-primary);\n}\n.ps-node .nh i.tile {\n width: 24px; height: 24px; border-radius: 6px; display: grid; place-items: center;\n color: var(--mj-text-inverse); font-size: 12px; flex: 0 0 auto;\n}\n.ps-node .nh .ps-tag { margin-left: auto; }\n.ps-node .nb { padding: 9px 11px; font-size: 12px; color: var(--mj-text-secondary); }\n.ps-node .nb-row { display: flex; justify-content: space-between; gap: 8px; padding: 2px 0; }\n.ps-node .nb-row .k { color: var(--mj-text-muted); }\n.ps-node.src { border-left-color: var(--ps-node-source); }\n.ps-node.src .nh i.tile { background: var(--ps-node-source); }\n.ps-node.feat { border-left-color: var(--ps-node-feature); }\n.ps-node.feat .nh i.tile { background: var(--ps-node-feature); }\n.ps-node.emb { border-left-color: var(--ps-node-embedding); }\n.ps-node.emb .nh i.tile { background: var(--ps-node-embedding); }\n.ps-node.target { border-left-color: var(--ps-node-target); }\n.ps-node.target .nh i.tile { background: var(--ps-node-target); }\n.ps-node.algo { border-left-color: var(--ps-node-algorithm); }\n.ps-node.algo .nh i.tile { background: var(--ps-node-algorithm); }\n.ps-node.output { border-left-color: var(--mj-status-success); }\n.ps-node.output .nh i.tile { background: var(--mj-status-success); }\n.ps-node.selected {\n border-color: var(--mj-brand-primary);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 18%, transparent);\n}\n.ps-node .port {\n position: absolute; width: 11px; height: 11px; border-radius: 50%;\n background: var(--mj-bg-surface); border: 2px solid var(--mj-brand-primary);\n top: 50%; transform: translateY(-50%);\n}\n.ps-node .port.in { left: -6px; }\n.ps-node .port.out { right: -6px; }\nsvg.ps-edges { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }\nsvg.ps-edges path { stroke: color-mix(in srgb, var(--mj-brand-primary) 45%, transparent); stroke-width: 2; fill: none; }\n\n/* palette chips */\n.ps-pchip {\n display: inline-flex; align-items: center; gap: 7px; padding: 6px 11px; border-radius: 8px;\n border: 1px dashed var(--mj-border-strong); background: var(--mj-bg-surface);\n font-size: 12.5px; font-weight: 600; color: var(--mj-text-secondary); cursor: grab;\n}\n.ps-pchip i { font-size: 12px; color: var(--mj-text-inverse); width: 20px; height: 20px; border-radius: 5px; display: grid; place-items: center; }\n.ps-pchip.s i { background: var(--ps-node-source); }\n.ps-pchip.f i { background: var(--ps-node-feature); }\n.ps-pchip.e i { background: var(--ps-node-embedding); }\n.ps-pchip.t i { background: var(--ps-node-target); }\n.ps-pchip.a i { background: var(--ps-node-algorithm); }\n\n/* segmented control */\n.ps-seg { display: inline-flex; border: 1px solid var(--mj-border-default); border-radius: 8px; overflow: hidden; }\n.ps-seg button {\n border: none; background: var(--mj-bg-surface); color: var(--mj-text-secondary);\n padding: 6px 12px; font-size: 12.5px; font-weight: 600; cursor: pointer;\n border-right: 1px solid var(--mj-border-default);\n}\n.ps-seg button:last-child { border-right: none; }\n.ps-seg button.on { background: var(--mj-brand-primary); color: var(--mj-text-inverse); }\n\n/* field control */\n.ps-field { margin-bottom: 14px; }\n.ps-field > label {\n display: block; font-size: 11px; text-transform: uppercase; letter-spacing: .04em;\n color: var(--mj-text-muted); font-weight: 700; margin-bottom: 6px;\n}\n.ps-input {\n width: 100%; padding: 8px 10px; border: 1px solid var(--mj-border-default);\n border-radius: 6px; font-size: 13px; background: var(--mj-bg-surface); color: var(--mj-text-primary);\n box-sizing: border-box;\n}\n.ps-input:focus { outline: none; border-color: var(--mj-brand-primary); box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 12%, transparent); }\n\n/* loading container */\n.ps-loading { display: flex; align-items: center; justify-content: center; padding: 60px 0; }\n\n/* ---- empty / prompt states ---- */\n.ps-empty {\n display: flex; flex-direction: column; align-items: center; justify-content: center;\n text-align: center; gap: 10px; padding: 56px 24px; color: var(--mj-text-muted);\n}\n.ps-empty .ps-empty-ico {\n width: 60px; height: 60px; border-radius: 16px; display: grid; place-items: center;\n background: var(--mj-bg-surface-sunken); color: var(--mj-text-disabled); font-size: 24px;\n}\n.ps-empty h3 { margin: 4px 0 0; font-size: 16px; font-weight: 650; color: var(--mj-text-secondary); }\n.ps-empty p { margin: 0; max-width: 420px; font-size: 13px; line-height: 1.5; }\n\n/* ---- confirm modal ---- */\n.ps-modal-backdrop {\n position: fixed; inset: 0; z-index: 1000;\n background: var(--mj-bg-overlay);\n display: flex; align-items: center; justify-content: center; padding: 24px;\n}\n.ps-modal {\n width: 100%; max-width: 480px; background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default); border-radius: 14px;\n box-shadow: var(--mj-shadow-lg, var(--mj-shadow-sm)); overflow: hidden;\n}\n.ps-modal-head {\n display: flex; align-items: center; gap: 12px; padding: 18px 20px;\n border-bottom: 1px solid var(--mj-border-subtle);\n}\n.ps-modal-head .ico {\n width: 38px; height: 38px; border-radius: 10px; display: grid; place-items: center;\n font-size: 16px; flex: 0 0 auto;\n background: var(--mj-status-info-bg); color: var(--mj-status-info-text);\n}\n.ps-modal-head.warn .ico { background: var(--mj-status-warning-bg); color: var(--mj-status-warning-text); }\n.ps-modal-head.danger .ico { background: var(--mj-status-error-bg); color: var(--mj-status-error-text); }\n.ps-modal-head h3 { margin: 0; font-size: 15.5px; font-weight: 650; color: var(--mj-text-primary); }\n.ps-modal-body { padding: 18px 20px; font-size: 13.5px; line-height: 1.55; color: var(--mj-text-secondary); }\n.ps-modal-body .ps-field { margin-top: 14px; margin-bottom: 0; }\n.ps-modal-foot {\n display: flex; align-items: center; gap: 10px; padding: 14px 20px;\n border-top: 1px solid var(--mj-border-subtle); background: var(--mj-bg-surface-card);\n}\n.ps-modal-foot .ps-spacer { flex: 1; }\n"] }]
164
+ `, styles: ["/* ============================================================================\n Predictive Studio \u2014 shared panel styles (design-token compliant)\n Maps the mockup design system onto MemberJunction semantic --mj-* tokens.\n Loaded by every PS panel component (styleUrls) under ViewEncapsulation.None,\n so the class names are global within the PS dashboard subtree.\n No hardcoded colors except the intentional categorical node/algorithm palette\n (declared once as --ps-* custom props below) which must stay distinct across\n light/dark to remain readable as diagram accents.\n ============================================================================ */\n\n/* Categorical accents for DAG node types + algorithm chips. These are\n data-visualization colors that must remain distinct regardless of theme\n (allowed exception #3 in the design-token rules). */\n.ps-panel {\n --ps-node-source: #6b7280;\n --ps-node-feature: #2050c8;\n --ps-node-embedding: #7b3ff0;\n --ps-node-target: #1f9d57;\n --ps-node-algorithm: #d98213;\n /* budget-gauge categorical accents (reuse the node palette so light/dark stay distinct) */\n --ps-gauge-cost: var(--ps-node-algorithm);\n --ps-gauge-iterations: var(--ps-node-feature);\n}\n\n.ps-panel {\n display: block;\n height: 100%;\n}\n\n/* ---- generic layout helpers ---- */\n.ps-row { display: flex; gap: 16px; }\n.ps-col { display: flex; flex-direction: column; gap: 16px; }\n.ps-wrap { flex-wrap: wrap; }\n.ps-spacer { flex: 1; }\n.ps-muted { color: var(--mj-text-muted); }\n.ps-small { font-size: 12.5px; }\n.ps-mono { font-family: ui-monospace, 'SF Mono', Menlo, Consolas, monospace; font-size: 12.5px; }\n.ps-section-title {\n font-size: 12px; text-transform: uppercase; letter-spacing: .06em;\n color: var(--mj-text-muted); margin: 4px 0 2px; font-weight: 700;\n}\n\n/* ---- cards ---- */\n.ps-card {\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n overflow: hidden;\n}\n.ps-card-head {\n display: flex; align-items: center; gap: 10px;\n padding: 14px 16px; border-bottom: 1px solid var(--mj-border-subtle);\n}\n.ps-card-head h3 { font-size: 14.5px; margin: 0; font-weight: 650; color: var(--mj-text-primary); }\n.ps-card-body { padding: 16px; }\n\n/* ---- KPI tiles ---- */\n.ps-kpi {\n background: var(--mj-bg-surface); border: 1px solid var(--mj-border-default);\n border-radius: 8px; padding: 16px 18px;\n}\n.ps-kpi .label { font-size: 12px; color: var(--mj-text-muted); font-weight: 600; }\n.ps-kpi .val { font-size: 26px; font-weight: 720; letter-spacing: -.02em; margin-top: 2px; color: var(--mj-text-primary); }\n\n/* ---- badges ---- */\n.ps-badge {\n display: inline-flex; align-items: center; gap: 5px;\n padding: 2px 9px; border-radius: 999px; font-size: 11.5px; font-weight: 650; line-height: 1.7;\n}\n.ps-badge.gray { background: var(--mj-bg-surface-sunken); color: var(--mj-text-secondary); }\n.ps-badge.blue { background: var(--mj-status-info-bg); color: var(--mj-status-info-text); }\n.ps-badge.green { background: var(--mj-status-success-bg); color: var(--mj-status-success-text); }\n.ps-badge.amber { background: var(--mj-status-warning-bg); color: var(--mj-status-warning-text); }\n.ps-badge.red { background: var(--mj-status-error-bg); color: var(--mj-status-error-text); }\n.ps-dot { width: 7px; height: 7px; border-radius: 50%; display: inline-block; }\n\n/* recommendation-level chips (the ranking matrix payoff) */\n.ps-lvl {\n display: inline-flex; align-items: center; justify-content: center;\n min-width: 92px; padding: 4px 10px; border-radius: 7px;\n font-weight: 650; font-size: 11.5px; color: var(--mj-text-inverse);\n}\n.ps-lvl-Primary { background: var(--mj-status-success); }\n.ps-lvl-Strong { background: var(--mj-status-info); }\n.ps-lvl-Viable { background: var(--mj-status-warning); }\n.ps-lvl-Weak { background: var(--mj-text-disabled); }\n.ps-lvl-NotRecommended { background: var(--mj-bg-surface-sunken); color: var(--mj-text-muted); }\n\n/* ---- tags ---- */\n.ps-tag {\n display: inline-block; padding: 2px 8px; border-radius: 6px;\n background: var(--mj-bg-surface-sunken); color: var(--mj-text-secondary);\n font-size: 11.5px; font-weight: 600;\n}\n.ps-tag.muted { opacity: .6; }\n.ps-divider { height: 1px; background: var(--mj-border-subtle); margin: 8px 0; }\n\n/* ---- tables ---- */\n.ps-table { width: 100%; border-collapse: collapse; font-size: 13px; }\n.ps-table th {\n text-align: left; font-size: 11.5px; text-transform: uppercase; letter-spacing: .04em;\n color: var(--mj-text-muted); font-weight: 700; padding: 9px 12px;\n border-bottom: 1px solid var(--mj-border-default);\n}\n.ps-table td { padding: 11px 12px; border-bottom: 1px solid var(--mj-border-subtle); color: var(--mj-text-primary); }\n.ps-table tr:hover td { background: var(--mj-bg-surface-hover); }\n.ps-table .num { text-align: right; font-variant-numeric: tabular-nums; }\n\n.ps-rank {\n width: 22px; height: 22px; border-radius: 6px; background: var(--mj-bg-surface-sunken);\n display: inline-grid; place-items: center; font-weight: 700; font-size: 11.5px; color: var(--mj-text-secondary);\n}\n.ps-rank.gold { background: var(--mj-status-warning-bg); color: var(--mj-status-warning-text); }\n\n/* ---- progress bars ---- */\n.ps-bar { height: 8px; border-radius: 999px; background: var(--mj-bg-surface-sunken); overflow: hidden; }\n.ps-bar > span { display: block; height: 100%; border-radius: 999px; background: var(--mj-brand-primary); }\n.ps-bar.warn > span { background: var(--mj-status-warning); }\n.ps-bar.run > span { background: var(--mj-brand-primary); }\n\n.ps-fbar { display: grid; grid-template-columns: minmax(150px, 1.3fr) 2fr 56px; align-items: center; gap: 14px; padding: 8px 0; }\n.ps-fbar + .ps-fbar { border-top: 1px solid var(--mj-border-subtle); }\n.ps-fbar .track { height: 10px; border-radius: var(--mj-radius-full); background: var(--mj-bg-surface-sunken); overflow: hidden; }\n.ps-fbar .track > span { display: block; height: 100%; border-radius: var(--mj-radius-full); background: var(--mj-brand-primary); }\n.ps-fbar .track > span.warn { background: var(--mj-status-warning); }\n.ps-fbar .name { color: var(--mj-text-primary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.ps-fbar > .ps-mono { text-align: right; color: var(--mj-text-secondary); font-variant-numeric: tabular-nums; }\n\n/* gauge */\n.ps-gauge {\n position: relative; width: 64px; height: 64px; border-radius: 50%; flex: 0 0 auto;\n background: conic-gradient(var(--ps-gauge-color, var(--mj-brand-primary)) calc(var(--ps-p, 40) * 1%), var(--mj-bg-surface-sunken) 0);\n display: grid; place-items: center;\n}\n.ps-gauge::after { content: ''; position: absolute; inset: 9px; background: var(--mj-bg-surface); border-radius: 50%; }\n.ps-gauge .g-val { position: relative; font-weight: 720; font-size: 14px; color: var(--mj-text-primary); }\n\n/* warning / info callouts */\n.ps-callout { display: flex; gap: 11px; padding: 12px 14px; border-radius: 10px; border: 1px solid; }\n.ps-callout.warn { background: var(--mj-status-warning-bg); border-color: var(--mj-status-warning-border); color: var(--mj-status-warning-text); }\n.ps-callout.info { background: var(--mj-status-info-bg); border-color: var(--mj-status-info-border); color: var(--mj-status-info-text); }\n.ps-callout.success { background: var(--mj-status-success-bg); border-color: var(--mj-status-success-border); color: var(--mj-status-success-text); }\n.ps-callout i { font-size: 16px; margin-top: 1px; }\n\n/* lifecycle stepper */\n.ps-stepper { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }\n.ps-step { display: flex; align-items: center; gap: 6px; font-size: 12px; font-weight: 600; color: var(--mj-text-muted); }\n.ps-step .pip {\n width: 22px; height: 22px; border-radius: 50%; border: 2px solid var(--mj-border-strong);\n display: grid; place-items: center; font-size: 11px;\n}\n.ps-step.done { color: var(--mj-status-success-text); }\n.ps-step.done .pip { background: var(--mj-status-success); border-color: var(--mj-status-success); color: var(--mj-text-inverse); }\n.ps-step.curr { color: var(--mj-brand-primary); }\n.ps-step.curr .pip { border-color: var(--mj-brand-primary); color: var(--mj-brand-primary); }\n.ps-step .ln { width: 26px; height: 2px; background: var(--mj-border-strong); }\n\n/* ---- DAG flow canvas ---- */\n.ps-flow {\n position: relative;\n background:\n linear-gradient(var(--mj-border-subtle) 1px, transparent 1px) 0 0 / 22px 22px,\n linear-gradient(90deg, var(--mj-border-subtle) 1px, transparent 1px) 0 0 / 22px 22px,\n var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-default); border-radius: 10px;\n min-height: 620px; overflow: hidden;\n}\n.ps-node {\n position: absolute; width: 190px; background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-strong); border-left-width: 3px; border-radius: 8px;\n cursor: pointer;\n}\n.ps-node .nh {\n display: flex; align-items: center; gap: 8px; padding: 9px 11px;\n border-bottom: 1px solid var(--mj-border-subtle); font-weight: 650; font-size: 12.5px; color: var(--mj-text-primary);\n}\n.ps-node .nh i.tile {\n width: 24px; height: 24px; border-radius: 6px; display: grid; place-items: center;\n color: var(--mj-text-inverse); font-size: 12px; flex: 0 0 auto;\n}\n.ps-node .nh .ps-tag { margin-left: auto; }\n.ps-node .nb { padding: 9px 11px; font-size: 12px; color: var(--mj-text-secondary); }\n.ps-node .nb-row { display: flex; justify-content: space-between; gap: 8px; padding: 2px 0; }\n.ps-node .nb-row .k { color: var(--mj-text-muted); }\n.ps-node.src { border-left-color: var(--ps-node-source); }\n.ps-node.src .nh i.tile { background: var(--ps-node-source); }\n.ps-node.feat { border-left-color: var(--ps-node-feature); }\n.ps-node.feat .nh i.tile { background: var(--ps-node-feature); }\n.ps-node.emb { border-left-color: var(--ps-node-embedding); }\n.ps-node.emb .nh i.tile { background: var(--ps-node-embedding); }\n.ps-node.target { border-left-color: var(--ps-node-target); }\n.ps-node.target .nh i.tile { background: var(--ps-node-target); }\n.ps-node.algo { border-left-color: var(--ps-node-algorithm); }\n.ps-node.algo .nh i.tile { background: var(--ps-node-algorithm); }\n.ps-node.output { border-left-color: var(--mj-status-success); }\n.ps-node.output .nh i.tile { background: var(--mj-status-success); }\n.ps-node.selected {\n border-color: var(--mj-brand-primary);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 18%, transparent);\n}\n.ps-node .port {\n position: absolute; width: 11px; height: 11px; border-radius: 50%;\n background: var(--mj-bg-surface); border: 2px solid var(--mj-brand-primary);\n top: 50%; transform: translateY(-50%);\n}\n.ps-node .port.in { left: -6px; }\n.ps-node .port.out { right: -6px; }\nsvg.ps-edges { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }\nsvg.ps-edges path { stroke: color-mix(in srgb, var(--mj-brand-primary) 45%, transparent); stroke-width: 2; fill: none; }\n\n/* palette chips */\n.ps-pchip {\n display: inline-flex; align-items: center; gap: 7px; padding: 6px 11px; border-radius: 8px;\n border: 1px dashed var(--mj-border-strong); background: var(--mj-bg-surface);\n font-size: 12.5px; font-weight: 600; color: var(--mj-text-secondary); cursor: grab;\n}\n.ps-pchip i { font-size: 12px; color: var(--mj-text-inverse); width: 20px; height: 20px; border-radius: 5px; display: grid; place-items: center; }\n.ps-pchip.s i { background: var(--ps-node-source); }\n.ps-pchip.f i { background: var(--ps-node-feature); }\n.ps-pchip.e i { background: var(--ps-node-embedding); }\n.ps-pchip.t i { background: var(--ps-node-target); }\n.ps-pchip.a i { background: var(--ps-node-algorithm); }\n\n/* segmented control */\n.ps-seg { display: inline-flex; border: 1px solid var(--mj-border-default); border-radius: 8px; overflow: hidden; }\n.ps-seg button {\n border: none; background: var(--mj-bg-surface); color: var(--mj-text-secondary);\n padding: 6px 12px; font-size: 12.5px; font-weight: 600; cursor: pointer;\n border-right: 1px solid var(--mj-border-default);\n}\n.ps-seg button:last-child { border-right: none; }\n.ps-seg button.on { background: var(--mj-brand-primary); color: var(--mj-text-inverse); }\n\n/* field control */\n.ps-field { margin-bottom: 14px; }\n.ps-field > label {\n display: block; font-size: 11px; text-transform: uppercase; letter-spacing: .04em;\n color: var(--mj-text-muted); font-weight: 700; margin-bottom: 6px;\n}\n.ps-input {\n width: 100%; padding: 8px 10px; border: 1px solid var(--mj-border-default);\n border-radius: 6px; font-size: 13px; background: var(--mj-bg-surface); color: var(--mj-text-primary);\n box-sizing: border-box;\n}\n.ps-input:focus { outline: none; border-color: var(--mj-brand-primary); box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 12%, transparent); }\n\n/* loading container */\n.ps-loading { display: flex; align-items: center; justify-content: center; padding: 60px 0; }\n\n/* ---- empty / prompt states ---- */\n.ps-empty {\n display: flex; flex-direction: column; align-items: center; justify-content: center;\n text-align: center; gap: 10px; padding: 56px 24px; color: var(--mj-text-muted);\n}\n.ps-empty .ps-empty-ico {\n width: 60px; height: 60px; border-radius: 16px; display: grid; place-items: center;\n background: var(--mj-bg-surface-sunken); color: var(--mj-text-disabled); font-size: 24px;\n}\n.ps-empty h3 { margin: 4px 0 0; font-size: 16px; font-weight: 650; color: var(--mj-text-secondary); }\n.ps-empty p { margin: 0; max-width: 420px; font-size: 13px; line-height: 1.5; }\n\n/* ---- confirm modal ---- */\n.ps-modal-backdrop {\n position: fixed; inset: 0; z-index: 1000;\n background: var(--mj-bg-overlay);\n display: flex; align-items: center; justify-content: center; padding: 24px;\n}\n.ps-modal {\n width: 100%; max-width: 480px; background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default); border-radius: 14px;\n box-shadow: var(--mj-shadow-lg, var(--mj-shadow-sm)); overflow: hidden;\n}\n.ps-modal-head {\n display: flex; align-items: center; gap: 12px; padding: 18px 20px;\n border-bottom: 1px solid var(--mj-border-subtle);\n}\n.ps-modal-head .ico {\n width: 38px; height: 38px; border-radius: 10px; display: grid; place-items: center;\n font-size: 16px; flex: 0 0 auto;\n background: var(--mj-status-info-bg); color: var(--mj-status-info-text);\n}\n.ps-modal-head.warn .ico { background: var(--mj-status-warning-bg); color: var(--mj-status-warning-text); }\n.ps-modal-head.danger .ico { background: var(--mj-status-error-bg); color: var(--mj-status-error-text); }\n.ps-modal-head h3 { margin: 0; font-size: 15.5px; font-weight: 650; color: var(--mj-text-primary); }\n.ps-modal-body { padding: 18px 20px; font-size: 13.5px; line-height: 1.55; color: var(--mj-text-secondary); }\n.ps-modal-body .ps-field { margin-top: 14px; margin-bottom: 0; }\n.ps-modal-foot {\n display: flex; align-items: center; gap: 10px; padding: 14px 20px;\n border-top: 1px solid var(--mj-border-subtle); background: var(--mj-bg-surface-card);\n}\n.ps-modal-foot .ps-spacer { flex: 1; }\n"] }]
165
165
  }], null, { title: [{
166
166
  type: Input
167
167
  }], icon: [{
@@ -1 +1 @@
1
- {"version":3,"file":"ps-confirm-modal.component.js","sourceRoot":"","sources":["../../../src/PredictiveStudio/components/ps-confirm-modal.component.ts"],"names":[],"mappings":"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","sourcesContent":["import { Component, EventEmitter, Input, Output, ViewEncapsulation } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { FormsModule } from '@angular/forms';\nimport { MJButtonDirective } from '@memberjunction/ng-ui-components';\n\n/** Visual variant of the confirm modal — drives the header icon-tile color. */\nexport type PSConfirmVariant = 'info' | 'warn' | 'danger';\n\n/**\n * Small, reusable confirmation modal for Predictive Studio's real mutations (model promote/archive,\n * experiment pause/cancel). Renders a token-styled backdrop + card with a title, a rich message\n * (projected via `<ng-content>`), an optional reason textarea, and a left-aligned primary confirm\n * button followed by Cancel (MJ button-placement convention). The host owns visibility and the\n * confirm/cancel handlers; this component is purely presentational + emits the captured reason.\n *\n * While `busy` is true the confirm button shows a spinner and both buttons disable, giving the\n * in-flight Remote Op a clear, non-dismissable feel.\n */\n@Component({\n standalone: true,\n selector: 'ps-confirm-modal',\n imports: [CommonModule, FormsModule, MJButtonDirective],\n encapsulation: ViewEncapsulation.None,\n styleUrls: ['../predictive-studio.shared.scss'],\n template: `\n <div class=\"ps-modal-backdrop\" data-testid=\"ps-confirm-modal\" (click)=\"onBackdrop()\">\n <div class=\"ps-modal\" (click)=\"$event.stopPropagation()\">\n <div class=\"ps-modal-head\" [class.warn]=\"variant === 'warn'\" [class.danger]=\"variant === 'danger'\">\n <span class=\"ico\"><i [class]=\"icon\"></i></span>\n <h3>{{ title }}</h3>\n </div>\n <div class=\"ps-modal-body\">\n <ng-content></ng-content>\n @if (showReason) {\n <div class=\"ps-field\">\n <label>{{ reasonLabel }}{{ reasonRequired ? ' *' : '' }}</label>\n <textarea\n class=\"ps-input\"\n rows=\"3\"\n data-testid=\"ps-confirm-reason\"\n [placeholder]=\"reasonPlaceholder\"\n [(ngModel)]=\"reason\"\n [disabled]=\"busy\"></textarea>\n </div>\n }\n </div>\n <div class=\"ps-modal-foot\">\n <button\n mjButton\n [variant]=\"variant === 'danger' ? 'danger' : 'primary'\"\n size=\"sm\"\n data-testid=\"ps-confirm-ok\"\n [disabled]=\"busy || (reasonRequired && !reason.trim())\"\n (click)=\"confirmed.emit(reason.trim())\">\n @if (busy) { <i class=\"fa-solid fa-circle-notch fa-spin\"></i> } @else { <i [class]=\"confirmIcon\"></i> }\n {{ confirmLabel }}\n </button>\n <span class=\"ps-spacer\"></span>\n <button mjButton variant=\"secondary\" size=\"sm\" data-testid=\"ps-confirm-cancel\" [disabled]=\"busy\" (click)=\"cancelled.emit()\">\n Cancel\n </button>\n </div>\n </div>\n </div>\n `,\n})\nexport class PSConfirmModalComponent {\n /** Modal heading. */\n @Input() title = 'Confirm';\n /** Header icon-tile Font Awesome class. */\n @Input() icon = 'fa-solid fa-circle-question';\n /** Confirm-button Font Awesome class (shown when not busy). */\n @Input() confirmIcon = 'fa-solid fa-check';\n /** Confirm-button label. */\n @Input() confirmLabel = 'Confirm';\n /** Visual variant (info / warn / danger). */\n @Input() variant: PSConfirmVariant = 'info';\n /** When true, render the reason textarea. */\n @Input() showReason = false;\n /** When true, the confirm button stays disabled until a non-empty reason is entered. */\n @Input() reasonRequired = false;\n /** Reason field label. */\n @Input() reasonLabel = 'Reason';\n /** Reason field placeholder. */\n @Input() reasonPlaceholder = 'Add an optional note…';\n /** In-flight flag — disables the buttons + shows the confirm spinner. */\n @Input() busy = false;\n\n /** The captured reason (two-way via the textarea). */\n public reason = '';\n\n /** Emitted with the trimmed reason when the user confirms. */\n @Output() confirmed = new EventEmitter<string>();\n /** Emitted when the user cancels (button or backdrop). */\n @Output() cancelled = new EventEmitter<void>();\n\n /** Backdrop click cancels — unless an operation is in flight. */\n public onBackdrop(): void {\n if (!this.busy) this.cancelled.emit();\n }\n}\n"]}
1
+ {"version":3,"file":"ps-confirm-modal.component.js","sourceRoot":"","sources":["../../../src/PredictiveStudio/components/ps-confirm-modal.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAC1F,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAC;;;;;;IAgCvD,AADF,8BAAsB,YACb;IAAA,YAAiD;IAAA,iBAAQ;IAChE,oCAMoB;IADlB,8SAAoB;IAExB,AADsB,iBAAW,EAC3B;;;IARG,eAAiD;IAAjD,oFAAiD;IAKtD,cAAiC;IAAjC,sDAAiC;IACjC,6CAAoB;IACpB,sCAAiB;;;IAYR,uBAAgD;;;IAAW,oBAA6B;;;IAA1B,iCAAqB;;AA9C5G;;;;;;;;;GASG;AAiDH,MAAM,OAAO,uBAAuB;IAClC,qBAAqB;IACZ,KAAK,GAAG,SAAS,CAAC;IAC3B,2CAA2C;IAClC,IAAI,GAAG,6BAA6B,CAAC;IAC9C,+DAA+D;IACtD,WAAW,GAAG,mBAAmB,CAAC;IAC3C,4BAA4B;IACnB,YAAY,GAAG,SAAS,CAAC;IAClC,6CAA6C;IACpC,OAAO,GAAqB,MAAM,CAAC;IAC5C,6CAA6C;IACpC,UAAU,GAAG,KAAK,CAAC;IAC5B,wFAAwF;IAC/E,cAAc,GAAG,KAAK,CAAC;IAChC,0BAA0B;IACjB,WAAW,GAAG,QAAQ,CAAC;IAChC,gCAAgC;IACvB,iBAAiB,GAAG,uBAAuB,CAAC;IACrD,yEAAyE;IAChE,IAAI,GAAG,KAAK,CAAC;IAEtB,sDAAsD;IAC/C,MAAM,GAAG,EAAE,CAAC;IAEnB,8DAA8D;IACpD,SAAS,GAAG,IAAI,YAAY,EAAU,CAAC;IACjD,0DAA0D;IAChD,SAAS,GAAG,IAAI,YAAY,EAAQ,CAAC;IAE/C,iEAAiE;IAC1D,UAAU;QACf,IAAI,CAAC,IAAI,CAAC,IAAI;YAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC;IACxC,CAAC;iHAjCU,uBAAuB;6DAAvB,uBAAuB;;YAzChC,8BAAqF;YAAvB,iGAAS,gBAAY,IAAC;YAClF,8BAAyD;YAAnC,uGAAS,wBAAwB,IAAC;YAEpD,AADF,8BAAmG,cAC/E;YAAA,oBAAsB;YAAA,iBAAO;YAC/C,0BAAI;YAAA,YAAW;YACjB,AADiB,iBAAK,EAChB;YACN,8BAA2B;YACzB,kBAAyB;YACzB,yFAAkB;YAYpB,iBAAM;YAEJ,AADF,+BAA2B,iBAOiB;YAAxC,qGAAS,mBAAe,iBAAa,CAAC,IAAC;YACyB,AAAhE,yFAAY,mEAA2D;YACvE,aACF;YAAA,iBAAS;YACT,4BAA+B;YAC/B,mCAA4H;YAA3B,qGAAS,oBAAgB,IAAC;YACzH,yBACF;YAGN,AADE,AADE,AADE,iBAAS,EACL,EACF,EACF;;YApCyB,eAAiC;YAAC,AAAlC,8CAAiC,oCAAsC;YAC3E,eAAc;YAAd,uBAAc;YAC/B,eAAW;YAAX,+BAAW;YAIf,eAWC;YAXD,yCAWC;YAKC,eAAuD;YAGvD,AAHA,yEAAuD,kEAGA;YAEvD,cAAuG;YAAvG,oCAAuG;YACvG,eACF;YADE,iDACF;YAE+E,eAAiB;YAAjB,mCAAiB;4BArC9F,YAAY,EAAE,WAAW,2DAAE,iBAAiB;;iFA6C3C,uBAAuB;cAhDnC,SAAS;6BACI,IAAI,YACN,kBAAkB,WACnB,CAAC,YAAY,EAAE,WAAW,EAAE,iBAAiB,CAAC,iBACxC,iBAAiB,CAAC,IAAI,YAE3B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCT;;kBAIA,KAAK;;kBAEL,KAAK;;kBAEL,KAAK;;kBAEL,KAAK;;kBAEL,KAAK;;kBAEL,KAAK;;kBAEL,KAAK;;kBAEL,KAAK;;kBAEL,KAAK;;kBAEL,KAAK;;kBAML,MAAM;;kBAEN,MAAM;;kFA5BI,uBAAuB","sourcesContent":["import { Component, EventEmitter, Input, Output, ViewEncapsulation } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { FormsModule } from '@angular/forms';\nimport { MJButtonDirective } from '@memberjunction/ng-ui-components';\n\n/** Visual variant of the confirm modal — drives the header icon-tile color. */\nexport type PSConfirmVariant = 'info' | 'warn' | 'danger';\n\n/**\n * Small, reusable confirmation modal for Predictive Studio's real mutations (model promote/archive,\n * experiment pause/cancel). Renders a token-styled backdrop + card with a title, a rich message\n * (projected via `<ng-content>`), an optional reason textarea, and a left-aligned primary confirm\n * button followed by Cancel (MJ button-placement convention). The host owns visibility and the\n * confirm/cancel handlers; this component is purely presentational + emits the captured reason.\n *\n * While `busy` is true the confirm button shows a spinner and both buttons disable, giving the\n * in-flight Remote Op a clear, non-dismissable feel.\n */\n@Component({\n standalone: true,\n selector: 'ps-confirm-modal',\n imports: [CommonModule, FormsModule, MJButtonDirective],\n encapsulation: ViewEncapsulation.None,\n styleUrls: ['../predictive-studio.shared.css'],\n template: `\n <div class=\"ps-modal-backdrop\" data-testid=\"ps-confirm-modal\" (click)=\"onBackdrop()\">\n <div class=\"ps-modal\" (click)=\"$event.stopPropagation()\">\n <div class=\"ps-modal-head\" [class.warn]=\"variant === 'warn'\" [class.danger]=\"variant === 'danger'\">\n <span class=\"ico\"><i [class]=\"icon\"></i></span>\n <h3>{{ title }}</h3>\n </div>\n <div class=\"ps-modal-body\">\n <ng-content></ng-content>\n @if (showReason) {\n <div class=\"ps-field\">\n <label>{{ reasonLabel }}{{ reasonRequired ? ' *' : '' }}</label>\n <textarea\n class=\"ps-input\"\n rows=\"3\"\n data-testid=\"ps-confirm-reason\"\n [placeholder]=\"reasonPlaceholder\"\n [(ngModel)]=\"reason\"\n [disabled]=\"busy\"></textarea>\n </div>\n }\n </div>\n <div class=\"ps-modal-foot\">\n <button\n mjButton\n [variant]=\"variant === 'danger' ? 'danger' : 'primary'\"\n size=\"sm\"\n data-testid=\"ps-confirm-ok\"\n [disabled]=\"busy || (reasonRequired && !reason.trim())\"\n (click)=\"confirmed.emit(reason.trim())\">\n @if (busy) { <i class=\"fa-solid fa-circle-notch fa-spin\"></i> } @else { <i [class]=\"confirmIcon\"></i> }\n {{ confirmLabel }}\n </button>\n <span class=\"ps-spacer\"></span>\n <button mjButton variant=\"secondary\" size=\"sm\" data-testid=\"ps-confirm-cancel\" [disabled]=\"busy\" (click)=\"cancelled.emit()\">\n Cancel\n </button>\n </div>\n </div>\n </div>\n `,\n})\nexport class PSConfirmModalComponent {\n /** Modal heading. */\n @Input() title = 'Confirm';\n /** Header icon-tile Font Awesome class. */\n @Input() icon = 'fa-solid fa-circle-question';\n /** Confirm-button Font Awesome class (shown when not busy). */\n @Input() confirmIcon = 'fa-solid fa-check';\n /** Confirm-button label. */\n @Input() confirmLabel = 'Confirm';\n /** Visual variant (info / warn / danger). */\n @Input() variant: PSConfirmVariant = 'info';\n /** When true, render the reason textarea. */\n @Input() showReason = false;\n /** When true, the confirm button stays disabled until a non-empty reason is entered. */\n @Input() reasonRequired = false;\n /** Reason field label. */\n @Input() reasonLabel = 'Reason';\n /** Reason field placeholder. */\n @Input() reasonPlaceholder = 'Add an optional note…';\n /** In-flight flag — disables the buttons + shows the confirm spinner. */\n @Input() busy = false;\n\n /** The captured reason (two-way via the textarea). */\n public reason = '';\n\n /** Emitted with the trimmed reason when the user confirms. */\n @Output() confirmed = new EventEmitter<string>();\n /** Emitted when the user cancels (button or backdrop). */\n @Output() cancelled = new EventEmitter<void>();\n\n /** Backdrop click cancels — unless an operation is in flight. */\n public onBackdrop(): void {\n if (!this.busy) this.cancelled.emit();\n }\n}\n"]}