<div class="viz">
<div class="tile">
<div class="lbl">이번 달 매즈</div>
<div class="row"><div class="num" id="num">0</div><div class="delta" id="delta"></div></div>
<svg id="spark" class="spark"></svg>
<div class="goal"><div class="goalbar"><div class="goalfill" id="fill"></div></div><div class="goaltxt" id="goaltxt"></div></div>
</div>
</div>
body{display:block}
.viz{width:100%;height:100%;padding:14px 16px;box-sizing:border-box;display:flex}
.tile{display:flex;flex-direction:column;gap:6px;width:100%;height:100%}
.lbl{font:600 clamp(10px,2.6vmin,13px)/1 system-ui,sans-serif;color:var(--muted)}
.row{display:flex;align-items:baseline;gap:10px}
.num{font:800 clamp(20px,7vmin,34px)/1 system-ui,sans-serif;color:var(--fg)}
.delta{font:700 clamp(10px,2.6vmin,13px)/1 system-ui,sans-serif}
.delta.up{color:var(--accent-3)}
.delta.down{color:var(--accent-2)}
.spark{width:100%;flex:1;min-height:0;display:block}
.goal{display:flex;align-items:center;gap:8px}
.goalbar{flex:1;height:6px;border-radius:3px;background:var(--line);overflow:hidden}
.goalfill{height:100%;background:var(--accent);border-radius:3px}
.goaltxt{font:600 clamp(8px,2vmin,10px)/1 system-ui,sans-serif;color:var(--muted);white-space:nowrap}
var svg = document.getElementById('spark');
var numEl = document.getElementById('num');
var deltaEl = document.getElementById('delta');
var fillEl = document.getElementById('fill');
var goalEl = document.getElementById('goaltxt');
var NS = 'http://www.w3.org/2000/svg';
function se(t){ return document.createElementNS(NS,t); }
function sa(e,o){ for (var k in o) e.setAttribute(k,o[k]); }
function R(a,b){ return a + Math.random()*(b-a); }
var GOAL = 100;
var N = 12;
var series = []; var v = 50;
for (var i=0;i<N;i++){ v = Math.max(20,Math.min(90, v+R(-12,12))); series.push(v); }
var cur = series.slice();
var W=300, H=200;
function measure(){
var r = svg.getBoundingClientRect();
W = Math.max(r.width,10); H = Math.max(r.height,10);
sa(svg,{ viewBox: '0 0 ' + W + ' ' + H });
}
function drawSpark(){
svg.innerHTML = '';
var padX=2,padY=4;
var pw=W-padX*2, ph=H-padY*2;
var pts = cur.map(function(val,i){ return { x:padX+(pw*i)/(N-1), y:padY+ph-(val/100)*ph }; });
var d = pts.map(function(p,i){ return (i===0?'M':'L')+p.x.toFixed(1)+','+p.y.toFixed(1); }).join(' ');
var path = se('path');
sa(path,{ d:d, fill:'none', stroke:'var(--accent)', 'stroke-width':1.8, 'stroke-linecap':'round', 'stroke-linejoin':'round', opacity:.7 });
svg.appendChild(path);
var last = pts[pts.length-1];
var dot = se('circle');
sa(dot,{ cx:last.x, cy:last.y, r:2.6, fill:'var(--accent)' });
svg.appendChild(dot);
}
function updateFacts(){
var val = cur[cur.length-1];
var prev = cur[cur.length-2];
numEl.textContent = Math.round(val*8.6).toLocaleString();
var pct = ((val-prev)/prev*100);
var up = pct >= 0;
deltaEl.textContent = (up?'▲ ':'▼ ') + Math.abs(pct).toFixed(1) + '%';
deltaEl.className = 'delta ' + (up?'up':'down');
var goalPct = Math.max(4,Math.min(100, (val/GOAL)*100));
fillEl.style.width = goalPct.toFixed(0)+'%';
goalEl.textContent = '목표 대비 ' + Math.round(goalPct) + '%';
}
function animate(target){
var from = cur.slice();
var t0 = performance.now();
function step(t){
var p = Math.min(1,(t-t0)/750);
var e = 1-Math.pow(1-p,3);
cur = from.map(function(v2,i){ return v2+(target[i]-v2)*e; });
drawSpark(); updateFacts();
if (p<1) requestAnimationFrame(step);
}
requestAnimationFrame(step);
}
function nextTarget(){
var v2=cur[cur.length-1], out=[];
for (var i=0;i<N;i++){ v2 = Math.max(15,Math.min(95, v2+R(-14,14))); out.push(v2); }
return out;
}
new ResizeObserver(function(){ measure(); drawSpark(); }).observe(svg);
measure();
drawSpark();
updateFacts();
animate(nextTarget());
setInterval(function(){ animate(nextTarget()); }, 3600);