Files
PeopleCostCounter/meeting-tracker-en.html
T

171 lines
14 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" data-theme="light">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Meeting Cost Tracker — English</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300..700&family=JetBrains+Mono:wght@400;600&display=swap" rel="stylesheet">
<style>
/* same styles as variant2 (kept compact) */
:root, [data-theme="light"] { --color-bg:#f7f6f2;--color-surface:#f9f8f5;--color-surface-2:#fbfbf9;--color-surface-offset:#f3f0ec;--color-divider:#dcd9d5;--color-border:#d4d1ca;--color-text:#28251d;--color-text-muted:#7a7974;--color-text-faint:#bab9b4;--color-primary:#01696f;--color-primary-hover:#0c4e54;--color-warning:#964219;--color-success:#437a22;--shadow-sm:0 1px 2px rgba(0,0,0,0.06);--shadow-md:0 4px 12px rgba(0,0,0,0.08);--radius-md:.5rem;--radius-lg:.75rem;--radius-xl:1rem;--text-xs:.75rem;--text-sm:0.9rem;--text-base:1rem;--space-3:.75rem;--space-4:1rem;--space-6:1.5rem;--space-8:2rem;--font-body:'Inter',system-ui,sans-serif;--font-mono:'JetBrains Mono',monospace}
[data-theme="dark"]{--color-bg:#171614;--color-surface:#1c1b19;--color-text:#cdccca}
*{box-sizing:border-box;margin:0;padding:0} body{min-height:100dvh;font-family:var(--font-body);font-size:var(--text-base);color:var(--color-text);background:var(--color-bg);display:flex;flex-direction:column;align-items:center;padding:var(--space-8) var(--space-4)}
.app-header{width:100%;max-width:720px;display:flex;align-items:center;justify-content:space-between;margin-bottom:var(--space-8)}
.logo-text{font-size:1.125rem;font-weight:600}
.logo-sub{font-size:0.85rem;color:var(--color-text-muted)}
.theme-toggle{width:36px;height:36px;border-radius:.5rem;display:flex;align-items:center;justify-content:center;border:1px solid rgba(0,0,0,0.06);background:transparent}
.card{width:100%;max-width:720px;background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-xl);padding:var(--space-8);box-shadow:var(--shadow-md)}
.form-row{display:grid;grid-template-columns:1fr 1fr auto;gap:1rem;align-items:end;margin-bottom:1.5rem}
.form-field{display:flex;flex-direction:column;gap:.5rem}
.form-label{font-size:.75rem;color:var(--color-text-muted);text-transform:uppercase}
.form-input{padding:.75rem 1rem;background:var(--color-bg);border:1px solid var(--color-border);border-radius:.5rem}
.input-prefix{position:absolute;left:1rem;top:50%;transform:translateY(-50%);color:var(--color-text-muted)}
.btn-start{padding:.6rem 1.2rem;background:var(--color-primary);color:#fff;border-radius:.5rem;display:flex;align-items:center;gap:.5rem}
.btn-reset{padding:.4rem .75rem;border:1px solid var(--color-border);border-radius:.5rem}
.divider{height:1px;background:var(--color-divider);margin:1.25rem 0}
.status-badge{display:inline-flex;align-items:center;gap:.5rem;padding:.25rem .75rem;border-radius:9999px;background:var(--color-surface-offset);color:var(--color-text-muted);border:1px solid var(--color-border)}
.running-indicator{width:10px;height:10px;border-radius:9999px;background:currentColor}
.running-indicator.active{animation:pulse 1.2s infinite}@keyframes pulse{0%{opacity:1}50%{opacity:.3}100%{opacity:1}}
.cost-display-wrap{position:relative;background:var(--color-surface-offset);border:1px solid var(--color-border);border-radius:1rem;padding:2rem;text-align:center;min-height:160px;display:flex;flex-direction:column;align-items:center;justify-content:center}
.cost-display-amount{font-family:var(--font-mono);font-size:clamp(2.5rem,6vw,4.5rem);font-weight:700}
.info-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:1rem;margin-top:1.5rem}
.info-tile{background:var(--color-surface-2);border:1px solid var(--color-border);border-radius:.5rem;padding:1rem}
.info-tile-label{font-size:.75rem;color:var(--color-text-muted);text-transform:uppercase;margin-bottom:.5rem}
.info-tile-value{font-family:var(--font-mono);font-weight:600}
</style>
</head>
<body>
<header class="app-header">
<div style="display:flex;align-items:center;gap:1rem">
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" aria-hidden="true"><circle cx="16" cy="16" r="14" stroke="currentColor" stroke-width="2"/><path d="M16 8v8l5 3" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><circle cx="16" cy="16" r="2" fill="currentColor"/></svg>
<div>
<div class="logo-text">Meeting Cost Tracker</div>
<div class="logo-sub">Live meeting cost overview</div>
</div>
</div>
<button class="theme-toggle" data-theme-toggle aria-label="Toggle theme">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="5"/></svg>
</button>
</header>
<main class="card">
<div class="form-row">
<div class="form-field">
<label class="form-label" for="devCount">Developers present</label>
<input class="form-input" type="number" id="devCount" min="1" max="500" value="5" aria-label="Developers present">
<div style="font-size:.85rem;color:var(--color-text-muted)">number of participants</div>
</div>
<div class="form-field">
<label class="form-label" for="avgSalary">Avg. monthly gross salary</label>
<div style="position:relative">
<span class="input-prefix">EUR</span>
<input class="form-input" type="number" id="avgSalary" min="1000" max="50000" value="5000" style="padding-left:3.5rem" aria-label="Average monthly salary">
</div>
<div style="font-size:.85rem;color:var(--color-text-muted)">brutto, avarage salary</div>
</div>
<button class="btn-start" id="mainBtn" onclick="handleBtn()">
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" id="btnIcon"><polygon points="5,3 19,12 5,21"/></svg>
<span id="btnLabel">Start</span>
</button>
</div>
<div class="divider"></div>
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:1rem">
<span class="status-badge" id="statusBadge"><span class="running-indicator" id="runIndicator"></span> Ready</span>
<button class="btn-reset" id="resetBtn" onclick="resetTimer()" style="display:none">Reset</button>
</div>
<div class="cost-display-wrap">
<div style="font-size:.75rem;color:var(--color-text-muted);text-transform:uppercase;margin-bottom:.5rem">Total cost so far</div>
<div class="cost-display-amount" id="costDisplay"><span style="font-family:var(--font-mono);font-size:1rem;margin-right:.4rem">EUR</span><span id="costValue">0.00</span></div>
<div style="margin-top:.5rem;font-size:.85rem;color:var(--color-text-muted)" id="elapsedDisplay">Not started</div>
</div>
<div class="info-grid" id="infoGrid" style="display:none">
<div class="info-tile"><div class="info-tile-label">Cost / second</div><div class="info-tile-value" id="tilePerSec">-</div></div>
<div class="info-tile"><div class="info-tile-label">Cost / minute</div><div class="info-tile-value" id="tilePerMin">-</div></div>
<div class="info-tile"><div class="info-tile-label">Cost / hour</div><div class="info-tile-value" id="tilePerHour">-</div></div>
<div class="info-tile"><div class="info-tile-label">People × salary</div><div class="info-tile-value" id="tileTotal">-</div></div>
<div class="info-tile"><div class="info-tile-label">Annual salary (per person)</div><div class="info-tile-value" id="tileAnnual">-</div></div>
<div class="info-tile"><div class="info-tile-label">incl. employer contributions (×1.2)</div><div class="info-tile-value" id="tileEmployer">-</div></div>
<div class="info-tile"><div class="info-tile-label">Total annual cost</div><div class="info-tile-value" id="tileAnnualTotal">-</div></div>
</div>
</main>
<script>
(function(){
var root=document.documentElement; var dark=false; var btn=document.querySelector('[data-theme-toggle]');
var sunSvg='<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="5"/></svg>';
var moonSvg='<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>';
if(btn){ root.setAttribute('data-theme', dark ? 'dark' : 'light'); btn.innerHTML = dark ? sunSvg : moonSvg; btn.addEventListener('click', function(){ dark=!dark; root.setAttribute('data-theme', dark ? 'dark' : 'light'); btn.innerHTML = dark ? sunSvg : moonSvg; }); }
})();
var timer=null,isRunning=false,startTime=null,pausedElapsed=0;
// English formatting (GBP/GB style but currency EUR)
function fmt(n){ return new Intl.NumberFormat('en-GB',{ style:'currency', currency:'EUR', minimumFractionDigits:2, maximumFractionDigits:2 }).format(n); }
function fmtPlain(n,d){ return n.toLocaleString('en-GB',{minimumFractionDigits:d,maximumFractionDigits:d}); }
function fmtElapsed(sec){ var h=Math.floor(sec/3600), m=Math.floor((sec%3600)/60), s=Math.floor(sec%60); if(h>0) return h + 'h ' + String(m).padStart(2,'0') + 'm ' + String(s).padStart(2,'0') + 's'; if(m>0) return m + 'm ' + String(s).padStart(2,'0') + 's'; return Math.floor(sec) + 's elapsed'; }
function getParams(){
var devCount=Math.max(1,parseInt(document.getElementById('devCount').value)||1);
var avgSalary=Math.max(0,parseFloat(document.getElementById('avgSalary').value)||5000);
var annualSalary=avgSalary*12;
var annualWithEmployer=annualSalary*1.2;
var totalCost=annualWithEmployer*devCount;
var costPerSecond=totalCost/220/8/60/60;
return { devCount:devCount, avgSalary:avgSalary, annualSalary:annualSalary, annualWithEmployer:annualWithEmployer, totalCost:totalCost, costPerSecond:costPerSecond };
}
function tick(){
var elapsed = pausedElapsed + (Date.now()-startTime)/1000;
var p = getParams();
var spent = p.costPerSecond * elapsed;
document.getElementById('costValue').textContent = fmtPlain(spent,2);
document.getElementById('elapsedDisplay').textContent = fmtElapsed(elapsed);
document.getElementById('tilePerSec').textContent = 'EUR ' + p.costPerSecond.toLocaleString('en-GB',{minimumFractionDigits:4,maximumFractionDigits:4});
document.getElementById('tilePerMin').textContent = 'EUR ' + fmtPlain(p.costPerSecond*60,2);
document.getElementById('tilePerHour').textContent = 'EUR ' + fmtPlain(p.costPerSecond*3600,2);
document.getElementById('tileTotal').textContent = p.devCount + ' x EUR ' + fmtPlain(p.avgSalary,2);
// annual / employer / total
document.getElementById('tileAnnual').textContent = fmtPlain(p.annualSalary,0) + ' €';
document.getElementById('tileEmployer').textContent = fmtPlain(p.annualWithEmployer,0) + ' €';
document.getElementById('tileAnnualTotal').textContent = fmtPlain(p.totalCost,0) + ' €';
}
function handleBtn(){ isRunning ? pauseTimer() : startTimer(); }
function startTimer(){
isRunning=true; startTime=Date.now(); timer=setInterval(tick,100);
document.getElementById('btnLabel').textContent='Pause';
document.getElementById('btnIcon').innerHTML='<rect x="6" y="4" width="4" height="16"/><rect x="14" y="4" width="4" height="16"/>';
document.getElementById('costDisplay').classList.add('running');
document.getElementById('statusBadge').className='status-badge running';
document.getElementById('statusBadge').innerHTML='<span class="running-indicator active"></span> Running';
document.getElementById('resetBtn').style.display='inline-flex';
document.getElementById('infoGrid').style.display='grid';
document.getElementById('devCount').disabled=true; document.getElementById('avgSalary').disabled=true;
tick();
}
function pauseTimer(){
isRunning=false; pausedElapsed += (Date.now()-startTime)/1000; clearInterval(timer); timer=null;
document.getElementById('btnLabel').textContent='Resume';
document.getElementById('btnIcon').innerHTML='<polygon points="5,3 19,12 5,21"/>';
document.getElementById('costDisplay').classList.remove('running');
document.getElementById('statusBadge').className='status-badge stopped';
document.getElementById('statusBadge').innerHTML='<span class="running-indicator"></span> Paused';
}
function resetTimer(){ if(timer) clearInterval(timer); timer=null; isRunning=false; pausedElapsed=0; startTime=null; document.getElementById('costValue').textContent='0.00'; document.getElementById('elapsedDisplay').textContent='Not started'; document.getElementById('btnLabel').textContent='Start'; document.getElementById('btnIcon').innerHTML='<polygon points="5,3 19,12 5,21"/>'; document.getElementById('costDisplay').classList.remove('running'); document.getElementById('statusBadge').className='status-badge'; document.getElementById('statusBadge').innerHTML='<span class="running-indicator"></span> Ready'; document.getElementById('resetBtn').style.display='none'; document.getElementById('infoGrid').style.display='none'; document.getElementById('devCount').disabled=false; document.getElementById('avgSalary').disabled=false; }
// input listeners: update per-second display when editing
document.getElementById('devCount').addEventListener('input', function(){ if(!isRunning){ var p=getParams(); if(p.costPerSecond>0) document.getElementById('tilePerSec').textContent='EUR '+p.costPerSecond.toLocaleString('en-GB',{minimumFractionDigits:4,maximumFractionDigits:4}); }});
document.getElementById('avgSalary').addEventListener('input', function(){ if(!isRunning){ var p=getParams(); if(p.costPerSecond>0) document.getElementById('tilePerSec').textContent='EUR '+p.costPerSecond.toLocaleString('en-GB',{minimumFractionDigits:4,maximumFractionDigits:4}); }});
// No auto-start: user must press Start.
</script>
</body>
</html>