mirror of
https://github.com/skoelle/PeopleCostCounter.git
synced 2026-09-18 04:10:25 +00:00
Compare commits
9
Commits
ace92a048c
...
0f23a04592
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0f23a04592 | ||
|
|
3c90346982 | ||
|
|
055e81b8e7 | ||
|
|
cfd1ffdb29 | ||
|
|
258fb956a0 | ||
|
|
87ed08c458 | ||
|
|
9fc8d3663f | ||
|
|
e5608a35e3 | ||
|
|
16cb63981a |
@@ -10,11 +10,11 @@ jobs:
|
|||||||
html-lint:
|
html-lint:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v7
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v7
|
||||||
with:
|
with:
|
||||||
node-version: '18'
|
node-version: '24'
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: npm install
|
run: npm install
|
||||||
- name: Run html-validate
|
- name: Run html-validate
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
node_modules/
|
||||||
@@ -33,8 +33,6 @@ Experimental variants (moved to `variants/`):
|
|||||||
- `variants/meeting-tracker-variant2.html`
|
- `variants/meeting-tracker-variant2.html`
|
||||||
- `variants/meeting-tracker-variant3.html`
|
- `variants/meeting-tracker-variant3.html`
|
||||||
|
|
||||||
- `meeting-cost-prompt.md` — internal prompt and notes used while developing the tracker
|
|
||||||
|
|
||||||
## Browser & dependencies
|
## Browser & dependencies
|
||||||
|
|
||||||
- No build tool or runtime dependencies. The pages use only vanilla HTML/CSS/JS.
|
- No build tool or runtime dependencies. The pages use only vanilla HTML/CSS/JS.
|
||||||
|
|||||||
@@ -210,7 +210,7 @@ function getParams(){
|
|||||||
var annualWithEmployer=annualSalary*1.2;
|
var annualWithEmployer=annualSalary*1.2;
|
||||||
var totalCost=annualWithEmployer*devCount;
|
var totalCost=annualWithEmployer*devCount;
|
||||||
var costPerSecond=totalCost/220/8/60/60;
|
var costPerSecond=totalCost/220/8/60/60;
|
||||||
return{devCount:devCount,avgSalary:avgSalary,totalCost:totalCost,costPerSecond:costPerSecond};
|
return{devCount:devCount,avgSalary:avgSalary,annualSalary:annualSalary,annualWithEmployer:annualWithEmployer,totalCost:totalCost,costPerSecond:costPerSecond};
|
||||||
}
|
}
|
||||||
function tick(){
|
function tick(){
|
||||||
var elapsed=pausedElapsed+(Date.now()-startTime)/1000;
|
var elapsed=pausedElapsed+(Date.now()-startTime)/1000;
|
||||||
@@ -222,10 +222,9 @@ function tick(){
|
|||||||
document.getElementById('tilePerMin').textContent='EUR '+fmt(p.costPerSecond*60);
|
document.getElementById('tilePerMin').textContent='EUR '+fmt(p.costPerSecond*60);
|
||||||
document.getElementById('tilePerHour').textContent='EUR '+fmt(p.costPerSecond*3600);
|
document.getElementById('tilePerHour').textContent='EUR '+fmt(p.costPerSecond*3600);
|
||||||
document.getElementById('tileTotal').textContent=p.devCount+' x EUR '+fmt(p.avgSalary);
|
document.getElementById('tileTotal').textContent=p.devCount+' x EUR '+fmt(p.avgSalary);
|
||||||
// show annual / employer / total year values (as integers)
|
var annual = p.annualSalary;
|
||||||
var annual = p.annualSalary || p.avgSalary*12;
|
var employer = p.annualWithEmployer;
|
||||||
var employer = p.annualWithEmployer || annual*1.2;
|
var annualTotal = p.totalCost;
|
||||||
var annualTotal = p.totalCost || employer * p.devCount;
|
|
||||||
document.getElementById('tileAnnual').textContent = annual.toLocaleString('de-DE',{minimumFractionDigits:0,maximumFractionDigits:0}) + ' €';
|
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('tileEmployer').textContent = employer.toLocaleString('de-DE',{minimumFractionDigits:0,maximumFractionDigits:0}) + ' €';
|
||||||
document.getElementById('tileAnnualTotal').textContent = annualTotal.toLocaleString('de-DE',{minimumFractionDigits:0,maximumFractionDigits:0}) + ' €';
|
document.getElementById('tileAnnualTotal').textContent = annualTotal.toLocaleString('de-DE',{minimumFractionDigits:0,maximumFractionDigits:0}) + ' €';
|
||||||
@@ -270,6 +269,10 @@ function resetTimer(){
|
|||||||
document.getElementById('avgSalary').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('de-DE',{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('de-DE',{minimumFractionDigits:4,maximumFractionDigits:4}); }});
|
||||||
|
|
||||||
// No auto-start: user must press Start. (Previously auto-started here.)
|
// No auto-start: user must press Start. (Previously auto-started here.)
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
+178
-72
@@ -10,62 +10,120 @@
|
|||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<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">
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300..700&family=JetBrains+Mono:wght@400;600&display=swap" rel="stylesheet">
|
||||||
<style>
|
<style>
|
||||||
/* same styles as variant2 (kept compact) */
|
:root, [data-theme="light"] {
|
||||||
: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}
|
--color-bg:#f7f6f2;--color-surface:#f9f8f5;--color-surface-2:#fbfbf9;
|
||||||
[data-theme="dark"]{--color-bg:#171614;--color-surface:#1c1b19;--color-text:#cdccca}
|
--color-surface-offset:#f3f0ec;--color-divider:#dcd9d5;--color-border:#d4d1ca;
|
||||||
*{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)}
|
--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)}
|
.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{display:flex;align-items:center;gap:var(--space-3)}
|
||||||
.logo-sub{font-size:0.85rem;color:var(--color-text-muted)}
|
.logo-icon{color:var(--color-primary)}
|
||||||
.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}
|
.logo-text{font-size:var(--text-lg);font-weight:600;color:var(--color-text);letter-spacing:-.02em}
|
||||||
.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)}
|
.logo-sub{font-size:var(--text-xs);color:var(--color-text-muted);margin-top:1px}
|
||||||
.form-row{display:grid;grid-template-columns:1fr 1fr auto;gap:1rem;align-items:end;margin-bottom:1.5rem}
|
.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)}
|
||||||
.form-field{display:flex;flex-direction:column;gap:.5rem}
|
.theme-toggle:hover{color:var(--color-text);background:var(--color-surface)}
|
||||||
.form-label{font-size:.75rem;color:var(--color-text-muted);text-transform:uppercase}
|
.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-input{padding:.75rem 1rem;background:var(--color-bg);border:1px solid var(--color-border);border-radius:.5rem}
|
.form-row{display:grid;grid-template-columns:1fr 1fr auto;gap:var(--space-4);align-items:end;margin-bottom:var(--space-6)}
|
||||||
.input-prefix{position:absolute;left:1rem;top:50%;transform:translateY(-50%);color:var(--color-text-muted)}
|
@media(max-width:600px){.form-row{grid-template-columns:1fr}}
|
||||||
.btn-start{padding:.6rem 1.2rem;background:var(--color-primary);color:#fff;border-radius:.5rem;display:flex;align-items:center;gap:.5rem}
|
.form-field{display:flex;flex-direction:column;gap:var(--space-2)}
|
||||||
.btn-reset{padding:.4rem .75rem;border:1px solid var(--color-border);border-radius:.5rem}
|
.form-label{font-size:var(--text-xs);font-weight:600;color:var(--color-text-muted);text-transform:uppercase;letter-spacing:.06em}
|
||||||
.divider{height:1px;background:var(--color-divider);margin:1.25rem 0}
|
.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%}
|
||||||
.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)}
|
.form-input:focus{border-color:var(--color-primary);box-shadow:0 0 0 3px oklch(from var(--color-primary) l c h/.15)}
|
||||||
.running-indicator{width:10px;height:10px;border-radius:9999px;background:currentColor}
|
.form-input:disabled{opacity:.5;cursor:not-allowed}
|
||||||
.running-indicator.active{animation:pulse 1.2s infinite}@keyframes pulse{0%{opacity:1}50%{opacity:.3}100%{opacity:1}}
|
.form-hint{font-size:var(--text-xs);color:var(--color-text-faint);margin-top:var(--space-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}
|
.input-prefix-wrap{position:relative}
|
||||||
.cost-display-amount{font-family:var(--font-mono);font-size:clamp(2.5rem,6vw,4.5rem);font-weight:700}
|
.input-prefix-wrap .form-input{padding-left:var(--space-8)}
|
||||||
.info-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:1rem;margin-top:1.5rem}
|
.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}
|
||||||
.info-tile{background:var(--color-surface-2);border:1px solid var(--color-border);border-radius:.5rem;padding:1rem}
|
.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}
|
||||||
.info-tile-label{font-size:.75rem;color:var(--color-text-muted);text-transform:uppercase;margin-bottom:.5rem}
|
.btn-start:hover{background:var(--color-primary-hover);box-shadow:var(--shadow-md)}
|
||||||
.info-tile-value{font-family:var(--font-mono);font-weight:600}
|
.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>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header class="app-header">
|
<header class="app-header">
|
||||||
<div style="display:flex;align-items:center;gap:1rem">
|
<div class="logo">
|
||||||
<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>
|
<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>
|
||||||
<div class="logo-text">Meeting Cost Tracker</div>
|
<div class="logo-text">Meeting Cost Tracker</div>
|
||||||
<div class="logo-sub">Live meeting cost overview</div>
|
<div class="logo-sub">Live meeting cost overview</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button class="theme-toggle" data-theme-toggle aria-label="Toggle theme">
|
<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>
|
<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>
|
</button>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main class="card">
|
<main class="card">
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<div class="form-field">
|
<div class="form-field">
|
||||||
<label class="form-label" for="devCount">Developers present</label>
|
<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">
|
<input class="form-input" type="number" id="devCount" min="1" max="500" value="5">
|
||||||
<div style="font-size:.85rem;color:var(--color-text-muted)">number of participants</div>
|
<div class="form-hint">Number of participants</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-field">
|
<div class="form-field">
|
||||||
<label class="form-label" for="avgSalary">Avg. monthly gross salary</label>
|
<label class="form-label" for="avgSalary">Avg. monthly gross salary</label>
|
||||||
<div style="position:relative">
|
<div class="input-prefix-wrap">
|
||||||
<span class="input-prefix">EUR</span>
|
<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">
|
<input class="form-input" type="number" id="avgSalary" min="1000" max="50000" value="5000" style="padding-left:3.5rem">
|
||||||
</div>
|
</div>
|
||||||
<div style="font-size:.85rem;color:var(--color-text-muted)">brutto, avarage salary</div>
|
<div class="form-hint">Brutto, average salary</div>
|
||||||
</div>
|
</div>
|
||||||
<button class="btn-start" id="mainBtn" onclick="handleBtn()">
|
<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>
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" id="btnIcon"><polygon points="5,3 19,12 5,21"/></svg>
|
||||||
@@ -73,43 +131,77 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
|
<div class="action-row">
|
||||||
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:1rem">
|
<span class="status-badge" id="statusBadge">
|
||||||
<span class="status-badge" id="statusBadge"><span class="running-indicator" id="runIndicator"></span> Ready</span>
|
<span class="running-indicator" id="runIndicator"></span>
|
||||||
<button class="btn-reset" id="resetBtn" onclick="resetTimer()" style="display:none">Reset</button>
|
Ready
|
||||||
|
</span>
|
||||||
|
<button class="btn-reset" id="resetBtn" onclick="resetTimer()" style="display:none">
|
||||||
|
Reset
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="cost-display-wrap">
|
<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-label">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 class="cost-display-amount" id="costDisplay">
|
||||||
<div style="margin-top:.5rem;font-size:.85rem;color:var(--color-text-muted)" id="elapsedDisplay">Not started</div>
|
<span class="cost-display-currency">EUR</span><span id="costValue">0.00</span>
|
||||||
|
</div>
|
||||||
|
<div class="cost-display-elapsed" id="elapsedDisplay">Not started</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="info-grid" id="infoGrid" style="display:none">
|
<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"><div class="info-tile-label">Cost / minute</div><div class="info-tile-value" id="tilePerMin">-</div></div>
|
<div class="info-tile-label">Cost / second</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-value" id="tilePerSec">-</div>
|
||||||
<div class="info-tile"><div class="info-tile-label">People × salary</div><div class="info-tile-value" id="tileTotal">-</div></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"><div class="info-tile-label">incl. employer contributions (×1.2)</div><div class="info-tile-value" id="tileEmployer">-</div></div>
|
<div class="info-tile-label">Cost / minute</div>
|
||||||
<div class="info-tile"><div class="info-tile-label">Total annual cost</div><div class="info-tile-value" id="tileAnnualTotal">-</div></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 x 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 (x1.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>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
(function(){
|
(function(){
|
||||||
var root=document.documentElement; var dark=false; var btn=document.querySelector('[data-theme-toggle]');
|
var root=document.documentElement;
|
||||||
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 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>';
|
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; }); }
|
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;
|
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 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 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 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(){
|
function getParams(){
|
||||||
var devCount=Math.max(1,parseInt(document.getElementById('devCount').value)||1);
|
var devCount=Math.max(1,parseInt(document.getElementById('devCount').value)||1);
|
||||||
var avgSalary=Math.max(0,parseFloat(document.getElementById('avgSalary').value)||5000);
|
var avgSalary=Math.max(0,parseFloat(document.getElementById('avgSalary').value)||5000);
|
||||||
@@ -119,7 +211,6 @@
|
|||||||
var costPerSecond=totalCost/220/8/60/60;
|
var costPerSecond=totalCost/220/8/60/60;
|
||||||
return{devCount:devCount,avgSalary:avgSalary,annualSalary:annualSalary,annualWithEmployer:annualWithEmployer,totalCost:totalCost,costPerSecond:costPerSecond};
|
return{devCount:devCount,avgSalary:avgSalary,annualSalary:annualSalary,annualWithEmployer:annualWithEmployer,totalCost:totalCost,costPerSecond:costPerSecond};
|
||||||
}
|
}
|
||||||
|
|
||||||
function tick(){
|
function tick(){
|
||||||
var elapsed=pausedElapsed+(Date.now()-startTime)/1000;
|
var elapsed=pausedElapsed+(Date.now()-startTime)/1000;
|
||||||
var p=getParams();
|
var p=getParams();
|
||||||
@@ -130,37 +221,52 @@
|
|||||||
document.getElementById('tilePerMin').textContent='EUR '+fmtPlain(p.costPerSecond*60,2);
|
document.getElementById('tilePerMin').textContent='EUR '+fmtPlain(p.costPerSecond*60,2);
|
||||||
document.getElementById('tilePerHour').textContent='EUR '+fmtPlain(p.costPerSecond*3600,2);
|
document.getElementById('tilePerHour').textContent='EUR '+fmtPlain(p.costPerSecond*3600,2);
|
||||||
document.getElementById('tileTotal').textContent=p.devCount+' x EUR '+fmtPlain(p.avgSalary,2);
|
document.getElementById('tileTotal').textContent=p.devCount+' x EUR '+fmtPlain(p.avgSalary,2);
|
||||||
// annual / employer / total
|
var annual = p.annualSalary;
|
||||||
document.getElementById('tileAnnual').textContent = fmtPlain(p.annualSalary,0) + ' €';
|
var employer = p.annualWithEmployer;
|
||||||
document.getElementById('tileEmployer').textContent = fmtPlain(p.annualWithEmployer,0) + ' €';
|
var annualTotal = p.totalCost;
|
||||||
document.getElementById('tileAnnualTotal').textContent = fmtPlain(p.totalCost,0) + ' €';
|
document.getElementById('tileAnnual').textContent=fmtPlain(annual,0)+' EUR';
|
||||||
|
document.getElementById('tileEmployer').textContent=fmtPlain(employer,0)+' EUR';
|
||||||
|
document.getElementById('tileAnnualTotal').textContent=fmtPlain(annualTotal,0)+' EUR';
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleBtn(){isRunning?pauseTimer():startTimer();}
|
function handleBtn(){isRunning?pauseTimer():startTimer();}
|
||||||
|
|
||||||
function startTimer(){
|
function startTimer(){
|
||||||
isRunning=true; startTime=Date.now(); timer=setInterval(tick,100);
|
isRunning=true;startTime=Date.now();
|
||||||
|
timer=setInterval(tick,100);
|
||||||
document.getElementById('btnLabel').textContent='Pause';
|
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('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('costDisplay').classList.add('running');
|
||||||
document.getElementById('statusBadge').className='status-badge running';
|
document.getElementById('statusBadge').className='status-badge running';
|
||||||
document.getElementById('statusBadge').innerHTML='<span class="running-indicator active"></span> Running';
|
document.getElementById('statusBadge').innerHTML='<span class="running-indicator active"></span> Running';
|
||||||
document.getElementById('resetBtn').style.display='inline-flex';
|
document.getElementById('resetBtn').style.display='flex';
|
||||||
document.getElementById('infoGrid').style.display='grid';
|
document.getElementById('infoGrid').style.display='grid';
|
||||||
document.getElementById('devCount').disabled=true; document.getElementById('avgSalary').disabled=true;
|
document.getElementById('devCount').disabled=true;
|
||||||
|
document.getElementById('avgSalary').disabled=true;
|
||||||
tick();
|
tick();
|
||||||
}
|
}
|
||||||
|
|
||||||
function pauseTimer(){
|
function pauseTimer(){
|
||||||
isRunning=false; pausedElapsed += (Date.now()-startTime)/1000; clearInterval(timer); timer=null;
|
isRunning=false;pausedElapsed+=(Date.now()-startTime)/1000;
|
||||||
|
clearInterval(timer);timer=null;
|
||||||
document.getElementById('btnLabel').textContent='Resume';
|
document.getElementById('btnLabel').textContent='Resume';
|
||||||
document.getElementById('btnIcon').innerHTML='<polygon points="5,3 19,12 5,21"/>';
|
document.getElementById('btnIcon').innerHTML='<polygon points="5,3 19,12 5,21"/>';
|
||||||
document.getElementById('costDisplay').classList.remove('running');
|
document.getElementById('costDisplay').classList.remove('running');
|
||||||
document.getElementById('statusBadge').className='status-badge stopped';
|
document.getElementById('statusBadge').className='status-badge stopped';
|
||||||
document.getElementById('statusBadge').innerHTML='<span class="running-indicator"></span> Paused';
|
document.getElementById('statusBadge').innerHTML='<span class="running-indicator"></span> Paused';
|
||||||
}
|
}
|
||||||
|
function resetTimer(){
|
||||||
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; }
|
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
|
// 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('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}); }});
|
||||||
|
|||||||
Generated
+201
@@ -0,0 +1,201 @@
|
|||||||
|
{
|
||||||
|
"name": "people-cost-counter",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "people-cost-counter",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"devDependencies": {
|
||||||
|
"html-validate": "^11.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@html-validate/stylish": {
|
||||||
|
"version": "6.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@html-validate/stylish/-/stylish-6.0.0.tgz",
|
||||||
|
"integrity": "sha512-d1/lI3qIXhGVJF3+MmKEBn1dRX6U4Z+BDaOdDI3E6SXcO+OQ7hC/3mSo6BIjwQlcuV6NdDOKm6QCrzIQL1EezQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": "^22.16.0 || >= 24.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@sidvind/better-ajv-errors": {
|
||||||
|
"version": "7.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sidvind/better-ajv-errors/-/better-ajv-errors-7.0.0.tgz",
|
||||||
|
"integrity": "sha512-imVsp5D3KxJ8+uUmu2dsLKnFg3qdX952v/L1gZoCa0NO2ivhQWHMpYM2KmpFrRXHWFjlqkNZ/935nxiTqXEi1A==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": "^22.12 || >= 24.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"ajv": "^8.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ajv": {
|
||||||
|
"version": "8.20.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz",
|
||||||
|
"integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"fast-deep-equal": "^3.1.3",
|
||||||
|
"fast-uri": "^3.0.1",
|
||||||
|
"json-schema-traverse": "^1.0.0",
|
||||||
|
"require-from-string": "^2.0.2"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/epoberezkin"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/fast-deep-equal": {
|
||||||
|
"version": "3.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||||
|
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/fast-uri": {
|
||||||
|
"version": "3.1.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz",
|
||||||
|
"integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==",
|
||||||
|
"dev": true,
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/fastify"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/fastify"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "BSD-3-Clause"
|
||||||
|
},
|
||||||
|
"node_modules/html-validate": {
|
||||||
|
"version": "11.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/html-validate/-/html-validate-11.6.2.tgz",
|
||||||
|
"integrity": "sha512-dlAAEeWOOdv9ya/n3pdSFTzzzGlrw+ZI43J/39zBgCSqzSg24lyqrouuBP9MEkSFAtXVZm64F35mlL7m43jCNA==",
|
||||||
|
"dev": true,
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/html-validate"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@html-validate/stylish": "^6.0.0",
|
||||||
|
"@sidvind/better-ajv-errors": "7.0.0",
|
||||||
|
"ajv": "^8.0.0",
|
||||||
|
"kleur": "^4.1.0",
|
||||||
|
"prompts": "^2.0.0",
|
||||||
|
"semver": "^7.0.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"html-validate": "bin/html-validate.mjs"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^22.22.0 || >= 24.8.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@jest/globals": "^29.0.3 || ^30.0.0",
|
||||||
|
"@vitest/expect": "^3.2.0 || ^4.0.1",
|
||||||
|
"jest": "^29.0.3 || ^30.0.0",
|
||||||
|
"jest-snapshot": "^29.0.3 || ^30.0.0",
|
||||||
|
"vitest": "^3.2.0 || ^4.0.1"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@jest/globals": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@vitest/expect": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"jest": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"jest-snapshot": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"vitest": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/json-schema-traverse": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/kleur": {
|
||||||
|
"version": "4.1.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz",
|
||||||
|
"integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/prompts": {
|
||||||
|
"version": "2.4.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz",
|
||||||
|
"integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"kleur": "^3.0.3",
|
||||||
|
"sisteransi": "^1.0.5"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/prompts/node_modules/kleur": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/require-from-string": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/semver": {
|
||||||
|
"version": "7.8.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
|
||||||
|
"integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC",
|
||||||
|
"bin": {
|
||||||
|
"semver": "bin/semver.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/sisteransi": {
|
||||||
|
"version": "1.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
|
||||||
|
"integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -3,7 +3,7 @@
|
|||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"html-validate": "^8.8.0"
|
"html-validate": "^11.0.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"lint:html": "html-validate \"**/*.html\""
|
"lint:html": "html-validate \"**/*.html\""
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||||
|
"extends": ["config:recommended"],
|
||||||
|
"schedule": ["before 6am on Monday"],
|
||||||
|
"packageRules": [
|
||||||
|
{
|
||||||
|
"matchManagers": ["github-actions"],
|
||||||
|
"groupName": "GitHub Actions",
|
||||||
|
"automerge": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matchManagers": ["dockerfile"],
|
||||||
|
"groupName": "Docker",
|
||||||
|
"automerge": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matchManagers": ["npm"],
|
||||||
|
"matchUpdateTypes": ["minor", "patch"],
|
||||||
|
"groupName": "npm minor/patch",
|
||||||
|
"automerge": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matchUpdateTypes": ["minor", "patch"],
|
||||||
|
"automerge": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matchUpdateTypes": ["major"],
|
||||||
|
"labels": ["major-update"],
|
||||||
|
"automerge": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user