diff --git a/index.html b/index.html index a9069d7..4e0f2b7 100644 --- a/index.html +++ b/index.html @@ -1699,64 +1699,104 @@ function setupSatsCard(cutoff) { }); const sortedDates = Object.keys(allDates).sort(); - /* Per-wallet: forward-fill balance across all dates */ - const dailyBalances = {}; + const wallets = wm.getWallets(); + /* Per-wallet forward-fill */ + const walletDailyBalances = {}; selectedAddr.forEach(addr => { + walletDailyBalances[addr] = {}; const snaps = addressSnapshots[addr] || []; const dated = snaps.map(s => ({ dateStr: s.block_timestamp.slice(0, 10), btcVal: getTokenAmount(s?.wallet?.cbBTC, 'cbBTC') + getTokenAmount(s?.collateral?.cbBTC, 'cbBTC') })).sort((a, b) => a.dateStr.localeCompare(b.dateStr)); - let balance = 0; for (const ds of sortedDates) { const match = dated.find(d => d.dateStr === ds); if (match) balance = match.btcVal; - if (!dailyBalances[ds]) dailyBalances[ds] = 0; - dailyBalances[ds] += balance; + walletDailyBalances[addr][ds] = balance; } }); - /* Sort by date, compute daily delta (acquisition) */ - const dailyDeltas = {}; - let prevTotal = 0; - sortedDates.forEach(ds => { - dailyDeltas[ds] = dailyBalances[ds] - prevTotal; - prevTotal = dailyBalances[ds]; + /* Per-wallet daily deltas */ + const walletDailyDeltas = {}; + selectedAddr.forEach(addr => { + walletDailyDeltas[addr] = {}; + let prev = 0; + for (const ds of sortedDates) { + const b = walletDailyBalances[addr][ds] || 0; + walletDailyDeltas[addr][ds] = b - prev; + prev = b; + } }); - /* Compute total sats acquired and days elapsed since first snapshot */ - const firstUTC = sortedDates.length > 0 ? Date.UTC(new Date(sortedDates[0]).getUTCFullYear(), new Date(sortedDates[0]).getUTCMonth(), new Date(sortedDates[0]).getUTCDate()) : Date.now(); - const lastUTC = sortedDates.length > 0 ? Date.UTC(new Date(sortedDates[sortedDates.length - 1]).getUTCFullYear(), new Date(sortedDates[sortedDates.length - 1]).getUTCMonth(), new Date(sortedDates[sortedDates.length - 1]).getUTCDate()) : Date.now(); - const daysElapsed = Math.max(1, Math.ceil((lastUTC - firstUTC) / DAY_MS)); - const totalBtc = sortedDates.length > 0 ? dailyBalances[sortedDates[sortedDates.length - 1]] : 0; - const totalSats = Math.round(totalBtc * 1e8); - const avgSatsPerDay = Math.round(totalSats / daysElapsed); + const firstUTC = sortedDates.length > 0 ? Date.UTC(new Date(sortedDates[0]).getUTCFullYear(), new Date(sortedDates[0]).getUTCMonth(), new Date(sortedDates[0]).getUTCDate()) : Date.now(); + const lastUTC = sortedDates.length > 0 ? Date.UTC(new Date(sortedDates[sortedDates.length - 1]).getUTCFullYear(), new Date(sortedDates[sortedDates.length - 1]).getUTCMonth(), new Date(sortedDates[sortedDates.length - 1]).getUTCDate()) : Date.now(); + const daysElapsed = Math.max(1, Math.ceil((lastUTC - firstUTC) / DAY_MS)); - /* Build continuous daily series: one data point per day */ - const filteredSats = []; - let runningTotalBtc = 0; - for (let t = firstUTC; t <= lastUTC; t += DAY_MS) { - const ds = new Date(t).toISOString().slice(0, 10); - runningTotalBtc += dailyDeltas[ds] || 0; - const daysSoFar = Math.max(1, Math.ceil((t - firstUTC) / DAY_MS) + 1); - const runningAvgSats = Math.round((runningTotalBtc * 1e8) / daysSoFar); - filteredSats.push([t, runningAvgSats]); - } - const latestAvg = filteredSats.length > 0 ? filteredSats[filteredSats.length - 1][1] : 0; - document.getElementById('avg-sats-val').innerText = new Intl.NumberFormat('en-US').format(latestAvg); - const options = { - chart: { id: 'instance-sats', type: 'area', height: 200, background: 'transparent', toolbar: { show: false }, sparkline: { enabled: false }, zoom: { enabled: true, type: 'x', autoScaleYaxis: true }, animations: { enabled: false } }, - series: [{ name: 'Avg Sats/Day', data: filteredSats }], + /* Per-wallet series: running avg sats/day */ + const series = selectedAddr.map(addr => { + const w = wallets.find(x => x.address === addr); + const nickname = w ? (w.nickname || addr.slice(0, 6)) : addr.slice(0, 6); + const data = []; + let runningTotalBtc = 0; + for (let t = firstUTC; t <= lastUTC; t += DAY_MS) { + const ds = new Date(t).toISOString().slice(0, 10); + runningTotalBtc += walletDailyDeltas[addr][ds] || 0; + const daysSoFar = Math.max(1, Math.ceil((t - firstUTC) / DAY_MS) + 1); + const runningAvgSats = Math.round((runningTotalBtc * 1e8) / daysSoFar); + if (t >= cutoff) { + data.push([t, runningAvgSats]); + } + } + return { name: nickname, data }; + }); + + const latestTotal = sortedDates.length > 0 ? (() => { + let s = 0; + selectedAddr.forEach(addr => { s += walletDailyBalances[addr][sortedDates[sortedDates.length - 1]] || 0; }); + return s; + })() : 0; + const avgSatsPerDay = Math.round((latestTotal * 1e8) / daysElapsed); + document.getElementById('avg-sats-val').innerText = new Intl.NumberFormat('en-US').format(avgSatsPerDay); + + const colors = selectedAddr.map(addr => getColorForWallet(addr)); + const gradientStops = colors.map(c => [ + { offset: 0, color: c, opacity: 0.35 }, + { offset: 100, color: c, opacity: 0 } + ]); + + const options = { + chart: { id: 'instance-sats', type: 'area', height: 200, background: 'transparent', toolbar: { show: false }, sparkline: { enabled: false }, zoom: { enabled: true, type: 'x', autoScaleYaxis: true }, animations: { enabled: false }, stacked: true }, + stackType: 'normal', + series: series, dataLabels: { enabled: false }, - colors: [orangeBrandColor], + colors: colors, stroke: { curve: 'smooth', width: 2 }, - fill: { type: 'gradient', gradient: { type: 'vertical', shadeIntensity: 0, colorStops: [{ offset: 0, color: orangeBrandColor, opacity: 0.16 }, { offset: 100, color: orangeBrandColor, opacity: 0 }] } }, + fill: { type: 'gradient', gradient: { type: 'vertical', shadeIntensity: 0, colorStops: gradientStops } }, grid: { show: false, padding: { left: 0, right: 0, top: 0, bottom: 0 } }, markers: { size: 0, hover: { size: 5 } }, xaxis: { type: 'datetime', labels: { show: true, style: { colors: '#4B5563', fontSize: '10px', fontWeight: 600 }, datetimeFormatter: { year: 'yyyy', month: 'MMM yy', day: 'dd MMM' } }, axisBorder: { show: false }, axisTicks: { show: false }, minTickInterval: 86400000 }, yaxis: { opposite: true, labels: { offsetX: -10, style: { colors: '#4B5563', fontSize: '10px', fontWeight: 600 }, formatter: (val) => { if (val >= 1e6) return (val / 1e6).toFixed(0) + 'M'; if (val >= 1e3) return (val / 1e3).toFixed(0) + 'k'; return val; } }, axisBorder: { show: false }, axisTicks: { show: false } }, - tooltip: { enabled: true, theme: 'dark', shared: false, intersect: false, custom: function({ series, seriesIndex, dataPointIndex, w }) { const rawData = w.config.series[seriesIndex].data[dataPointIndex]; if (!rawData) return ''; const date = new Date(rawData[0]); const dateString = date.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }); const sats = rawData[1]; const btcVal = (sats / 1e8).toFixed(6); const priceStr = todayBtcPrice ? new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', maximumFractionDigits: 0 }).format(todayBtcPrice) : '—'; const dollarDay = todayBtcPrice ? new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', minimumFractionDigits: 0, maximumFractionDigits: 0 }).format((sats / 1e8) * todayBtcPrice) : '—'; return '