Onboard experiment products and MVP pre-onboarding

Adds two roles to the console. Each posts to its own n8n webhook
(/experiment-onboarding, /mvp-onboarding) and carries the Discord
channel its thread belongs in as discordChannelId:
- experiment -> #experiment-products (1511998970216448031)
- mvp        -> #mvps-pre-onboarding (1534550504401670194)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
denisireri
2026-10-14 11:44:57 +03:00
parent 7d7e57ce76
commit 02336cb4b5
+154 -6
View File
@@ -15,6 +15,8 @@
--client:#1596d8; --client-soft:#123a5c; --client:#1596d8; --client-soft:#123a5c;
--intern:#39c8a8; --intern-soft:#1d4a45; --intern:#39c8a8; --intern-soft:#1d4a45;
--contractor:#f0a94b; --contractor-soft:#4d3820; --contractor:#f0a94b; --contractor-soft:#4d3820;
--experiment:#a78bfa; --experiment-soft:#33295a;
--mvp:#f472b6; --mvp-soft:#552440;
--accent:var(--client); --accent-soft:var(--client-soft); --accent:var(--client); --accent-soft:var(--client-soft);
--ok:#42d392; --err:#ff6b6b; --ok:#42d392; --err:#ff6b6b;
--radius:14px; --radius-sm:9px; --radius:14px; --radius-sm:9px;
@@ -53,7 +55,7 @@ body{
.step-sep{flex:1;height:1px;background:var(--line-soft);min-width:12px;max-width:40px} .step-sep{flex:1;height:1px;background:var(--line-soft);min-width:12px;max-width:40px}
/* role selector */ /* role selector */
.roles{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin-bottom:22px} .roles{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:10px;margin-bottom:22px}
.role{position:relative;cursor:pointer;border:1px solid var(--line);background:var(--panel);border-radius:var(--radius); .role{position:relative;cursor:pointer;border:1px solid var(--line);background:var(--panel);border-radius:var(--radius);
padding:16px;text-align:left;color:var(--ink);transition:border-color .2s,background .2s,transform .12s;display:flex;flex-direction:column;gap:9px} padding:16px;text-align:left;color:var(--ink);transition:border-color .2s,background .2s,transform .12s;display:flex;flex-direction:column;gap:9px}
.role:hover{transform:translateY(-2px)} .role:hover{transform:translateY(-2px)}
@@ -65,6 +67,8 @@ body{
.role[data-role="client"]{--r:var(--client);--r-soft:var(--client-soft);--r-line:#3a478a} .role[data-role="client"]{--r:var(--client);--r-soft:var(--client-soft);--r-line:#3a478a}
.role[data-role="intern"]{--r:var(--intern);--r-soft:var(--intern-soft);--r-line:#2c6b60} .role[data-role="intern"]{--r:var(--intern);--r-soft:var(--intern-soft);--r-line:#2c6b60}
.role[data-role="contractor"]{--r:var(--contractor);--r-soft:var(--contractor-soft);--r-line:#77571f} .role[data-role="contractor"]{--r:var(--contractor);--r-soft:var(--contractor-soft);--r-line:#77571f}
.role[data-role="experiment"]{--r:var(--experiment);--r-soft:var(--experiment-soft);--r-line:#5b4a99}
.role[data-role="mvp"]{--r:var(--mvp);--r-soft:var(--mvp-soft);--r-line:#8a3a68}
.role[aria-pressed="true"]{background:var(--panel-2);border-color:var(--r);box-shadow:inset 0 0 0 1px var(--r),0 8px 24px -14px var(--r)} .role[aria-pressed="true"]{background:var(--panel-2);border-color:var(--r);box-shadow:inset 0 0 0 1px var(--r),0 8px 24px -14px var(--r)}
.role[aria-pressed="true"]::after{content:"";position:absolute;top:12px;right:12px;width:8px;height:8px;border-radius:50%;background:var(--r); .role[aria-pressed="true"]::after{content:"";position:absolute;top:12px;right:12px;width:8px;height:8px;border-radius:50%;background:var(--r);
box-shadow:0 0 0 3px color-mix(in srgb,var(--r) 25%,transparent)} box-shadow:0 0 0 3px color-mix(in srgb,var(--r) 25%,transparent)}
@@ -215,6 +219,16 @@ select{appearance:none;background-image:url("data:image/svg+xml,%3Csvg xmlns='ht
<span class="rname">Contractor</span> <span class="rname">Contractor</span>
<span class="rdesc">A scoped engagement. Captures terms and deliverables.</span> <span class="rdesc">A scoped engagement. Captures terms and deliverables.</span>
</button> </button>
<button type="button" class="role" data-role="experiment" aria-pressed="false">
<span class="glyph"><svg viewBox="0 0 24 24"><path d="M9 3h6M10 3v6L4.5 18.5A1.7 1.7 0 006 21h12a1.7 1.7 0 001.5-2.5L14 9V3M7 15h10"/></svg></span>
<span class="rname">Experiment product</span>
<span class="rdesc">An internal bet. Captures hypothesis, signal, and kill criteria.</span>
</button>
<button type="button" class="role" data-role="mvp" aria-pressed="false">
<span class="glyph"><svg viewBox="0 0 24 24"><path d="M5 19c-1.5-1.5-2-5 0-7l7-7c2-2 6-2 7-2 0 1 0 5-2 7l-7 7c-2 2-5.5 1.5-7 0zM15 9h.01M5 19l-2 2"/></svg></span>
<span class="rname">MVP pre-onboarding</span>
<span class="rdesc">A venture before kickoff. Captures problem, users, and must-haves.</span>
</button>
</div> </div>
<div class="panel"> <div class="panel">
@@ -523,6 +537,127 @@ select{appearance:none;background-image:url("data:image/svg+xml,%3Csvg xmlns='ht
<p style="font-size:11px;color:var(--ink-faint);margin-top:10px;line-height:1.5">Note: do not enter tax IDs, bank details, or other sensitive payment information here. Those are collected separately through a secure channel.</p> <p style="font-size:11px;color:var(--ink-faint);margin-top:10px;line-height:1.5">Note: do not enter tax IDs, bank details, or other sensitive payment information here. Those are collected separately through a secure channel.</p>
</div> </div>
<!-- EXPERIMENT PRODUCT -->
<div class="role-fields" data-fields="experiment">
<div class="section-label">Experiment</div>
<div class="grid">
<div class="field">
<label>Product / experiment name <span class="req">required</span></label>
<input name="productName" placeholder="Short working title" autocomplete="off" />
</div>
<div class="field">
<label>Stage</label>
<select name="stage"><option value="">Select…</option><option>Idea</option><option>Prototype</option><option>Live test</option><option>Scaling</option></select>
</div>
<div class="field full">
<label>Hypothesis <span class="req">required</span></label>
<textarea name="hypothesis" placeholder="We believe [users] will [behaviour] because [reason]."></textarea>
</div>
<div class="field full">
<label>Target users / market</label>
<input name="targetUsers" placeholder="Who this is for" autocomplete="off" />
</div>
<div class="field full">
<label>Success signal <span class="hint">what proves it works</span></label>
<textarea name="successMetrics" placeholder="e.g. 50 signups in 2 weeks, 10% conversion."></textarea>
</div>
<div class="field full">
<label>Kill criteria <span class="hint">when we stop</span></label>
<textarea name="killCriteria" placeholder="e.g. under 10 signups after 2 weeks."></textarea>
</div>
<div class="field">
<label>Start date</label>
<input type="date" name="startDate" />
</div>
<div class="field">
<label>Review / decision date</label>
<input type="date" name="endDate" />
</div>
<div class="field">
<label>Tech stack <span class="hint">planned or current</span></label>
<input name="techStack" placeholder="e.g. Next.js, n8n, Supabase" autocomplete="off" />
</div>
<div class="field">
<label>Budget / time cap</label>
<input name="budgetCap" placeholder="e.g. $300, 40 hours" autocomplete="off" />
</div>
<div class="field">
<label>Team <span class="hint">comma-separated</span></label>
<input name="teamSuggestions" placeholder="e.g. Denis, Amina" autocomplete="off" />
</div>
<div class="field">
<label>Internal owner <span class="req">required</span></label>
<input name="owner" placeholder="Who owns this internally" autocomplete="off" />
</div>
<div class="field full">
<label>Relevant links <span class="hint">comma-separated</span></label>
<input name="links" placeholder="brief, figma, repo, landing page…" autocomplete="off" />
</div>
</div>
</div>
<!-- MVP PRE-ONBOARDING -->
<div class="role-fields" data-fields="mvp">
<div class="section-label">Venture & Contact</div>
<div class="grid">
<div class="field">
<label>Contact email <span class="req">required</span></label>
<input type="email" name="contactEmail" placeholder="name@company.com" autocomplete="off" />
</div>
<div class="field">
<label>Contact phone <span class="hint">optional</span></label>
<input name="contactPhone" placeholder="+254…" autocomplete="off" />
</div>
<div class="field">
<label>Product name <span class="req">required</span></label>
<input name="productName" placeholder="Working name of the MVP" autocomplete="off" />
</div>
<div class="field">
<label>Current stage</label>
<select name="stage"><option value="">Select…</option><option>Idea only</option><option>Validated problem</option><option>Designs / prototype</option><option>Partial build</option></select>
</div>
</div>
<div class="section-label">Problem & Scope</div>
<div class="grid">
<div class="field full">
<label>Problem statement <span class="req">required</span></label>
<textarea name="problem" placeholder="What problem does this MVP solve, and for whom?"></textarea>
</div>
<div class="field full">
<label>Target users</label>
<input name="targetUsers" placeholder="Who will use the first version" autocomplete="off" />
</div>
<div class="field full">
<label>Must-have features <span class="hint">the smallest useful version</span></label>
<textarea name="mustHaves" placeholder="The features the MVP cannot launch without."></textarea>
</div>
<div class="field full">
<label>Validation so far <span class="hint">optional</span></label>
<textarea name="validation" placeholder="Interviews, waitlist, pilots, pre-sales…"></textarea>
</div>
<div class="field">
<label>Budget range</label>
<select name="budgetRange">
<option value="">Select…</option>
<option>&lt; $1k</option><option>$1k–5k</option><option>$5k–20k</option><option>$20k+</option><option>Not sure yet</option>
</select>
</div>
<div class="field">
<label>Target launch date</label>
<input type="date" name="endDate" />
</div>
<div class="field">
<label>Internal owner <span class="req">required</span></label>
<input name="owner" placeholder="Who owns this internally" autocomplete="off" />
</div>
<div class="field">
<label>Relevant links <span class="hint">comma-separated</span></label>
<input name="links" placeholder="pitch deck, figma, site…" autocomplete="off" />
</div>
</div>
</div>
<!-- governance: privacy + consent --> <!-- governance: privacy + consent -->
<div class="governance"> <div class="governance">
<h3><svg viewBox="0 0 24 24"><path d="M12 2l8 4v6c0 5-3.5 8-8 10-4.5-2-8-5-8-10V6l8-4z"/></svg> How your information is used</h3> <h3><svg viewBox="0 0 24 24"><path d="M12 2l8 4v6c0 5-3.5 8-8 10-4.5-2-8-5-8-10V6l8-4z"/></svg> How your information is used</h3>
@@ -585,14 +720,23 @@ const BASE = "https://autonomy.digitalchances.co.ke/webhook";
const ENDPOINTS = { const ENDPOINTS = {
client: BASE + "/client-onboarding", client: BASE + "/client-onboarding",
intern: BASE + "/intern-onboarding", intern: BASE + "/intern-onboarding",
contractor: BASE + "/contractor-onboarding" contractor: BASE + "/contractor-onboarding",
experiment: BASE + "/experiment-onboarding",
mvp: BASE + "/mvp-onboarding"
};
// Discord channels the n8n workflows open threads in; sent with each submission.
const DISCORD_CHANNELS = {
experiment: "1511998970216448031", // #experiment-products
mvp: "1534550504401670194" // #mvps-pre-onboarding
}; };
const SHARED_SECRET = ""; // set to match the workflows' SECRET const SHARED_SECRET = ""; // set to match the workflows' SECRET
const ROLE_META = { const ROLE_META = {
client: { tag:"Client", title:"Client engagement", required:["name","contactEmail","projectName","projectGoal","owner"], nameLabel:"Company name" }, client: { tag:"Client", title:"Client engagement", required:["name","contactEmail","projectName","projectGoal","owner"], nameLabel:"Company name" },
intern: { tag:"Intern", title:"Intern placement", required:["name","focusArea","mentor","owner"], nameLabel:"Full name" }, intern: { tag:"Intern", title:"Intern placement", required:["name","focusArea","mentor","owner"], nameLabel:"Full name" },
contractor: { tag:"Contractor", title:"Contractor engagement", required:["name","specialty","scope","owner"], nameLabel:"Full name" } contractor: { tag:"Contractor", title:"Contractor engagement", required:["name","specialty","scope","owner"], nameLabel:"Full name" },
experiment: { tag:"Experiment", title:"Experiment product", required:["name","productName","hypothesis","owner"], nameLabel:"Lead / team name" },
mvp: { tag:"MVP", title:"MVP pre-onboarding", required:["name","contactEmail","productName","problem","owner"], nameLabel:"Founder / company name" }
}; };
const INTERN_HANDBOOK = `# Digital Chances - Intern Handbook const INTERN_HANDBOOK = `# Digital Chances - Intern Handbook
@@ -670,6 +814,7 @@ function gotoStep(n){
function collect(){ function collect(){
const data = { type: currentRole, submittedAt: new Date().toISOString() }; const data = { type: currentRole, submittedAt: new Date().toISOString() };
if(DISCORD_CHANNELS[currentRole]) data.discordChannelId = DISCORD_CHANNELS[currentRole];
form.querySelectorAll("[data-shared]").forEach(i=>{ if(i.name) data[i.name]=i.value.trim(); }); form.querySelectorAll("[data-shared]").forEach(i=>{ if(i.name) data[i.name]=i.value.trim(); });
const active = document.querySelector(`.role-fields[data-fields="${currentRole}"]`); const active = document.querySelector(`.role-fields[data-fields="${currentRole}"]`);
active.querySelectorAll("input,select,textarea").forEach(i=>{ if(i.name) data[i.name]=i.value.trim(); }); active.querySelectorAll("input,select,textarea").forEach(i=>{ if(i.name) data[i.name]=i.value.trim(); });
@@ -706,7 +851,7 @@ form.addEventListener("submit",(e)=>{
function buildReview(data){ function buildReview(data){
const rows = el("reviewRows"); rows.innerHTML = ""; const rows = el("reviewRows"); rows.innerHTML = "";
const skip = new Set(["type","submittedAt"]); const skip = new Set(["type","submittedAt","discordChannelId"]);
Object.keys(data).forEach(k=>{ Object.keys(data).forEach(k=>{
if(skip.has(k) || !data[k]) return; if(skip.has(k) || !data[k]) return;
const row = document.createElement("div"); row.className="rev-row"; const row = document.createElement("div"); row.className="rev-row";
@@ -750,11 +895,14 @@ el("submitBtn").addEventListener("click", async ()=>{
function showConfirm(ref){ function showConfirm(ref){
el("refNumber").textContent = ref; el("refNumber").textContent = ref;
const m = ROLE_META[currentRole]; const m = ROLE_META[currentRole];
el("confirmTitle").textContent = `${m.tag} engagement started`; el("confirmTitle").textContent = { experiment:"Experiment registered", mvp:"MVP pre-onboarding received" }[currentRole]
|| `${m.tag} engagement started`;
const steps = { const steps = {
client: ["An internal owner has been assigned to your engagement.","A project space is being set up.","Expect a first response within 1–2 business days."], client: ["An internal owner has been assigned to your engagement.","A project space is being set up.","Expect a first response within 1–2 business days."],
intern: ["Your placement record has been created.","Your mentor will be notified.","Download your documents below and read them before day one."], intern: ["Your placement record has been created.","Your mentor will be notified.","Download your documents below and read them before day one."],
contractor: ["Your engagement record has been created.","Contract and deliverables are being reviewed.","Expect next steps from your point of contact shortly."] contractor: ["Your engagement record has been created.","Contract and deliverables are being reviewed.","Expect next steps from your point of contact shortly."],
experiment: ["Your experiment record has been created.","A thread is being opened in #experiment-products.","Post progress there and judge it against your kill criteria on the decision date."],
mvp: ["Your MVP record has been created.","A thread is being opened in #mvps-pre-onboarding.","An owner will review scope and reach out to schedule a kickoff."]
}[currentRole]; }[currentRole];
el("confirmMsg").textContent = "Your submission has been received and routed to the right owner at Digital Chances."; el("confirmMsg").textContent = "Your submission has been received and routed to the right owner at Digital Chances.";
el("nextSteps").innerHTML = steps.map(s=>`<li>${s}</li>`).join(""); el("nextSteps").innerHTML = steps.map(s=>`<li>${s}</li>`).join("");