Compare commits

..
11 changed files with 301 additions and 109 deletions
+182
View File
@@ -0,0 +1,182 @@
# AGENTS.md — PeopleCostCounter
This file guides future AI sessions and contributors working on this project.
## Project Overview
**PeopleCostCounter** ("Meeting Cost Tracker") is a tiny, self-contained, single-page HTML
app that calculates and displays — in real time — how much a meeting is costing while it
runs. It takes the number of developers present and the average monthly gross salary,
then counts up a running total cost once the user presses **Start**.
- **No build step.** Each `.html` file is a complete, standalone page.
- **No runtime dependencies.** Only vanilla HTML, CSS, and JavaScript.
- **No external JS libraries.** Google Fonts (Inter / JetBrains Mono) are loaded via CDN;
pages work fine if fonts are blocked.
- **No persistence.** No localStorage, cookies, or server-side code.
## Project Structure
```
PeopleCostCounter/
├── LICENSE # MIT License
├── README.md # User-facing documentation
├── package.json # Dev tooling config (html-validate only)
├── .htmlvalidate.json # HTML validation config/rules
├── screenshot.png # Promotional screenshot
├── meeting-cost-prompt.md # Original development prompt / notes
├── meeting-tracker-de.html # Canonical build — German UI
├── meeting-tracker-en.html # Canonical build — English UI
├── .github/
│ └── workflows/
│ └── html-lint.yml # CI: runs html-validate on push/PR
└── variants/
├── meeting-tracker-variant1.html # Experimental — dark theme
├── meeting-tracker-variant2.html # Experimental — dark gradient theme
└── meeting-tracker-variant3.html # Experimental — alternate styling
```
### Canonical vs. Variant Files
- `meeting-tracker-de.html` and `meeting-tracker-en.html` at the repository root are the
**canonical builds**. Changes to the tracker should generally be applied to both.
- Files in `variants/` are **experimental or alternative designs** and may diverge from
the canonical builds. Use them as a reference for styling ideas, but prefer editing the
canonical files for functional changes.
- `meeting-cost-prompt.md` is an internal development artifact (the original prompt used to
generate the tracker). It is not part of the public UI and can be referenced for
context on design decisions and the cost formula.
## Cost Formula
```
AnnualSalary = AvgMonthlySalary × 12
AnnualSalaryWithEmployer = AnnualSalary × 1.2 # 20% employer overhead
TotalAnnualCost = AnnualSalaryWithEmployer × DevelopersPresent
CostPerSecond = TotalAnnualCost ÷ 220 ÷ 8 ÷ 60 ÷ 60
```
- **220 days** = approximate working days per year (accounts for weekends and holidays).
- **8 hours** = hours per working day.
- **1.2 multiplier** = simplified German employer contribution factor (~20%). This is an
approximation, not payroll-grade accounting.
- See the README "What it calculates" section for full context and references.
### Test Values
| Developers | Monthly Salary | Cost/sec | Cost after 60s |
|------------|----------------|---------------|-----------------|
| 8 | 5,000 € | ~0.0909 € | ~5.45 € |
## Development Workflow
### Linting
HTML validation is performed with [html-validate](https://html-validate.org/).
```bash
# Install (creates node_modules)
npm install
# Lint all HTML files
npx html-validate "**/*.html"
# Via npm script
npm run lint:html
```
The CI workflow (`.github/workflows/html-lint.yml`) runs `npx html-validate "**/*.html"`
on every push and pull request targeting `main`. Ensure linting passes before committing.
### Local Preview
There is no dev server. Simply open any HTML file directly in a browser:
```bash
# Or use any static file server / Live Server extension
open meeting-tracker-en.html
```
### Adding Features or Fixing Bugs
1. Apply changes to **both** canonical files (`meeting-tracker-de.html` and
`meeting-tracker-en.html`) to keep them in sync.
2. If the change is language-specific (e.g., German number formatting vs. English),
update the relevant locale string only.
3. Run `npx html-validate "**/*.html"` to verify the HTML is still valid.
4. Test the cost calculation with the test values above.
### Branching Conventions
- Create branches named `feature/...` or `fix/...` for changes.
- Prefer small, focused pull requests.
## Code Conventions
### HTML Files
- Each source file begins with two license header comment lines:
```html
<!-- Copyright (c) 2026 Stefan Koelle (https://stefankoelle.de) -->
<!-- Licensed under the MIT License. See LICENSE file in project root for details. -->
```
- Inline CSS and JavaScript are used (no external `.css` or `.js` files) to keep each page
fully self-contained.
- Inline styles are permitted (the `.htmlvalidate.json` config disables the
`no-inline-style` rule).
- Semantic HTML is preferred: `<header>`, `<main>`, `<section>`, `<form>`, `<label>`,
`<button>`, etc.
- Accessibility: buttons have `aria-label` attributes where the visual label is icon-only.
SVG icons use `aria-hidden="true"`.
### JavaScript
- Vanilla JavaScript (no frameworks or libraries).
- The English (`meeting-tracker-en.html`) version includes a **theme toggle** (light/dark)
and a **pause/resume** button.
- The German (`meeting-tracker-de.html`) version uses **Start/Reset** (no pause feature).
- Event handlers are attached via `onclick` in the HTML for buttons (e.g.,
`onclick="handleBtn()"`).
- Number formatting uses `Intl.NumberFormat` with `de-DE` locale (German) or `en-GB` locale
(English), both with `currency: 'EUR'`.
- The cost counter ticks via `setInterval` at 100ms for the running display and updates
the per-second display on input change.
### CSS
- CSS custom properties (variables) are defined in `:root` for color palette, typography,
spacing, and shadows.
- Both light and dark themes are supported via the `data-theme` attribute on `<html>`.
- Use `clamp()` for responsive font sizes where appropriate (e.g., the main cost display).
### `.htmlvalidate.json`
This file configures html-validate. The following rules are relaxed for this project:
| Rule | Setting | Reason |
|-----------------------|:-------:|----------------------------------|
| `no-implicit-button-type` | off | Buttons use default styling |
| `no-inline-style` | off | Inline styles are used throughout |
| `void-style` | off | Mixed HTML5 void element styles |
| `doctype-style` | off | Mixed `<!DOCTYPE html>` and `<!doctype html>` |
| `aria-label-misuse` | off | Lenient ARIA checking |
| `no-redundant-aria-label` | off | Lenient ARIA checking |
## Adding New Files
- Source code files should include the appropriate license header comment at the top.
- For HTML files:
```html
<!-- Copyright (c) 2026 Stefan Koelle (https://stefankoelle.de) -->
<!-- Licensed under the MIT License. See LICENSE file in project root for details. -->
```
- For files with a shebang line (e.g., Python `.py`), insert the license header **after**
the shebang.
- Skip files that already contain the header.
- Markdown, JSON, package config files, and workflow files typically do not require headers.
## License
MIT License - Copyright (c) 2026 Stefan Koelle (https://stefankoelle.de)
- Full text in `LICENSE`
- License headers in all source code files
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Stefan Koelle (https://stefankoelle.de)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+88
View File
@@ -0,0 +1,88 @@
# Meeting Cost Tracker
A small single-page tool that shows, in real time, how much a meeting is costing while it runs.
It is intentionally simple: enter how many developers are present, enter the average monthly gross salary, press **Start**, and the page begins counting the cost every second.
![Screenshot of the tracker](screenshot.png)
## Quick start
Open the tracker in your browser. The project is published via GitHub Pages — live demo URLs:
- English UI: https://skoelle.github.io/PeopleCostCounter/meeting-tracker-en.html
- German UI: https://skoelle.github.io/PeopleCostCounter/meeting-tracker-de.html
You can also open the local HTML files directly in a browser (no build step required). Recommended browsers: Chrome, Edge, Firefox.
If you prefer a live-reload development workflow, use an editor extension such as Live Server (VS Code) or any simple static file server.
## Controls
- **Start** begins the live counter at zero.
- **Reset** stops the counter and sets the accumulated cost back to zero.
- The cost per second recalculates when inputs change; the accumulated cost updates once per second while running.
## Files of interest
- `meeting-tracker-de.html` / `meeting-tracker-en.html` — canonical builds (German / English)
Experimental variants (moved to `variants/`):
- `variants/meeting-tracker-variant1.html`
- `variants/meeting-tracker-variant2.html`
- `variants/meeting-tracker-variant3.html`
- `meeting-cost-prompt.md` — internal prompt and notes used while developing the tracker
## Browser & dependencies
- No build tool or runtime dependencies. The pages use only vanilla HTML/CSS/JS.
- Google Fonts are used for typography (Inter / JetBrains Mono); pages work fine if fonts are blocked.
## Example (quick test)
- Developers: `8`
- Avg. monthly: `5000`
- Expected: annual per-dev = `60.000 €`, incl. employer ≈ `72.000 €`, total for 8 ≈ `576.000 €`, cost/sec ≈ `0.0909 €`, after 60s ≈ `5,45 €`.
---
## What it calculates
The tracker estimates meeting cost from salary, employer overhead, team size, and elapsed time. The calculation is based on the idea that one developers salary can be converted into an annual employer cost, then into a working-hour and working-second cost. Similar meeting-cost tools commonly start from annual salary, convert it to an hourly rate, and multiply by meeting duration and headcount. [meetingtoll](https://www.meetingtoll.com/blog/meeting-cost-formula-per-employee)
### Formula
The app uses this formula:
```
AnnualSalary = AvgMonthlySalary × 12
AnnualSalaryWithEmployerContribution = AnnualSalary × 1.2
TotalAnnualCost = AnnualSalaryWithEmployerContribution × DevelopersPresent
CostPerSecond = TotalAnnualCost ÷ 220 ÷ 8 ÷ 60 ÷ 60
```
The 1.2 multiplier is a simplified overhead factor for employer contributions, which is in the same rough range as public Germany payroll references that place employer burden around 2023% above gross salary. [boundlesshq](https://boundlesshq.com/blog/payroll-in-germany/)
### Why 220 days
The app divides by 220 working days instead of 365 calendar days. That makes the result closer to actual working time, because meetings happen during paid work, not across the full calendar year. [capme](https://www.capme.app/meeting-cost-calculator)
Using 220 days, 8 hours per day, and 60 minutes per hour is a practical simplification that turns annual cost into a live per-second burn rate. It is not a payroll-grade accounting model, but it is a useful way to visualize meeting cost in real time. [meetingking](https://meetingking.com/meeting-cost-calculator/)
## Notes
This tool is designed to make meeting costs visible, not to produce exact payroll accounting. In real companies, the true employer cost can vary by insurance rates, caps, bonuses, and other overhead, so the 1.2 factor should be understood as a simple approximation. [payrollgermany](https://payrollgermany.de/blog/employer-contributions-to-social-security-in-germany-a-comprehensive-guide/)
The result is best used as a conversation starter: it helps teams notice how quickly meeting time turns into money.
## Contributing
- Prefer small, focused pull requests. Create a branch named `feature/...` or `fix/...` for changes.
- If you want me to push changes, tell me whether to create a PR or commit directly to `main`.
## License
Licensed under the [MIT License](LICENSE) - Copyright (c) 2026 Stefan Koelle (https://stefankoelle.de)
-63
View File
@@ -1,63 +0,0 @@
# Meeting Cost Tracker
A small single-page tool that shows, in real time, how much a meeting is costing while it runs.
It is intentionally simple: enter how many developers are present, enter the average monthly gross salary, press **Start**, and the page begins counting the cost every second.
## What it calculates
The tracker estimates meeting cost from salary, employer overhead, team size, and elapsed time. The calculation is based on the idea that one developers salary can be converted into an annual employer cost, then into a working-hour and working-second cost. Similar meeting-cost tools commonly start from annual salary, convert it to an hourly rate, and multiply by meeting duration and headcount. [meetingtoll](https://www.meetingtoll.com/blog/meeting-cost-formula-per-employee)
## Formula
The app uses this formula:
\[
AnnualSalary = AvgMonthlySalary \times 12
\]
\[
AnnualSalaryWithEmployerContribution = AnnualSalary \times 1.2
\]
\[
TotalAnnualCost = AnnualSalaryWithEmployerContribution \times DevelopersPresent
\]
\[
CostPerSecond = \frac{TotalAnnualCost}{220 \times 8 \times 60 \times 60}
\]
The 1.2 multiplier is a simplified overhead factor for employer contributions, which is in the same rough range as public Germany payroll references that place employer burden around 20 to 23 percent above gross salary. [boundlesshq](https://boundlesshq.com/blog/payroll-in-germany/)
## Why 220 days
The app divides by 220 working days instead of 365 calendar days. That makes the result closer to actual working time, because meetings happen during paid work, not across the full calendar year. [capme](https://www.capme.app/meeting-cost-calculator)
Using 220 days, 8 hours per day, and 60 minutes per hour is a practical simplification that turns annual cost into a live per-second burn rate. It is not a payroll-grade accounting model, but it is a useful way to visualize meeting cost in real time. [meetingking](https://meetingking.com/meeting-cost-calculator/)
## Example
If 5 developers are present and the average monthly gross salary is 5,000 €:
- Annual salary per developer: 60,000 €
- Annual salary incl. employer contribution: 72,000 €
- Total annual cost for the meeting: 360,000 €
- Cost per second: about 0.0568 €
- Cost per minute: about 3.41 €
- Cost per hour: about 204.55 €
These numbers are consistent with the formula above.
## Controls
- **Start** begins the live counter at zero.
- **Reset** stops the counter and sets the accumulated cost back to zero.
- The cost per second is recalculated whenever the inputs change.
- The accumulated cost updates once per second while the counter is running.
## Notes
This tool is designed to make meeting costs visible, not to produce exact payroll accounting. In real companies, the true employer cost can vary by insurance rates, caps, bonuses, and other overhead, so the 1.2 factor should be understood as a simple approximation. [payrollgermany](https://payrollgermany.de/blog/employer-contributions-to-social-security-in-germany-a-comprehensive-guide/)
The result is best used as a conversation starter: it helps teams notice how quickly meeting time turns into money.
+2
View File
@@ -1,3 +1,5 @@
<!-- Copyright (c) 2026 Stefan Koelle (https://stefankoelle.de) -->
<!-- Licensed under the MIT License. See LICENSE file in project root for details. -->
<!DOCTYPE html> <!DOCTYPE html>
<html lang="de" data-theme="light"> <html lang="de" data-theme="light">
<head> <head>
+2
View File
@@ -1,3 +1,5 @@
<!-- Copyright (c) 2026 Stefan Koelle (https://stefankoelle.de) -->
<!-- Licensed under the MIT License. See LICENSE file in project root for details. -->
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en" data-theme="light"> <html lang="en" data-theme="light">
<head> <head>
-46
View File
@@ -1,46 +0,0 @@
<!DOCTYPE html>
<html lang="en" data-theme="light">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Meeting Cost Tracker (EN)</title>
<style>
/* Minimal styles to match variant2 look */
body{font-family:Inter,system-ui,sans-serif;background:#f7f6f2;color:#28251d;padding:40px}
.card{max-width:900px;margin:0 auto;background:#fff;padding:20px;border-radius:12px}
.label{font-size:12px;color:#6b7280}
.big{font-family:monospace;font-size:48px}
</style>
</head>
<body>
<div class="card">
<h1>Meeting Cost Tracker</h1>
<div>
<label class="label">Developers present</label>
<input id="devCount" type="number" value="5" min="1" />
</div>
<div>
<label class="label">Avg monthly gross salary (€)</label>
<input id="avgSalary" type="number" value="5000" min="1" />
</div>
<div style="margin-top:12px">
<button id="startBtn">Start</button>
<button id="resetBtn">Reset</button>
</div>
<hr/>
<div>
<div class="label">Total cost so far</div>
<div id="total" class="big">0,00 €</div>
<div class="label">Cost per second: <span id="perSec">0,00 €</span></div>
</div>
</div>
<script>
/* Simple duplicate of variant2 calculations in English */
function fmt(n){return n.toLocaleString('de-DE',{minimumFractionDigits:2,maximumFractionDigits:2})+' €'}
function calc(){var d=Number(document.getElementById('devCount').value)||1;var s=Number(document.getElementById('avgSalary').value)||5000;var annual=s*12;var withEmp=annual*1.2;var total=withEmp*d;var perSec=total/220/8/3600;return{d:d,s:s,annual:annual,withEmp:withEmp,total:total,perSec:perSec}}
var timer=null, acc=0;
document.getElementById('startBtn').addEventListener('click',function(){ if(timer) return; var p=calc(); timer=setInterval(function(){acc+=p.perSec;document.getElementById('total').textContent=fmt(acc);},1000); document.getElementById('perSec').textContent=fmt(calc().perSec);});
document.getElementById('resetBtn').addEventListener('click',function(){ clearInterval(timer);timer=null;acc=0;document.getElementById('total').textContent=fmt(0);});
</script>
</body>
</html>
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

@@ -1,3 +1,5 @@
<!-- Copyright (c) 2026 Stefan Koelle (https://stefankoelle.de) -->
<!-- Licensed under the MIT License. See LICENSE file in project root for details. -->
<!DOCTYPE html> <!DOCTYPE html>
<html lang="de" data-theme="dark"> <html lang="de" data-theme="dark">
<head> <head>
@@ -1,3 +1,5 @@
<!-- Copyright (c) 2026 Stefan Koelle (https://stefankoelle.de) -->
<!-- Licensed under the MIT License. See LICENSE file in project root for details. -->
<!doctype html> <!doctype html>
<html lang="de"> <html lang="de">
<head> <head>
@@ -1,3 +1,5 @@
<!-- Copyright (c) 2026 Stefan Koelle (https://stefankoelle.de) -->
<!-- Licensed under the MIT License. See LICENSE file in project root for details. -->
<!DOCTYPE html> <!DOCTYPE html>
<html lang="de"> <html lang="de">
<head> <head>