362 lines
20 KiB
HTML
362 lines
20 KiB
HTML
<!doctype html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<title>T00-DRIP Launch Tracker</title>
|
||
<style>
|
||
:root {
|
||
--brown: #4b2e20;
|
||
--brown-2: #2c1a12;
|
||
--cream: #f7efe6;
|
||
--gold: #d4af37;
|
||
--pink: #e7b4b8;
|
||
--green: #2f6b4f;
|
||
--red: #9b2c2c;
|
||
--ink: #251d19;
|
||
--muted: #75665e;
|
||
--line: #ddcec2;
|
||
--white: #fffdf9;
|
||
}
|
||
|
||
* { box-sizing: border-box; }
|
||
body {
|
||
margin: 0;
|
||
color: var(--ink);
|
||
background: var(--cream);
|
||
font: 15px/1.45 system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||
}
|
||
header {
|
||
padding: 28px 20px 24px;
|
||
color: white;
|
||
background: linear-gradient(135deg, var(--brown-2), var(--brown));
|
||
border-bottom: 5px solid var(--gold);
|
||
}
|
||
header .wrap, main { max-width: 1180px; margin: auto; }
|
||
h1 { margin: 0 0 4px; font-family: Georgia, serif; font-size: clamp(26px, 4vw, 42px); }
|
||
header p { margin: 4px 0 0; color: #f5ddd7; }
|
||
main { padding: 20px; }
|
||
.summary {
|
||
display: grid;
|
||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||
gap: 12px;
|
||
margin-bottom: 16px;
|
||
}
|
||
.card, .gate, .notice {
|
||
background: var(--white);
|
||
border: 1px solid var(--line);
|
||
border-radius: 10px;
|
||
box-shadow: 0 3px 10px rgb(44 26 18 / 6%);
|
||
}
|
||
.card { padding: 14px; }
|
||
.card strong { display: block; font-size: 26px; color: var(--brown); }
|
||
.card span { color: var(--muted); font-size: 12px; text-transform: uppercase; letter-spacing: .08em; }
|
||
.notice { padding: 14px 16px; margin-bottom: 16px; border-left: 5px solid var(--gold); }
|
||
.notice strong { color: var(--brown); }
|
||
.progress { height: 12px; overflow: hidden; background: #e9ded5; border-radius: 999px; margin: 10px 0 18px; }
|
||
.progress > div { height: 100%; width: 0; background: linear-gradient(90deg, var(--pink), var(--gold)); transition: width .2s; }
|
||
.toolbar { display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 16px; }
|
||
button, select {
|
||
min-height: 38px;
|
||
padding: 8px 12px;
|
||
border: 1px solid var(--brown);
|
||
border-radius: 7px;
|
||
background: var(--white);
|
||
color: var(--brown);
|
||
font: inherit;
|
||
font-weight: 650;
|
||
}
|
||
button { cursor: pointer; }
|
||
button.primary { background: var(--brown); color: white; }
|
||
button:hover { filter: brightness(.96); }
|
||
.gate { margin: 0 0 14px; overflow: hidden; }
|
||
.gate-head {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
gap: 12px;
|
||
align-items: center;
|
||
padding: 14px 16px;
|
||
color: white;
|
||
background: var(--brown);
|
||
}
|
||
.gate-head h2 { margin: 0; font-family: Georgia, serif; font-size: 20px; }
|
||
.gate-head span { font-size: 12px; color: #f3ddd4; }
|
||
.tasks { width: 100%; border-collapse: collapse; }
|
||
th, td { padding: 11px 10px; border-bottom: 1px solid #eadfd7; text-align: left; vertical-align: top; }
|
||
th { color: var(--muted); background: #fbf6f0; font-size: 11px; text-transform: uppercase; letter-spacing: .07em; }
|
||
tr:last-child td { border-bottom: 0; }
|
||
tr.done .task-title { text-decoration: line-through; color: var(--muted); }
|
||
tr.blocked { background: #fff7f4; }
|
||
.check { width: 42px; text-align: center; }
|
||
input[type="checkbox"] { width: 20px; height: 20px; accent-color: var(--green); cursor: pointer; }
|
||
.task-title { display: block; font-weight: 700; }
|
||
.task-note { display: block; margin-top: 3px; color: var(--muted); font-size: 13px; }
|
||
.owner { white-space: nowrap; font-weight: 650; }
|
||
.status {
|
||
display: inline-block;
|
||
padding: 3px 8px;
|
||
border-radius: 999px;
|
||
font-size: 11px;
|
||
font-weight: 750;
|
||
white-space: nowrap;
|
||
}
|
||
.status.waiting { color: #6d4e00; background: #fff0bd; }
|
||
.status.next { color: #154b36; background: #dff4e9; }
|
||
.status.blocked { color: #7b1f1f; background: #f8dada; }
|
||
.status.later { color: #555; background: #ececec; }
|
||
.status.complete { color: #154b36; background: #dff4e9; }
|
||
.evidence { color: var(--muted); font-size: 12px; }
|
||
.hidden { display: none; }
|
||
footer { padding: 12px 20px 28px; color: var(--muted); text-align: center; font-size: 12px; }
|
||
|
||
@media (max-width: 800px) {
|
||
.summary { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||
.tasks, .tasks tbody, .tasks tr, .tasks td { display: block; width: 100%; }
|
||
.tasks thead { display: none; }
|
||
.tasks tr { position: relative; padding: 10px 12px 10px 50px; border-bottom: 1px solid var(--line); }
|
||
.tasks td { padding: 4px 0; border: 0; }
|
||
.tasks td.check { position: absolute; left: 14px; top: 12px; width: 24px; }
|
||
}
|
||
|
||
@media print {
|
||
body { background: white; }
|
||
header { color: black; background: white; border-bottom: 2px solid black; }
|
||
header p { color: #333; }
|
||
.toolbar { display: none; }
|
||
.card, .gate, .notice { box-shadow: none; break-inside: avoid; }
|
||
.gate-head { color: black; background: #eee; }
|
||
.gate-head span { color: #333; }
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<header>
|
||
<div class="wrap">
|
||
<h1>T00-DRIP Launch Tracker</h1>
|
||
<p>The Daily Pour · current gate: blueprint review and approval</p>
|
||
</div>
|
||
</header>
|
||
|
||
<main>
|
||
<section class="summary" aria-label="Progress summary">
|
||
<div class="card"><strong id="percent">0%</strong><span>Complete</span></div>
|
||
<div class="card"><strong id="done-count">0</strong><span>Completed</span></div>
|
||
<div class="card"><strong id="open-count">0</strong><span>Open</span></div>
|
||
<div class="card"><strong id="blocked-count">0</strong><span>Blocked / waiting</span></div>
|
||
</section>
|
||
|
||
<div class="progress" aria-label="Overall progress"><div id="progress-bar"></div></div>
|
||
|
||
<section class="notice">
|
||
<strong>Next move:</strong> receive the $211 Fiverr blueprint package, verify it against the current T00 requirements, and return one consolidated correction list before submission.
|
||
</section>
|
||
|
||
<div class="toolbar">
|
||
<select id="filter" aria-label="Filter tasks">
|
||
<option value="all">All tasks</option>
|
||
<option value="open">Open only</option>
|
||
<option value="blocked">Blocked / waiting</option>
|
||
<option value="complete">Complete</option>
|
||
</select>
|
||
<button class="primary" type="button" id="save">Save</button>
|
||
<button type="button" id="export">Export backup</button>
|
||
<button type="button" id="import">Import backup</button>
|
||
<button type="button" onclick="window.print()">Print / PDF</button>
|
||
<input class="hidden" id="import-file" type="file" accept="application/json">
|
||
</div>
|
||
|
||
<div id="gates"></div>
|
||
</main>
|
||
|
||
<footer>
|
||
Repository-backed working tracker. Browser state is stored locally; export a backup before changing computers.
|
||
</footer>
|
||
|
||
<script>
|
||
const storageKey = "daily-pour-t00-launch-tracker-v1";
|
||
|
||
const gates = [
|
||
{
|
||
title: "1. Company foundation",
|
||
purpose: "The entity and controlling agreement exist.",
|
||
tasks: [
|
||
["entity-formed", "SnS Hospitality Group LLC formed", "Sam", "complete", "Owner-confirmed and recorded in repository."],
|
||
["ein", "Federal EIN obtained", "Sam", "complete", "Store the official letter with legal originals."],
|
||
["oa", "Hospitality Operating Agreement executed", "Sam + Kiowa", "complete", "Authoritative signed copy is in Documenso."],
|
||
["dba", "File The Daily Pour assumed business name", "Sam", "next", "Confirm filing office and retain the accepted record."],
|
||
["bank", "Confirm operating-company bank account and controls", "Sam", "next", "Hospitality account only; Holdings remains asset-only and debt-free."]
|
||
]
|
||
},
|
||
{
|
||
title: "2. Blueprint package",
|
||
purpose: "Obtain a complete, internally consistent design before submission.",
|
||
tasks: [
|
||
["blueprint-receive", "Receive Fiverr blueprint package", "Designer", "waiting", "Paid $211. Obtain PDF plus editable/source files promised in the scope."],
|
||
["blueprint-shell", "Verify actual trailer shell and openings", "Sam", "blocked", "8.5×26 ft, 7 ft 6 in interior, ramp, V-nose, wheel wells, employee door and serving window."],
|
||
["blueprint-workflow", "Verify two-person production workflow", "Sam", "blocked", "Order/POS → production → finishing → drive-through handoff; preserve aisle and door clearances."],
|
||
["blueprint-equipment", "Reconcile every equipment item and quantity", "Sam", "blocked", "Use current requirements, not the older one-machine/propane design."],
|
||
["blueprint-plumbing", "Verify potable, gray, black and sink design", "Designer", "blocked", "Three-compartment sink, separate hand sink, restroom, accessible tanks, filtration and electric water heat."],
|
||
["blueprint-electric", "Verify electrical concept uses final load calculation", "Electrician / Designer", "blocked", "Do not approve panel, shore power, generator, inverter or battery sizing from guesses."],
|
||
["blueprint-weight", "Create loaded weight and balance schedule", "Designer / Fabricator", "blocked", "Include tanks, equipment, finishes, inventory, batteries, generator, axle and tongue loading."],
|
||
["blueprint-winter", "Verify South Bend winterization", "Designer / Plumber", "blocked", "Protect tanks, pumps, drains and vulnerable lines from freezing."],
|
||
["blueprint-fire", "Resolve ventilation and fire-protection notes", "Designer / Fire authority", "blocked", "Microwave and air fryer are reheating-only; no generic Type I hood assumption."],
|
||
["blueprint-corrections", "Send one consolidated correction list", "Sam", "blocked", "Avoid scattered messages and version confusion."],
|
||
["blueprint-freeze", "Approve submission version and archive superseded drafts", "Sam", "blocked", "One file must be clearly marked current."]
|
||
]
|
||
},
|
||
{
|
||
title: "3. Agency and professional approval",
|
||
purpose: "Secure written acceptance before irreversible construction.",
|
||
tasks: [
|
||
["health-contact", "Confirm plan-review process with St. Joseph County", "Kiowa", "next", "Request the current mobile-food plan-review packet and submission requirements."],
|
||
["commissary", "Obtain written commissary determination", "Kiowa", "next", "Confirm whether a self-contained unit still requires a licensed base or agreement."],
|
||
["sink-rule", "Confirm warewashing requirement", "Kiowa", "next", "Current design uses a three-compartment sink unless written approval permits otherwise."],
|
||
["food-cert-rule", "Confirm required food-protection credentials", "Kiowa", "next", "Determine certified-manager coverage and any staff food-handler requirements."],
|
||
["zoning", "Confirm location, zoning and mobile-vendor requirements", "Kiowa", "next", "Requirements may depend on the final operating parcel."],
|
||
["fire-review", "Confirm fire review requirements", "Kiowa", "later", "Use the final appliance, generator, battery and electrical design."],
|
||
["submit-plan", "Submit complete blueprint and equipment package", "Kiowa", "blocked", "Submit only after internal correction and professional calculations."],
|
||
["respond-comments", "Track and close every reviewer comment", "Kiowa + Designer", "blocked", "Record response, owner, date and revised sheet for each comment."],
|
||
["approval", "Receive written plan approval", "Kiowa", "blocked", "Approval evidence is the gate to irreversible buildout."]
|
||
]
|
||
},
|
||
{
|
||
title: "4. Site and unit economics",
|
||
purpose: "Prove the location and current design can support the business.",
|
||
tasks: [
|
||
["site-shortlist", "Create data-backed site shortlist", "Kiowa", "next", "Traffic, access, queue geometry, visibility, utilities, rent, competition and operating restrictions."],
|
||
["site-layout", "Validate drive-through circulation", "Kiowa", "blocked", "Confirm safe entry, queue, service-window alignment and exit."],
|
||
["site-utilities", "Document available site utilities", "Kiowa", "blocked", "Power, potable water, sewer/waste, cellular coverage, snow removal and overnight security."],
|
||
["site-economics", "Calculate break-even for the actual site", "Sam", "blocked", "Use actual rent, labor, utilities, debt, COGS and operating hours."],
|
||
["lease-review", "Review and execute lot agreement", "Sam + Attorney", "blocked", "Do not sign until design, use, utilities, signage, access and termination rights align."],
|
||
["backup-site", "Keep one qualified backup location", "Kiowa", "later", "Mobility only reduces risk when another viable site exists."]
|
||
]
|
||
},
|
||
{
|
||
title: "5. Funding and build readiness",
|
||
purpose: "Know the true cost and funding source before committing capital.",
|
||
tasks: [
|
||
["vendor-quotes", "Collect current trailer, buildout and equipment quotes", "Sam", "blocked", "Quotes must match the approved design and include tax, delivery and installation."],
|
||
["uses-funds", "Create final uses-of-funds schedule", "Sam", "blocked", "Include design, permits, contingency and opening working capital."],
|
||
["projection", "Update 24-month model from actual quotes", "Sam", "blocked", "Use ramped demand and sensitivity cases; do not assume 15% COGS without evidence."],
|
||
["funding-choice", "Select and approve operating-company funding", "Sam", "blocked", "No debt, guarantee or credit belongs in Holdings."],
|
||
["insurance-quotes", "Bind required insurance", "Kiowa + Sam", "blocked", "Match trailer, food service, employees, towing and location requirements."],
|
||
["build-contract", "Approve build contract and change-control terms", "Sam", "blocked", "Scope must reference the approved drawings and named equipment schedule."],
|
||
["purchase", "Authorize trailer and build purchases", "Sam", "blocked", "Only after approval, funding and contract gates are satisfied."]
|
||
]
|
||
},
|
||
{
|
||
title: "6. People and operating readiness",
|
||
purpose: "Open with qualified leadership and minimum viable controls.",
|
||
tasks: [
|
||
["manager-profile", "Finalize Location Manager role and scorecard", "Kiowa", "next", "Hire an operator who owns the unit; do not design a permanent shift for Sam."],
|
||
["cert-person", "Secure required certified food-protection coverage", "Kiowa", "next", "Credential requirement must match written county guidance."],
|
||
["barista-skill", "Recruit or train espresso-capable opening staff", "Kiowa", "next", "Use the recipe/spec books as the training standard."],
|
||
["employer-setup", "Complete payroll, withholding, unemployment and workers' compensation setup", "Sam + CPA", "blocked", "Complete before the first employee starts work."],
|
||
["handbook", "Complete and legally review the employee handbook", "Kiowa + Attorney", "next", "Prioritize food safety, health/safety, attendance, cash, discipline and acknowledgment."],
|
||
["sops", "Complete minimum opening SOP set", "Kiowa", "next", "Open/close, cleaning, temperature logs, recipes, inventory, POS/cash, incidents and equipment failure."],
|
||
["training", "Run opening-team training and readiness check", "Location Manager", "blocked", "Demonstrate workflow, sanitation, service, cash and emergency procedures."],
|
||
["permit-inspect", "Pass final inspections and receive operating permits", "Kiowa", "blocked", "Retain written approvals and renewal dates."],
|
||
["soft-open", "Run controlled soft opening", "Location Manager", "blocked", "Measure throughput, ticket, waste, labor and failure points before full launch."],
|
||
["launch", "Open T00-DRIP", "Location Manager", "blocked", "Sam monitors results; he does not become the daily operator."]
|
||
]
|
||
}
|
||
];
|
||
|
||
let state = JSON.parse(localStorage.getItem(storageKey) || "{}");
|
||
|
||
function currentStatus(task) {
|
||
return state[task[0]]?.status || task[3];
|
||
}
|
||
|
||
function render() {
|
||
const host = document.getElementById("gates");
|
||
const filter = document.getElementById("filter").value;
|
||
host.innerHTML = "";
|
||
|
||
for (const gate of gates) {
|
||
const section = document.createElement("section");
|
||
section.className = "gate";
|
||
section.innerHTML = `
|
||
<div class="gate-head"><div><h2>${gate.title}</h2><span>${gate.purpose}</span></div></div>
|
||
<table class="tasks"><thead><tr><th class="check">Done</th><th>Task</th><th>Owner</th><th>Status</th></tr></thead><tbody></tbody></table>`;
|
||
const tbody = section.querySelector("tbody");
|
||
let visible = 0;
|
||
|
||
for (const task of gate.tasks) {
|
||
const status = currentStatus(task);
|
||
const done = status === "complete";
|
||
const blocked = status === "blocked" || status === "waiting";
|
||
if ((filter === "open" && done) || (filter === "blocked" && !blocked) || (filter === "complete" && !done)) continue;
|
||
visible++;
|
||
const tr = document.createElement("tr");
|
||
tr.className = `${done ? "done" : ""} ${blocked ? "blocked" : ""}`;
|
||
tr.innerHTML = `
|
||
<td class="check"><input type="checkbox" data-id="${task[0]}" ${done ? "checked" : ""} aria-label="Complete ${task[1]}"></td>
|
||
<td><span class="task-title">${task[1]}</span><span class="task-note">${task[4]}</span></td>
|
||
<td class="owner">${task[2]}</td>
|
||
<td><span class="status ${status}">${status.replace("blocked", "blocked").replace("next", "next")}</span></td>`;
|
||
tbody.appendChild(tr);
|
||
}
|
||
if (visible) host.appendChild(section);
|
||
}
|
||
|
||
document.querySelectorAll('input[type="checkbox"][data-id]').forEach(box => {
|
||
box.addEventListener("change", event => {
|
||
const id = event.target.dataset.id;
|
||
const original = gates.flatMap(g => g.tasks).find(t => t[0] === id);
|
||
state[id] = { status: event.target.checked ? "complete" : original[3], updated: new Date().toISOString() };
|
||
save(false);
|
||
render();
|
||
});
|
||
});
|
||
updateSummary();
|
||
}
|
||
|
||
function updateSummary() {
|
||
const tasks = gates.flatMap(g => g.tasks);
|
||
const done = tasks.filter(t => currentStatus(t) === "complete").length;
|
||
const blocked = tasks.filter(t => ["blocked", "waiting"].includes(currentStatus(t))).length;
|
||
const percent = Math.round((done / tasks.length) * 100);
|
||
document.getElementById("percent").textContent = `${percent}%`;
|
||
document.getElementById("done-count").textContent = done;
|
||
document.getElementById("open-count").textContent = tasks.length - done;
|
||
document.getElementById("blocked-count").textContent = blocked;
|
||
document.getElementById("progress-bar").style.width = `${percent}%`;
|
||
}
|
||
|
||
function save(notify = true) {
|
||
localStorage.setItem(storageKey, JSON.stringify(state));
|
||
if (notify) alert("Progress saved in this browser.");
|
||
}
|
||
|
||
document.getElementById("filter").addEventListener("change", render);
|
||
document.getElementById("save").addEventListener("click", () => save(true));
|
||
document.getElementById("export").addEventListener("click", () => {
|
||
const blob = new Blob([JSON.stringify({ version: 1, exported: new Date().toISOString(), state }, null, 2)], { type: "application/json" });
|
||
const link = document.createElement("a");
|
||
link.href = URL.createObjectURL(blob);
|
||
link.download = `t00-launch-progress-${new Date().toISOString().slice(0, 10)}.json`;
|
||
link.click();
|
||
URL.revokeObjectURL(link.href);
|
||
});
|
||
document.getElementById("import").addEventListener("click", () => document.getElementById("import-file").click());
|
||
document.getElementById("import-file").addEventListener("change", async event => {
|
||
const file = event.target.files[0];
|
||
if (!file) return;
|
||
try {
|
||
const parsed = JSON.parse(await file.text());
|
||
if (!parsed.state || typeof parsed.state !== "object") throw new Error("Invalid tracker backup");
|
||
state = parsed.state;
|
||
save(false);
|
||
render();
|
||
} catch (error) {
|
||
alert(`Import failed: ${error.message}`);
|
||
}
|
||
event.target.value = "";
|
||
});
|
||
|
||
render();
|
||
</script>
|
||
</body>
|
||
</html>
|