Skip to content

Instantly share code, notes, and snippets.

@Lenniott
Created May 22, 2025 16:29
Show Gist options
  • Save Lenniott/dab1913c31078ca07fbcdd2b9e6b7a34 to your computer and use it in GitHub Desktop.
Save Lenniott/dab1913c31078ca07fbcdd2b9e6b7a34 to your computer and use it in GitHub Desktop.
Untitled
<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>
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