diff options
| author | Matthias Andreas Benkard <code@mail.matthias.benkard.de> | 2026-08-07 12:49:30 +0200 |
|---|---|---|
| committer | Matthias Andreas Benkard <code@mail.matthias.benkard.de> | 2026-08-22 19:18:03 +0200 |
| commit | bf8c6d7cf96ba8602f8b2c34ef5345f461d74f3a (patch) | |
| tree | 4ae3c36fffd505c73b1cd7241dd91340d5a8bfbd /app/index.html | |
| parent | 8a2862590bb76d90597fe02432ac2e01c70d26a2 (diff) | |
Windows-95-Design
Diffstat (limited to 'app/index.html')
| -rw-r--r-- | app/index.html | 610 |
1 files changed, 413 insertions, 197 deletions
diff --git a/app/index.html b/app/index.html index 83ab1b1..22b14ee 100644 --- a/app/index.html +++ b/app/index.html @@ -3,163 +3,313 @@ <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"> -<meta name="theme-color" content="#1f3864"> +<meta name="theme-color" content="#000080"> <meta name="description" content="Betriebliche Altersvorsorge, Altersvorsorgedepot und privates ETF-Depot bei gleichem Nettoaufwand vergleichen. Rechtsstand 2026."> <title>Altersvorsorge-Vergleich</title> <link rel="manifest" href="manifest.json"> <link rel="apple-touch-icon" href="icon-192.png"> <style> +/* ===================================================================== + Windows 3.1 / NT 3.5. 16-Farben-VGA-Palette, harte 1-Pixel-Kanten. + Wo 3.1 kein passendes Steuerelement kannte, wird das aus Windows 95 / + NT 4 genommen und als solches kenntlich gemacht: + - Schieberegler (Trackbar) -> Win95 common control, 3.1 hatte keinen + - Registerkarten (Tab control) -> Win95, 3.1 loeste das mit Dialogen + - versenkter Rahmen (CLIENTEDGE)-> Win95, liest sich besser als 3.1s + einfacher schwarzer Rahmen + Keine Dunkelvariante: das Silber IST das Thema. + ===================================================================== */ :root{ - --bg:#f6f7fa; --card:#ffffff; --ink:#15181d; --muted:#5c6470; --line:#dde1e8; - --accent:#1f3864; --bav:#c9761d; --av:#2266b8; --priv:#2b8a5a; - --good:#1e7a48; --bad:#b3261e; --chip:#eef1f6; - --r:14px; --sh:0 1px 2px rgba(16,24,40,.06), 0 6px 20px rgba(16,24,40,.05); - --mono:ui-monospace,SFMono-Regular,"SF Mono",Menlo,Consolas,monospace; -} -@media (prefers-color-scheme: dark){ - :root{ --bg:#0e1116; --card:#161a21; --ink:#e7eaf0; --muted:#97a0ae; --line:#262c36; - --accent:#8fb0e8; --bav:#e29a4b; --av:#5c9ce8; --priv:#4fb583; - --good:#4fb583; --bad:#e5726a; --chip:#1e242d; - --sh:0 1px 2px rgba(0,0,0,.35), 0 6px 20px rgba(0,0,0,.25); } + /* die 16 VGA-Farben, aus denen alles gebaut ist */ + --black:#000000; --maroon:#800000; --green:#008000; --olive:#808000; + --navy:#000080; --purple:#800080; --teal:#008080; --silver:#C0C0C0; + --gray:#808080; --red:#FF0000; --lime:#00FF00; --yellow:#FFFF00; + --blue:#0000FF; --fuchsia:#FF00FF;--aqua:#00FFFF; --white:#FFFFFF; + /* Win95 kannte zusaetzlich das helle Grau der aeusseren Kante */ + --face-lt:#DFDFDF; + + --desk:var(--teal); /* Programm-Manager-Hintergrund */ + --face:var(--silver); /* Dialogflaeche */ + --field:var(--white); /* Eingabefeld */ + --ink:var(--black); + --dim:var(--gray); /* NUR fuer Deaktiviertes und Zierrat */ + --title:var(--navy); + --title-fg:var(--white); + + --bav:#800000; --av:#000080; --priv:#008000; + --good:#008000; --bad:#800000; + + --ui:"MS Sans Serif","Microsoft Sans Serif",Tahoma,Geneva,Verdana,Arial,sans-serif; + --mono:"Fixedsys","Lucida Console","Courier New",monospace; } *{box-sizing:border-box} html{-webkit-text-size-adjust:100%} -body{margin:0;background:var(--bg);color:var(--ink); - font:16px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif; +body{margin:0;background:var(--desk);color:var(--ink);font:13px/1.45 var(--ui); padding-bottom:env(safe-area-inset-bottom)} -.wrap{max-width:920px;margin:0 auto;padding:16px 14px 64px} -h1{font-size:1.25rem;margin:.2em 0 .1em;letter-spacing:-.01em} -h2{font-size:1.02rem;margin:0 0 .1em;letter-spacing:-.01em} -h3{font-size:.9rem;margin:1.2em 0 .5em;color:var(--muted);text-transform:uppercase;letter-spacing:.06em} -p{margin:.4em 0} -.sub{color:var(--muted);font-size:.85rem} -.card{background:var(--card);border:1px solid var(--line);border-radius:var(--r);padding:14px;box-shadow:var(--sh);margin:12px 0} -.card>h2{margin-bottom:.35em} +.wrap{max-width:940px;margin:0 auto;padding:10px 8px 40px} + +/* ---------- Die drei Kantenformen ---------- + Sie sind unten ueberall ausgeschrieben statt als Hilfsklassen vergeben, + damit jede Regel fuer sich lesbar bleibt. Zum Nachschlagen: + + erhaben (Schaltflaeche, Win 3.1): + border:1px solid #000 + inset 1px 1px 0 #FFF + inset -1px -1px 0 #808080 + erhaben (Fenster/Kachel, Win 95, aeussere Kante zusaetzlich): + border-color:#DFDFDF #000 #000 #DFDFDF + dieselben inset-Schatten + versenkt (Eingabefeld, Klientenbereich): + border-color:#808080 #FFF #FFF #808080 + inset 1px 1px 0 #000 + + inset -1px -1px 0 #DFDFDF + geaetzt (Gruppenrahmen): nur border-color:#808080 #FFF #FFF #808080 +*/ + +/* ---------- Fenster ---------- */ +.win{background:var(--face);margin:0 0 8px; + border:1px solid;border-color:var(--face-lt) var(--black) var(--black) var(--face-lt); + box-shadow:inset 1px 1px 0 var(--white), inset -1px -1px 0 var(--gray)} +.titlebar{display:flex;align-items:center;gap:3px;background:var(--title);color:var(--title-fg); + padding:2px 2px 2px 3px;margin:2px;font-weight:700;font-size:12px;user-select:none} +.tbtext{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap; + letter-spacing:.01em;padding-left:2px} +/* Systemmenue-Kasten links: unter 3.1 ein Kasten mit waagerechtem Strich */ +.sysbox{width:17px;height:15px;flex:0 0 auto;background:var(--face);position:relative; + border:1px solid;border-color:var(--white) var(--black) var(--black) var(--white)} +.sysbox::after{content:"";position:absolute;left:2px;right:2px;top:6px;height:2px; + background:var(--black);border-top:1px solid var(--gray)} +.tbbtn{width:17px;height:15px;flex:0 0 auto;padding:0;background:var(--face);color:var(--black); + font:9px/13px var(--ui);cursor:pointer; + border:1px solid;border-color:var(--white) var(--black) var(--black) var(--white)} +.tbbtn:active{border-color:var(--black) var(--white) var(--white) var(--black)} +.winbody{padding:9px 10px 11px} +.win.rolled .winbody{display:none} + +/* ---------- Schrift ---------- */ +h1{font-size:15px;margin:0 0 2px;font-weight:700} +h2{font-size:13px;margin:0 0 3px;font-weight:700} +h3{font-size:12px;margin:12px 0 5px;font-weight:700} +p{margin:4px 0} +/* Grau ist unter Windows die Farbe des Deaktivierten. Lesbarer Text bleibt + schwarz und wird ueber Groesse und Kursivstellung abgestuft. */ +.sub{font-size:12px;font-style:italic} +.pill{display:inline-block;background:var(--face);padding:0 5px;font-size:11px; + border:1px solid;border-color:var(--gray) var(--white) var(--white) var(--gray)} + +/* ---------- Schaltflaechen (Win 3.1) ---------- */ +.btn,.qm,.tabs button,.sb-up{font-family:var(--ui)} +.btn{appearance:none;background:var(--face);color:var(--ink);font-size:12px;padding:4px 12px; + min-height:25px;cursor:pointer;border:1px solid var(--black); + box-shadow:inset 1px 1px 0 var(--white), inset -1px -1px 0 var(--gray)} +.btn:active{box-shadow:inset 1px 1px 0 var(--gray), inset -1px -1px 0 var(--white); + padding:5px 11px 3px 13px} +.btn.pri{font-weight:700;outline:1px solid var(--black);outline-offset:1px} +.btn[disabled]{color:var(--gray);text-shadow:1px 1px 0 var(--white);cursor:default} +.qm{appearance:none;width:17px;height:16px;padding:0;background:var(--face);color:var(--ink); + font-size:11px;font-weight:700;line-height:14px;text-align:center;cursor:pointer; + margin-left:5px;vertical-align:-2px;border:1px solid var(--black); + box-shadow:inset 1px 1px 0 var(--white), inset -1px -1px 0 var(--gray)} +.qm:active{box-shadow:inset 1px 1px 0 var(--gray), inset -1px -1px 0 var(--white)} +.row{display:flex;gap:6px;flex-wrap:wrap;align-items:center} + +/* ---------- Fokusrechteck ---------- * + Das gepunktete Rechteck ist das Original und zugleich barrierefrei. */ +:focus-visible{outline:1px dotted var(--black);outline-offset:-4px} +input[type=text]:focus-visible,input[type=number]:focus-visible{outline-offset:-3px} + /* ---------- Ergebniskarten ---------- */ -#metricbar{display:flex;gap:6px;flex-wrap:wrap;margin:10px 0 4px} -.seg{display:inline-flex;background:var(--chip);border-radius:10px;padding:3px;gap:2px;flex-wrap:wrap} -.seg button{appearance:none;border:0;background:transparent;color:var(--muted);font:inherit;font-size:.84rem; - padding:7px 11px;border-radius:8px;cursor:pointer;white-space:nowrap;min-height:36px} -.seg button[aria-pressed=true]{background:var(--card);color:var(--ink);font-weight:600;box-shadow:var(--sh)} -#cards{display:grid;grid-template-columns:repeat(3,1fr);gap:10px} +#metricbar{display:flex;gap:0;flex-wrap:wrap;margin:2px 0 5px} +.seg{display:inline-flex;gap:0;flex-wrap:wrap} +#metricbar button,.seg.btnseg button{appearance:none;background:var(--face);color:var(--ink); + font:12px var(--ui);padding:4px 11px;min-height:25px;cursor:pointer;white-space:nowrap; + border:1px solid var(--black);margin-right:-1px; + box-shadow:inset 1px 1px 0 var(--white), inset -1px -1px 0 var(--gray)} +/* Gedrueckt = versenkt, dazu das Rastermuster der eingerasteten Werkzeugleiste + aus Windows 3.1. */ +#metricbar button[aria-pressed=true],.seg.btnseg button[aria-pressed=true]{font-weight:700; + box-shadow:inset 1px 1px 0 var(--gray), inset -1px -1px 0 var(--white); + background:repeating-conic-gradient(var(--face) 0% 25%, var(--face-lt) 0% 50%) 0 0/2px 2px} +.seg.btnseg{margin-left:auto;align-self:flex-end;padding-bottom:2px} +.seg.btnseg button{font-size:11px;padding:3px 9px;min-height:22px} +#cards{display:grid;grid-template-columns:repeat(3,1fr);gap:6px} @media(max-width:640px){#cards{grid-template-columns:1fr}} -.opt{border:1px solid var(--line);border-radius:12px;padding:12px;position:relative;overflow:hidden; - background:var(--card);transition:border-color .15s} -.opt::before{content:"";position:absolute;inset:0 auto 0 0;width:4px;background:var(--c)} -.opt.win{border-color:var(--c)} -.opt .nm{font-size:.8rem;color:var(--muted);font-weight:600;display:flex;align-items:center;gap:6px} -.opt .big{font-size:1.5rem;font-weight:700;font-variant-numeric:tabular-nums;letter-spacing:-.02em;margin:.12em 0} -@media(max-width:640px){.opt .big{font-size:1.35rem}} -.opt .sec{font-size:.78rem;color:var(--muted);font-variant-numeric:tabular-nums} -.delta{font-size:.78rem;font-weight:600;font-variant-numeric:tabular-nums;display:inline-block; - padding:1px 7px;border-radius:99px;background:var(--chip);margin-top:4px} -.delta.up{color:var(--good)} .delta.dn{color:var(--bad)} .delta.eq{color:var(--muted)} -.crown{font-size:.68rem;background:var(--c);color:#fff;border-radius:99px;padding:1px 7px;font-weight:700} -@media (prefers-color-scheme: dark){.crown{color:#0e1116}} -#baserow{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-top:10px;font-size:.8rem;color:var(--muted)} - -/* ---------- Eingaben ---------- */ -.field{display:grid;grid-template-columns:1fr auto;gap:2px 10px;align-items:center;padding:9px 0;border-top:1px solid var(--line)} -.field:first-of-type{border-top:0} -.field .lab{font-size:.9rem;grid-column:1} +.opt{background:var(--face);padding:7px 9px 8px;position:relative; + border:1px solid;border-color:var(--white) var(--gray) var(--gray) var(--white)} +.opt.lead{border-color:var(--gray) var(--white) var(--white) var(--gray);background:var(--face-lt)} +.opt .nm{font-size:11px;font-weight:700;display:flex;align-items:center;gap:5px} +.opt .nm::before{content:"";width:9px;height:9px;flex:0 0 auto;background:var(--c); + border:1px solid var(--black)} +.opt .big{font-size:20px;font-weight:700;font-family:var(--mono);letter-spacing:-.02em;margin:3px 0 1px} +@media(max-width:640px){.opt .big{font-size:18px}} +.opt .sec{font-size:11px;font-family:var(--mono)} +.delta{font-size:11px;font-weight:700;font-family:var(--mono);display:inline-block; + padding:0 5px;background:var(--face);margin-top:2px; + border:1px solid;border-color:var(--gray) var(--white) var(--white) var(--gray)} +.delta.up{color:var(--good)} .delta.dn{color:var(--bad)} .delta.eq{color:var(--dim)} +.crown{font-size:10px;background:var(--title);color:var(--white);padding:0 5px;font-weight:700} +#baserow{display:flex;align-items:center;gap:7px;flex-wrap:wrap;margin-top:9px;font-size:11px} + +/* ---------- Meldungsfelder ---------- * + Nachgebaut nach der 3.1-Meldungsbox: Symbol links, Text rechts. */ +.msg{display:flex;gap:8px;align-items:flex-start;background:var(--face); + font-size:12px;line-height:1.45;padding:7px 9px;margin:9px 0 0; + border:1px solid;border-color:var(--white) var(--gray) var(--gray) var(--white)} +.msg::before{content:attr(data-ico);flex:0 0 auto;width:20px;height:20px;line-height:19px; + text-align:center;font-weight:700;font-size:14px;color:var(--white);background:var(--title); + border:1px solid var(--black)} +.msg.warn::before{background:var(--maroon)} +.msg.note::before{background:var(--navy)} +.msg.demo::before{background:var(--olive)} +.verdict{margin:9px 0 0;padding:8px 10px;font-size:12.5px;line-height:1.5;background:var(--face-lt); + border:1px solid;border-color:var(--gray) var(--white) var(--white) var(--gray)} +.verdict b{font-weight:700} +.verdict .band{display:block;margin-top:6px;padding-top:6px;font-size:11.5px;font-style:italic; + border-top:1px solid var(--gray)} +.warn{color:var(--maroon);font-size:12px;font-weight:700} + +/* ---------- Eingabefelder ---------- */ +/* Geaetzte Trennlinie: eine graue Zeile mit einer weissen darunter — genau so + trennte Windows 3.1 Dialogbereiche. #DFDFDF allein waere auf Silber unsichtbar. */ +.field{display:grid;grid-template-columns:1fr auto;gap:1px 9px;align-items:center; + padding:6px 0 7px;border-bottom:1px solid var(--gray);box-shadow:0 1px 0 var(--white)} +.field:last-child{border-bottom:0;box-shadow:none} +.field .lab{font-size:12px;grid-column:1} .field .val{grid-column:2;text-align:right} .field .rng{grid-column:1/-1} -.field .hint{grid-column:1/-1;font-size:.76rem;color:var(--muted);margin-top:2px;display:none} +.field .hint{grid-column:1/-1;font-size:11.5px;line-height:1.45;margin-top:4px;display:none; + background:var(--white);padding:6px 8px; + border:1px solid;border-color:var(--gray) var(--white) var(--white) var(--gray)} .field.open .hint{display:block} -.qm{appearance:none;border:0;background:var(--chip);color:var(--muted);width:19px;height:19px;border-radius:99px; - font-size:.72rem;line-height:19px;text-align:center;padding:0;cursor:pointer;margin-left:5px;vertical-align:1px} -input[type=number],input[type=text].num{width:8.2em;font:inherit;font-size:.92rem;font-variant-numeric:tabular-nums; - text-align:right;padding:6px 8px;border:1px solid var(--line);border-radius:8px;background:var(--bg);color:var(--ink);min-height:34px} -input[type=range]{width:100%;margin:6px 0 0;accent-color:var(--accent);height:26px} -.suffix{color:var(--muted);font-size:.85rem;margin-left:4px} -.field.pair-top{border-bottom:0;padding-bottom:4px} -.field.pair-bottom{border-top:0;padding-top:4px;position:relative} -.field.pair-bottom::before{content:"";position:absolute;left:-8px;top:-6px;bottom:14px;width:3px; - border-radius:2px;background:var(--accent);opacity:.28} -.field.pair-top::after{content:"";position:absolute;left:-8px;width:3px} -.couple{grid-column:1/-1;font-size:.74rem;color:var(--muted);margin-top:4px;display:flex;gap:5px;align-items:baseline} -.couple span{color:var(--accent);font-weight:700} -.livenote{grid-column:1/-1;font-size:.75rem;line-height:1.45;color:var(--bad);margin-top:5px; - background:var(--chip);border-radius:8px;padding:6px 9px} -details{border-top:1px solid var(--line)} -details>summary{cursor:pointer;padding:11px 2px;font-weight:600;font-size:.92rem;list-style:none;display:flex; - justify-content:space-between;align-items:center;gap:8px;min-height:44px} +input[type=text].num,input[type=number]{width:8.6em;font:13px var(--mono);text-align:right; + padding:2px 4px;min-height:23px;background:var(--field);color:var(--ink); + border:1px solid;border-color:var(--gray) var(--white) var(--white) var(--gray); + box-shadow:inset 1px 1px 0 var(--black), inset -1px -1px 0 var(--face-lt)} +.suffix{font-size:11px;margin-left:4px} + +/* ---------- Trackbar (Windows 95) ---------- */ +input[type=range]{-webkit-appearance:none;appearance:none;width:100%;margin:7px 0 3px; + height:24px;background:transparent;cursor:pointer} +input[type=range]::-webkit-slider-runnable-track{height:4px;background:var(--face); + border:1px solid;border-color:var(--gray) var(--white) var(--white) var(--gray); + box-shadow:inset 1px 1px 0 var(--black)} +input[type=range]::-moz-range-track{height:4px;background:var(--face); + border:1px solid;border-color:var(--gray) var(--white) var(--white) var(--gray); + box-shadow:inset 1px 1px 0 var(--black)} +input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;width:11px;height:20px; + margin-top:-8px;background:var(--face);border:1px solid var(--black); + box-shadow:inset 1px 1px 0 var(--white), inset -1px -1px 0 var(--gray)} +input[type=range]::-moz-range-thumb{width:11px;height:20px;border-radius:0; + background:var(--face);border:1px solid var(--black); + box-shadow:inset 1px 1px 0 var(--white), inset -1px -1px 0 var(--gray)} + +/* ---------- Optionsfelder (Windows 3.1) ---------- */ +.radios{grid-column:1/-1;display:flex;flex-wrap:wrap;gap:3px 16px;margin-top:3px} +.radios label{display:inline-flex;align-items:center;gap:6px;font-size:12px;cursor:pointer; + min-height:22px;padding:1px 2px} +.radios input{appearance:none;-webkit-appearance:none;margin:0;width:13px;height:13px;flex:0 0 auto; + border-radius:50%;background:var(--white);position:relative;cursor:pointer; + border:1px solid;border-color:var(--gray) var(--white) var(--white) var(--gray); + box-shadow:inset 1px 1px 0 var(--black), inset -1px -1px 0 var(--face-lt)} +.radios input:checked::after{content:"";position:absolute;inset:3px;border-radius:50%; + background:var(--black)} +.radios label:has(input:checked){font-weight:700} +.radios input:focus-visible{outline:none} +.radios label:has(input:focus-visible){outline:1px dotted var(--black);outline-offset:0} + +/* ---------- Kopplungsband und Live-Notiz ---------- */ +.field.pair-top{border-bottom:0;box-shadow:none;padding-bottom:2px} +.field.pair-bottom{border-top:0;padding-top:2px;position:relative} +.couple{grid-column:1/-1;font-size:11px;margin-top:3px;display:flex;gap:5px;align-items:baseline; + font-style:italic} +.couple span{font-weight:700;font-style:normal} +.livenote{grid-column:1/-1;font-size:11.5px;line-height:1.45;margin-top:5px;padding:5px 8px; + background:var(--face);color:var(--maroon); + border:1px solid;border-color:var(--white) var(--gray) var(--gray) var(--white)} + +/* ---------- Aufklappbares (3.1 kannte das nicht; Schaltflaeche wie „Optionen >>“) ---------- */ +details{margin:0 0 3px} +details>summary{cursor:pointer;list-style:none;display:flex;align-items:center;gap:7px; + font-size:12px;font-weight:700;padding:4px 9px;min-height:25px;background:var(--face); + border:1px solid var(--black); + box-shadow:inset 1px 1px 0 var(--white), inset -1px -1px 0 var(--gray)} details>summary::-webkit-details-marker{display:none} -details>summary::after{content:"\25BE";color:var(--muted);transition:transform .15s;font-size:.8rem} -details[open]>summary::after{transform:rotate(180deg)} -summary .cnt{font-weight:400;color:var(--muted);font-size:.8rem;margin-left:auto;margin-right:4px} -.dbody{padding:0 0 8px} - -/* ---------- Diagramme ---------- */ -.tabs{display:flex;gap:4px;overflow-x:auto;-webkit-overflow-scrolling:touch;margin-bottom:8px;padding-bottom:2px} -.tabs button{appearance:none;border:1px solid var(--line);background:var(--card);color:var(--muted);font:inherit; - font-size:.82rem;padding:7px 11px;border-radius:99px;cursor:pointer;white-space:nowrap;min-height:36px} -.tabs button[aria-pressed=true]{background:var(--accent);border-color:var(--accent);color:#fff;font-weight:600} -@media (prefers-color-scheme: dark){.tabs button[aria-pressed=true]{color:#0e1116}} -canvas{width:100%;display:block;touch-action:pan-y} -.legend{display:flex;gap:12px;flex-wrap:wrap;font-size:.78rem;color:var(--muted);margin-top:8px} -.legend i{display:inline-block;width:11px;height:3px;border-radius:2px;vertical-align:3px;margin-right:5px} -.readout{font-size:.8rem;color:var(--muted);min-height:1.4em;margin-top:6px;font-variant-numeric:tabular-nums} +details>summary::before{content:"\25B8";font-size:10px;width:9px} +details[open]>summary::before{content:"\25BE"} +summary .cnt{font-weight:400;margin-left:auto;font-size:11px;font-style:italic} +.dbody{padding:7px 9px 4px;background:var(--face); + border:1px solid;border-color:var(--white) var(--gray) var(--gray) var(--white); + border-top:0} + +/* ---------- Registerkarten (Windows 95) ---------- */ +.tabs{display:flex;gap:1px;overflow-x:auto;-webkit-overflow-scrolling:touch; + margin:0 0 -1px;padding:2px 0 0;position:relative;z-index:2} +.tabs button{appearance:none;background:var(--face);color:var(--ink);font-size:12px; + padding:4px 11px 5px;min-height:25px;cursor:pointer;white-space:nowrap; + border:1px solid;border-color:var(--white) var(--gray) transparent var(--white); + border-bottom:0;position:relative;top:2px} +.tabs button[aria-pressed=true]{font-weight:700;top:0;padding:5px 13px 6px;background:var(--face); + border-color:var(--white) var(--black) transparent var(--white)} +#chartwrap{background:var(--face);padding:8px 9px 9px;position:relative; + border:1px solid;border-color:var(--white) var(--black) var(--black) var(--white); + box-shadow:inset -1px -1px 0 var(--gray)} +canvas{width:100%;display:block;touch-action:pan-y;background:var(--white); + border:1px solid;border-color:var(--gray) var(--white) var(--white) var(--gray); + box-shadow:inset 1px 1px 0 var(--black), inset -1px -1px 0 var(--face-lt)} +.legend{display:flex;gap:11px;flex-wrap:wrap;font-size:11px;margin-top:7px} +.legend i{display:inline-block;width:11px;height:9px;vertical-align:-1px;margin-right:5px; + border:1px solid var(--black)} +.readout{font-size:11.5px;min-height:1.4em;margin-top:6px;font-family:var(--mono); + background:var(--face);padding:3px 6px; + border:1px solid;border-color:var(--gray) var(--white) var(--white) var(--gray)} +.note{font-size:11.5px;line-height:1.45;margin:7px 0 0;font-style:italic} /* ---------- Tabellen ---------- */ -.scroll{overflow-x:auto;-webkit-overflow-scrolling:touch} -table{border-collapse:collapse;width:100%;font-size:.8rem;font-variant-numeric:tabular-nums} -th,td{padding:5px 7px;text-align:right;border-bottom:1px solid var(--line);white-space:nowrap} -th:first-child,td:first-child{text-align:left;white-space:normal;min-width:12em} -thead th{color:var(--muted);font-weight:600;font-size:.74rem;text-transform:uppercase;letter-spacing:.04em; - position:sticky;top:0;background:var(--card)} -tr.hl td{font-weight:700} -.note{font-size:.78rem;color:var(--muted);margin:.5em 0 0} -.btn{appearance:none;border:1px solid var(--line);background:var(--card);color:var(--ink);font:inherit;font-size:.84rem; - padding:8px 12px;border-radius:9px;cursor:pointer;min-height:38px} -.btn:hover{border-color:var(--accent)} -.btn.pri{background:var(--accent);border-color:var(--accent);color:#fff} -@media (prefers-color-scheme: dark){.btn.pri{color:#0e1116}} -.row{display:flex;gap:8px;flex-wrap:wrap;align-items:center} -.warn{color:var(--bad);font-size:.82rem;font-weight:600} -.hintbox{font-size:.8rem;line-height:1.55;background:var(--chip);border-left:3px solid var(--accent); - border-radius:0 9px 9px 0;padding:9px 12px;margin-top:12px;color:var(--muted)} -.hintbox b{color:var(--ink)} -footer{color:var(--muted);font-size:.76rem;margin-top:26px;line-height:1.6} -kbd{font:inherit;font-family:var(--mono);font-size:.85em;background:var(--chip);padding:1px 5px;border-radius:5px} -a{color:var(--accent)} -.pill{display:inline-block;background:var(--chip);border-radius:99px;padding:1px 8px;font-size:.75rem;color:var(--muted)} - -/* ---------- mitlaufende Ergebnisleiste ---------- */ -#stickybar{position:fixed;top:0;left:0;right:0;z-index:50;background:var(--card); - border-bottom:1px solid var(--line);box-shadow:0 2px 12px rgba(16,24,40,.10); - padding-top:env(safe-area-inset-top)} -@media (prefers-color-scheme: dark){#stickybar{box-shadow:0 2px 12px rgba(0,0,0,.4)}} -.sb-inner{max-width:920px;margin:0 auto;padding:6px 14px;display:flex;align-items:center;gap:10px} +.scroll{overflow:auto;-webkit-overflow-scrolling:touch;max-height:60vh;background:var(--white); + border:1px solid;border-color:var(--gray) var(--white) var(--white) var(--gray); + box-shadow:inset 1px 1px 0 var(--black)} +table{border-collapse:collapse;width:100%;font:11.5px var(--mono);background:var(--white)} +th,td{padding:2px 6px;text-align:right;white-space:nowrap;border-right:1px solid var(--face-lt); + border-bottom:1px solid var(--face-lt)} +th:first-child,td:first-child{text-align:left;white-space:normal;min-width:13em} +thead th{font:700 11px var(--ui);position:sticky;top:0;z-index:1;background:var(--face); + border:1px solid;border-color:var(--white) var(--gray) var(--gray) var(--white)} +tr.hl td{font-weight:700;background:var(--face-lt)} + +/* ---------- Mitlaufende Leiste = Statuszeile ---------- */ +#stickybar{position:fixed;top:0;left:0;right:0;z-index:50;background:var(--face); + padding-top:env(safe-area-inset-top); + border-bottom:1px solid var(--black);box-shadow:0 1px 0 var(--gray), inset 0 1px 0 var(--white)} +.sb-inner{max-width:940px;margin:0 auto;padding:3px 8px 4px;display:flex;align-items:center;gap:6px} .sb-col{flex:1;min-width:0} -.sb-cap{font-size:.64rem;color:var(--muted);letter-spacing:.05em;text-transform:uppercase; - margin-bottom:1px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis} -#sb-vals{display:flex;gap:14px;min-width:0;overflow:hidden} -.sb-i{min-width:0;flex:1} -.sb-i .n{font-size:.66rem;color:var(--muted);font-weight:600;white-space:nowrap; - overflow:hidden;text-overflow:ellipsis;display:block} -.sb-i .v{font-size:.98rem;font-weight:700;font-variant-numeric:tabular-nums;letter-spacing:-.01em; - white-space:nowrap;display:flex;align-items:baseline;gap:5px} -.sb-i .v b{width:7px;height:7px;border-radius:99px;background:var(--c);flex:0 0 auto} -.sb-i .d{font-size:.68rem;font-weight:600;font-variant-numeric:tabular-nums} -.sb-i .d.up{color:var(--good)} .sb-i .d.dn{color:var(--bad)} .sb-i .d.eq{color:var(--muted)} -.sb-up{appearance:none;border:1px solid var(--line);background:var(--bg);color:var(--muted); - width:34px;height:34px;border-radius:99px;flex:0 0 auto;cursor:pointer;font-size:.95rem;line-height:1} -@media(max-width:480px){ .sb-inner{padding:6px 10px;gap:6px} #sb-vals{gap:8px} - .sb-i .v{font-size:.88rem} .sb-i .n{font-size:.62rem} } - -/* ---------- Klartext-Fazit und Beispielwert-Hinweis ---------- */ -.verdict{font-size:.88rem;line-height:1.55;margin:12px 0 0;padding:11px 13px;border-radius:11px; - background:var(--chip);border-left:3px solid var(--accent)} -.verdict b{font-weight:700} -.verdict .band{display:block;margin-top:5px;font-size:.78rem;color:var(--muted)} -.demobox{font-size:.82rem;line-height:1.5;margin:0 0 10px;padding:9px 12px;border-radius:10px; - background:var(--chip);border-left:3px solid var(--bav);color:var(--muted)} -.demobox b{color:var(--ink)} - -/* ---------- Tastaturbedienung sichtbar machen ---------- */ -:focus-visible{outline:2px solid var(--accent);outline-offset:2px;border-radius:6px} +.sb-cap{font-size:10px;font-weight:700;margin-bottom:2px;white-space:nowrap;overflow:hidden; + text-overflow:ellipsis} +#sb-vals{display:flex;gap:4px;min-width:0} +.sb-i{min-width:0;flex:1;background:var(--face);padding:1px 5px; + border:1px solid;border-color:var(--gray) var(--white) var(--white) var(--gray)} +.sb-i .n{font-size:10px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;display:block} +.sb-i .v{font:700 13px var(--mono);white-space:nowrap;display:flex;align-items:baseline;gap:4px} +.sb-i .v b{width:8px;height:8px;background:var(--c);flex:0 0 auto;border:1px solid var(--black)} +.sb-i .d{font:700 10px var(--mono)} +.sb-i .d.up{color:var(--good)} .sb-i .d.dn{color:var(--bad)} .sb-i .d.eq{color:var(--dim)} +.sb-up{appearance:none;width:25px;height:25px;flex:0 0 auto;padding:0;background:var(--face); + color:var(--ink);font-size:11px;cursor:pointer;border:1px solid var(--black); + box-shadow:inset 1px 1px 0 var(--white), inset -1px -1px 0 var(--gray)} +.sb-up:active{box-shadow:inset 1px 1px 0 var(--gray), inset -1px -1px 0 var(--white)} +@media(max-width:480px){ .sb-inner{padding:3px 5px 4px;gap:4px} #sb-vals{gap:3px} + .sb-i{padding:1px 3px} .sb-i .v{font-size:11.5px} .sb-i .n{font-size:9px} } + +/* ---------- Bildlaufleisten ---------- */ +*{scrollbar-width:auto;scrollbar-color:var(--face) var(--face-lt)} +::-webkit-scrollbar{width:16px;height:16px;background: + repeating-conic-gradient(var(--white) 0% 25%, var(--face) 0% 50%) 0 0/2px 2px} +::-webkit-scrollbar-thumb{background:var(--face); + border:1px solid;border-color:var(--white) var(--black) var(--black) var(--white); + box-shadow:inset 1px 1px 0 var(--white), inset -1px -1px 0 var(--gray)} +::-webkit-scrollbar-corner{background:var(--face)} + +footer{font-size:11px;line-height:1.5;margin-top:12px;background:var(--face);padding:8px 10px; + border:1px solid;border-color:var(--white) var(--gray) var(--gray) var(--white)} +kbd{font:11px var(--mono);background:var(--face);padding:0 4px; + border:1px solid;border-color:var(--white) var(--gray) var(--gray) var(--white)} +a{color:var(--navy)} @media (prefers-reduced-motion: reduce){ *{transition:none !important;scroll-behavior:auto !important} } </style> </head> @@ -186,23 +336,23 @@ a{color:var(--accent)} </header> <!-- ======================= ERGEBNIS ======================= --> -<section class="card" id="result"> +<section class="win" id="w-result"> <h2>Ergebnis</h2> <div id="metricbar" class="seg" role="group" aria-label="Kennzahl"></div> <p class="sub" id="metricdesc" style="margin:2px 0 10px"></p> - <p id="demo" class="demobox" hidden></p> + <p id="demo" class="msg demo" data-ico="?" hidden></p> <div id="cards"></div> <p id="verdict" class="verdict"></p> <div id="baserow"> <span id="basetext"></span> <button class="btn" id="pin" style="padding:4px 9px;min-height:30px;font-size:.78rem">Als Referenz merken</button> </div> - <p id="rankhint" class="hintbox" hidden></p> - <p id="bisektwarn" class="warn" hidden></p> + <p id="rankhint" class="msg note" data-ico="i" hidden></p> + <p id="bisektwarn" class="msg warn" data-ico="!" hidden></p> </section> <!-- ======================= KERNANGABEN ======================= --> -<section class="card"> +<section class="win"> <h2>Ihre Angaben</h2> <p class="sub" style="margin-bottom:6px">Die Zahlen rechnen sich bei jeder Änderung sofort neu. Mit <span class="qm" style="cursor:default">?</span> erklärt sich jedes Feld selbst.</p> @@ -210,7 +360,7 @@ a{color:var(--accent)} </section> <!-- ======================= DIAGRAMME ======================= --> -<section class="card"> +<section class="win" data-title="Diagramme"> <div class="tabs" id="tabs" role="tablist"></div> <div id="chartwrap"> <canvas id="chart" height="260"></canvas> @@ -221,21 +371,21 @@ a{color:var(--accent)} </section> <!-- ======================= WEITERE ANGABEN ======================= --> -<section class="card"> +<section class="win"> <h2 style="margin-bottom:6px">Weitere Angaben</h2> <p class="sub" style="margin-bottom:2px">Alles hier hat sinnvolle Voreinstellungen. Nur anfassen, wenn es auf Sie zutrifft.</p> <div id="groups"></div> </section> <!-- ======================= RECHENWEG ======================= --> -<section class="card"> +<section class="win"> <h2 style="margin-bottom:6px">Rechenweg</h2> <p class="sub" style="margin-bottom:2px">Für alle, die es nachvollziehen wollen.</p> <div id="details"></div> </section> <!-- ======================= WEITERGEBEN ======================= --> -<section class="card"> +<section class="win"> <h2>Speichern und weitergeben</h2> <p class="sub">Ihre Eingaben liegen ausschließlich in diesem Browser (<kbd>localStorage</kbd>). Nichts wird übertragen, es gibt keinen Server und keine Tracker.</p> @@ -247,7 +397,7 @@ a{color:var(--accent)} </div> <input type="file" id="file" accept=".json,application/json" hidden> <p class="sub" id="sharemsg" style="margin-top:8px"></p> - <p class="hintbox" id="installhint" hidden></p> + <p class="msg note" data-ico="i" id="installhint" hidden></p> </section> <footer> @@ -659,11 +809,11 @@ root.AVModell = { rechne:rechne, waterfall:waterfall, bruttoVonNetto:bruttoVonNe /* nom/satz/gen: das Fazit ist ein deutscher Satz, kein Etikettensalat. „mehr als privates ETF-Depot“ liest sich wie eine Tabellenzeile. */ var OPTS = [ - { k:'bav', name:'bAV (Entgeltumwandlung)', short:'bAV', color:'var(--bav)', raw:'#c9761d', + { k:'bav', name:'bAV (Entgeltumwandlung)', short:'bAV', color:'var(--bav)', raw:'#800000', nom:'die betriebliche Altersvorsorge', satz:'Die betriebliche Altersvorsorge', gen:'der bAV' }, - { k:'av', name:'Altersvorsorgedepot', short:'AV-Depot', color:'var(--av)', raw:'#2266b8', + { k:'av', name:'Altersvorsorgedepot', short:'AV-Depot', color:'var(--av)', raw:'#000080', nom:'das Altersvorsorgedepot', satz:'Das Altersvorsorgedepot', gen:'des Altersvorsorgedepots' }, - { k:'priv', name:'privates ETF-Depot', short:'privat', color:'var(--priv)', raw:'#2b8a5a', + { k:'priv', name:'privates ETF-Depot', short:'privat', color:'var(--priv)', raw:'#008000', nom:'das private ETF-Depot', satz:'Das private ETF-Depot', gen:'des privaten ETF-Depots' } ]; function opt(k){ return OPTS.filter(function(o){return o.k===k;})[0]; } @@ -989,14 +1139,26 @@ function buildField(f){ wrap.appendChild(lab); if (f.kind === 'seg'){ - var seg = el('div','seg'); seg.style.gridColumn = '1/-1'; seg.style.marginTop = '4px'; + /* Windows 3.1 kannte keine Segmentschalter — die richtige Entsprechung + fuer eine Auswahl unter wenigen Alternativen ist die Optionsfeldgruppe. + Sie ist zugleich das semantisch korrekte Element und bringt die + Pfeiltastennavigation der Radiogruppe geschenkt mit. */ + var box = el('div','radios'); + var gname = 'r_' + f.src + '_' + f.k; f.opts.forEach(function(o){ - var b = el('button', null, o[1]); b.type='button'; - b.setAttribute('aria-pressed', fieldValue(f) === o[0]); - b.onclick = function(){ arm(f.k); setFieldValue(f, o[0]); refresh(); }; - seg.appendChild(b); + var lb = document.createElement('label'); + var rb = document.createElement('input'); + rb.type = 'radio'; rb.name = gname; rb.value = String(o[0]); + rb.checked = fieldValue(f) === o[0]; + rb.addEventListener('change', function(){ + if (!rb.checked) return; + arm(f.k); setFieldValue(f, o[0]); refresh(); + }); + lb.appendChild(rb); + lb.appendChild(document.createTextNode(' ' + o[1])); + box.appendChild(lb); }); - wrap.appendChild(seg); + wrap.appendChild(box); } else { var val = el('div','val'); var inp = document.createElement('input'); @@ -1091,8 +1253,8 @@ function syncVisibility(){ if (w._sync) w._sync(); if (w._live && R) w._live(R); if (f.kind === 'seg'){ - var bs = w.querySelectorAll('.seg button'); - for (var i=0;i<bs.length;i++) bs[i].setAttribute('aria-pressed', fieldValue(f) === f.opts[i][0]); + var bs = w.querySelectorAll('.radios input'); + for (var i=0;i<bs.length;i++) bs[i].checked = (fieldValue(f) === f.opts[i][0]); } }); } @@ -1124,7 +1286,7 @@ function renderCards(){ vals.forEach(function(v,i){ if (isFinite(v) && v > best){ best = v; bi = i; } }); OPTS.forEach(function(o,i){ - var c = el('div','opt' + (i===bi?' win':'')); + var c = el('div','opt' + (i===bi?' lead':'')); c.style.setProperty('--c', o.raw); var nm = el('div','nm'); nm.appendChild(document.createTextNode(o.name)); if (i===bi){ var cr = el('span','crown','vorn'); cr.style.setProperty('--c', o.raw); nm.appendChild(cr); } @@ -1340,22 +1502,26 @@ function niceTicks(lo, hi, n){ for(var v=s; v<=hi+step*1e-9; v+=step) t.push(Math.abs(v)<step*1e-9?0:v); return t; } +var UIFONT = '11px "MS Sans Serif","Microsoft Sans Serif",Tahoma,Verdana,sans-serif'; function axes(g,w,h,pad,xr,yr,fmtY,fmtX,xt){ - var ink=css('--ink'), muted=css('--muted'), line=css('--line'); + var ink=css('--ink'), grid=css('--gray'); var yt = niceTicks(yr[0], yr[1], 5); - g.font = '11px system-ui, sans-serif'; g.textBaseline='middle'; + g.font = UIFONT; g.textBaseline='middle'; yt.forEach(function(v){ - var y = pad.t + (1-(v-yr[0])/(yr[1]-yr[0]))*(h-pad.t-pad.b); - g.strokeStyle = Math.abs(v)<1e-9 ? muted : line; - g.lineWidth = Math.abs(v)<1e-9 ? 1 : 1; + var y = Math.round(pad.t + (1-(v-yr[0])/(yr[1]-yr[0]))*(h-pad.t-pad.b)); + var null0 = Math.abs(v) < 1e-9; + g.strokeStyle = null0 ? ink : grid; + g.lineWidth = 1; + if (!null0) g.setLineDash([1,2]); // gepunktetes Raster wie in Excel 4.0 g.beginPath(); g.moveTo(pad.l, y+.5); g.lineTo(w-pad.r, y+.5); g.stroke(); - g.fillStyle = muted; g.textAlign='right'; + g.setLineDash([]); + g.fillStyle = ink; g.textAlign='right'; g.fillText(fmtY(v), pad.l-6, y); }); if (xt){ - g.textAlign='center'; g.textBaseline='top'; g.fillStyle=muted; + g.textAlign='center'; g.textBaseline='top'; g.fillStyle=ink; xt.forEach(function(v){ - var x = pad.l + (v-xr[0])/(xr[1]-xr[0])*(w-pad.l-pad.r); + var x = Math.round(pad.l + (v-xr[0])/(xr[1]-xr[0])*(w-pad.l-pad.r)); g.fillText(fmtX(v), x, h-pad.b+6); }); } @@ -1467,11 +1633,11 @@ function drawSens(){ var Y = function(v){ return pad.t + (1-(v-yr[0])/(yr[1]-yr[0]))*(h-pad.t-pad.b); }; // Marke am aktuellen Wert - g.strokeStyle = css('--muted'); g.setLineDash([4,4]); g.lineWidth = 1; + g.strokeStyle = css('--ink'); g.setLineDash([3,3]); g.lineWidth = 1; g.beginPath(); g.moveTo(X(s.cur), pad.t); g.lineTo(X(s.cur), h-pad.b); g.stroke(); g.setLineDash([]); OPTS.forEach(function(o){ - g.strokeStyle = o.raw; g.lineWidth = 2.2; g.lineJoin='round'; g.beginPath(); + g.strokeStyle = o.raw; g.lineWidth = 2; g.lineJoin='miter'; g.beginPath(); s.pts[o.k].forEach(function(p,i){ var x=X(p[0]), y=Y(p[1]); if(i) g.lineTo(x,y); else g.moveTo(x,y); }); g.stroke(); var cp = nearest(s.pts[o.k], s.cur); @@ -1488,7 +1654,7 @@ function drawSens(){ var yv = s.pts[k][i-1][1] + t*(s.pts[k][i][1]-s.pts[k][i-1][1]); g.strokeStyle = css('--ink'); g.lineWidth=1.5; g.beginPath(); g.arc(X(xv), Y(yv), 5.5, 0, 6.284); g.stroke(); - g.fillStyle = css('--ink'); g.font='600 10px system-ui,sans-serif'; g.textAlign='center'; g.textBaseline='bottom'; + g.fillStyle = css('--ink'); g.font='bold '+UIFONT; g.textAlign='center'; g.textBaseline='bottom'; g.fillText(fmtX(xv), X(xv), Y(yv)-8); } } @@ -1496,8 +1662,8 @@ function drawSens(){ if (hoverX != null){ var xv2 = xr[0] + clamp((hoverX-pad.l)/(w-pad.l-pad.r),0,1)*(xr[1]-xr[0]); - g.strokeStyle = css('--ink'); g.globalAlpha=.35; g.lineWidth=1; - g.beginPath(); g.moveTo(X(xv2), pad.t); g.lineTo(X(xv2), h-pad.b); g.stroke(); g.globalAlpha=1; + g.strokeStyle = css('--ink'); g.globalAlpha=1; g.lineWidth=1; g.setLineDash([1,1]); + g.beginPath(); g.moveTo(X(xv2), pad.t); g.lineTo(X(xv2), h-pad.b); g.stroke(); g.setLineDash([]); var out = [f.label + ' = ' + formatViewNumber(f, vw(xv2)) + ' ' + fieldUnit(f) + ' → ']; OPTS.forEach(function(o){ out.push(o.short + ' ' + M.fmt(nearest(s.pts[o.k], xv2)[1])); }); $('#readout').textContent = out[0] + out.slice(1).join(' · '); @@ -1559,9 +1725,9 @@ function drawWaterfall(){ var barL = 8, barR = w-8, avail = barR-barL; var X = function(v){ return barL + (v-lo)/(hi-lo)*avail; }; - g.font = '11px system-ui,sans-serif'; + g.font = UIFONT; var zero = X(0); - g.strokeStyle = css('--muted'); g.globalAlpha=.5; + g.strokeStyle = css('--gray'); g.globalAlpha=1; g.beginPath(); g.moveTo(zero+.5, pad.t); g.lineTo(zero+.5, h-pad.b); g.stroke(); g.globalAlpha=1; run = 0; @@ -1575,13 +1741,13 @@ function drawWaterfall(){ g.fillStyle = col; g.globalAlpha = it.type==='abs'?1:.85; g.fillRect(xa, y, Math.max(2, xb-xa), bh); g.globalAlpha=1; if (it.type==='rel' && i<items.length-1){ - g.strokeStyle = css('--line'); g.setLineDash([2,3]); + g.strokeStyle = css('--gray'); g.setLineDash([1,2]); g.beginPath(); g.moveTo(X(b), y+bh); g.lineTo(X(b), y+rowH); g.stroke(); g.setLineDash([]); } g.fillStyle = css('--ink'); g.textAlign='left'; g.textBaseline='bottom'; - g.font = it.type==='abs' ? '600 11.5px system-ui,sans-serif' : '11.5px system-ui,sans-serif'; + g.font = it.type==='abs' ? 'bold '+UIFONT : UIFONT; g.fillText(it.label, 8, y-2); - g.textAlign='right'; g.font='600 11.5px system-ui,sans-serif'; + g.textAlign='right'; g.font='bold '+UIFONT; g.fillStyle = it.type==='abs' ? css('--ink') : (it.wert>=0?css('--good'):css('--bad')); var t = (it.type==='rel'?(it.wert>=0?'+':'−'):'') + nf0.format(Math.round(Math.abs(it.wert))) + ' €'; g.fillText(t, w-8, y-2); @@ -1631,13 +1797,13 @@ function drawKapital(){ axes(g,w,h,pad,xr,yr,function(v){return num(v/1000,0)+'k';}, function(v){return num(v,0);}, niceTicks(a0, xhi, 6).filter(function(v){return v>=a0&&v<=xhi;})); - g.strokeStyle = css('--muted'); g.setLineDash([4,4]); + g.strokeStyle = css('--ink'); g.setLineDash([3,3]); g.beginPath(); g.moveTo(X(S.I.rentbeg), pad.t); g.lineTo(X(S.I.rentbeg), h-pad.b); g.stroke(); g.setLineDash([]); - g.fillStyle = css('--muted'); g.font='10px system-ui,sans-serif'; g.textAlign='left'; g.textBaseline='top'; + g.fillStyle = css('--ink'); g.font=UIFONT; g.textAlign='left'; g.textBaseline='top'; g.fillText('Rentenbeginn', X(S.I.rentbeg)+4, pad.t+2); OPTS.forEach(function(o){ - g.strokeStyle=o.raw; g.lineWidth=2.2; g.lineJoin='round'; g.beginPath(); + g.strokeStyle=o.raw; g.lineWidth=2; g.lineJoin='miter'; g.beginPath(); series[o.k].forEach(function(p,i){ var x=X(p[0]), y=Y(p[1]); if(i) g.lineTo(x,y); else g.moveTo(x,y); }); g.stroke(); }); @@ -1666,20 +1832,20 @@ function drawZerlegung(){ if (!(maxV > 0) || !isFinite(maxV)) maxV = 1; var pad = { l:8, r:8, t:22, b:8 }; var colW = (w-16)/3, barH = 26; - g.font='11px system-ui,sans-serif'; + g.font=UIFONT; OPTS.forEach(function(o,i){ var x0 = 8 + i*colW + 6, bw = colW-12; var y = pad.t; - g.fillStyle=css('--muted'); g.textAlign='center'; g.textBaseline='alphabetic'; - g.font='600 11px system-ui,sans-serif'; + g.fillStyle=css('--ink'); g.textAlign='center'; g.textBaseline='alphabetic'; + g.font='bold '+UIFONT; g.fillText(o.short, x0+bw/2, 14); // gestapelte Abzüge vom Brutto var brutto = R.monatBrutto[o.k]; var segs = [ { v: brutto + R.monatSteuer[o.k] + R.monatKV[o.k] + R.monatEP[o.k], c:o.raw, l:'netto' }, - { v: -R.monatSteuer[o.k], c:'#8a8f98', l:'Steuer' }, - { v: -R.monatKV[o.k], c:'#c0574f', l:'KV/PV' }, - { v: -R.monatEP[o.k], c:'#7a6ab0', l:'Rente' } + { v: -R.monatSteuer[o.k], c:'#808080', l:'Steuer' }, + { v: -R.monatKV[o.k], c:'#800000', l:'KV/PV' }, + { v: -R.monatEP[o.k], c:'#800080', l:'Rente' } ]; var acc = 0; var scale = (h-pad.t-pad.b-24)/(maxV||1); @@ -1688,17 +1854,17 @@ function drawZerlegung(){ var hgt = s.v*scale; g.fillStyle = s.c; g.fillRect(x0, h-pad.b-24-acc-hgt, bw, hgt); if (hgt > 15){ - g.fillStyle='#fff'; g.font='10px system-ui,sans-serif'; g.textAlign='center'; + g.fillStyle='#fff'; g.font=UIFONT; g.textAlign='center'; g.fillText(s.l + ' ' + nf0.format(Math.round(s.v)), x0+bw/2, h-pad.b-24-acc-hgt/2+3); } acc += hgt; }); - g.fillStyle=css('--ink'); g.font='700 12px system-ui,sans-serif'; g.textAlign='center'; + g.fillStyle=css('--ink'); g.font='bold 12px "MS Sans Serif",Tahoma,Verdana,sans-serif'; g.textAlign='center'; g.fillText(eur2(R.monat[o.k]), x0+bw/2, h-pad.b-8); }); - $('#legend').innerHTML = '<span><i style="background:#8a8f98"></i>Steuer</span>' - + '<span><i style="background:#c0574f"></i>KV/PV</span>' - + '<span><i style="background:#7a6ab0"></i>entgangene gesetzliche Rente</span>' + $('#legend').innerHTML = '<span><i style="background:#808080"></i>Steuer</span>' + + '<span><i style="background:#800000"></i>KV/PV</span>' + + '<span><i style="background:#800080"></i>entgangene gesetzliche Rente</span>' + '<span style="margin-left:auto">farbiger Sockel = das, was übrig bleibt</span>'; $('#readout').textContent = 'Alles als konstante Monatsleistung in heutiger Kaufkraft. Weil Annuitisierung linear ist, ' + 'addieren sich die Blöcke exakt.'; @@ -1722,7 +1888,7 @@ function buildTabs(){ host.appendChild(b); }); if (S.tab==='wf'){ - var sw = el('div','seg'); sw.style.marginLeft='auto'; + var sw = el('div','seg btnseg'); sw.style.marginLeft='auto'; [['bav','bAV'],['av','AV-Depot']].forEach(function(o){ var b = el('button', null, o[1]); b.type='button'; b.setAttribute('aria-pressed', S.wfZiel===o[0]); @@ -1972,7 +2138,7 @@ $('#file').onchange = function(e){ var o = JSON.parse(rd.result); if (o.I) for (var k in AVModell.DEFAULT_INPUT) if (typeof o.I[k]==='number') S.I[k]=o.I[k]; if (o.P) for (var p in AVModell.DEFAULT_PARAM) if (typeof o.P[p]==='number') S.P[p]=o.P[p]; - BASE = null; PINNED = false; armed = null; + BASE = null; PINNED = false; armed = null; koppelWarn = null; buildInputs(); refresh(); $('#sharemsg').textContent = 'Geladen.'; } catch(err){ $('#sharemsg').textContent = 'Die Datei ließ sich nicht lesen: ' + err.message; } @@ -1982,22 +2148,72 @@ $('#file').onchange = function(e){ $('#reset').onclick = function(){ S.I = Object.assign({}, AVModell.DEFAULT_INPUT); S.P = Object.assign({}, AVModell.DEFAULT_PARAM); - BASE = null; PINNED = false; armed = null; + BASE = null; PINNED = false; armed = null; koppelWarn = null; try { localStorage.removeItem(LSKEY); } catch(e){} history.replaceState(null,'',location.pathname); buildInputs(); refresh(); $('#sharemsg').textContent = 'Zurückgesetzt auf den Basisfall.'; }; +/* Es gibt nur ein Farbschema. Silber auf Petrol ist hier nicht Dekoration, + sondern die Sache selbst — eine Dunkelvariante gäbe es nicht zu skinnen. */ var rt = null; window.addEventListener('resize', function(){ clearTimeout(rt); rt = setTimeout(drawChart, 120); }); -if (window.matchMedia) { - var mq = window.matchMedia('(prefers-color-scheme: dark)'); - (mq.addEventListener ? mq.addEventListener.bind(mq,'change') : mq.addListener.bind(mq))(function(){ drawChart(); }); + +/* ===================================================================== + TEIL 12 — FENSTERRAHMEN + Jeder Abschnitt bekommt eine Titelleiste im Stil von Windows 3.1: + Systemmenue-Kasten links, Titel, Minimieren rechts. Der Knopf ist + nicht Zierrat — er klappt den Fensterinhalt tatsaechlich ein, was auf + dem Telefon nuetzlich ist. Der Zustand wird mitgespeichert. + ===================================================================== */ +function dressWindows(){ + var host = document.querySelector('.wrap'); + var wins = [].slice.call(host.querySelectorAll(':scope > header, :scope > section.win')); + wins.forEach(function(sec, i){ + if (sec.querySelector(':scope > .titlebar')) return; + var isHeader = sec.tagName === 'HEADER'; + if (isHeader) sec.className = 'win'; + + var h = sec.querySelector('h1, h2'); + var title = sec.getAttribute('data-title') + || (h ? h.textContent.replace(/‑/g, '-') : 'Fenster ' + (i+1)); + if (h) h.remove(); + + var key = 'w_' + (sec.id || ('n' + i)); + var body = el('div','winbody'); + while (sec.firstChild) body.appendChild(sec.firstChild); + + var bar = el('div','titlebar'); + bar.appendChild(el('span','sysbox')); + bar.appendChild(el('span','tbtext', title)); + var mn = el('button','tbbtn','▬'); + mn.type = 'button'; + mn.title = 'Minimieren'; + mn.setAttribute('aria-expanded', 'true'); + bar.appendChild(mn); + + sec.appendChild(bar); sec.appendChild(body); + + var setRolled = function(r, save){ + sec.classList.toggle('rolled', r); + mn.textContent = r ? '■' : '▬'; + mn.title = r ? 'Wiederherstellen' : 'Minimieren'; + mn.setAttribute('aria-expanded', String(!r)); + S.open[key] = !r; + if (save) saveState(); + if (!r) drawChart(); // eingeklapptes Canvas hat Breite 0 + }; + mn.onclick = function(){ setRolled(!sec.classList.contains('rolled'), true); }; + /* Doppelklick auf die Titelleiste klappt ein - wie unter Windows */ + bar.ondblclick = function(e){ if (e.target !== mn) mn.onclick(); }; + if (S.open[key] === false) setRolled(true, false); + }); } /* --- Start --- */ loadState(); +dressWindows(); buildMetricBar(); buildTabs(); buildInputs(); R = AVModell.rechne(S.I, S.P); refresh(); |
