sns-network-solutions/businesses/sns-hospitality-group/coffee-trailers/T-00/docs/daily-pour-launch-tracker.html
2026-08-27 06:36:14 -04:00

362 lines
20 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!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>