feat: billableaua.de — add billable hours calculator
Interactive calculator: enter annual hours target + vacation days, shows actual billable hours per working day after subtracting weekends, vacation, and public holidays. Makes the absurdity of 2400h targets visible.
This commit is contained in:
@@ -157,6 +157,91 @@
|
|||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Rechner */
|
||||||
|
.rechner {
|
||||||
|
padding: 120px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rechner h2 {
|
||||||
|
font-size: clamp(2.5rem, 7vw, 4rem);
|
||||||
|
font-weight: 900;
|
||||||
|
letter-spacing: -0.02em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin-bottom: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rechner h2 .dot { color: var(--red); }
|
||||||
|
|
||||||
|
.rechner-grid {
|
||||||
|
display: flex;
|
||||||
|
gap: 32px;
|
||||||
|
margin-bottom: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rechner-input {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rechner-input label {
|
||||||
|
display: block;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: var(--text-dim);
|
||||||
|
font-weight: 300;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rechner-input input {
|
||||||
|
width: 100%;
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid var(--text-muted);
|
||||||
|
color: var(--text);
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 2.4rem;
|
||||||
|
font-weight: 700;
|
||||||
|
padding: 12px 16px;
|
||||||
|
text-align: center;
|
||||||
|
outline: none;
|
||||||
|
transition: border-color 0.3s;
|
||||||
|
-moz-appearance: textfield;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rechner-input input::-webkit-inner-spin-button,
|
||||||
|
.rechner-input input::-webkit-outer-spin-button {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rechner-input input:focus {
|
||||||
|
border-color: var(--red);
|
||||||
|
}
|
||||||
|
|
||||||
|
.rechner-result {
|
||||||
|
font-size: clamp(1.3rem, 3vw, 1.8rem);
|
||||||
|
font-weight: 300;
|
||||||
|
color: var(--text-dim);
|
||||||
|
line-height: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rechner-result .num {
|
||||||
|
color: var(--red);
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 1.15em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rechner-result .verdict {
|
||||||
|
display: block;
|
||||||
|
margin-top: 24px;
|
||||||
|
font-size: clamp(1.5rem, 4vw, 2.2rem);
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
.rechner { padding: 80px 0; }
|
||||||
|
.rechner-grid { flex-direction: column; gap: 24px; }
|
||||||
|
}
|
||||||
|
|
||||||
/* Silence */
|
/* Silence */
|
||||||
.silence {
|
.silence {
|
||||||
padding: 160px 0;
|
padding: 160px 0;
|
||||||
@@ -227,6 +312,25 @@
|
|||||||
|
|
||||||
<div class="line"></div>
|
<div class="line"></div>
|
||||||
|
|
||||||
|
<section class="rechner">
|
||||||
|
<div class="wrap">
|
||||||
|
<h2 class="reveal">Rechne selbst<span class="dot">.</span></h2>
|
||||||
|
<div class="rechner-grid reveal">
|
||||||
|
<div class="rechner-input">
|
||||||
|
<label>Billable-Stunden / Jahr</label>
|
||||||
|
<input type="number" id="hours" value="2400" min="0" max="5000">
|
||||||
|
</div>
|
||||||
|
<div class="rechner-input">
|
||||||
|
<label>Urlaubstage</label>
|
||||||
|
<input type="number" id="vacation" value="25" min="0" max="60">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="rechner-result reveal" id="result"></div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div class="line"></div>
|
||||||
|
|
||||||
<section class="kritik">
|
<section class="kritik">
|
||||||
<div class="wrap">
|
<div class="wrap">
|
||||||
|
|
||||||
@@ -277,6 +381,37 @@
|
|||||||
}, { threshold: 0.15 });
|
}, { threshold: 0.15 });
|
||||||
|
|
||||||
document.querySelectorAll('.reveal').forEach(el => observer.observe(el));
|
document.querySelectorAll('.reveal').forEach(el => observer.observe(el));
|
||||||
|
|
||||||
|
// Rechner
|
||||||
|
function calculate() {
|
||||||
|
const hours = parseInt(document.getElementById('hours').value) || 0;
|
||||||
|
const vacation = parseInt(document.getElementById('vacation').value) || 0;
|
||||||
|
const weekends = 104;
|
||||||
|
const feiertage = 10;
|
||||||
|
const workingDays = 365 - weekends - vacation - feiertage;
|
||||||
|
const perDay = workingDays > 0 ? (hours / workingDays) : 0;
|
||||||
|
const perDayStr = perDay.toFixed(1).replace('.', ',');
|
||||||
|
|
||||||
|
let verdict = '';
|
||||||
|
if (perDay > 10) {
|
||||||
|
verdict = '<span class="verdict">Das ist kein Job. <span class="dim">Das ist ein Lifestyle.</span></span>';
|
||||||
|
} else if (perDay > 8) {
|
||||||
|
verdict = '<span class="verdict">Jeden Tag über <span class="num">8</span> Stunden billable. <span class="dim">Ohne Mittagspause, ohne Admin, ohne Atmen.</span></span>';
|
||||||
|
} else if (perDay > 6) {
|
||||||
|
verdict = '<span class="verdict">Klingt machbar? <span class="dim">Vergiss nicht: billable ≠ Arbeitszeit.</span></span>';
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('result').innerHTML =
|
||||||
|
'<span class="num">' + workingDays + '</span> Arbeitstage ' +
|
||||||
|
'<span class="dim">(365 − ' + weekends + ' Wochenenden − ' + vacation + ' Urlaub − ' + feiertage + ' Feiertage)</span><br>' +
|
||||||
|
'<span class="num">' + hours.toLocaleString('de-DE') + '</span> Stunden ÷ <span class="num">' + workingDays + '</span> Tage = ' +
|
||||||
|
'<span class="num">' + perDayStr + '</span> Stunden pro Tag.' +
|
||||||
|
verdict;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('hours').addEventListener('input', calculate);
|
||||||
|
document.getElementById('vacation').addEventListener('input', calculate);
|
||||||
|
calculate();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
Reference in New Issue
Block a user