Created
May 22, 2025 16:29
-
-
Save Lenniott/dab1913c31078ca07fbcdd2b9e6b7a34 to your computer and use it in GitHub Desktop.
Untitled
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<head> | |
<meta charset="UTF-8" /> | |
<meta name="viewport" content="width=device-width, initial-scale=1" /> | |
<title>Feedback Loop Simulator v18</title> | |
<script src="https://cdn.tailwindcss.com"></script> | |
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script> | |
<link | |
rel="stylesheet" | |
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css" | |
/> | |
</head> | |
<body class="bg-gray-100 min-h-screen flex items-center justify-center p-4"> | |
<div class="w-full max-w-3xl bg-white shadow-lg rounded-lg p-6 space-y-6"> | |
<h1 class="text-2xl font-bold text-center">Feedback Loop Simulator v18</h1> | |
<div id="scenario" class="text-base text-gray-700"></div> | |
<div id="mainArea" class="flex flex-col items-center"></div> | |
<div class="overflow-x-auto"> | |
<table | |
id="resultsTable" | |
class="w-full text-center border-separate border-spacing-y-2" | |
> | |
<thead> | |
<tr class="text-gray-500 text-sm"> | |
<th class="px-2">Week</th> | |
<th class="px-2">Budget</th> | |
<th class="px-2">Goal</th> | |
</tr> | |
</thead> | |
<tbody></tbody> | |
</table> | |
</div> | |
<div class="bg-white p-4 rounded border"> | |
<canvas id="barChart"></canvas> | |
</div> | |
</div> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const config = { | |
budget: 10000, | |
initialGoal: 20, | |
totalWeeks: 10, | |
costPerWeek: 1005, | |
costIterate: 200, | |
costPivot: 300, | |
costFeedback: 50, | |
scenario: | |
"You lead a critical project with limited funds and a tight schedule.", | |
situations: { | |
actions: { | |
start: "you kicked off the project after receiving initial criteria.", | |
continue: "you continued with your approach.", | |
iterate: "you implemented new ideas to improve your approach.", | |
pivot: "you decided to pivot to a new strategy." | |
}, | |
feedback: { | |
positive: "Feedback was positive.", | |
neutral: "Feedback was neutral.", | |
negative: "Feedback was negative." | |
} | |
} | |
}; | |
let hasFeedback = false | |
let budget, week, goal, approachValue, ended; | |
const data = []; | |
const feedbackUsedByWeek = {}; | |
let chart; | |
function init() { | |
budget = config.budget; | |
week = 0; | |
goal = config.initialGoal; | |
approachValue = getApproach(); | |
ended = false; | |
data.length = 0; | |
for (const k in feedbackUsedByWeek) delete feedbackUsedByWeek[k]; | |
renderScenario(); | |
initChart(); | |
} | |
function renderScenario() { | |
const el = document.getElementById("scenario"); | |
el.innerHTML = `<p>${config.scenario}</p>`; | |
const btn = document.createElement("button"); | |
btn.textContent = "Kick Off Project"; | |
btn.className = | |
"mt-4 bg-blue-600 hover:bg-blue-800 text-white py-2 px-4 rounded"; | |
btn.onclick = () => weekAction("start", false); | |
el.appendChild(btn); | |
document.getElementById("mainArea").innerHTML = ""; | |
} | |
function initChart() { | |
const ctx = document.getElementById("barChart").getContext("2d"); | |
chart = new Chart(ctx, { | |
type: "bar", | |
data: { | |
labels: [], | |
datasets: [ | |
{ label: "Goal", data: [], backgroundColor: "#ef4444" }, | |
{ label: "Budget %", data: [], backgroundColor: "#3b82f6" } | |
] | |
}, | |
options: { scales: { y: { beginAtZero: true, max: 100 } } } | |
}); | |
} | |
function weekAction(action, feedback) { | |
if (ended) return; | |
week++; | |
budget -= config.costPerWeek; | |
if (action === "iterate") budget -= config.costIterate; | |
if (action === "pivot") budget -= config.costPivot; | |
if (feedback) budget -= config.costFeedback; | |
if (action === "pivot") approachValue = getApproach(); | |
const iterVal = action === "iterate" ? rand(-1, 3) : 0; | |
goal += approachValue + iterVal; | |
if (feedback && week > 1) feedbackUsedByWeek[week - 1] = true; | |
data.push({ week, budget, goal }); | |
renderWeek(action, feedback); | |
renderTable(); | |
if (week >= config.totalWeeks || budget <= 0) end(); | |
hasFeedback = false; | |
} | |
function renderWeek(action, feedback) { | |
const area = document.getElementById("mainArea"); | |
const prev = data[data.length - 1]; | |
area.innerHTML = ""; | |
const txt = document.createElement("p"); | |
let msg = `Week ${prev.week} roundup: ${config.situations.actions[action]}`; | |
if (feedback) msg += ` ${config.situations.feedback.negative}`; | |
txt.textContent = msg + " What would you like to do?"; | |
area.appendChild(txt); | |
const wrap = document.createElement("div"); | |
wrap.className = "flex gap-2 mt-4"; | |
["continue", "iterate", "pivot"].forEach((a) => { | |
const b = document.createElement("button"); | |
b.textContent = a.charAt(0).toUpperCase() + a.slice(1); | |
b.className = "bg-gray-700 hover:bg-gray-900 text-white py-1 px-3 rounded"; | |
b.onclick = () => weekAction(a, hasFeedback); | |
wrap.appendChild(b); | |
}); | |
if (week > 1) { | |
const fb = document.createElement("button"); | |
fb.textContent = "Feedback"; | |
fb.className = | |
`bg-slate-700 hover:bg-slate-900 text-white py-1 px-3 rounded`; | |
fb.onclick = () => { | |
hasFeedback = !hasFeedback | |
if(hasFeedback){ | |
fb.classList.remove("hover:bg-slate-900") | |
fb.classList.remove("bg-slate-700") | |
fb.classList.add("hover:bg-green-900") | |
fb.classList.add("bg-green-700") | |
}else{ | |
fb.classList.add("hover:bg-slate-900") | |
fb.classList.add("bg-slate-700") | |
fb.classList.remove("hover:bg-green-900") | |
fb.classList.remove("bg-green-700") | |
} | |
}; | |
wrap.appendChild(fb); | |
} | |
if (week >= 3) { | |
const r = document.createElement("button"); | |
r.textContent = "Release"; | |
r.className = "bg-red-600 hover:bg-red-800 text-white py-1 px-3 rounded"; | |
r.onclick = end; | |
wrap.appendChild(r); | |
} | |
area.appendChild(wrap); | |
} | |
function renderTable() { | |
const tb = document.querySelector("#resultsTable tbody"); | |
tb.innerHTML = ""; | |
chart.data.labels = []; | |
chart.data.datasets.forEach((ds) => (ds.data = [])); | |
data.forEach((d) => { | |
chart.data.labels.push("W" + d.week); | |
const show = ended || feedbackUsedByWeek[d.week] === true; | |
chart.data.datasets[0].data.push(show ? d.goal : null); | |
chart.data.datasets[1].data.push( | |
Math.round((d.budget / config.budget) * 100) | |
); | |
const tr = document.createElement("tr"); | |
tr.className = "text-lg"; | |
tr.innerHTML = `<td class='px-2'>${d.week}</td><td class='px-2'>$${ | |
d.budget | |
}</td><td class='px-2'>${show ? d.goal : "—"}</td>`; | |
tb.appendChild(tr); | |
}); | |
chart.update(); | |
} | |
function end() { | |
ended = true; | |
renderTable(); | |
document.getElementById("mainArea").innerHTML = ""; | |
} | |
function getApproach() { | |
return rand(0, 2) ? rand(1, 3) : rand(-3, -1); | |
} | |
function rand(a, b) { | |
return Math.floor(Math.random() * (b - a + 1)) + a; | |
} | |
window.onload = init; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment