)[key];
+ const color = ATTACK_COLOR[state] ?? C.muted;
+ return `${label}: ${state}`;
+ };
+
+ const div = document.createElement('div');
+ div.className = 'mfl-detail';
+ div.innerHTML = `
+ ${step.title}
+ ${step.detail}
+ ${step.stat}
+
+ ${badge('finetune', 'Fine-tune')}
+ ${badge('quantize', 'Quantize')}
+ ${badge('merge', 'Merge')}
+
+ ${attackState && attackId !== 'none' ? `
+
+ ${attack.label}: ${attack.description}
+
` : ''}
+ `;
+ panel.appendChild(div);
+}
+
+/* ── Main mount ────────────────────────────────────────────────────────────── */
+export default function mount(container: HTMLElement): () => void {
+ const layout = createArtifactLayout(container, {
+ wideTemplate: 'rail',
+ stageAspect: '720/260',
+ });
+ const { stage, panel, controls, caption } = layout;
+
+ /* ── State ── */
+ let selectedId: string | null = null;
+ let attackId = 'none';
+
+ /* ── Styles ── */
+ const style = document.createElement('style');
+ style.textContent = `
+ .mfl-svg { width: 100%; height: 100%; display: block; cursor: default; }
+
+ .mfl-empty { padding: 12px 4px; }
+ .mfl-empty-hint {
+ color: ${C.muted}; font-size: .82rem; font-style: italic;
+ line-height: 1.5; margin: 0 0 10px;
+ }
+
+ .mfl-detail { display: flex; flex-direction: column; gap: 10px; padding: 4px; }
+ .mfl-title { font-size: .95rem; font-weight: 600; color: ${C.text}; margin: 0; }
+ .mfl-text { font-size: .80rem; color: ${C.muted}; line-height: 1.55; margin: 0; }
+ .mfl-stat { font-family: monospace; font-size: .75rem; color: ${C.cyan};
+ background: rgba(34,211,238,.07); border: 1px solid rgba(34,211,238,.18);
+ border-radius: 6px; padding: 5px 8px; }
+ .mfl-badges { display: flex; flex-wrap: wrap; gap: 6px; }
+ .mfl-badge {
+ font-family: monospace; font-size: .70rem; padding: 3px 8px;
+ border-radius: 99px; border: 1px solid var(--bc);
+ color: var(--bc); background: color-mix(in srgb, var(--bc) 12%, transparent);
+ white-space: nowrap; flex: none;
+ }
+ .mfl-attack-note {
+ font-size: .76rem; color: ${C.muted}; line-height: 1.5;
+ border-left: 2px solid var(--ac); padding-left: 8px;
+ }
+ .mfl-attack-label { color: var(--ac); font-weight: 600; }
+ .mfl-attack-desc {
+ font-size: .80rem; color: ${C.muted}; line-height: 1.5;
+ border-left: 2px solid ${C.amber}; padding-left: 8px;
+ margin-top: 8px;
+ }
+
+ .mfl-controls { display: flex; flex-wrap: wrap; gap: 7px; }
+ .mfl-ctl-label { font-size: .72rem; color: ${C.faint}; font-family: monospace;
+ letter-spacing: .08em; text-transform: uppercase; margin-bottom: 6px;
+ display: block; }
+ .mfl-btn {
+ font-family: monospace; font-size: .72rem; padding: 4px 11px;
+ border-radius: 6px; cursor: pointer; border: 1px solid ${C.line};
+ color: ${C.muted}; background: transparent; transition: border-color .15s, color .15s;
+ white-space: nowrap; flex: none;
+ }
+ .mfl-btn:hover { border-color: ${C.lineBright}; color: ${C.text}; }
+ .mfl-btn.is-active { border-color: var(--ab); color: var(--ab);
+ background: color-mix(in srgb, var(--ab) 10%, transparent); }
+ .mfl-caption { font-size: .70rem; color: ${C.faint}; line-height: 1.5; }
+ `;
+ stage.appendChild(style);
+
+ /* ── SVG stage ── */
+ const svg = document.createElementNS(NS, 'svg') as SVGSVGElement;
+ svg.className.baseVal = 'mfl-svg';
+ stage.appendChild(svg);
+
+ /* ── Controls ── */
+ const ctrlLabel = document.createElement('span');
+ ctrlLabel.className = 'mfl-ctl-label';
+ ctrlLabel.textContent = 'Attack scenario';
+ controls.appendChild(ctrlLabel);
+
+ const btnsRow = document.createElement('div');
+ btnsRow.className = 'mfl-controls';
+ controls.appendChild(btnsRow);
+
+ const ATTACK_COLORS: Record = {
+ none: C.accent,
+ finetune: C.amber,
+ quantize: C.amber,
+ merge: C.red,
+ };
+
+ const attackBtns: HTMLButtonElement[] = [];
+ data.attacks.forEach(a => {
+ const btn = document.createElement('button');
+ btn.className = 'mfl-btn' + (a.id === attackId ? ' is-active' : '');
+ btn.textContent = a.label;
+ btn.style.setProperty('--ab', ATTACK_COLORS[a.id] ?? C.accent);
+ btn.addEventListener('click', () => {
+ attackId = a.id;
+ attackBtns.forEach((b, bi) => {
+ const isActive = data.attacks[bi].id === a.id;
+ b.classList.toggle('is-active', isActive);
+ });
+ redraw();
+ });
+ btnsRow.appendChild(btn);
+ attackBtns.push(btn);
+ });
+
+ /* ── Caption ── */
+ const cap = document.createElement('p');
+ cap.className = 'mfl-caption';
+ cap.innerHTML =
+ 'Sources: Nasery et al. 2025 (arXiv:2502.07760) · Xu et al. 2024 (arXiv:2409.08846) · Zhang et al. 2025 (arXiv:2507.20650) · Xu et al. 2025 (arXiv:2508.11548)';
+ caption.appendChild(cap);
+
+ /* ── Redraw ── */
+ function onStepClick(id: string) {
+ selectedId = selectedId === id ? null : id;
+ redraw();
+ }
+
+ function redraw() {
+ buildFlowDiagram(svg, selectedId, attackId, onStepClick);
+ const step = selectedId ? data.steps.find(s => s.id === selectedId) ?? null : null;
+ renderPanel(panel, step, attackId, data.attacks);
+ }
+
+ redraw();
+
+ return () => {
+ layout.dispose();
+ style.remove();
+ };
+}