Three-line chart with EMA, VWAP, bid/ask flash and gradient fill.
<!doctype html>
<!-- Example: sparkline-ticker — 3-line chart + bid/ask flash + gradient area-fill -->
<html data-duration="8" data-aspect="16:9"><head><style>
:root {
--cream:#f6f5f1; --cream-2:#efece4; --ink:#0a0a0a; --mute:#6b6862;
--line:#e3dfd3; --signal:#ff3b1f; --signal-2:#ff6a4a; --frame:#ffb800;
--green:#1f8a5b; --blue:#2b66ff;
}
* { box-sizing: border-box; }
body { margin:0; }
body { background: var(--cream); color: var(--ink); height: 100vh;
display: grid; place-items: center; font-family: ui-monospace, monospace; }
.card { width: 880px; padding: 44px; background: white;
border: 1px solid var(--line); border-radius: 20px;
box-shadow: 0 30px 80px -40px rgba(0,0,0,.25); }
.row { display: flex; align-items: flex-start; justify-content: space-between; gap: 24px; }
.row .lbl { font-size: 11px; letter-spacing: .22em; color: var(--mute); text-transform: uppercase; }
.price { font-size: 56px; font-weight: 700; letter-spacing: -0.02em; margin-top: 4px; }
.delta { font-size: 14px; margin-top: 2px; }
.legend { display: flex; gap: 14px; font-size: 11px; letter-spacing: .15em; margin-top: 8px; }
.legend i { display: inline-block; width: 10px; height: 10px; border-radius: 50%;
vertical-align: middle; margin-right: 6px; }
.flash { padding: 4px 10px; border-radius: 6px; font-size: 11px; letter-spacing: .15em;
opacity: 0; transition: opacity .12s; }
.flash.up { background: rgba(31,138,91,.14); color: var(--green); }
.flash.dn { background: rgba(255,59,31,.14); color: var(--signal); }
svg { width: 100%; height: 240px; margin-top: 22px; }
.line { fill: none; stroke-width: 2.5; stroke-linejoin: round; stroke-linecap: round; }
.l1 { stroke: var(--signal); } .l2 { stroke: var(--blue); } .l3 { stroke: var(--green); }
.cur { fill: var(--signal); }
.grid { stroke: var(--line); stroke-width: 1; stroke-dasharray: 2 4; }
</style></head><body>
<div class="card">
<div class="row">
<div>
<div class="lbl">HFRAME · 24h</div>
<div class="price" id="px">$ 0.00</div>
<div class="delta" id="dl">▲ 0.00%</div>
</div>
<div style="text-align:right">
<div class="flash up" id="bf">BID ↑ 0.00</div>
<div class="flash dn" id="af" style="margin-top:4px">ASK ↓ 0.00</div>
<div class="legend" style="margin-top:12px">
<span><i style="background:var(--signal)"></i>PRICE</span>
<span><i style="background:var(--blue)"></i>EMA(12)</span>
<span><i style="background:var(--green)"></i>VWAP</span>
</div>
</div>
</div>
<svg viewBox="0 0 600 220" preserveAspectRatio="none">
<defs>
<linearGradient id="fill" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#ff3b1f" stop-opacity="0.22"/>
<stop offset="100%" stop-color="#ff3b1f" stop-opacity="0"/>
</linearGradient>
</defs>
<g>
<line class="grid" x1="0" y1="60" x2="600" y2="60"/>
<line class="grid" x1="0" y1="110" x2="600" y2="110"/>
<line class="grid" x1="0" y1="160" x2="600" y2="160"/>
</g>
<path id="area" fill="url(#fill)" d=""/>
<path id="l3" class="line l3" d=""/>
<path id="l2" class="line l2" d=""/>
<path id="l1" class="line l1" d=""/>
<circle id="cur" class="cur" r="5" cx="0" cy="0"/>
</svg>
</div>
<script>
var N = 60;
var W = 600, H = 220;
var series1 = [], series2 = [], series3 = [];
for (var i = 0; i < N; i++) {
var p = 30 + 14 * Math.sin(i * 0.36) + 6 * Math.sin(i * 1.25 + 1.4);
series1.push(p);
// EMA(12): trailing weighted average.
var ema = series1.slice(Math.max(0, i - 12), i + 1).reduce(function(a, b) { return a + b; }, 0) / Math.min(12, i + 1);
series2.push(ema);
// VWAP: cumulative.
var sum = series1.slice(0, i + 1).reduce(function(a, b) { return a + b; }, 0);
series3.push(sum / (i + 1));
}
var l1 = document.getElementById('l1');
var l2 = document.getElementById('l2');
var l3 = document.getElementById('l3');
var area = document.getElementById('area');
var cur = document.getElementById('cur');
var px = document.getElementById('px');
var dl = document.getElementById('dl');
var bf = document.getElementById('bf');
var af = document.getElementById('af');
var t = 0;
function pathFor(arr, n) {
var d = '';
for (var i = 0; i < n; i++) {
var x = (i / (N - 1)) * W;
var y = H - (arr[i] / 60) * (H - 24) - 12;
d += (i === 0 ? 'M' : 'L') + x.toFixed(1) + ',' + y.toFixed(1) + ' ';
}
return d;
}
function render() {
var u = Math.min(1, t / 6.4);
var n = Math.max(2, Math.floor(N * u));
var d1 = pathFor(series1, n);
l1.setAttribute('d', d1);
l2.setAttribute('d', pathFor(series2, n));
l3.setAttribute('d', pathFor(series3, n));
var lastX = ((n - 1) / (N - 1)) * W;
area.setAttribute('d', d1 + 'L' + lastX.toFixed(1) + ',' + H + ' L0,' + H + ' Z');
var ly = H - (series1[n - 1] / 60) * (H - 24) - 12;
cur.setAttribute('cx', lastX);
cur.setAttribute('cy', ly);
var v = series1[n - 1];
px.textContent = '$ ' + v.toFixed(2);
var change = ((v - series1[0]) / series1[0]) * 100;
dl.textContent = (change >= 0 ? '▲ ' : '▼ ') + change.toFixed(2) + '%';
dl.style.color = change >= 0 ? 'var(--green)' : 'var(--signal)';
// Bid/ask flash on every new bar arrival (when n changes integer).
var prev = series1[Math.max(0, n - 2)];
var diff = v - prev;
bf.textContent = 'BID ↑ ' + (v - 0.03).toFixed(2);
af.textContent = 'ASK ↓ ' + (v + 0.03).toFixed(2);
bf.style.opacity = diff > 0 ? '1' : '0.25';
af.style.opacity = diff < 0 ? '1' : '0.25';
}
addEventListener('hf-seek', function(e) { t = e.detail.time; render(); });
render();
</script>
</body></html>