<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Bulldog Command Center</title>
<style>
:root{ --ink:#1B2559; --bg:#F5F6F8; --line:#E5E8EF; --muted:#64748B; --accent:#2563EB; --card:#FFFFFF; }
*{box-sizing:border-box}
html,body{margin:0;padding:0;background:var(--bg);color:var(--ink);font-family:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;}
.wrap{max-width:1120px;margin:0 auto;padding:24px 16px 48px;}
#login{min-height:100vh;display:flex;align-items:center;justify-content:center;padding:24px;}
.loginCard{background:var(--card);border:1px solid var(--line);border-radius:18px;padding:28px;max-width:380px;width:100%;box-shadow:0 1px 3px rgba(0,0,0,.04);}
.logo{width:44px;height:44px;border-radius:12px;background:var(--accent);color:#fff;font-weight:800;font-size:20px;display:flex;align-items:center;justify-content:center;}
h1{font-size:22px;font-weight:700;letter-spacing:-.01em;margin:14px 0 4px;}
.sub{color:var(--muted);font-size:13px;margin:0 0 18px;}
input[type=email]{width:100%;border:1px solid #CBD5E1;border-radius:9px;padding:11px 12px;font-size:14px;outline:none;}
input[type=email]:focus{border-color:var(--accent);box-shadow:0 0 0 3px rgba(37,99,235,.12);}
button{cursor:pointer;font-family:inherit;}
.btn{background:var(--accent);color:#fff;border:none;border-radius:9px;padding:11px 14px;font-size:14px;font-weight:600;width:100%;margin-top:12px;}
.btn:disabled{opacity:.6;cursor:default;}
.msg{font-size:13px;margin-top:12px;padding:10px 12px;border-radius:9px;display:none;}
.msg.show{display:block;}
.msg.ok{background:#DCFCE7;color:#166534;}
.msg.err{background:#FEE2E2;color:#991B1B;}
.head{background:var(--ink);color:#fff;border-radius:20px;padding:18px 22px;display:flex;flex-wrap:wrap;gap:14px;align-items:center;justify-content:space-between;}
.head .left{display:flex;align-items:center;gap:12px;}
.head h1{margin:0;font-size:20px;color:#fff;}
.head .sub{color:#9FB0CE;margin:2px 0 0;}
.head .right{display:flex;align-items:center;gap:10px;}
.pill{display:inline-flex;align-items:center;gap:6px;font-size:12px;font-weight:600;}
.dot{width:8px;height:8px;border-radius:50%;}
.ghost{background:#2A3568;color:#D7DEEC;border:none;border-radius:9px;padding:9px 12px;font-size:13px;font-weight:600;}
.danger{background:var(--accent);color:#fff;border:none;border-radius:9px;padding:9px 13px;font-size:13px;font-weight:600;}
.kpis{display:grid;grid-template-columns:repeat(2,1fr);gap:12px;margin-top:20px;}
@media(min-width:640px){.kpis{grid-template-columns:repeat(3,1fr);}}
@media(min-width:1024px){.kpis{grid-template-columns:repeat(6,1fr);}}
.kpi{background:var(--card);border:1px solid var(--line);border-radius:14px;padding:14px 16px;text-align:left;}
.kpi .n{font-size:24px;font-weight:700;font-variant-numeric:tabular-nums;letter-spacing:-.02em;}
.kpi .l{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--muted);margin-top:2px;}
.kpi.click{cursor:pointer;transition:border-color .15s;}
.kpi.click:hover{border-color:#94A3B8;}
.kpi.active{border-color:var(--accent);}
.panel{background:var(--card);border:1px solid var(--line);border-radius:14px;padding:14px;margin-top:20px;}
.frow{display:flex;flex-wrap:wrap;gap:8px;align-items:center;}
.frow input[type=text]{flex:1;min-width:180px;border:1px solid #CBD5E1;border-radius:8px;padding:8px 11px;font-size:14px;outline:none;}
.frow input[type=text]:focus{border-color:var(--accent);box-shadow:0 0 0 3px rgba(37,99,235,.12);}
.frow select{appearance:none;border:1px solid #CBD5E1;border-radius:8px;background:#fff;padding:8px 10px;font-size:13px;font-weight:600;color:#334155;}
.chips{display:flex;flex-wrap:wrap;gap:6px;margin-top:10px;}
.chip{border:none;border-radius:999px;padding:5px 11px;font-size:12px;font-weight:600;}
.clear{background:none;border:none;color:var(--muted);font-size:13px;text-decoration:underline;text-underline-offset:2px;font-weight:500;}
.count{font-size:13px;color:var(--muted);margin:14px 2px 8px;}
.count b{color:var(--ink);}
.tableWrap{background:var(--card);border:1px solid var(--line);border-radius:14px;overflow:hidden;}
.scroll{overflow-x:auto;}
table{width:100%;border-collapse:collapse;font-size:14px;}
thead tr{background:#F1F4F9;text-align:left;}
th{font-size:11px;text-transform:uppercase;letter-spacing:.04em;color:var(--muted);font-weight:600;padding:10px 12px;white-space:nowrap;}
td{padding:9px 12px;border-top:1px solid #EEF1F6;vertical-align:top;}
.item .name{font-weight:500;}
.item .meta{font-size:11px;color:#94A3B8;font-variant-numeric:tabular-nums;margin-top:2px;}
.editsel{appearance:none;border:1px solid transparent;border-radius:7px;padding:4px 22px 4px 9px;font-size:12px;font-weight:600;cursor:pointer;background-repeat:no-repeat;background-position:right 7px center;background-image:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10" viewBox="0 0 12 12"><path d="M3 4.5l3 3 3-3" stroke="%23999" stroke-width="1.6" fill="none" stroke-linecap="round"/></svg>');}
.editsel:hover{border-color:#CBD5E1;}
.editsel:focus{outline:none;border-color:var(--accent);box-shadow:0 0 0 3px rgba(37,99,235,.12);}
.editsel.muted{color:#94A3B8;background-color:#F8FAFC;}
.expand{display:none;background:#F8FAFC;}
.expand.show{display:table-row;}
.expand td{font-size:13px;color:#334155;line-height:1.6;white-space:pre-wrap;}
.expand .dates{margin-top:8px;font-size:12px;color:#94A3B8;display:flex;gap:18px;flex-wrap:wrap;}
.expand .due{color:#991B1B;font-weight:600;}
.disclose{cursor:pointer;}
#toast{position:fixed;bottom:20px;left:50%;transform:translateX(-50%);background:var(--ink);color:#fff;padding:9px 16px;border-radius:999px;font-size:13px;font-weight:600;opacity:0;transition:opacity .2s;pointer-events:none;z-index:50;}
#toast.show{opacity:1;}
.foot{font-size:12px;color:#94A3B8;margin-top:16px;padding:0 2px;}
.hidden{display:none !important;}
</style>
</head>
<body>
<div id="login">
<div class="loginCard">
<div class="logo">B</div>
<h1>Bulldog Command Center</h1>
<p class="sub">Private tracker. Sign in with your work email to get a one-time login link.</p>
<input id="email" type="email" placeholder="you@bulldogadjusters.com" autocomplete="email" />
<button class="btn" id="sendBtn">Email me a login link</button>
<div class="msg" id="loginMsg"></div>
</div>
</div>
<div id="app" class="hidden">
<div class="wrap">
<div class="head">
<div class="left">
<div class="logo">B</div>
<div>
<h1>Bulldog Command Center</h1>
<p class="sub">Projects & Ideas Tracker · live & editable</p>
</div>
</div>
<div class="right">
<span class="pill"><span class="dot" id="liveDot" style="background:#22C55E"></span><span id="liveTxt">LIVE</span></span>
<button class="ghost" id="refreshBtn">Refresh</button>
<button class="danger" id="logoutBtn">Sign out</button>
</div>
</div>
<div class="kpis" id="kpis"></div>
<div class="panel">
<div class="frow">
<input id="q" type="text" placeholder="Search items, details, BD-#…" />
<select id="ownerF"></select>
<select id="typeF"></select>
<select id="priF">
<option value="All">Any priority</option>
<option value="High">High</option><option value="Medium">Medium</option>
<option value="Low">Low</option><option value="None">No priority</option>
</select>
<button class="clear hidden" id="clearBtn">Clear</button>
</div>
<div class="chips" id="sysChips"></div>
</div>
<div class="count" id="count"></div>
<div class="tableWrap">
<div class="scroll">
<table>
<thead><tr>
<th>Item</th><th>System</th><th>Type</th><th>Owner</th><th>Status</th><th>Priority</th>
</tr></thead>
<tbody id="tbody"></tbody>
</table>
</div>
</div>
<p class="foot">Every field is a dropdown — changes save to Supabase instantly. Click a row's name to see full context. Reads & writes are restricted to your account only.</p>
</div>
</div>
<div id="toast"></div>
<script type="module">
import { createClient } from "https://esm.sh/@supabase/supabase-js@2";
const SUPABASE_URL = "https://gzghwszoezxopcswrtdt.supabase.co";
const SUPABASE_KEY = "sb_publishable_f9S90BFXa3yI4dxV2OO33Q_uEPQ1-74";
const sb = createClient(SUPABASE_URL, SUPABASE_KEY);
const ENUMS = {
system:["ESPO","YVA","Reporting Tool","BigQuery","Sales Tool","Cross-cutting","Other"],
type:["Idea","Feature","To-do","Bug","Process"],
owner:["Shumi","Adrian","Sauham","Max","Suvradip","Deep","Shana","Unassigned"],
status:["New","Scoped","In Progress","Blocked","Shipped","Killed"],
priority:["High","Medium","Low"]
};
const SYS={"ESPO":["#EEF2FF","#3730A3","#6366F1"],"YVA":["#ECFEFF","#155E75","#06B6D4"],"Reporting Tool":["#F0FDF4","#166534","#22C55E"],"BigQuery":["#FFFBEB","#92400E","#F59E0B"],"Sales Tool":["#FDF2F8","#9D174D","#EC4899"],"Cross-cutting":["#FAF5FF","#6B21A8","#A855F7"],"Other":["#F1F5F9","#334155","#64748B"]};
const STA={"New":["#E0E7FF","#3730A3"],"Scoped":["#DBEAFE","#1E40AF"],"In Progress":["#FEF3C7","#92400E"],"Blocked":["#FEE2E2","#991B1B"],"Shipped":["#DCFCE7","#166534"],"Killed":["#E5E7EB","#6B7280"]};
const PRI={"High":["#FEE2E2","#991B1B"],"Medium":["#FEF3C7","#92400E"],"Low":["#F1F5F9","#475569"]};
const STATUSES=ENUMS.status;
let rows=[];
let f={q:"",sys:[],sta:[],owner:"All",type:"All",pri:"All"};
let expanded=null;
const $=id=>document.getElementById(id);
const esc=s=>(s==null?"":String(s)).replace(/[&<>"]/g,c=>({"&":"&","<":"<",">":">",'"':"""}[c]));
function toast(t){const el=$("toast");el.textContent=t;el.classList.add("show");clearTimeout(el._t);el._t=setTimeout(()=>el.classList.remove("show"),1600);}
function fmtDay(d){if(!d)return"";try{return new Date(d).toLocaleDateString(undefined,{month:"short",day:"numeric",year:"numeric"});}catch(e){return d;}}
async function init(){
sb.auth.onAuthStateChange((_e,session)=>{ if(session){showApp();} });
const {data:{session}}=await sb.auth.getSession();
if(session){ showApp(); } else { $("login").classList.remove("hidden"); }
}
function showApp(){
if(!$("app").classList.contains("hidden")) return;
$("login").classList.add("hidden");
$("app").classList.remove("hidden");
buildStaticControls();
loadRows();
}
$("sendBtn").addEventListener("click", async ()=>{
const email=$("email").value.trim();
const m=$("loginMsg");
if(!email){m.className="msg show err";m.textContent="Enter your email.";return;}
$("sendBtn").disabled=true;
const {error}=await sb.auth.signInWithOtp({email,options:{emailRedirectTo:window.location.href.split("#")[0]}});
if(error){m.className="msg show err";m.textContent=error.message;}
else{m.className="msg show ok";m.textContent="Check your inbox — tap the link to sign in. You can close this tab after.";}
$("sendBtn").disabled=false;
});
$("email").addEventListener("keydown",e=>{if(e.key==="Enter")$("sendBtn").click();});
$("logoutBtn").addEventListener("click", async ()=>{await sb.auth.signOut();location.reload();});
$("refreshBtn").addEventListener("click", loadRows);
async function loadRows(){
$("liveTxt").textContent="Loading…";$("liveDot").style.background="#F59E0B";
const {data,error}=await sb.from("items").select("*").order("id");
if(error){$("liveTxt").textContent="ERROR";toast("Load failed: "+error.message);return;}
rows=data||[];
$("liveDot").style.background="#22C55E";$("liveTxt").textContent="LIVE";
render();
}
async function updateField(id,field,value){
const v=(field==="priority"&&value==="")?null:value;
const row=rows.find(r=>r.id===id); const prev=row?row[field]:undefined;
if(row) row[field]=v;
render();
const {error}=await sb.from("items").update({[field]:v}).eq("id",id);
if(error){ if(row)row[field]=prev; render(); toast("Save failed: "+error.message); }
else { toast("Saved"); }
}
function buildStaticControls(){
const oF=$("ownerF"); oF.innerHTML='<option value="All">All owners</option>'+ENUMS.owner.map(o=>`<option>${o}</option>`).join("");
const tF=$("typeF"); tF.innerHTML='<option value="All">All types</option>'+ENUMS.type.map(t=>`<option>${t}</option>`).join("");
oF.addEventListener("change",e=>{f.owner=e.target.value;expanded=null;render();});
tF.addEventListener("change",e=>{f.type=e.target.value;expanded=null;render();});
$("priF").addEventListener("change",e=>{f.pri=e.target.value;expanded=null;render();});
$("q").addEventListener("input",e=>{f.q=e.target.value;expanded=null;render();});
$("clearBtn").addEventListener("click",()=>{f={q:"",sys:[],sta:[],owner:"All",type:"All",pri:"All"};$("q").value="";$("ownerF").value="All";$("typeF").value="All";$("priF").value="All";expanded=null;render();});
const chips=$("sysChips");
chips.innerHTML=ENUMS.system.map(s=>`<button class="chip" data-sys="${s}"></button>`).join("");
chips.querySelectorAll("[data-sys]").forEach(b=>b.addEventListener("click",()=>{
const s=b.getAttribute("data-sys");
f.sys=f.sys.includes(s)?f.sys.filter(x=>x!==s):[...f.sys,s];expanded=null;render();
}));
}
function filtered(){
const ql=f.q.trim().toLowerCase();
return rows.filter(r=>{
if(ql){const hay=(r.item+" "+(r.details||"")+" "+(r.task_id||"")).toLowerCase();if(!hay.includes(ql))return false;}
if(f.sys.length&&!f.sys.includes(r.system))return false;
if(f.sta.length&&!f.sta.includes(r.status))return false;
if(f.owner!=="All"&&r.owner!==f.owner)return false;
if(f.type!=="All"&&r.type!==f.type)return false;
if(f.pri!=="All"){if(f.pri==="None"){if(r.priority)return false;}else if(r.priority!==f.pri)return false;}
return true;
});
}
function selHtml(id,field,value){
const opts=ENUMS[field];
let map=field==="system"?SYS:field==="status"?STA:field==="priority"?PRI:null;
let style="";
if(map&&value&&map[value]){style=`background-color:${map[value][0]};color:${map[value][1]};`;}
let html=`<select class="editsel${(field==='priority'&&!value)?' muted':''}" style="${style}" data-id="${id}" data-field="${field}">`;
if(field==="priority"){html+=`<option value=""${!value?" selected":""}>—</option>`;}
for(const o of opts){html+=`<option value="${esc(o)}"${o===value?" selected":""}>${esc(o)}</option>`;}
html+=`</select>`;
return html;
}
function render(){
const counts={total:rows.length};STATUSES.forEach(s=>counts[s]=rows.filter(r=>r.status===s).length);
const kpiDefs=[["Total","total",null],["New","New",STA.New],["In Progress","In Progress",STA["In Progress"]],["Blocked","Blocked",STA.Blocked],["Shipped","Shipped",STA.Shipped],["Killed","Killed",STA.Killed]];
$("kpis").innerHTML=kpiDefs.map(([label,key,c])=>{
const active=key!=="total"&&f.sta.includes(key);
const col=c?`color:${c[1]}`:"";
return `<button class="kpi click${active?" active":""}" data-kpi="${key}"><div class="n" style="${col}">${counts[key]}</div><div class="l">${label}</div></button>`;
}).join("");
$("kpis").querySelectorAll("[data-kpi]").forEach(b=>b.addEventListener("click",()=>{
const k=b.getAttribute("data-kpi");
if(k==="total"){f.sta=[];}else{f.sta=f.sta.includes(k)?f.sta.filter(x=>x!==k):[...f.sta,k];}
expanded=null;render();
}));
$("sysChips").querySelectorAll("[data-sys]").forEach(b=>{
const s=b.getAttribute("data-sys");const on=f.sys.includes(s);const c=SYS[s];
b.style.background=on?c[2]:c[0];b.style.color=on?"#fff":c[1];b.textContent=s;
});
$("clearBtn").classList.toggle("hidden",!(f.q||f.sys.length||f.sta.length||f.owner!=="All"||f.type!=="All"||f.pri!=="All"));
const list=filtered();
$("count").innerHTML=`Showing <b>${list.length}</b> of ${rows.length} items`;
const tb=$("tbody");
tb.innerHTML=list.map(r=>{
const meta=esc(r.task_id)+(r.target_date?" · due "+esc(fmtDay(r.target_date)):"");
const main=`<tr>
<td class="item"><div class="name disclose" data-exp="${r.id}">${esc(r.item)}</div><div class="meta">${meta}</div></td>
<td>${selHtml(r.id,"system",r.system)}</td>
<td>${selHtml(r.id,"type",r.type)}</td>
<td>${selHtml(r.id,"owner",r.owner)}</td>
<td>${selHtml(r.id,"status",r.status)}</td>
<td>${selHtml(r.id,"priority",r.priority||"")}</td>
</tr>`;
const exp=`<tr class="expand${expanded===r.id?" show":""}"><td colspan="6">${esc(r.details||"No additional detail.")}<div class="dates">${r.target_date?`<span class="due">Target: ${esc(fmtDay(r.target_date))}</span>`:""}${r.created_at?`<span>Added ${esc(fmtDay(r.created_at))}</span>`:""}</div></td></tr>`;
return main+exp;
}).join("")|| `<tr><td colspan="6" style="text-align:center;color:#94A3B8;padding:40px">No items match these filters.</td></tr>`;
tb.querySelectorAll(".editsel").forEach(s=>s.addEventListener("change",e=>{
updateField(Number(e.target.getAttribute("data-id")),e.target.getAttribute("data-field"),e.target.value);
}));
tb.querySelectorAll("[data-exp]").forEach(d=>d.addEventListener("click",()=>{
const id=Number(d.getAttribute("data-exp"));expanded=expanded===id?null:id;render();
}));
}
init();
</script>
</body>
</html>