Skip to content

Commit 86972e4

Browse files
Update index.html
1 parent 83019a5 commit 86972e4

1 file changed

Lines changed: 235 additions & 6 deletions

File tree

‎index.html‎

Lines changed: 235 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,100 @@ <h1 class="mt-4 text-3xl font-semibold leading-tight sm:text-4xl">
4545
showBusinessPlanModal: false,
4646
showEvaluationModal: false,
4747
showPartnerEventsModal: false,
48+
showCsvModal: false,
4849
activeStep: 0,
4950
band: 'band1',
51+
roeView: 'partner', // 'partner' | 'internal'
52+
defaultBandByType: { Registered:'band3', Preferred:'band2', Premier:'band1', MSP:'band1' },
53+
simpleROE: {
54+
Registered: {
55+
summary: "You can refer and transact. Co-sell upgrades your margin; order-only stays referral.",
56+
sourcing: [
57+
{label:"Partner Sourced", value:"You found the opp + registered it + helped close → Full margin when you drive the sale."},
58+
{label:"Recast Sourced (Co-Sell)", value:"We brought the lead; you help sell → Shared margin."},
59+
{label:"Transactional/Referral", value:"Intro or PO only → Referral margin."}
60+
],
61+
bands: [
62+
{band:"Band 2 – Co-Sell", when:["Host/lead discovery","Own next steps/MAP","Participate in demo/validation","Coordinate quote/security"], rule:"Any 2+ qualify"},
63+
{band:"Band 3 – Transactional", when:["Intro only or PO processing"], rule:"Default if <2 activities above"}
64+
],
65+
renewals:[
66+
"PoR keeps renewal if active (renewal plan ≥90 days out + QBR/health checks).",
67+
"If inactive or customer reassigns, Recast may reassign renewal."
68+
],
69+
csv: {
70+
blurb:"Upload event or campaign lists for fast routing. Influence ≠ margin.",
71+
required:["Partner Name","Campaign Name/ID","Account Name (and Domain)","First Name","Last Name","Email","Country","Source Type","Attribution Intent","Requested PoR (Y/N)"],
72+
flow:[
73+
"Create/Update Campaign Members; de-dupe by email; match Account by Domain/Name.",
74+
"Stamp: Lead Source=Partner Event; Sourcing Partner; Attribution (Primary/Influence).",
75+
"Option: 'Create Deal Registration' → PQL → Channel approval in 2 business days."
76+
]
77+
}
78+
},
79+
Preferred: {
80+
summary:"You co-sell with us; consistent activity earns Band 2. Own full cycle → Band 1.",
81+
sourcing: [
82+
{label:"Partner Sourced", value:"You found the opp + registered it + helped close → Full margin when you drive the sale."},
83+
{label:"Recast Sourced (Co-Sell)", value:"We brought the lead; you help sell → Shared margin."},
84+
{label:"Transactional/Referral", value:"Intro or PO only → Referral margin."}
85+
],
86+
bands:[
87+
{band:"Band 1 – Full-Cycle", when:["Sourced + registered","Owns qual→close with light Recast support"], rule:"Full margin"},
88+
{band:"Band 2 – Co-Sell", when:["Shared discovery/demo/pricing; active MAP"], rule:"Shared margin"}
89+
],
90+
renewals:["PoR retains renewal with activity compliance; else may be reassigned."],
91+
csv: {
92+
blurb:"Upload event or campaign lists for fast routing. Influence ≠ margin.",
93+
required:["Partner Name","Campaign Name/ID","Account Name (and Domain)","First Name","Last Name","Email","Country","Source Type","Attribution Intent","Requested PoR (Y/N)"],
94+
flow:[
95+
"Create/Update Campaign Members; de-dupe by email; match Account by Domain/Name.",
96+
"Stamp: Lead Source=Partner Event; Sourcing Partner; Attribution (Primary/Influence).",
97+
"Option: 'Create Deal Registration' → PQL → Channel approval in 2 business days."
98+
]
99+
}
100+
},
101+
Premier: {
102+
summary:"Strategic partner. Default expectation is Band 1 unless co-sell is requested.",
103+
sourcing: [
104+
{label:"Partner Sourced", value:"You found the opp + registered it + helped close → Full margin when you drive the sale."},
105+
{label:"Recast Sourced (Co-Sell)", value:"We brought the lead; you help sell → Shared margin."},
106+
{label:"Transactional/Referral", value:"Intro or PO only → Referral margin."}
107+
],
108+
bands:[
109+
{band:"Band 1 – Full-Cycle", when:["Sourced + end-to-end ownership"], rule:"Full margin"},
110+
{band:"Band 2 – Co-Sell", when:["Recast Sourced or heavier presales help"], rule:"Shared margin"}
111+
],
112+
renewals:["PoR renewal protection with activity; expansions follow Bands."],
113+
csv: {
114+
blurb:"Upload event or campaign lists for fast routing. Influence ≠ margin.",
115+
required:["Partner Name","Campaign Name/ID","Account Name (and Domain)","First Name","Last Name","Email","Country","Source Type","Attribution Intent","Requested PoR (Y/N)"],
116+
flow:[
117+
"Create/Update Campaign Members; de-dupe by email; match Account by Domain/Name.",
118+
"Stamp: Lead Source=Partner Event; Sourcing Partner; Attribution (Primary/Influence).",
119+
"Option: 'Create Deal Registration' → PQL → Channel approval in 2 business days."
120+
]
121+
}
122+
},
123+
MSP: {
124+
summary:"Managed service delivery; monthly economics; PoR protections per MSP ROE.",
125+
sourcing:[{label:"MSP Motion", value:"You package Recast in services; co-sell on net-new logos."}],
126+
bands:[
127+
{band:"Band 1", when:["MSP drives sale and delivery"], rule:"Programmed in MSP price book"},
128+
{band:"Band 2", when:["Co-sell with Recast SE/AE"], rule:"Shared margin"}
129+
],
130+
renewals:["Incumbent MSP retains renewal if active in lifecycle reviews."],
131+
csv: {
132+
blurb:"Upload event or campaign lists for fast routing. Influence ≠ margin. Emphasize multitenant lists.",
133+
required:["Partner Name","Campaign Name/ID","Account Name (and Domain)","First Name","Last Name","Email","Country","Source Type","Attribution Intent","Requested PoR (Y/N)"],
134+
flow:[
135+
"Create/Update Campaign Members; de-dupe by email; match Account by Domain/Name.",
136+
"Stamp: Lead Source=Partner Event; Sourcing Partner; Attribution (Primary/Influence).",
137+
"Option: 'Create Deal Registration' → PQL → Channel approval in 2 business days."
138+
]
139+
}
140+
}
141+
},
50142
exampleACV: 100000,
51143
salesCycle: {
52144
stages: {
@@ -483,6 +575,7 @@ <h1 class="mt-4 text-3xl font-semibold leading-tight sm:text-4xl">
483575
if (STATE.showPartnerEventsModal) {
484576
html += renderPartnerEventsModal();
485577
}
578+
if (STATE.showCsvModal) html += renderCsvModal();
486579
document.getElementById('app').innerHTML = html;
487580
lucide.createIcons();
488581
addEventListeners();
@@ -705,17 +798,100 @@ <h4 class="font-medium mb-2">${r.band} - Up to ${r.upTo}</h4>
705798
return html;
706799
}
707800
function renderROEContent() {
801+
// MSP already has its own ROE block; keep that path
708802
if (STATE.partnerType === 'MSP') {
709-
let html = '<h2 class="text-xl font-medium mb-6 flex items-center gap-3"><i data-lucide="gavel" class="h-6 w-6 stroke-[#2563eb]"></i>Rules of Engagement</h2>';
710-
html += '<ol class="space-y-6 list-decimal pl-6">';
803+
// Add partner/internal toggle even for MSP
804+
return renderROEWithToggle(renderSimpleROE(STATE.partnerType), renderInternalROE());
805+
}
806+
return renderROEWithToggle(renderSimpleROE(STATE.partnerType), renderInternalROE());
807+
}
808+
function renderROEWithToggle(partnerHtml, internalHtml){
809+
const toggle = `
810+
<div class="flex items-center justify-between mb-4">
811+
<h2 class="text-xl font-medium flex items-center gap-3">
812+
<i data-lucide="gavel" class="h-6 w-6 stroke-[#2563eb]"></i>Rules of Engagement
813+
</h2>
814+
<div class="inline-flex rounded-full bg-gray-100 p-1">
815+
<button data-roeview="partner" class="px-3 py-1 text-sm rounded-full ${STATE.roeView==='partner'?'bg-white shadow':''}">Partner-facing</button>
816+
<button data-roeview="internal" class="px-3 py-1 text-sm rounded-full ${STATE.roeView==='internal'?'bg-white shadow':''}">Internal</button>
817+
</div>
818+
</div>`;
819+
const body = STATE.roeView==='partner' ? partnerHtml : internalHtml;
820+
return toggle + body;
821+
}
822+
function renderSimpleROE(type){
823+
const d = STATE.simpleROE[type];
824+
const defBand = STATE.defaultBandByType[type] || 'band2';
825+
const bandMeta = STATE.salesCycle.bandMeta[defBand];
826+
827+
const card = (title, inner) => `
828+
<div class="rounded-xl bg-white shadow-md hover:shadow-lg transition overflow-hidden">
829+
<h3 class="text-lg font-semibold p-5 border-b border-[#E2E8F0] bg-gray-50 flex items-center gap-2">
830+
<i data-lucide="file-text" class="h-5 w-5 stroke-[#2563eb]"></i>${title}
831+
</h3>
832+
<div class="p-5">${inner}</div>
833+
</div>`;
834+
835+
const list = (items) => `<ul class="space-y-2">${items.map(i=>`
836+
<li class="flex items-start gap-3">
837+
<span class="mt-1 bg-[#2563eb] rounded-full p-1"><i data-lucide="check" class="h-3 w-3 stroke-white"></i></span>
838+
<span class="text-gray-700 leading-relaxed">${typeof i==='string'?i:`<strong>${i.label}:</strong> ${i.value}`}</span>
839+
</li>`).join('')}</ul>`;
840+
841+
const bandsTable = `
842+
<div class="overflow-x-auto">
843+
<table class="min-w-full divide-y divide-[#E2E8F0]">
844+
<thead><tr>
845+
<th class="px-4 py-2 text-left text-sm font-semibold">Band</th>
846+
<th class="px-4 py-2 text-left text-sm font-semibold">When</th>
847+
<th class="px-4 py-2 text-left text-sm font-semibold">Rule</th>
848+
</tr></thead>
849+
<tbody class="divide-y divide-[#E2E8F0]">
850+
${d.bands.map(b => `
851+
<tr>
852+
<td class="px-4 py-3 text-sm font-medium">${b.band}</td>
853+
<td class="px-4 py-3 text-sm">
854+
<ul class="list-disc pl-5 space-y-1">${(b.when||[]).map(w=>`<li>${w}</li>`).join('')}</ul>
855+
</td>
856+
<td class="px-4 py-3 text-sm">${b.rule||''}</td>
857+
</tr>`).join('')}
858+
</tbody>
859+
</table>
860+
</div>`;
861+
862+
const csvBlock = `
863+
<p class="text-gray-700 leading-relaxed mb-3">${d.csv.blurb}</p>
864+
<div class="grid md:grid-cols-3 gap-4">
865+
<div><div class="font-medium mb-1">Required Columns</div>${list(d.csv.required)}</div>
866+
<div><div class="font-medium mb-1">What Happens</div>${list(d.csv.flow)}</div>
867+
<div class="flex items-start">
868+
<button id="csvSopBtn" class="rounded-full bg-[#2563eb] hover:bg-[#1d4ed8] px-4 py-2 text-sm text-white shadow-sm">CSV Upload SOP</button>
869+
</div>
870+
</div>`;
871+
872+
return `
873+
<div class="space-y-6">
874+
<div class="flex flex-wrap items-center gap-2">
875+
<span class="px-3 py-1 text-xs rounded-2xl ${bandMeta.color}">${bandMeta.name}</span>
876+
<span class="text-xs text-gray-600">Default expectation for ${type}</span>
877+
</div>
878+
${card("Sourcing & Attribution", list(d.sourcing))}
879+
${card("Registration Bands (What You Earn)", bandsTable)}
880+
${card("Renewals", list(d.renewals))}
881+
${card("Uploading Event/Co-Marketing Leads", csvBlock)}
882+
</div>`;
883+
}
884+
function renderInternalROE(){
885+
if (STATE.partnerType === 'MSP') {
886+
let html = '<ol class="space-y-6 list-decimal pl-6">';
711887
STATE.msp.roe.forEach(rule => {
712888
html += `<li class="text-gray-700 leading-relaxed">${rule.replace(/\n/g, '<br>')}</li>`;
713889
});
714890
html += '</ol>';
715891
return html;
716892
} else {
717-
let html = '<h2 class="text-xl font-medium mb-6 flex items-center gap-3"><i data-lucide="gavel" class="h-6 w-6 stroke-[#2563eb]"></i>Rules of Engagement</h2>';
718-
html += '<div class="space-y-6">';
893+
// reuse existing blocks
894+
let html = '<div class="space-y-6">';
719895
html += renderROESection('Rules of Engagement (ROE)', renderPrinciplesTable());
720896
html += renderROESection('Margin Bands & Definitions', renderBandsTable());
721897
html += renderROESection('Partner Margin Governance', renderGovernanceTable());
@@ -1107,6 +1283,29 @@ <h2 class="text-xl font-medium mb-6 flex items-center gap-3"><i data-lucide="sca
11071283
html += '</section>';
11081284
return html;
11091285
}
1286+
function renderCsvModal(){
1287+
const sample = "Partner Name,Campaign Name,Account Name,Domain,First Name,Last Name,Email,Country,Source Type,Attribution Intent,Requested PoR\nAcme Partner,2025-03 Webinar,Contoso,contoso.com,Jane,Doe,jane@contoso.com,US,Webinar,Partner Sourced,Yes";
1288+
return `
1289+
<div class="fixed inset-0 z-50 flex items-center justify-center bg-black/50 px-4" id="csvModal">
1290+
<div class="relative max-w-3xl w-full bg-white rounded-xl shadow-2xl p-8 max-h-[90vh] overflow-y-auto">
1291+
<button class="absolute top-4 right-4 text-gray-600 hover:text-gray-900" id="closeCsvModal"><i data-lucide="x" class="h-6 w-6"></i></button>
1292+
<h2 class="text-xl font-medium mb-4 flex items-center gap-3"><i data-lucide="upload" class="h-6 w-6 stroke-[#2563eb]"></i>CSV Upload SOP</h2>
1293+
<ol class="list-decimal pl-6 space-y-2 text-gray-700">
1294+
<li>Download the template CSV.</li>
1295+
<li>Fill required columns; one row per contact.</li>
1296+
<li>Choose “Create Deal Registration” for qualified rows.</li>
1297+
<li>Channel approves within 2 business days (PoR + Band applied).</li>
1298+
</ol>
1299+
<div class="mt-4">
1300+
<pre class="bg-gray-50 p-3 rounded-lg text-xs overflow-auto">${sample}</pre>
1301+
</div>
1302+
<div class="mt-4 flex gap-2">
1303+
<button class="rounded-full bg-[#2563eb] text-white px-4 py-2 text-sm" id="downloadCsvTpl">Download Template</button>
1304+
<button class="rounded-full border px-4 py-2 text-sm" id="closeCsvModal2">Close</button>
1305+
</div>
1306+
</div>
1307+
</div>`;
1308+
}
11101309
function addEventListeners() {
11111310
document.getElementById('partnerType').addEventListener('change', (e) => {
11121311
STATE.partnerType = e.target.value;
@@ -1210,22 +1409,52 @@ <h2 class="text-xl font-medium mb-6 flex items-center gap-3"><i data-lucide="sca
12101409
render();
12111410
});
12121411
}
1213-
const modals = document.querySelectorAll('#businessPlanModal, #partnerEventsModal, #evaluationModal');
1412+
document.querySelectorAll('[data-roeview]').forEach(btn=>{
1413+
btn.addEventListener('click',()=>{
1414+
STATE.roeView = btn.dataset.roeview;
1415+
render();
1416+
});
1417+
});
1418+
const csvBtn = document.getElementById('csvSopBtn');
1419+
if (csvBtn){
1420+
csvBtn.addEventListener('click',()=>{
1421+
STATE.showCsvModal = true;
1422+
render();
1423+
});
1424+
}
1425+
const closeCsv = document.getElementById('closeCsvModal');
1426+
const closeCsv2 = document.getElementById('closeCsvModal2');
1427+
if (closeCsv || closeCsv2){
1428+
[closeCsv, closeCsv2].filter(Boolean).forEach(b=>b.addEventListener('click',()=>{ STATE.showCsvModal=false; render(); }));
1429+
}
1430+
const dl = document.getElementById('downloadCsvTpl');
1431+
if (dl){
1432+
dl.addEventListener('click',()=>{
1433+
const csv = `Partner Name,Campaign Name,Account Name,Domain,First Name,Last Name,Email,Country,Source Type,Attribution Intent,Requested PoR\n`;
1434+
const blob = new Blob([csv], {type:'text/csv'});
1435+
const url = URL.createObjectURL(blob);
1436+
const a = document.createElement('a'); a.href=url; a.download='recast_partner_upload_template.csv'; a.click();
1437+
URL.revokeObjectURL(url);
1438+
});
1439+
}
1440+
const modals = document.querySelectorAll('#businessPlanModal, #partnerEventsModal, #evaluationModal, #csvModal');
12141441
modals.forEach(modal => {
12151442
modal.addEventListener('click', (e) => {
12161443
if (e.target === modal) {
12171444
STATE.showBusinessPlanModal = false;
12181445
STATE.showPartnerEventsModal = false;
12191446
STATE.showEvaluationModal = false;
1447+
STATE.showCsvModal = false;
12201448
render();
12211449
}
12221450
});
12231451
});
12241452
document.addEventListener('keydown', (e) => {
1225-
if (e.key === 'Escape' && (STATE.showBusinessPlanModal || STATE.showPartnerEventsModal || STATE.showEvaluationModal)) {
1453+
if (e.key === 'Escape' && (STATE.showBusinessPlanModal || STATE.showPartnerEventsModal || STATE.showEvaluationModal || STATE.showCsvModal)) {
12261454
STATE.showBusinessPlanModal = false;
12271455
STATE.showPartnerEventsModal = false;
12281456
STATE.showEvaluationModal = false;
1457+
STATE.showCsvModal = false;
12291458
render();
12301459
}
12311460
});

0 commit comments

Comments
 (0)