← back to Cc Command Center

public/index.html

206 lines

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Claude Code — Command Center</title>
<style>
  :root{
    --bg:#1c1c1f; --panel:#212125; --panel2:#26262b; --line:#33333a;
    --ink:#e9e9ec; --mut:#9a9aa3; --mut2:#6f6f78; --accent:#d8674a;
    --blue:#5b8def; --cell:#2a2a30; --c1:#1d3a63; --c2:#2f5ea8; --c3:#4f86e0; --c4:#7fb0ff;
    --shadow:0 18px 50px rgba(0,0,0,.55);
  }
  *{box-sizing:border-box}
  html,body{margin:0;height:100%}
  body{background:var(--bg);color:var(--ink);font:14px/1.45 -apple-system,BlinkMacSystemFont,"Segoe UI",Inter,sans-serif;overflow:hidden}
  .app{display:grid;grid-template-columns:236px 1fr;height:100vh}

  /* sidebar */
  .side{background:#19191c;border-right:1px solid var(--line);padding:10px;display:flex;flex-direction:column;gap:4px;overflow:auto}
  .winbtns{display:flex;gap:7px;padding:6px 6px 12px}
  .winbtns i{width:12px;height:12px;border-radius:50%;display:inline-block}
  .winbtns i:nth-child(1){background:#ff5f57}.winbtns i:nth-child(2){background:#febc2e}.winbtns i:nth-child(3){background:#28c840}
  .tabs{display:flex;gap:4px;background:#212125;border:1px solid var(--line);border-radius:9px;padding:3px;margin-bottom:8px}
  .tabs button{flex:1;background:none;border:0;color:var(--mut);padding:5px;border-radius:6px;font-size:12.5px;cursor:pointer}
  .tabs button.on{background:#34343b;color:var(--ink)}
  .navi{display:flex;align-items:center;gap:9px;padding:7px 9px;border-radius:8px;color:var(--ink);cursor:pointer;font-size:13.5px}
  .navi:hover{background:#26262b}
  .navi .ic{width:15px;text-align:center;color:var(--mut)}
  .seclabel{color:var(--mut2);font-size:11px;letter-spacing:.08em;text-transform:uppercase;margin:14px 9px 4px;font-weight:600}
  .rec{display:flex;align-items:center;gap:8px;padding:6px 9px;border-radius:7px;color:var(--mut);cursor:pointer;font-size:12.7px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  .rec:hover{background:#26262b;color:var(--ink)}
  .rec .dot{width:6px;height:6px;border-radius:50%;background:#4a4a52;flex:none}
  .who{margin-top:auto;display:flex;align-items:center;gap:9px;padding:9px;border-top:1px solid var(--line);color:var(--mut);font-size:12.5px}
  .who .av{width:20px;height:20px;border-radius:6px;background:linear-gradient(135deg,#d8674a,#b23e6e);color:#fff;display:grid;place-items:center;font-size:11px;font-weight:700}

  /* main */
  .main{position:relative;overflow:hidden}
  .canvas{position:absolute;inset:0;overflow:auto;padding:34px 40px}
  h1{font-size:21px;font-weight:600;margin:0 0 22px;display:flex;align-items:center;gap:10px}
  .star{color:var(--accent)}
  .dash{max-width:760px}
  .dashhead{display:flex;justify-content:space-between;align-items:center;margin-bottom:10px}
  .pillrow{display:flex;gap:6px}
  .pill{background:#2a2a30;border:1px solid var(--line);color:var(--mut);font-size:12px;padding:3px 10px;border-radius:7px;cursor:pointer}
  .pill.on{color:var(--ink);background:#34343b}
  .card{background:var(--panel);border:1px solid var(--line);border-radius:12px;overflow:hidden}
  .grid{display:grid;grid-template-columns:repeat(4,1fr)}
  .cell{padding:13px 15px;border-right:1px solid var(--line);border-bottom:1px solid var(--line)}
  .cell:nth-child(4n){border-right:0}
  .cell .k{color:var(--mut);font-size:12px;margin-bottom:5px}
  .cell .v{font-size:19px;font-weight:600;letter-spacing:-.01em}
  .heatwrap{padding:15px}
  .heat{display:grid;grid-auto-flow:column;grid-template-rows:repeat(7,11px);gap:3px;overflow-x:auto}
  .h{width:11px;height:11px;border-radius:2.5px;background:var(--cell)}
  .h.l1{background:var(--c1)}.h.l2{background:var(--c2)}.h.l3{background:var(--c3)}.h.l4{background:var(--c4)}
  .flavor{color:var(--mut);font-size:12.5px;padding:0 15px 15px}
  .models{display:flex;flex-wrap:wrap;gap:7px;margin-top:16px}
  .mchip{background:var(--panel);border:1px solid var(--line);border-radius:8px;padding:6px 11px;font-size:12.5px;color:var(--mut)}
  .mchip b{color:var(--ink);font-weight:600}
  .hint{margin-top:26px;color:var(--mut2);font-size:12.5px}
  .toolbar{margin-top:14px;display:flex;gap:8px;flex-wrap:wrap}
  .tbtn{background:#2a2a30;border:1px solid var(--line);color:var(--ink);padding:7px 13px;border-radius:8px;cursor:pointer;font-size:12.5px}
  .tbtn:hover{background:#34343b}

  /* floating session windows */
  .swin{position:absolute;width:430px;height:300px;background:var(--panel);border:1px solid var(--line);border-radius:12px;box-shadow:var(--shadow);display:flex;flex-direction:column;overflow:hidden}
  .swin .bar{display:flex;align-items:center;gap:8px;padding:9px 11px;border-bottom:1px solid var(--line);cursor:move;background:var(--panel2)}
  .swin .bar .ttl{font-size:12.7px;font-weight:600;flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  .swin .bar .proj{font-size:10.5px;color:var(--mut2);background:#2a2a30;padding:2px 7px;border-radius:5px;border:1px solid var(--line);max-width:130px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .swin .bar .x{cursor:pointer;color:var(--mut);width:18px;text-align:center}
  .swin .bar .x:hover{color:var(--ink)}
  .swin .body{flex:1;overflow:auto;padding:12px 13px;font-size:12.7px;color:var(--mut)}
  .msg{margin-bottom:10px}
  .msg .role{font-size:10.5px;text-transform:uppercase;letter-spacing:.06em;color:var(--mut2);margin-bottom:3px}
  .msg.u .bub{background:#2f5ea8;color:#eaf1ff;display:inline-block;padding:7px 11px;border-radius:10px}
  .msg.a .bub{color:var(--ink)}
  .swin .foot{border-top:1px solid var(--line);padding:8px 11px;display:flex;justify-content:space-between;align-items:center;font-size:11px;color:var(--mut2)}
  .swin .resz{position:absolute;right:2px;bottom:2px;width:15px;height:15px;cursor:nwse-resize;
    background:linear-gradient(135deg,transparent 50%,var(--mut2) 50%,var(--mut2) 60%,transparent 60%,transparent 72%,var(--mut2) 72%,var(--mut2) 82%,transparent 82%)}
  .gen{position:absolute;right:16px;bottom:12px;color:var(--mut2);font-size:11px}
</style>
</head>
<body>
<div class="app">
  <aside class="side">
    <div class="winbtns"><i></i><i></i><i></i></div>
    <div class="tabs"><button>Chat</button><button>Cowork</button><button class="on">Code</button></div>
    <div class="navi"><span class="ic">+</span> New session</div>
    <div class="navi"><span class="ic">⟳</span> Routines</div>
    <div class="navi"><span class="ic">▦</span> Artifacts</div>
    <div class="navi"><span class="ic">⚙</span> Customize</div>
    <div class="seclabel">Recents</div>
    <div id="recents"></div>
    <div class="who"><span class="av" id="avatar">S</span><span id="whoname">Steve · Designer Wallcoverings</span></div>
  </aside>

  <main class="main">
    <div class="canvas" id="canvas">
      <h1><span class="star">✳</span> What's up next, <span id="hname">Steve</span>?</h1>
      <div class="dash">
        <div class="card">
          <div class="dashhead" style="padding:11px 15px 0">
            <div class="pillrow"><span class="pill on">Overview</span><span class="pill">Models</span></div>
            <div class="pillrow"><span class="pill on">All</span><span class="pill">30d</span><span class="pill">7d</span></div>
          </div>
          <div class="grid" id="cards" style="margin-top:11px"></div>
          <div class="heatwrap"><div class="heat" id="heat"></div></div>
          <div class="flavor" id="flavor"></div>
        </div>
        <div class="models" id="models"></div>
        <div class="hint">Open a session from <b>Recents</b> to pop it out as a floating window — then drag the title bar, resize from the corner, or tile them all.</div>
        <div class="toolbar">
          <button class="tbtn" onclick="openTop(3)">Open recent 3</button>
          <button class="tbtn" onclick="tileWins()">Tile</button>
          <button class="tbtn" onclick="cascadeWins()">Cascade</button>
          <button class="tbtn" onclick="closeAll()">Close all</button>
        </div>
      </div>
    </div>
    <div class="gen" id="gen"></div>
  </main>
</div>

<script>
let DATA=null, wins=[], z=10, cascade=0;
const $=s=>document.querySelector(s);
const esc=s=>(s||"").replace(/[&<>]/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;'}[c]));
const nf=n=>n.toLocaleString();

function heatClass(c){ if(!c)return''; if(c<5)return'l1'; if(c<15)return'l2'; if(c<40)return'l3'; return'l4'; }

fetch('./data/stats.json').then(r=>r.json()).then(d=>{ DATA=d; render();
  if(location.hash==='#demo'){ openTop(4); setTimeout(tileWins,50); } });

function render(){
  const c=DATA.cards;
  $('#hname').textContent=DATA.user;
  $('#whoname').firstChild ? $('#whoname').textContent=DATA.user+' · Designer Wallcoverings':0;
  $('#avatar').textContent=DATA.user[0];
  const cards=[
    ['Sessions', nf(c.sessions)],['Messages', nf(c.messages)],
    ['Total tokens', tok(c.total_tokens)],['Active days', nf(c.active_days)],
    ['Current streak', c.current_streak+'d'],['Longest streak', c.longest_streak+'d'],
    ['Peak hour', c.peak_hour],['Favorite model', c.favorite_model],
  ];
  $('#cards').innerHTML=cards.map(([k,v])=>`<div class="cell"><div class="k">${k}</div><div class="v">${v}</div></div>`).join('');
  $('#heat').innerHTML=DATA.heatmap.map(h=>`<div class="h ${heatClass(h.count)}" title="${h.date}: ${h.count}"></div>`).join('');
  $('#flavor').textContent=DATA.flavor;
  $('#models').innerHTML=DATA.models.map(m=>`<span class="mchip"><b>${esc(m.model)}</b> · ${nf(m.count)} msgs</span>`).join('');
  $('#recents').innerHTML=DATA.recents.map((s,i)=>
    `<div class="rec" title="${esc(s.title)}" onclick="openWin(${i})"><span class="dot"></span>${esc(s.title)}</div>`).join('');
  $('#gen').textContent='generated '+DATA.generated_at;
}
function tok(n){ if(n>=1e9)return (n/1e9).toFixed(1)+'B'; if(n>=1e6)return (n/1e6).toFixed(1)+'M'; if(n>=1e3)return (n/1e3).toFixed(1)+'K'; return n; }

function openWin(i){
  const s=DATA.recents[i]; if(!s) return;
  if(wins.find(w=>w.dataset.sid===s.id)) return; // no dupes
  const el=document.createElement('div'); el.className='swin'; el.dataset.sid=s.id;
  const x=70+(cascade*30)%360, y=60+(cascade*28)%240; cascade++;
  el.style.left=x+'px'; el.style.top=y+'px'; el.style.zIndex=++z;
  el.innerHTML=`
    <div class="bar"><span class="proj">${esc(s.project)}</span>
      <span class="ttl">${esc(s.title)}</span><span class="x">✕</span></div>
    <div class="body">
      ${s.snippet?`<div class="msg u"><div class="role">You</div><div class="bub">${esc(s.snippet)}</div></div>`:''}
      <div class="msg a"><div class="role"><span style="color:var(--accent)">✳</span> Claude</div>
        <div class="bub">${esc(s.last)||'(session resumed)'}</div></div>
    </div>
    <div class="foot"><span>${nf(s.user_msgs)} prompts · ${tok(s.tokens)} tokens</span>
      <span>${s.ts?new Date(s.ts).toLocaleString(undefined,{month:'short',day:'numeric',hour:'numeric',minute:'2-digit'}):''}</span></div>
    <div class="resz"></div>`;
  $('.main').appendChild(el); wins.push(el);
  el.addEventListener('mousedown',()=>el.style.zIndex=++z);
  el.querySelector('.x').onclick=()=>{ el.remove(); wins=wins.filter(w=>w!==el); };
  drag(el, el.querySelector('.bar'));
  resize(el, el.querySelector('.resz'));
}
function openTop(n){ for(let i=0;i<n;i++) openWin(i); }
function closeAll(){ wins.forEach(w=>w.remove()); wins=[]; cascade=0; }
function cascadeWins(){ cascade=0; wins.forEach(w=>{ const x=70+(cascade*30)%360,y=60+(cascade*28)%240; cascade++; w.style.left=x+'px';w.style.top=y+'px';w.style.zIndex=++z;}); }
function tileWins(){
  const n=wins.length; if(!n)return; const cols=Math.ceil(Math.sqrt(n)), rows=Math.ceil(n/cols);
  const m=$('.main').getBoundingClientRect(), pad=12;
  const w=(m.width-pad*(cols+1))/cols, h=(m.height-pad*(rows+1))/rows;
  wins.forEach((el,i)=>{ const r=Math.floor(i/cols),col=i%cols;
    el.style.left=(pad+col*(w+pad))+'px'; el.style.top=(pad+r*(h+pad))+'px';
    el.style.width=w+'px'; el.style.height=h+'px'; });
}
function drag(el,handle){
  let sx,sy,ox,oy,on=false;
  handle.addEventListener('mousedown',e=>{ if(e.target.classList.contains('x'))return; on=true; sx=e.clientX;sy=e.clientY;ox=el.offsetLeft;oy=el.offsetTop; e.preventDefault();});
  window.addEventListener('mousemove',e=>{ if(!on)return; el.style.left=(ox+e.clientX-sx)+'px'; el.style.top=(oy+e.clientY-sy)+'px';});
  window.addEventListener('mouseup',()=>on=false);
}
function resize(el,h){
  let sx,sy,ow,oh,on=false;
  h.addEventListener('mousedown',e=>{ on=true; sx=e.clientX;sy=e.clientY;ow=el.offsetWidth;oh=el.offsetHeight; e.preventDefault();e.stopPropagation();});
  window.addEventListener('mousemove',e=>{ if(!on)return; el.style.width=Math.max(280,ow+e.clientX-sx)+'px'; el.style.height=Math.max(160,oh+e.clientY-sy)+'px';});
  window.addEventListener('mouseup',()=>on=false);
}
</script>
</body>
</html>