l3on3/maximum-martech-ops-demo
0
1'use strict';2 3const viewMeta = {4 overview: ['CONTENT-TO-REVENUE / OVERVIEW', 'The engine, at a glance.'],5 leads: ['DEMAND OPERATIONS / LEADS & CRM', 'Qualified demand, in motion.'],6 content: ['CONTENT OPERATIONS / PRODUCTION', 'The machine behind the media.'],7 attribution: ['DATA / REVENUE ATTRIBUTION', 'Every dollar has a journey.'],8 automations: ['SYSTEMS / AUTOMATION HEALTH', 'Reliable by design.']9};10 11const $ = (selector, root = document) => root.querySelector(selector);12const $$ = (selector, root = document) => [...root.querySelectorAll(selector)];13const wait = (milliseconds) => new Promise((resolve) => window.setTimeout(resolve, milliseconds));14 15const appState = {16 activeView: 'overview',17 demoStatus: 'idle',18 currentRun: null,19 pipeline: 148400,20 capturedLeads: 486,21 qualifiedLeads: 154,22 campaignLeads: 84,23 campaignPipeline: 51200,24 eventsProcessed: 12842,25 approvedContent: false,26 genericRetryComplete: false,27 takeoverActive: false,28 recordsByEmail: new Map([29 ['maya@northstarhealth.com', { id: 'MM-1028', email: 'maya@northstarhealth.com', name: 'Maya Chen', company: 'Northstar Health', existing: true }]30 ])31};32 33const sidebar = $('#sidebar');34const sidebarScrim = $('#sidebarScrim');35const mobileMenu = $('#mobileMenu');36const pageEyebrow = $('#pageEyebrow');37const pageTitle = $('#pageTitle');38const demoTray = $('#demoTray');39const demoForm = $('#demoLeadForm');40const demoFormError = $('#demoFormError');41const demoState = $('#demoState');42const demoSteps = $('#demoSteps');43const demoPerson = $('#demoPerson');44const demoSource = $('#demoSource');45const demoSummary = $('#demoSummary');46const demoAudit = $('#demoAudit');47const demoAuditList = $('#demoAuditList');48const mockNotification = $('#mockNotification');49const replayDemoButton = $('#replayDemo');50const retryDemoButton = $('#retryDemo');51const takeoverLeadButton = $('#takeoverLead');52const inspectLeadButton = $('#inspectLead');53 54function switchView(viewName, focusHeading = false) {55 if (!viewMeta[viewName]) return;56 appState.activeView = viewName;57 $$('.nav-item').forEach((item) => {58 const active = item.dataset.view === viewName;59 item.classList.toggle('is-active', active);60 if (active) item.setAttribute('aria-current', 'page');61 else item.removeAttribute('aria-current');62 });63 $$('[data-view-panel]').forEach((panel) => {64 panel.hidden = panel.dataset.viewPanel !== viewName;65 });66 [pageEyebrow.textContent, pageTitle.textContent] = viewMeta[viewName];67 closeMobileNavigation();68 window.scrollTo({ top: 0, behavior: 'smooth' });69 if (focusHeading) {70 pageTitle.setAttribute('tabindex', '-1');71 pageTitle.focus({ preventScroll: true });72 }73}74 75function openMobileNavigation() {76 sidebar.classList.add('is-open');77 sidebarScrim.hidden = false;78 mobileMenu.setAttribute('aria-expanded', 'true');79}80 81function closeMobileNavigation() {82 sidebar.classList.remove('is-open');83 sidebarScrim.hidden = true;84 mobileMenu.setAttribute('aria-expanded', 'false');85}86 87function toast(title, detail, type = 'success') {88 const region = $('#toastRegion');89 const node = document.createElement('div');90 node.className = `toast ${type === 'error' ? 'error' : ''}`;91 node.innerHTML = `<i>${type === 'error' ? '!' : '✓'}</i><div><strong>${escapeHtml(title)}</strong><span>${escapeHtml(detail)}</span></div>`;92 region.append(node);93 window.setTimeout(() => {94 node.style.opacity = '0';95 node.style.transform = 'translateY(6px)';96 window.setTimeout(() => node.remove(), 220);97 }, 3600);98}99 100function escapeHtml(value) {101 return String(value)102 .replaceAll('&', '&')103 .replaceAll('<', '<')104 .replaceAll('>', '>')105 .replaceAll('"', '"')106 .replaceAll("'", ''');107}108 109function normalizeSpaces(value) {110 return String(value || '').trim().replace(/\s+/g, ' ');111}112 113function normalizeSubmission(formData) {114 const utm = normalizeSpaces(formData.get('utm'))115 .toLowerCase()116 .replace(/[^a-z0-9]+/g, '_')117 .replace(/^_+|_+$/g, '');118 return {119 name: normalizeSpaces(formData.get('name')),120 email: normalizeSpaces(formData.get('email')).toLowerCase(),121 company: normalizeSpaces(formData.get('company')),122 source: normalizeSpaces(formData.get('source')),123 utm,124 budget: Number(formData.get('budget')),125 need: normalizeSpaces(formData.get('need')),126 timing: normalizeSpaces(formData.get('timing')),127 simulateFailure: formData.get('simulateFailure') === 'on'128 };129}130 131function validateSubmission(record) {132 const errors = [];133 if (record.name.length < 2) errors.push('Contact name is required.');134 if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(record.email)) errors.push('Enter a valid work email.');135 if (record.company.length < 2) errors.push('Company is required.');136 if (!record.source) errors.push('Source is required.');137 if (!record.utm) errors.push('UTM campaign is required.');138 if (!Number.isFinite(record.budget) || record.budget <= 0) errors.push('Budget is required.');139 if (!record.need) errors.push('Primary need is required.');140 if (!record.timing) errors.push('Timing is required.');141 return errors;142}143 144function scoreLead(record) {145 const budgetPoints = record.budget >= 20000 ? 30 : record.budget >= 10000 ? 24 : record.budget >= 5000 ? 15 : 6;146 const needPoints = {147 automation: 25,148 'content-system': 23,149 attribution: 22,150 'landing-crm': 20151 }[record.need] || 12;152 const timingPoints = { now: 20, '30d': 15, '90d': 9, exploring: 3 }[record.timing] || 3;153 const sourcePoints = {154 YouTube: 15,155 LinkedIn: 14,156 Newsletter: 12,157 Podcast: 11,158 'Organic search': 8159 }[record.source] || 7;160 const campaignPoints = record.utm ? 10 : 0;161 const total = Math.min(100, budgetPoints + needPoints + timingPoints + sourcePoints + campaignPoints);162 const tier = total >= 80 ? 'A' : total >= 60 ? 'B' : 'C';163 const stage = tier === 'A' ? 'Qualified' : tier === 'B' ? 'MQL' : 'Nurture';164 const owner = tier === 'A' ? 'Mackenzie' : tier === 'B' ? 'Max' : 'Nurture bot';165 return {166 total,167 tier,168 stage,169 owner,170 reasons: [171 ['Budget fit', budgetPoints, 30],172 ['Need fit', needPoints, 25],173 ['Timing', timingPoints, 20],174 ['Source intent', sourcePoints, 15],175 ['UTM quality', campaignPoints, 10]176 ]177 };178}179 180function stableId(email) {181 let hash = 0;182 for (const character of email) hash = ((hash << 5) - hash + character.charCodeAt(0)) | 0;183 return `MM-${String(1100 + (Math.abs(hash) % 8800)).padStart(4, '0')}`;184}185 186function initials(name) {187 return normalizeSpaces(name).split(' ').slice(0, 2).map((part) => part[0]?.toUpperCase() || '').join('') || 'LD';188}189 190function formatMoney(value) {191 return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', maximumFractionDigits: 0 }).format(value);192}193 194function formatCompact(value) {195 if (value >= 1000000) return `$${(value / 1000000).toFixed(1)}M`;196 if (value >= 1000) return `$${(value / 1000).toFixed(value % 1000 === 0 ? 0 : 1)}K`;197 return formatMoney(value);198}199 200function setDemoState(state, title, detail) {201 demoState.dataset.state = state;202 $('strong', demoState).textContent = title;203 $('span', demoState).textContent = detail;204}205 206function resetStepVisuals() {207 $$('[data-demo-step]').forEach((step, index) => {208 step.classList.remove('is-running', 'is-complete', 'is-error');209 $('em', step).textContent = '—';210 $('i span', step).textContent = String(index + 1);211 });212 $('#demoScoreReasons').replaceChildren();213}214 215function markStep(stepName, state, timing = '') {216 const step = $(`[data-demo-step="${stepName}"]`);217 if (!step) return;218 step.classList.remove('is-running', 'is-complete', 'is-error');219 step.classList.add(`is-${state}`);220 $('em', step).textContent = timing || (state === 'complete' ? 'DONE' : state === 'error' ? 'FAILED' : 'RUNNING');221}222 223function appendAudit(eventName, result, detail, variant = '') {224 demoAudit.hidden = false;225 const row = document.createElement('div');226 row.className = `audit-row ${variant}`;227 const time = new Date().toLocaleTimeString('en-US', { hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit' });228 row.innerHTML = `<time>${time}</time><span>${escapeHtml(eventName)} · ${escapeHtml(detail)}</span><b>${escapeHtml(result)}</b>`;229 demoAuditList.prepend(row);230 const count = demoAuditList.children.length;231 $('#auditCount').textContent = `${count} event${count === 1 ? '' : 's'}`;232}233 234function appendSystemLog(title, detail, status = 'success') {235 const list = $('#automationLog');236 if (!list) return;237 const entry = document.createElement('div');238 entry.className = `log-entry ${status === 'error' ? 'is-error' : ''}`;239 const now = new Date().toLocaleTimeString('en-US', { hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit' });240 entry.innerHTML = `<i class="log-status ${status === 'error' ? 'error' : 'success'}">${status === 'error' ? '!' : '✓'}</i><div><header><strong>${escapeHtml(title)}</strong><span>${now}</span></header><p>${escapeHtml(detail)}</p></div><button type="button" aria-label="Expand log event">⌄</button>`;241 list.prepend(entry);242}243 244function prepareDemoDetails(record) {245 $('.avatar', demoPerson).textContent = initials(record.name);246 $('#demoPersonName').textContent = record.name;247 $('#demoPersonCompany').textContent = record.company;248 $('#demoLeadBadge').textContent = 'INBOUND';249 const originAssets = {250 YouTube: '“The Founder Content Flywheel”',251 LinkedIn: '“Build an engine, not a feed”',252 Newsletter: '“The Operator Brief — Issue 18”',253 Podcast: '“Founder Systems: Scale Without Drift”',254 'Organic search': '“Content Operations for Founder Brands”'255 };256 $('#demoOriginAsset').textContent = originAssets[record.source] || 'Maximum content touchpoint';257 $('#demoOriginPath').textContent = `${record.source} → Content OS landing page`;258 $('#demoUtm').textContent = `utm_campaign=${record.utm}`;259 $('p', $('[data-demo-step="capture"]')).textContent = 'Identity resolved • normalized sample record accepted';260 demoPerson.hidden = false;261 demoSource.hidden = false;262 demoSteps.hidden = false;263}264 265function showValidationError(errors) {266 demoFormError.textContent = errors.join(' ');267 demoFormError.hidden = false;268 setDemoState('error', 'Submission needs attention', 'Nothing was written. Correct the highlighted fields and submit again.');269 appendAudit('submission.validation_failed', 'NO WRITE', errors.join(' '), 'error');270 toast('Lead not submitted', errors[0], 'error');271 appState.demoStatus = 'validation-error';272}273 274function clearValidation() {275 demoFormError.hidden = true;276 demoFormError.textContent = '';277 $$('input, select', demoForm).forEach((field) => field.classList.remove('is-invalid'));278}279 280function setControlsForRun() {281 replayDemoButton.hidden = true;282 retryDemoButton.hidden = true;283 takeoverLeadButton.hidden = true;284 inspectLeadButton.hidden = true;285 demoSummary.hidden = true;286 mockNotification.hidden = true;287}288 289async function submitDemoLead(event) {290 event.preventDefault();291 if (appState.demoStatus === 'running') return;292 clearValidation();293 const record = normalizeSubmission(new FormData(demoForm));294 const errors = validateSubmission(record);295 if (errors.length) {296 $$('input[required], select[required]', demoForm).forEach((field) => {297 if (!normalizeSpaces(field.value)) field.classList.add('is-invalid');298 if (field.name === 'email' && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(field.value.trim())) field.classList.add('is-invalid');299 });300 showValidationError(errors);301 return;302 }303 304 appState.demoStatus = 'running';305 setControlsForRun();306 resetStepVisuals();307 demoAuditList.replaceChildren();308 $('#auditCount').textContent = '0 events';309 demoForm.hidden = true;310 prepareDemoDetails(record);311 setDemoState('loading', 'Validating and normalizing submission', 'Email, source, campaign, budget, need, and timing are being normalized locally.');312 record.score = scoreLead(record);313 record.id = stableId(record.email);314 record.startedAt = performance.now();315 record.pausedDuration = 0;316 record.uiCreated = false;317 record.attributed = false;318 appState.currentRun = record;319 320 markStep('capture', 'running');321 appendAudit('lead.received', 'ACCEPTED', `source=${record.source}; utm=${record.utm}`);322 appendSystemLog('Sample lead received', `${record.email} • source=${record.source} • campaign=${record.utm}`);323 await wait(480);324 325 const duplicate = appState.recordsByEmail.get(record.email);326 if (duplicate) {327 record.duplicateOf = duplicate.id;328 markStep('capture', 'complete', '0.3s');329 const captureText = $('p', $('[data-demo-step="capture"]'));330 captureText.textContent = `Existing contact ${duplicate.id} matched • CRM create skipped`;331 appendAudit('identity.duplicate_detected', 'NO CREATE', `${record.email} → ${duplicate.id}`, 'duplicate');332 appendAudit('crm.activity_appended', 'UPDATED', 'New content touch attached to existing record');333 appendSystemLog('Duplicate lead safely merged', `${record.email} matched ${duplicate.id}; CRM create skipped`);334 $('#demoLeadBadge').textContent = 'DUPLICATE';335 setDemoState('duplicate', 'Duplicate detected before CRM write', `Matched ${duplicate.id}. One activity was appended; CRM record count stayed unchanged.`);336 appState.demoStatus = 'duplicate';337 replayDemoButton.hidden = false;338 inspectLeadButton.hidden = false;339 takeoverLeadButton.hidden = false;340 return;341 }342 343 appendAudit('lead.normalized', 'VALID', `${record.email}; campaign=${record.utm}`);344 markStep('capture', 'complete', '0.4s');345 await runScoreStep(record);346}347 348async function runScoreStep(record) {349 markStep('score', 'running');350 setDemoState('loading', 'Calculating a deterministic fit score', 'The same inputs always return the same points, tier, stage, and owner.');351 await wait(520);352 const score = record.score;353 $('#demoScoreResult').textContent = `${score.total} / ${score.tier}-tier • ${score.tier === 'A' ? 'high commercial intent' : score.tier === 'B' ? 'qualified for review' : 'nurture until intent rises'}`;354 const reasons = $('#demoScoreReasons');355 reasons.replaceChildren(...score.reasons.map(([label, points, max]) => {356 const item = document.createElement('span');357 item.textContent = `${label} +${points}/${max}`;358 return item;359 }));360 appendAudit('lead.scored', `${score.total}/${score.tier}`, score.reasons.map(([name, value]) => `${name} +${value}`).join('; '));361 appendSystemLog('Lead scored deterministically', `${record.id} • ${score.total}/${score.tier} • ${score.stage}`);362 markStep('score', 'complete', '0.5s');363 await runCrmStep(record);364}365 366async function runCrmStep(record) {367 markStep('crm', 'running');368 setDemoState('loading', 'Writing one idempotent CRM record', `Key ${record.id} is reserved from the normalized email.`);369 await wait(520);370 if (!appState.recordsByEmail.has(record.email)) {371 appState.recordsByEmail.set(record.email, record);372 updateCrmUi(record);373 }374 const crmText = $('p', $('[data-demo-step="crm"]'));375 crmText.textContent = `${record.id} • ${formatMoney(record.budget)} • ${record.score.stage}`;376 appendAudit('crm.contact_upserted', 'CREATED', `${record.id}; idempotency_key=${record.id}`);377 appendAudit('crm.opportunity_upserted', 'CREATED', `${formatMoney(record.budget)}; stage=${record.score.stage}`);378 appendSystemLog('CRM opportunity created', `${record.id} • ${record.company} • ${formatMoney(record.budget)} • ${record.score.stage}`);379 markStep('crm', 'complete', '0.5s');380 await runRouteStep(record, false);381}382 383async function runRouteStep(record, isRetry) {384 markStep('route', 'running');385 setDemoState('loading', isRetry ? 'Replaying only the failed Slack delivery' : 'Assigning owner and creating follow-up', isRetry ? `CRM write ${record.id} is protected and will not run again.` : 'Routing by tier, segment, and operator capacity.');386 await wait(isRetry ? 650 : 540);387 const routeText = $('p', $('[data-demo-step="route"]'));388 routeText.textContent = `${record.score.owner} • #new-qualified-leads • SLA ${record.score.tier === 'A' ? '5m' : '30m'}`;389 if (!record.taskCreated) {390 record.taskCreated = true;391 appendAudit('task.followup_created', 'CREATED', `owner=${record.score.owner}; SLA=${record.score.tier === 'A' ? '5m' : '30m'}`);392 }393 394 if (record.simulateFailure && !isRetry && !record.failureSimulated) {395 record.failureSimulated = true;396 record.pausedAt = performance.now();397 markStep('route', 'error', 'TIMEOUT');398 appendAudit('slack.notification_failed', 'RETRY QUEUED', `HTTP 504; attempt 1/3; crm=${record.id} preserved`, 'error');399 appendSystemLog('Mock Slack delivery timed out', `${record.id} • retry queued • CRM write preserved`, 'error');400 setDemoState('error', 'Slack mock timed out after the CRM write', 'The lead and task are safe. Retry only the failed delivery or take over manually.');401 appState.demoStatus = 'integration-error';402 retryDemoButton.hidden = false;403 takeoverLeadButton.hidden = false;404 inspectLeadButton.hidden = false;405 syncStaticFailure(record);406 return;407 }408 409 record.slackDelivered = true;410 markStep('route', 'complete', isRetry ? 'RETRIED' : '0.6s');411 appendAudit('slack.notification_sent', isRetry ? 'RECOVERED' : 'DELIVERED', `#new-qualified-leads; owner=${record.score.owner}`);412 appendSystemLog(isRetry ? 'Slack handoff recovered' : 'Owner assigned and Slack brief sent', `${record.id} → ${record.score.owner} • SLA started`);413 showMockNotification(record, isRetry);414 await runAttributionStep(record);415}416 417async function runAttributionStep(record) {418 markStep('attribute', 'running');419 setDemoState('loading', 'Resolving campaign-to-pipeline attribution', `${record.source}, ${record.utm}, and ${record.id} are joined into one buyer path.`);420 await wait(540);421 if (!record.attributed) {422 record.attributed = true;423 updateAttributionUi(record);424 }425 const attributionText = $('p', $('[data-demo-step="attribute"]'));426 attributionText.textContent = `${record.utm} • ${formatMoney(record.budget)} pipeline • content influenced`;427 appendAudit('attribution.touch_resolved', 'MATCHED', `${record.source} → ${record.utm} → ${record.id}`);428 appendAudit('attribution.pipeline_updated', `+${formatMoney(record.budget)}`, 'Campaign and content influence recalculated');429 appendSystemLog('Revenue attribution updated', `${record.utm} +${formatMoney(record.budget)} pipeline • ${record.id}`);430 markStep('attribute', 'complete', '0.5s');431 finishDemo(record);432}433 434function finishDemo(record) {435 appState.demoStatus = 'success';436 const duration = ((performance.now() - record.startedAt - (record.pausedDuration || 0)) / 1000).toFixed(1);437 setDemoState('success', 'Flow complete with an auditable handoff', `${record.id} is qualified, assigned, task-backed, notified, and attributed.`);438 $('#demoDuration').textContent = `${duration}s`;439 $('strong', demoSummary).childNodes[0].textContent = `Lead captured → ${formatCompact(record.budget)} pipeline in `;440 demoSummary.hidden = false;441 replayDemoButton.hidden = false;442 takeoverLeadButton.hidden = false;443 inspectLeadButton.hidden = false;444 retryDemoButton.hidden = true;445 $('#demoLeadBadge').textContent = `${record.score.total} / ${record.score.tier}`;446 appState.eventsProcessed += 7;447 $('#eventsProcessed').textContent = appState.eventsProcessed.toLocaleString('en-US');448 toast('Content-to-revenue flow complete', `${record.name} became ${formatCompact(record.budget)} in attributable sample pipeline.`);449}450 451function updateCrmUi(record) {452 if (record.uiCreated) return;453 record.uiCreated = true;454 appState.capturedLeads += 1;455 if (record.score.tier !== 'C') appState.qualifiedLeads += 1;456 appState.pipeline += record.budget;457 $('#leadMetric').textContent = appState.capturedLeads.toLocaleString('en-US');458 $('#qualifiedMetric').textContent = appState.qualifiedLeads.toLocaleString('en-US');459 $('#flowQualifiedMetric').textContent = appState.qualifiedLeads.toLocaleString('en-US');460 $('#navLeadCount').textContent = appState.qualifiedLeads.toLocaleString('en-US');461 $('#pipelineMetric').textContent = formatCompact(appState.pipeline);462 $('#flowPipelineMetric').textContent = formatCompact(appState.pipeline);463 $('#pipelineTotal').textContent = formatMoney(appState.pipeline);464 ['#leadMetric', '#qualifiedMetric', '#flowQualifiedMetric', '#pipelineMetric', '#flowPipelineMetric'].forEach((selector) => flashMetric($(selector)));465 insertLeadRow(record);466 insertPipelineCard(record);467}468 469function insertLeadRow(record) {470 const table = $('.lead-table');471 const row = document.createElement('button');472 const scoreClass = record.score.tier === 'A' ? 'score-a' : record.score.tier === 'B' ? 'score-b' : 'score-c';473 const statusClass = record.score.tier === 'A' ? 'qualified' : 'nurture';474 row.type = 'button';475 row.className = 'data-row lead-row new-demo-record';476 row.dataset.lead = [record.name, record.score.total, `${record.need} + ${record.timing}`, record.score.owner, record.score.stage, record.score.tier, record.company, record.source].join('|');477 row.setAttribute('role', 'row');478 row.innerHTML = `<span><b>${escapeHtml(record.name)}</b><small>${escapeHtml(record.company)} • SAMPLE</small></span><span><em class="score ${scoreClass}">${record.score.total} / ${record.score.tier}</em></span><span><b>${escapeHtml(record.need.replaceAll('-', ' '))}</b><small>${escapeHtml(record.utm)}</small></span><span><i class="mini-avatar">${escapeHtml(initials(record.score.owner))}</i>${escapeHtml(record.score.owner)}</span><span><em class="pill ${statusClass}">${escapeHtml(record.score.stage)}</em></span>`;479 table.insertBefore(row, $('.lead-row', table));480}481 482function insertPipelineCard(record) {483 const column = $('.pipeline-column');484 const card = document.createElement('div');485 card.className = 'deal-card featured new-demo-record';486 card.innerHTML = `<div><i class="source-icon video">✦</i><span>${escapeHtml(formatMoney(record.budget))}</span></div><strong>${escapeHtml(record.company)} — ${escapeHtml(record.need.replaceAll('-', ' '))}</strong><small>${escapeHtml(record.name)} • ${record.score.total} score • SAMPLE</small><footer><i class="mini-avatar">${escapeHtml(initials(record.score.owner))}</i><span>SLA active</span></footer>`;487 column.insertBefore(card, $('.deal-card', column));488}489 490function updateAttributionUi(record) {491 appState.campaignLeads += 1;492 appState.campaignPipeline += record.budget;493 $('#demoCampaignLeads').textContent = appState.campaignLeads.toLocaleString('en-US');494 $('#demoCampaignPipeline').textContent = formatCompact(appState.campaignPipeline);495 $('#demoCampaignRow').classList.add('new-demo-record');496 flashMetric($('#demoCampaignLeads'));497 flashMetric($('#demoCampaignPipeline'));498}499 500function flashMetric(element) {501 if (!element) return;502 element.classList.remove('metric-flash');503 void element.offsetWidth;504 element.classList.add('metric-flash');505}506 507function showMockNotification(record, recovered = false) {508 $('#mockSlackTitle').textContent = `${recovered ? 'Recovered: ' : ''}New ${record.score.tier}-tier lead assigned to ${record.score.owner}`;509 $('#mockSlackBody').textContent = `${record.name} • ${record.company} • ${formatCompact(record.budget)} potential • SLA due in ${record.score.tier === 'A' ? '5' : '30'} minutes`;510 mockNotification.hidden = false;511}512 513function syncStaticFailure(record) {514 $('#slackLastRun').textContent = 'just now';515 $('#slackStatus').textContent = '1 retry';516 $('#slackStatus').className = 'pill warning';517 const failed = $('#failedLog');518 failed.classList.add('is-error');519 $('header strong', failed).textContent = 'Sample Slack delivery timed out';520 $('p', failed).innerHTML = `<code>${escapeHtml(record.id)}</code> • attempt 1/3 • CRM write preserved`;521}522 523async function retryDemoFailure() {524 const record = appState.currentRun;525 if (!record || appState.demoStatus !== 'integration-error') return;526 if (record.pausedAt) {527 record.pausedDuration += performance.now() - record.pausedAt;528 record.pausedAt = null;529 }530 retryDemoButton.disabled = true;531 retryDemoButton.textContent = 'Retrying…';532 appendAudit('retry.requested', 'QUEUED', `slack step only; idempotency_key=${record.id}`);533 await runRouteStep(record, true);534 retryDemoButton.disabled = false;535 retryDemoButton.textContent = 'Retry failed step';536 resolveStaticFailure();537}538 539function resolveStaticFailure() {540 appState.genericRetryComplete = true;541 $('#slackLastRun').textContent = 'just now';542 $('#slackSuccess').textContent = '99.1%';543 $('#slackStatus').textContent = 'Healthy';544 $('#slackStatus').className = 'pill success';545 const failed = $('#failedLog');546 if (failed) {547 failed.classList.remove('is-error');548 const status = $('.log-status', failed);549 status.className = 'log-status success';550 status.textContent = '✓';551 $('header strong', failed).textContent = 'Slack delivery recovered on retry';552 $('.log-error-detail', failed)?.remove();553 const button = $('[data-retry]', failed);554 if (button) button.replaceWith(document.createTextNode('Recovered'));555 }556}557 558async function genericRetry() {559 if (appState.demoStatus === 'integration-error') {560 await retryDemoFailure();561 return;562 }563 if (appState.genericRetryComplete) {564 toast('Already recovered', 'The retry was idempotent and the workflow is healthy.');565 return;566 }567 $$('[data-retry]').forEach((button) => {568 button.disabled = true;569 button.textContent = 'Retrying…';570 });571 toast('Retry queued', 'Only the failed mock Slack delivery will replay.');572 await wait(850);573 resolveStaticFailure();574 appendSystemLog('Slack delivery recovered', 'evt_83fa • attempt 3/3 • CRM write not repeated');575 toast('Delivery recovered', 'The CRM write was not repeated; the Slack step is healthy.');576}577 578async function takeOverCurrentLead() {579 const record = appState.currentRun;580 if (!record) {581 toast('No lead selected', 'Run or select a sample lead before taking over.', 'error');582 return;583 }584 if (record.manualTakeover) {585 toast('Already in manual control', `${record.name} is assigned to Max with full context.`);586 return;587 }588 record.manualTakeover = true;589 if (record.pausedAt) {590 record.pausedDuration += performance.now() - record.pausedAt;591 record.pausedAt = null;592 }593 record.score.owner = 'Max (manual)';594 appendAudit('operator.takeover', 'ASSIGNED', `Max; automation paused for ${record.id}`);595 appendSystemLog('Operator took over sample lead', `${record.id} → Max • event state preserved`);596 takeoverLeadButton.textContent = 'Manual owner: Max';597 $('#demoLeadBadge').textContent = 'MANUAL';598 setDemoState('success', 'Max now owns this lead manually', 'Automation state and all prior writes were preserved for a clean handoff.');599 toast('Manual takeover active', `${record.name} is now assigned to Max.`);600 if (appState.demoStatus === 'integration-error') {601 markStep('route', 'complete', 'MANUAL');602 retryDemoButton.hidden = true;603 mockNotification.hidden = true;604 await runAttributionStep(record);605 }606}607 608function resetDemoForAnotherLead() {609 appState.demoStatus = 'idle';610 appState.currentRun = null;611 demoForm.hidden = false;612 demoPerson.hidden = true;613 demoSource.hidden = true;614 demoSteps.hidden = true;615 demoSummary.hidden = true;616 demoAudit.hidden = true;617 mockNotification.hidden = true;618 demoAuditList.replaceChildren();619 resetStepVisuals();620 replayDemoButton.hidden = true;621 retryDemoButton.hidden = true;622 takeoverLeadButton.hidden = true;623 takeoverLeadButton.textContent = 'Take over manually';624 inspectLeadButton.hidden = true;625 clearValidation();626 setDemoState('empty', 'Ready for a sample submission', 'Complete fields above, then run the deterministic workflow.');627 $('input[name="email"]', demoForm).focus();628}629 630function openDemo() {631 if (['success', 'duplicate'].includes(appState.demoStatus)) resetDemoForAnotherLead();632 demoTray.classList.add('is-open');633 demoTray.setAttribute('aria-hidden', 'false');634 document.body.style.overflow = window.innerWidth <= 650 ? 'hidden' : '';635 window.setTimeout(() => {636 const focusTarget = demoForm.hidden ? $('#closeDemo') : $('input[name="name"]', demoForm);637 focusTarget?.focus();638 }, 380);639}640 641function closeDemo() {642 demoTray.classList.remove('is-open');643 demoTray.setAttribute('aria-hidden', 'true');644 document.body.style.overflow = '';645}646 647function selectLeadRow(row) {648 $$('.lead-row').forEach((lead) => lead.classList.toggle('is-selected', lead === row));649 const [name, score, signal, owner, stage, tier, company, source] = row.dataset.lead.split('|');650 $('#inspectorName').textContent = name;651 $('#inspectorCompany').textContent = company;652 const scoreRing = $('#inspectorScore');653 scoreRing.style.setProperty('--score', score);654 $('strong', scoreRing).textContent = score;655 $('span', scoreRing).textContent = `${tier}-TIER`;656 $('#inspectorSource').textContent = source;657 $('#inspectorAction').textContent = tier === 'A' ? `Send personalized audit before ${stage.toLowerCase()}` : tier === 'B' ? `Review ${signal.toLowerCase()} and qualify` : 'Keep in nurture until intent rises';658 $('#assignLead').textContent = owner === 'Unassigned' ? 'Assign owner' : `Owner: ${owner}`;659}660 661function approveContent(action) {662 const spotlight = $('#approvalSpotlight');663 if (action === 'approve') {664 if (appState.approvedContent) {665 toast('Already scheduled', 'This sample asset is queued for Jul 27 at 10:00 AM.');666 return;667 }668 appState.approvedContent = true;669 $('#awaitingCount').textContent = '1';670 $('#boardApprovalCount').textContent = '1';671 $('#scheduledCount').textContent = '7';672 $('[data-content-action="approve"]').textContent = '✓ Approved • scheduled';673 $('[data-content-action="approve"]').disabled = true;674 spotlight.classList.add('new-demo-record');675 appendSystemLog('Content asset approved', 'asset_0201 → LinkedIn • scheduled Jul 27 10:00');676 toast('Approved and scheduled', 'The sample LinkedIn video moved to Jul 27 at 10:00 AM.');677 } else {678 $('#approvalTitle').textContent = 'Why founder content breaks at scale — revision requested';679 $('.approval-meta .pill').textContent = 'REVISION REQUESTED';680 toast('Revision routed', 'The editor received the sample note with the current cut attached.');681 }682}683 684function updateAttributionModel(model) {685 const options = {686 influence: ['$42,800', '<strong>Content influence</strong> credits verified content interactions that occurred before opportunity creation and materially advanced the buyer.'],687 linear: ['$39,460', '<strong>Linear multi-touch</strong> splits credit evenly across all verified touches in the known buyer journey.'],688 first: ['$31,200', '<strong>First touch</strong> assigns revenue credit to the first known acquisition source in the journey.'],689 last: ['$36,900', '<strong>Last touch</strong> assigns credit to the final known interaction before opportunity creation.']690 };691 const [revenue, explanation] = options[model] || options.influence;692 $('#influencedRevenue').textContent = revenue;693 $('p', $('#modelExplanation')).innerHTML = explanation;694 flashMetric($('#influencedRevenue'));695}696 697async function copyUtmRules() {698 const rules = 'utm_source={channel}&utm_medium={format}&utm_campaign={initiative}&utm_content={asset_slug}';699 try {700 await navigator.clipboard.writeText(rules);701 toast('UTM rules copied', rules);702 } catch {703 toast('UTM naming rule', rules);704 }705}706 707function toggleGlobalTakeover() {708 appState.takeoverActive = !appState.takeoverActive;709 const button = $('#takeoverButton');710 const status = $('#takeoverWorkflow strong');711 if (appState.takeoverActive) {712 status.innerHTML = '<i style="background:#a66b17"></i> Manual queue active';713 status.style.color = '#a66b17';714 button.textContent = 'Resume automation';715 appendSystemLog('Human takeover enabled', 'New lead events enter the visible manual queue');716 toast('Manual queue active', 'In-flight events finished safely; new leads await an operator.');717 } else {718 status.innerHTML = '<i></i> Automation active';719 status.style.color = '';720 button.textContent = 'Pause & take over';721 appendSystemLog('Automation resumed', 'Queued lead events are processing in received order');722 toast('Automation resumed', 'Queued sample events are processing in order.');723 }724}725 726$$('.nav-item').forEach((button) => button.addEventListener('click', () => switchView(button.dataset.view)));727$$('[data-view-jump]').forEach((button) => button.addEventListener('click', () => switchView(button.dataset.viewJump, true)));728mobileMenu.addEventListener('click', () => sidebar.classList.contains('is-open') ? closeMobileNavigation() : openMobileNavigation());729sidebarScrim.addEventListener('click', closeMobileNavigation);730 731$$('[data-run-demo]').forEach((button) => button.addEventListener('click', openDemo));732$('#closeDemo').addEventListener('click', closeDemo);733demoForm.addEventListener('submit', submitDemoLead);734replayDemoButton.addEventListener('click', resetDemoForAnotherLead);735retryDemoButton.addEventListener('click', retryDemoFailure);736takeoverLeadButton.addEventListener('click', takeOverCurrentLead);737inspectLeadButton.addEventListener('click', () => {738 closeDemo();739 switchView('leads', true);740 const first = $('.lead-row');741 if (first) selectLeadRow(first);742});743$('#duplicatePreset').addEventListener('click', () => {744 $('input[name="name"]', demoForm).value = 'Maya Chen';745 $('input[name="email"]', demoForm).value = 'maya@northstarhealth.com';746 $('input[name="company"]', demoForm).value = 'Northstar Health';747 toast('Duplicate fixture loaded', 'Submit it to verify that the CRM create is skipped.');748});749$('#auditToggle').addEventListener('click', (event) => {750 const expanded = event.currentTarget.getAttribute('aria-expanded') === 'true';751 event.currentTarget.setAttribute('aria-expanded', String(!expanded));752 demoAuditList.hidden = expanded;753 $('i', event.currentTarget).textContent = expanded ? '⌄' : '⌃';754});755 756document.addEventListener('click', (event) => {757 const leadRow = event.target.closest('.lead-row');758 if (leadRow) selectLeadRow(leadRow);759 const retry = event.target.closest('[data-retry]');760 if (retry && retry !== retryDemoButton) genericRetry();761 const contentAction = event.target.closest('[data-content-action]');762 if (contentAction) approveContent(contentAction.dataset.contentAction);763 const rowMenu = event.target.closest('.row-menu');764 if (rowMenu) toast('Workflow controls', 'Run history, owner, retry policy, and integration boundary are available in production.');765});766 767$('#leadSearch').addEventListener('input', (event) => {768 const query = event.target.value.trim().toLowerCase();769 $$('.lead-row').forEach((row) => { row.hidden = !row.textContent.toLowerCase().includes(query); });770});771$('#rescoreLead').addEventListener('click', async (event) => {772 event.currentTarget.disabled = true;773 event.currentTarget.textContent = 'Scoring…';774 await wait(600);775 event.currentTarget.disabled = false;776 event.currentTarget.textContent = 'Re-score sample';777 toast('Score unchanged', 'Deterministic inputs produced the same tier and explanation.');778});779$('#assignLead').addEventListener('click', (event) => {780 event.currentTarget.textContent = 'Owner: Max';781 toast('Owner assigned', 'A sample follow-up task and SLA were created for Max.');782});783$('#attributionModel').addEventListener('change', (event) => updateAttributionModel(event.target.value));784$('#copyUtm').addEventListener('click', copyUtmRules);785$('#takeoverButton').addEventListener('click', toggleGlobalTakeover);786$('#clearLogFilter').addEventListener('click', (event) => {787 event.currentTarget.textContent = 'Showing all events';788 toast('All events visible', 'Success, retry, manual, and error states are included.');789});790 791document.addEventListener('keydown', (event) => {792 if (event.key === 'Escape') {793 if (demoTray.classList.contains('is-open')) closeDemo();794 else closeMobileNavigation();795 }796});797 798window.addEventListener('resize', () => {799 if (window.innerWidth > 860) closeMobileNavigation();800});801 802switchView('overview');803 