@@ -798,11 +798,11 @@ async function loadFinance() {
// ── SVG-Balkendiagramm ────────────────────────────────────────
// Bei langen Abrechnungsjahren nicht jeden einzelnen Tag zeigen — statt 112
// Tagesbalken lieber ~16 Wochenbalken, deutlich lesbarer und trotzdem
// aussagekräftig für "würde sich Spot lohn en" .
// Tagesbalken lieber Wochenbalken, und davon nur die letzten 14 (reicht für
// einen Trend, bleibt aber lesbar statt endlos zu scroll en) .
const rawN = days . length ;
const bucket = rawN > 180 ? 14 : rawN > 21 ? 7 : 1 ;
const periods = bucket === 1 ? days . map ( d => ( {
const allPeriods = bucket === 1 ? days . map ( d => ( {
fixed _eur : d . fixed _eur , spot _eur : d . spot _eur , label : d . date . slice ( 5 ) ,
} ) ) : ( ( ) => {
const out = [ ] ;
@@ -811,21 +811,24 @@ async function loadFinance() {
const fixed = chunk . reduce ( ( s , d ) => s + ( d . fixed _eur || 0 ) , 0 ) ;
const spotVals = chunk . map ( d => d . spot _eur ) . filter ( v => v != null ) ;
const spot = spotVals . length ? spotVals . reduce ( ( a , b ) => a + b , 0 ) : null ;
const from = chunk [ 0 ] . date . slice ( 5 ) , to = chunk [ chunk . length - 1 ] . date . slice ( 5 ) ;
out . push ( { fixed _eur : fixed , spot _eur : spot , label : from === to ? from : ` ${ from } – ${ to } ` } ) ;
out . push ( { fixed _eur : fixed , spot _eur : spot , label : chunk [ 0 ] . date . slice ( 5 ) } ) ;
}
return out ;
} ) ( ) ;
const MAX _PERIODS = 14 ;
const periods = allPeriods . slice ( - MAX _PERIODS ) ;
// Echtes responsives SVG: viewBox in einer "Mobile-first"-Breite, skaliert per
// width:100% frei auf den Container (Handy wie PC) — kein Scrollen mehr nötig,
// da wir uns auf max. 14 Balken begrenzen.
const n = periods . length ;
const H = 190 , PL = 44 , PR = 12 , PT = 14 , PB = 30 ;
const MIN _SLOT = 34 ; // px pro Balkenpaar — bewusst großzügig, jetzt wo wenige Balken übrig sind
const W = Math . max ( 600 , PL + PR + n * MIN _SLOT ) ;
const W = 380 , H = 220 , PL = 40 , PR = 8 , PT = 14 , PB = 46 ;
const cW = W - PL - PR ;
const cH = H - PT - PB ;
const slot = cW / n ;
const barW = Math . min ( 22 , Math . max ( 4 , slot * 0.34 ) ) ;
const barGap = Math . max ( 2 , slot * 0.06 ) ;
const barW = Math . max ( 3 , slot * 0.36 ) ;
const barGap = Math . max ( 1.5 , slot * 0.08 ) ;
const rBar = Math . min ( 2.5 , barW / 2 ) ;
const allVals = periods . flatMap ( d => [ d . fixed _eur , d . spot _eur ] . filter ( v => v != null ) ) ;
const maxVal = Math . max ( ... allVals , 0.01 ) ;
@@ -835,12 +838,12 @@ async function loadFinance() {
for ( let i = 0 ; i <= yTicks ; i ++ ) {
const v = maxVal * i / yTicks ;
const y = PT + cH - ( cH * i / yTicks ) ;
gridLines += ` <line x1=" ${ PL } " y1=" ${ y . toFixed ( 1 ) } " x2=" ${ W - PR } " y2=" ${ y . toFixed ( 1 ) } " stroke=" ${ C . grid } " stroke-width="1" stroke-dasharray="2,4 "/> ` ;
yLabels += ` <text x=" ${ PL - 8 } " y=" ${ ( y + 3 ) . toFixed ( 1 ) } " text-anchor="end" font-size="10 " fill="#8b949e"> ${ fEur ( v ) } </text> ` ;
gridLines += ` <line x1=" ${ PL } " y1=" ${ y . toFixed ( 1 ) } " x2=" ${ W - PR } " y2=" ${ y . toFixed ( 1 ) } " stroke=" ${ C . grid } " stroke-width="1" stroke-dasharray="2,3 "/> ` ;
yLabels += ` <text x=" ${ PL - 6 } " y=" ${ ( y + 3 ) . toFixed ( 1 ) } " text-anchor="end" font-size="9 " fill="#8b949e"> ${ fEur ( v ) } </text> ` ;
}
const labelStep = Math . max ( 1 , Math . round ( 50 / slot ) ) ;
const rBar = Math . min ( 3 , barW / 2 ) ;
// Labels schräg statt horizontal — braucht bei 14 Balken pro Label deutlich
// weniger Breite, dadurch bleiben trotz Platzmangel alle 14 lesbar.
let bars = '' , xLabels = '' ;
periods . forEach ( ( d , i ) => {
const cx = PL + i * slot + slot / 2 ;
@@ -850,9 +853,8 @@ async function loadFinance() {
const hSpot = Math . max ( 0 , cH * d . spot _eur / maxVal ) ;
bars += ` <rect x=" ${ ( cx + barGap / 2 ) . toFixed ( 1 ) } " y=" ${ ( PT + cH - hSpot ) . toFixed ( 1 ) } " width=" ${ barW . toFixed ( 1 ) } " height=" ${ Math . max ( hSpot , 1 ) . toFixed ( 1 ) } " rx=" ${ rBar } " fill="url(#gradSpot)"/> ` ;
}
if ( i % labelStep === 0 ) {
xLabels += ` <text x=" ${ cx . toFixed ( 1 ) } " y=" ${ H - 6 } " text-anchor="middle " font-size="9 " fill="#8b949e"> ${ d . label } </text> ` ;
}
const ly = H - PB + 12 ;
xLabels += ` <text x=" ${ cx . toFixed ( 1 ) } " y=" ${ ly } " text-anchor="end " font-size="8.5 " fill="#8b949e" transform="rotate(-40 ${ cx . toFixed ( 1 ) } ${ ly } )" >${ d . label } </text> ` ;
} ) ;
const defs = ` <defs>
@@ -867,6 +869,7 @@ async function loadFinance() {
</defs> ` ;
const bucketLabel = bucket === 1 ? 'Tag' : bucket === 7 ? 'Woche' : '2 Wochen' ;
const rangeNote = allPeriods . length > MAX _PERIODS ? ` · letzte ${ n } ` : '' ;
const legend = ` <div style="display:flex;gap:18px;margin-top:10px;font-size:11px;color:var(--text-dim)">
<span style="display:inline-flex;align-items:center;gap:6px"><span style="width:10px;height:10px;border-radius:3px;background: ${ C . fixed } ;display:inline-block"></span>Festpreis</span>
<span style="display:inline-flex;align-items:center;gap:6px"><span style="width:10px;height:10px;border-radius:3px;background: ${ C . spot } ;display:inline-block"></span>Spot (hypothetisch)</span>
@@ -874,12 +877,10 @@ async function loadFinance() {
const chartSection = `
<div style="border-top:1px solid var(--border);padding-top:24px;margin-top:4px">
<div style="font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.08em;color:var(--text-dim);margin-bottom:14px">Kosten je ${ bucketLabel } </div>
<div style="overflow-x:auto ">
<svg viewBox="0 0 ${ W } ${ H } " width=" ${ W } " height=" ${ H } " style="width: ${ W } px;max-width:none;display:block">
${ defs } ${ gridLines } ${ yLabels } ${ bars } ${ xLabels }
</svg>
</div>
<div style="font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.08em;color:var(--text-dim);margin-bottom:14px">Kosten je ${ bucketLabel } ${ rangeNote } </div>
<svg viewBox="0 0 ${ W } ${ H } " style="width:100%;max-width:640px;height:auto;display:block ">
${ defs } ${ gridLines } ${ yLabels } ${ bars } ${ xLabels }
</svg>
${ legend }
</div> ` ;