quickcheck / index.html
Edeka's picture
Upload 3 files
93dbb16 verified
Raw History Blame Contribute Delete
18.8 kB
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="description" content="Bilanz QuickCheck: Bilanzkennzahlen lokal im Browser berechnen und Geschäftsjahre vergleichen.">
<title>Bilanz QuickCheck</title>
<style>
:root{
--bg:#071522;
--panel:#0d2233;
--panel2:#102a3e;
--line:#24465f;
--text:#edf7fb;
--muted:#9eb7c5;
--cyan:#26d0ce;
--blue:#3e8cff;
--green:#55d98b;
--warn:#ffd166;
--danger:#ff7b7b;
--white:#fff;
--shadow:0 18px 60px rgba(0,0,0,.28);
}
*{box-sizing:border-box}
html{scroll-behavior:smooth}
body{
margin:0;
font-family:Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
background:
radial-gradient(circle at 10% 0%, rgba(38,208,206,.14), transparent 28rem),
radial-gradient(circle at 90% 15%, rgba(62,140,255,.13), transparent 28rem),
var(--bg);
color:var(--text);
line-height:1.5;
}
a{color:var(--cyan)}
.wrap{max-width:1180px;margin:auto;padding:26px 20px 60px}
.hero{
border:1px solid var(--line);
background:linear-gradient(135deg,rgba(16,42,62,.95),rgba(7,21,34,.96));
border-radius:26px;
padding:30px;
box-shadow:var(--shadow);
margin-bottom:20px;
}
.badge{
display:inline-flex;gap:8px;align-items:center;
padding:7px 11px;border-radius:999px;
background:rgba(38,208,206,.1);
border:1px solid rgba(38,208,206,.28);
color:#b9ffff;font-size:13px;font-weight:700;
}
h1{font-size:clamp(32px,5vw,58px);line-height:1.02;margin:16px 0 10px;letter-spacing:-.04em}
.hero p{max-width:780px;color:var(--muted);font-size:17px;margin:0}
.hero-actions{display:flex;flex-wrap:wrap;gap:10px;margin-top:22px}
button{
border:0;border-radius:12px;padding:11px 15px;
font-weight:800;cursor:pointer;font-size:14px;
transition:.15s transform,.15s opacity,.15s background;
}
button:hover{transform:translateY(-1px)}
.primary{background:linear-gradient(135deg,var(--cyan),var(--blue));color:#04111c}
.secondary{background:#17354a;color:var(--text);border:1px solid var(--line)}
.ghost{background:transparent;color:var(--muted);border:1px solid var(--line)}
.grid{display:grid;grid-template-columns:1fr 1fr;gap:18px}
.panel{
border:1px solid var(--line);
background:rgba(13,34,51,.94);
border-radius:20px;
padding:20px;
box-shadow:0 12px 38px rgba(0,0,0,.15);
}
.panel h2{margin:0 0 5px;font-size:20px}
.sub{color:var(--muted);font-size:13px;margin-bottom:16px}
.year-label{
display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:14px
}
.year-input{
width:90px;border-radius:10px;border:1px solid var(--line);
background:#081a28;color:var(--text);padding:8px 10px;font-weight:800
}
.section-title{
margin:18px 0 9px;
font-size:12px;letter-spacing:.12em;text-transform:uppercase;color:#7fe5e3;font-weight:900
}
.field-grid{display:grid;grid-template-columns:1fr 160px;gap:8px 12px;align-items:center}
label{font-size:14px;color:#d8e7ee}
input[type="number"]{
width:100%;padding:10px 12px;border-radius:10px;
border:1px solid var(--line);background:#081a28;color:var(--text);
outline:none;font-size:15px;text-align:right;
}
input[type="number"]:focus{border-color:var(--cyan);box-shadow:0 0 0 3px rgba(38,208,206,.08)}
.summary-row{
margin-top:15px;padding-top:13px;border-top:1px solid var(--line);
display:grid;grid-template-columns:1fr 1fr;gap:10px
}
.sum-box{background:#081a28;border:1px solid var(--line);padding:12px;border-radius:12px}
.sum-box small{display:block;color:var(--muted)}
.sum-box strong{font-size:18px}
.balance-message{
margin-top:10px;padding:10px 12px;border-radius:11px;
font-size:13px;border:1px solid var(--line);background:#081a28;color:var(--muted)
}
.results{margin-top:18px}
.results-head{display:flex;align-items:end;justify-content:space-between;gap:12px;margin-bottom:12px}
.results-head h2{margin:0;font-size:26px}
.results-head p{margin:0;color:var(--muted);font-size:13px}
.metrics{display:grid;grid-template-columns:repeat(3,1fr);gap:12px}
.metric{
background:linear-gradient(180deg,#102a3e,#0a1d2c);
border:1px solid var(--line);border-radius:16px;padding:15px;min-height:128px
}
.metric-name{font-size:13px;color:var(--muted);font-weight:700}
.metric-value{font-size:28px;font-weight:900;margin:5px 0 2px;letter-spacing:-.03em}
.metric-delta{font-size:12px;font-weight:800;min-height:18px}
.metric-info{font-size:12px;color:#89a6b6;margin-top:7px}
.up{color:var(--green)} .down{color:var(--danger)} .flat{color:var(--muted)}
.visuals{display:grid;grid-template-columns:1fr 1fr;gap:18px;margin-top:18px}
.stack{display:flex;width:100%;height:28px;border-radius:999px;overflow:hidden;background:#081a28;border:1px solid var(--line);margin:10px 0 12px}
.seg{height:100%;min-width:0;transition:width .35s ease}
.legend{display:flex;flex-wrap:wrap;gap:10px 14px;font-size:12px;color:var(--muted)}
.dot{width:9px;height:9px;border-radius:50%;display:inline-block;margin-right:5px}
.s1{background:#26d0ce}.s2{background:#3e8cff}.s3{background:#8f7cff}.s4{background:#55d98b}.s5{background:#ffd166}
.explainer{margin-top:18px}
details{border-top:1px solid var(--line);padding:12px 0}
details:first-of-type{border-top:0}
summary{cursor:pointer;font-weight:800}
details p{color:var(--muted);font-size:13px;margin:8px 0 0}
.notice{
margin-top:18px;border:1px solid rgba(255,209,102,.28);
background:rgba(255,209,102,.06);border-radius:16px;padding:16px;color:#dbe6ea;font-size:13px
}
.privacy{
display:flex;gap:10px;align-items:flex-start;
margin-top:18px;border:1px solid rgba(85,217,139,.25);
background:rgba(85,217,139,.07);padding:14px 16px;border-radius:16px
}
.privacy strong{color:#b9ffd2}
footer{margin-top:24px;color:#6f8c9d;font-size:12px;text-align:center}
.hidden{display:none!important}
@media (max-width:880px){
.grid,.visuals{grid-template-columns:1fr}
.metrics{grid-template-columns:repeat(2,1fr)}
}
@media (max-width:560px){
.wrap{padding:14px 12px 40px}
.hero,.panel{padding:17px;border-radius:16px}
.field-grid{grid-template-columns:1fr 135px}
.metrics{grid-template-columns:1fr}
.summary-row{grid-template-columns:1fr}
}
@media print{
body{background:white;color:#111}
.wrap{max-width:none;padding:0}
.hero-actions,.input-area,.privacy,footer{display:none!important}
.hero,.panel,.metric{box-shadow:none;background:white;color:#111;border-color:#ccc}
.hero p,.sub,.metric-name,.metric-info,.results-head p,.legend,.notice,details p{color:#444}
.results{margin-top:0}
.metrics{grid-template-columns:repeat(3,1fr)}
.visuals{grid-template-columns:1fr 1fr}
.badge{color:#111;background:#eee;border-color:#ccc}
}
</style>
</head>
<body>
<div class="wrap">
<section class="hero">
<span class="badge">📊 Bilanz · Local-first Financial Tool</span>
<h1>Bilanz QuickCheck</h1>
<p>Zentrale Bilanzkennzahlen berechnen, zwei Geschäftsjahre vergleichen und die Finanzstruktur übersichtlich darstellen – direkt im Browser.</p>
<div class="hero-actions">
<button class="primary" onclick="loadDemo()">Beispieldaten laden</button>
<button class="secondary" onclick="window.print()">Bericht drucken / PDF</button>
<button class="ghost" onclick="resetAll()">Zurücksetzen</button>
</div>
<div class="privacy">
<div>🔒</div>
<div><strong>Private by design:</strong> Alle Werte werden ausschließlich in deinem Browser verarbeitet. Dieser Static Space überträgt oder speichert keine eingegebenen Finanzdaten.</div>
</div>
</section>
<div class="grid input-area">
<section class="panel">
<div class="year-label">
<div>
<h2>Aktuelles Jahr</h2>
<div class="sub">Pflichtangaben für die Kennzahlen</div>
</div>
<input class="year-input" id="year0" type="number" value="2025" min="1900" max="2200" aria-label="Aktuelles Geschäftsjahr">
</div>
<div id="fields0"></div>
<div id="summary0"></div>
</section>
<section class="panel">
<div class="year-label">
<div>
<h2>Vorjahr <span style="color:var(--muted);font-weight:500">(optional)</span></h2>
<div class="sub">Für Veränderungsanalyse</div>
</div>
<input class="year-input" id="year1" type="number" value="2024" min="1900" max="2200" aria-label="Vorjahr">
</div>
<div id="fields1"></div>
<div id="summary1"></div>
</section>
</div>
<section class="panel results">
<div class="results-head">
<div>
<h2>Kennzahlen</h2>
<p>Rechnerische Orientierung – keine fachliche Unternehmensbewertung.</p>
</div>
</div>
<div class="metrics" id="metrics"></div>
</section>
<div class="visuals">
<section class="panel">
<h2>Vermögensstruktur</h2>
<div class="sub">Zusammensetzung der Aktivseite im aktuellen Jahr</div>
<div class="stack" id="assetsStack"></div>
<div class="legend" id="assetsLegend"></div>
</section>
<section class="panel">
<h2>Finanzierungsstruktur</h2>
<div class="sub">Zusammensetzung der Passivseite im aktuellen Jahr</div>
<div class="stack" id="liabStack"></div>
<div class="legend" id="liabLegend"></div>
</section>
</div>
<section class="panel explainer">
<h2>Kennzahlen verstehen</h2>
<div class="sub">Formeln und neutrale Einordnung</div>
<details open>
<summary>Eigenkapitalquote</summary>
<p>Eigenkapital ÷ Bilanzsumme × 100. Sie beschreibt den Anteil des Vermögens, der rechnerisch durch Eigenkapital finanziert ist. Branchen und Geschäftsmodelle unterscheiden sich stark.</p>
</details>
<details>
<summary>Verschuldungsgrad</summary>
<p>Fremdkapital ÷ Eigenkapital × 100. Die Kennzahl stellt Fremd- und Eigenfinanzierung gegenüber. Bei sehr geringem oder negativem Eigenkapital verliert die Kennzahl an Aussagekraft.</p>
</details>
<details>
<summary>Working Capital</summary>
<p>Umlaufvermögen minus kurzfristige Verbindlichkeiten. Es zeigt den rechnerischen Überschuss beziehungsweise Fehlbetrag kurzfristig gebundener Vermögenswerte gegenüber kurzfristigen Verpflichtungen.</p>
</details>
<details>
<summary>Liquidität 1., 2. und 3. Grades</summary>
<p>Vergleichen liquide Mittel, liquide Mittel plus Forderungen beziehungsweise das gesamte Umlaufvermögen mit kurzfristigen Verbindlichkeiten. Die Aussage hängt unter anderem von Zahlungszielen, Forderungsqualität und Vorratsstruktur ab.</p>
</details>
<details>
<summary>Anlagendeckung I und II</summary>
<p>Vergleichen Anlagevermögen mit langfristig verfügbarem Kapital. Anlagendeckung I verwendet Eigenkapital, Anlagendeckung II zusätzlich langfristige Verbindlichkeiten.</p>
</details>
</section>
<div class="notice">
<strong>Wichtiger Hinweis:</strong> Bilanz QuickCheck ist ein technisches Analyse- und Lernwerkzeug. Es ersetzt keine Steuerberatung, Rechtsberatung, Wirtschaftsprüfung, Kreditentscheidung oder Unternehmensbewertung. Kennzahlen sind ohne Branchen-, Zeitreihen- und Unternehmenskontext nicht abschließend interpretierbar.
</div>
<footer>Bilanz · Independent Hugging Face Community Project</footer>
</div>
<script>
const fieldDefs = [
{group:"Aktiva", key:"fixed", label:"Anlagevermögen"},
{group:"Aktiva", key:"inventory", label:"Vorräte"},
{group:"Aktiva", key:"receivables", label:"Forderungen"},
{group:"Aktiva", key:"cash", label:"Liquide Mittel"},
{group:"Aktiva", key:"otherCurrent", label:"Sonstiges Umlaufvermögen"},
{group:"Passiva", key:"equity", label:"Eigenkapital"},
{group:"Passiva", key:"longDebt", label:"Langfristige Verbindlichkeiten"},
{group:"Passiva", key:"shortDebt", label:"Kurzfristige Verbindlichkeiten"},
{group:"Passiva", key:"provisions", label:"Rückstellungen"},
{group:"Passiva", key:"otherLiab", label:"Sonstige Passiva"}
];
function euro(n){
if(!Number.isFinite(n)) return "–";
return new Intl.NumberFormat("de-DE",{style:"currency",currency:"EUR",maximumFractionDigits:0}).format(n);
}
function pct(n){
if(!Number.isFinite(n)) return "–";
return new Intl.NumberFormat("de-DE",{maximumFractionDigits:1,minimumFractionDigits:1}).format(n)+" %";
}
function num(id){
const v=parseFloat(document.getElementById(id)?.value);
return Number.isFinite(v)?v:0;
}
function safeDiv(a,b){ return b!==0 ? a/b : NaN; }
function renderFields(yearIndex){
let html="", lastGroup="";
for(const f of fieldDefs){
if(f.group!==lastGroup){
html+=`<div class="section-title">${f.group}</div><div class="field-grid">`;
lastGroup=f.group;
}
html+=`<label for="${f.key}${yearIndex}">${f.label}</label>
<input id="${f.key}${yearIndex}" data-y="${yearIndex}" type="number" min="0" step="1000" placeholder="0" inputmode="decimal">`;
const next=fieldDefs[fieldDefs.indexOf(f)+1];
if(!next || next.group!==f.group) html+="</div>";
}
document.getElementById("fields"+yearIndex).innerHTML=html;
}
function getYear(i){
const x={};
fieldDefs.forEach(f=>x[f.key]=num(f.key+i));
x.current=x.inventory+x.receivables+x.cash+x.otherCurrent;
x.assets=x.fixed+x.current;
x.debt=x.longDebt+x.shortDebt+x.provisions+x.otherLiab;
x.liabilities=x.equity+x.debt;
x.diff=x.assets-x.liabilities;
return x;
}
function metricsFor(x){
return {
equityRatio: safeDiv(x.equity,x.assets)*100,
debtRatio: safeDiv(x.debt,x.liabilities)*100,
debtEquity: safeDiv(x.debt,x.equity)*100,
workingCapital: x.current-x.shortDebt,
cashRatio: safeDiv(x.cash,x.shortDebt)*100,
quickRatio: safeDiv(x.cash+x.receivables,x.shortDebt)*100,
currentRatio: safeDiv(x.current,x.shortDebt)*100,
cover1: safeDiv(x.equity,x.fixed)*100,
cover2: safeDiv(x.equity+x.longDebt,x.fixed)*100,
fixedIntensity: safeDiv(x.fixed,x.assets)*100,
cashShare: safeDiv(x.cash,x.assets)*100
};
}
const metricDefs = [
["equityRatio","Eigenkapitalquote","pct","Eigenkapital ÷ Bilanzsumme"],
["debtRatio","Fremdkapitalquote","pct","Fremdkapital ÷ Passivsumme"],
["debtEquity","Verschuldungsgrad","pct","Fremdkapital ÷ Eigenkapital"],
["workingCapital","Working Capital","eur","Umlaufvermögen − kurzfr. Verbindlichkeiten"],
["cashRatio","Liquidität 1. Grades","pct","Liquide Mittel ÷ kurzfr. Verbindlichkeiten"],
["quickRatio","Liquidität 2. Grades","pct","Liquide Mittel + Forderungen ÷ kurzfr. Verbindlichkeiten"],
["currentRatio","Liquidität 3. Grades","pct","Umlaufvermögen ÷ kurzfr. Verbindlichkeiten"],
["cover1","Anlagendeckung I","pct","Eigenkapital ÷ Anlagevermögen"],
["cover2","Anlagendeckung II","pct","EK + langfristige Verbindlichkeiten ÷ Anlagevermögen"],
["fixedIntensity","Anlagenintensität","pct","Anlagevermögen ÷ Bilanzsumme"],
["cashShare","Anteil liquide Mittel","pct","Liquide Mittel ÷ Bilanzsumme"]
];
function hasPrior(){
return fieldDefs.some(f=>num(f.key+"1")!==0);
}
function renderSummary(i,x){
const tolerance=Math.max(x.assets,x.liabilities,1)*0.001;
const balanced=Math.abs(x.diff)<=tolerance;
document.getElementById("summary"+i).innerHTML=`
<div class="summary-row">
<div class="sum-box"><small>Summe Aktiva</small><strong>${euro(x.assets)}</strong></div>
<div class="sum-box"><small>Summe Passiva</small><strong>${euro(x.liabilities)}</strong></div>
</div>
<div class="balance-message" style="border-color:${balanced?'rgba(85,217,139,.28)':'rgba(255,123,123,.35)'}">
${balanced?'✓ Aktiv- und Passivsumme stimmen rechnerisch überein.':'⚠ Bilanzdifferenz: '+euro(x.diff)+'. Eingaben prüfen.'}
</div>`;
}
function deltaText(a,b,type){
if(!Number.isFinite(a)||!Number.isFinite(b)) return ["","flat"];
const d=a-b;
const threshold=type==="eur" ? 0.5 : 0.05;
if(Math.abs(d)<threshold) return ["Keine Veränderung","flat"];
const value=type==="eur"?euro(Math.abs(d)):pct(Math.abs(d));
return [(d>0?"▲ ":"▼ ")+value+" ggü. Vorjahr",d>0?"up":"down"];
}
function renderMetrics(m0,m1){
const prior=hasPrior();
document.getElementById("metrics").innerHTML=metricDefs.map(([k,name,type,info])=>{
const val=type==="eur"?euro(m0[k]):pct(m0[k]);
let d=["","flat"];
if(prior) d=deltaText(m0[k],m1[k],type);
return `<div class="metric">
<div class="metric-name">${name}</div>
<div class="metric-value">${val}</div>
<div class="metric-delta ${d[1]}">${d[0]}</div>
<div class="metric-info">${info}</div>
</div>`;
}).join("");
}
function stack(el, legendEl, items){
const total=items.reduce((s,x)=>s+x.value,0);
document.getElementById(el).innerHTML=items.map((x,i)=>{
const w=total?Math.max(0,x.value/total*100):0;
return `<div class="seg s${i+1}" style="width:${w}%" title="${x.name}: ${pct(w)}"></div>`;
}).join("");
document.getElementById(legendEl).innerHTML=items.map((x,i)=>{
const w=total?x.value/total*100:0;
return `<span><i class="dot s${i+1}"></i>${x.name} ${pct(w)}</span>`;
}).join("");
}
function update(){
const y0=getYear(0), y1=getYear(1);
renderSummary(0,y0); renderSummary(1,y1);
renderMetrics(metricsFor(y0),metricsFor(y1));
stack("assetsStack","assetsLegend",[
{name:"Anlagevermögen",value:y0.fixed},
{name:"Vorräte",value:y0.inventory},
{name:"Forderungen",value:y0.receivables},
{name:"Liquide Mittel",value:y0.cash},
{name:"Sonst. UV",value:y0.otherCurrent}
]);
stack("liabStack","liabLegend",[
{name:"Eigenkapital",value:y0.equity},
{name:"Langfr. Verb.",value:y0.longDebt},
{name:"Kurzfr. Verb.",value:y0.shortDebt},
{name:"Rückstellungen",value:y0.provisions},
{name:"Sonst. Passiva",value:y0.otherLiab}
]);
}
function setVals(i, vals){
for(const [k,v] of Object.entries(vals)){
const e=document.getElementById(k+i);
if(e) e.value=v;
}
}
function loadDemo(){
setVals(0,{
fixed:4800000,inventory:950000,receivables:1750000,cash:1200000,otherCurrent:300000,
equity:3600000,longDebt:2450000,shortDebt:1950000,provisions:700000,otherLiab:300000
});
setVals(1,{
fixed:4550000,inventory:900000,receivables:1600000,cash:950000,otherCurrent:250000,
equity:3250000,longDebt:2400000,shortDebt:1850000,provisions:550000,otherLiab:200000
});
document.getElementById("year0").value=2025;
document.getElementById("year1").value=2024;
update();
}
function resetAll(){
fieldDefs.forEach(f=>{document.getElementById(f.key+"0").value="";document.getElementById(f.key+"1").value=""});
update();
}
renderFields(0);
renderFields(1);
document.addEventListener("input",e=>{
if(e.target.matches('input[type="number"]')) update();
});
update();
</script>
</body>
</html>