Files
PeopleCostCounter/meeting-tracker-de.html
T

275 lines
17 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="de" data-theme="light">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Meeting Cost Tracker</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>
: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 oklch(0.2 0.01 80/0.06);
--shadow-md:0 4px 12px oklch(0.2 0.01 80/0.08);
--shadow-lg:0 12px 32px oklch(0.2 0.01 80/0.12);
--radius-sm:.375rem;--radius-md:.5rem;--radius-lg:.75rem;--radius-xl:1rem;--radius-full:9999px;
--text-xs:clamp(.75rem,.7rem + .25vw,.875rem);
--text-sm:clamp(.875rem,.8rem + .35vw,1rem);
--text-base:clamp(1rem,.95rem + .25vw,1.125rem);
--text-lg:clamp(1.125rem,1rem + .75vw,1.5rem);
--text-xl:clamp(1.5rem,1.2rem + 1.25vw,2.25rem);
--space-1:.25rem;--space-2:.5rem;--space-3:.75rem;--space-4:1rem;
--space-5:1.25rem;--space-6:1.5rem;--space-8:2rem;
--font-body:'Inter','Helvetica Neue',sans-serif;
--font-mono:'JetBrains Mono','Courier New',monospace;
--transition:180ms cubic-bezier(.16,1,.3,1);
}
[data-theme="dark"] {
--color-bg:#171614;--color-surface:#1c1b19;--color-surface-2:#201f1d;
--color-surface-offset:#22211f;--color-divider:#262523;--color-border:#393836;
--color-text:#cdccca;--color-text-muted:#797876;--color-text-faint:#5a5957;
--color-primary:#4f98a3;--color-primary-hover:#227f8b;
--color-warning:#bb653b;--color-success:#6daa45;
--shadow-sm:0 1px 2px oklch(0 0 0/.2);
--shadow-md:0 4px 12px oklch(0 0 0/.3);
--shadow-lg:0 12px 32px oklch(0 0 0/.4);
}
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
html{-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility}
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)}
input,button{font:inherit;color:inherit}
a,button,input{transition:color var(--transition),background var(--transition),border-color var(--transition),box-shadow var(--transition)}
button{cursor:pointer;background:none;border:none}
.app-header{width:100%;max-width:720px;display:flex;align-items:center;justify-content:space-between;margin-bottom:var(--space-8)}
.logo{display:flex;align-items:center;gap:var(--space-3)}
.logo-icon{color:var(--color-primary)}
.logo-text{font-size:var(--text-lg);font-weight:600;color:var(--color-text);letter-spacing:-.02em}
.logo-sub{font-size:var(--text-xs);color:var(--color-text-muted);margin-top:1px}
.theme-toggle{width:36px;height:36px;border-radius:var(--radius-md);display:flex;align-items:center;justify-content:center;color:var(--color-text-muted);border:1px solid oklch(from var(--color-text) l c h/.1)}
.theme-toggle:hover{color:var(--color-text);background:var(--color-surface)}
.card{width:100%;max-width:720px;background:var(--color-surface);border:1px solid oklch(from var(--color-text) l c h/.08);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:var(--space-4);align-items:end;margin-bottom:var(--space-6)}
@media(max-width:600px){.form-row{grid-template-columns:1fr}}
.form-field{display:flex;flex-direction:column;gap:var(--space-2)}
.form-label{font-size:var(--text-xs);font-weight:600;color:var(--color-text-muted);text-transform:uppercase;letter-spacing:.06em}
.form-input{padding:var(--space-3) var(--space-4);background:var(--color-bg);border:1px solid var(--color-border);border-radius:var(--radius-md);font-size:var(--text-base);color:var(--color-text);outline:none;width:100%}
.form-input:focus{border-color:var(--color-primary);box-shadow:0 0 0 3px oklch(from var(--color-primary) l c h/.15)}
.form-input:disabled{opacity:.5;cursor:not-allowed}
.form-hint{font-size:var(--text-xs);color:var(--color-text-faint);margin-top:var(--space-1)}
.input-prefix-wrap{position:relative}
.input-prefix-wrap .form-input{padding-left:var(--space-8)}
.input-prefix{position:absolute;left:var(--space-3);top:50%;transform:translateY(-50%);color:var(--color-text-muted);font-size:var(--text-sm);pointer-events:none;line-height:1}
.btn-start{padding:var(--space-3) var(--space-6);background:var(--color-primary);color:#fff;border-radius:var(--radius-md);font-size:var(--text-sm);font-weight:600;white-space:nowrap;display:flex;align-items:center;gap:var(--space-2);box-shadow:var(--shadow-sm);height:44px}
.btn-start:hover{background:var(--color-primary-hover);box-shadow:var(--shadow-md)}
.btn-start:active{transform:scale(.98)}
.btn-reset{padding:var(--space-2) var(--space-4);border:1px solid var(--color-border);border-radius:var(--radius-md);font-size:var(--text-xs);color:var(--color-text-muted);font-weight:500;display:flex;align-items:center;gap:var(--space-2)}
.btn-reset:hover{color:var(--color-text);border-color:var(--color-text-muted)}
.divider{height:1px;background:var(--color-divider);margin:var(--space-6) 0}
.action-row{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:var(--space-3);margin-bottom:var(--space-6)}
.status-badge{display:inline-flex;align-items:center;gap:var(--space-2);padding:var(--space-1) var(--space-3);border-radius:var(--radius-full);font-size:var(--text-xs);font-weight:600;letter-spacing:.04em;text-transform:uppercase;background:var(--color-surface-offset);color:var(--color-text-muted);border:1px solid var(--color-border)}
.status-badge.running{background:color-mix(in oklch,var(--color-warning) 12%,var(--color-surface));color:var(--color-warning);border-color:color-mix(in oklch,var(--color-warning) 30%,var(--color-border))}
.status-badge.stopped{background:color-mix(in oklch,var(--color-success) 10%,var(--color-surface));color:var(--color-success);border-color:color-mix(in oklch,var(--color-success) 30%,var(--color-border))}
.running-indicator{width:10px;height:10px;border-radius:var(--radius-full);background:currentColor;display:inline-block}
@keyframes pulse{0%{opacity:1}50%{opacity:.3}100%{opacity:1}}
.running-indicator.active{animation:pulse 1.2s ease-in-out infinite}
.cost-display-wrap{position:relative;background:var(--color-surface-offset);border:1px solid oklch(from var(--color-text) l c h/.06);border-radius:var(--radius-lg);padding:var(--space-8) var(--space-8) var(--space-6);text-align:center;min-height:160px;display:flex;flex-direction:column;align-items:center;justify-content:center}
.cost-display-label{font-size:var(--text-xs);color:var(--color-text-muted);text-transform:uppercase;letter-spacing:.08em;font-weight:600;margin-bottom:var(--space-3)}
.cost-display-amount{font-family:var(--font-mono);font-size:clamp(2.5rem,6vw,4.5rem);font-weight:600;letter-spacing:-.03em;color:var(--color-text);line-height:1;transition:color .3s ease}
.cost-display-amount.running{color:var(--color-warning)}
.cost-display-currency{font-size:var(--text-xl);font-weight:400;color:var(--color-text-muted);vertical-align:super;margin-right:var(--space-2);font-family:var(--font-mono)}
.cost-display-elapsed{margin-top:var(--space-3);font-size:var(--text-xs);color:var(--color-text-faint);font-family:var(--font-mono)}
.info-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:var(--space-3);margin-top:var(--space-6)}
.info-tile{background:var(--color-surface-2,var(--color-surface));border:1px solid oklch(from var(--color-text) l c h/.06);border-radius:var(--radius-md);padding:var(--space-4)}
.info-tile-label{font-size:var(--text-xs);color:var(--color-text-muted);text-transform:uppercase;letter-spacing:.05em;font-weight:600;margin-bottom:var(--space-2)}
.info-tile-value{font-family:var(--font-mono);font-size:var(--text-sm);font-weight:600;color:var(--color-text)}
</style>
</head>
<body>
<header class="app-header">
<div class="logo">
<svg class="logo-icon" width="32" height="32" viewBox="0 0 32 32" fill="none" aria-label="Meeting Cost Tracker">
<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">Echtzeit-Kostenübersicht fuer Meetings</div>
</div>
</div>
<button class="theme-toggle" data-theme-toggle aria-label="Theme umschalten">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="5"/><path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"/>
</svg>
</button>
</header>
<main class="card">
<div class="form-row">
<div class="form-field">
<label class="form-label" for="devCount">Entwickler anwesend</label>
<input class="form-input" type="number" id="devCount" min="1" max="500" value="5">
<div class="form-hint">Anzahl der Teilnehmer</div>
</div>
<div class="form-field">
<label class="form-label" for="avgSalary">Avg. Monatsgehalt</label>
<div class="input-prefix-wrap">
<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">
</div>
<div class="form-hint">Brutto, durchschnittlich</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 class="action-row">
<span class="status-badge" id="statusBadge">
<span class="running-indicator" id="runIndicator"></span>
Bereit
</span>
<button class="btn-reset" id="resetBtn" onclick="resetTimer()" style="display:none">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/></svg>
Zuruecksetzen
</button>
</div>
<div class="cost-display-wrap">
<div class="cost-display-label">Verbrauchte Meeting-Kosten</div>
<div class="cost-display-amount" id="costDisplay">
<span class="cost-display-currency">EUR</span><span id="costValue">0,00</span>
</div>
<div class="cost-display-elapsed" id="elapsedDisplay">Noch nicht gestartet</div>
</div>
<div class="info-grid" id="infoGrid" style="display:none">
<div class="info-tile">
<div class="info-tile-label">Kosten / Sekunde</div>
<div class="info-tile-value" id="tilePerSec">-</div>
</div>
<div class="info-tile">
<div class="info-tile-label">Kosten / Minute</div>
<div class="info-tile-value" id="tilePerMin">-</div>
</div>
<div class="info-tile">
<div class="info-tile-label">Kosten / Stunde</div>
<div class="info-tile-value" id="tilePerHour">-</div>
</div>
<div class="info-tile">
<div class="info-tile-label">Personen x Gehalt</div>
<div class="info-tile-value" id="tileTotal">-</div>
</div>
<div class="info-tile">
<div class="info-tile-label">Jahresgehalt gesamt</div>
<div class="info-tile-value" id="tileAnnual">-</div>
</div>
<div class="info-tile">
<div class="info-tile-label">inkl. Arbeitgeberanteil (×1,2)</div>
<div class="info-tile-value" id="tileEmployer">-</div>
</div>
<div class="info-tile">
<div class="info-tile-label">Gesamtkosten / Jahr</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"/><path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"/></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){
// initialize button/icon to reflect light theme
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;
function fmt(n){return n.toLocaleString('de-DE',{minimumFractionDigits:2,maximumFractionDigits:2});}
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 s+'s gelaufen';
}
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,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=fmt(spent);
document.getElementById('elapsedDisplay').textContent=fmtElapsed(elapsed);
document.getElementById('tilePerSec').textContent='EUR '+p.costPerSecond.toLocaleString('de-DE',{minimumFractionDigits:4,maximumFractionDigits:4});
document.getElementById('tilePerMin').textContent='EUR '+fmt(p.costPerSecond*60);
document.getElementById('tilePerHour').textContent='EUR '+fmt(p.costPerSecond*3600);
document.getElementById('tileTotal').textContent=p.devCount+' x EUR '+fmt(p.avgSalary);
// show annual / employer / total year values (as integers)
var annual = p.annualSalary || p.avgSalary*12;
var employer = p.annualWithEmployer || annual*1.2;
var annualTotal = p.totalCost || employer * p.devCount;
document.getElementById('tileAnnual').textContent = annual.toLocaleString('de-DE',{minimumFractionDigits:0,maximumFractionDigits:0}) + ' €';
document.getElementById('tileEmployer').textContent = employer.toLocaleString('de-DE',{minimumFractionDigits:0,maximumFractionDigits:0}) + ' €';
document.getElementById('tileAnnualTotal').textContent = annualTotal.toLocaleString('de-DE',{minimumFractionDigits:0,maximumFractionDigits: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> Laeuft';
document.getElementById('resetBtn').style.display='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='Weiter';
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> Pausiert';
}
function resetTimer(){
if(timer)clearInterval(timer);
timer=null;isRunning=false;pausedElapsed=0;startTime=null;
document.getElementById('costValue').textContent='0,00';
document.getElementById('elapsedDisplay').textContent='Noch nicht gestartet';
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> Bereit';
document.getElementById('resetBtn').style.display='none';
document.getElementById('infoGrid').style.display='none';
document.getElementById('devCount').disabled=false;
document.getElementById('avgSalary').disabled=false;
}
// No auto-start: user must press Start. (Previously auto-started here.)
</script>
</body>
</html>