Files
zeekay 7c5d9aa269 the face is Zen, and it ships from @hanzo/font
One family. Font stacks name Zen and fall back to the native faces, tokens
name the role rather than a face, and the packages that used to supply a
face now point at @hanzo/font.

Self-hosted @font-face blocks are removed rather than renamed: rewriting the
family in one would leave a src pointing at a file this repo no longer ships.
@hanzo/font carries the binaries and declares the faces, so the local copies
go with the declaration that used them.

Comments naming a face we no longer use are deleted, not reworded.
2026-09-01 08:16:50 -07:00

121 lines
8.6 KiB
HTML

<!doctype html>
<html><head><meta charset="utf-8"><link rel="stylesheet" href="lib.css">
<style>
.ex-grid { display: grid; grid-template-columns: 1fr 320px 300px; gap: 20px; flex: 1; min-height: 0; }
.price-row { display: flex; align-items: baseline; gap: 14px; }
.price { font-size: 34px; font-weight: 750; font-variant-numeric: tabular-nums; letter-spacing: -0.01em; }
.chart-wrap { flex: 1; margin-top: 10px; }
.chart-wrap svg { width: 100%; height: 100%; }
.draw { stroke-dasharray: 1700; stroke-dashoffset: 1700; }
.draw.go { animation: draw 2.4s cubic-bezier(0.4, 0, 0.2, 1) forwards; }
@keyframes draw { to { stroke-dashoffset: 0; } }
.fillfade { opacity: 0; transition: opacity 1.2s ease 1.6s; }
.fillfade.go { opacity: 0.9; }
.book { display: flex; flex-direction: column; font-family: Zen Mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace; font-size: 12px; }
.book .lvl { display: grid; grid-template-columns: 1fr 1fr 1fr; padding: 8px 16px; position: relative; font-variant-numeric: tabular-nums; }
.book .lvl .bar { position: absolute; inset: 0; opacity: 0.09; }
.book .lvl.ask .bar { background: var(--red); }
.book .lvl.bid .bar { background: var(--green); }
.book .lvl span { position: relative; }
.book .lvl.ask span:first-child { color: var(--red); }
.book .lvl.bid span:first-child { color: var(--green); }
.book .mid { padding: 8px 16px; text-align: center; font-weight: 700; font-size: 14px; color: var(--gold); border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); }
.ticket { display: flex; flex-direction: column; gap: 12px; }
.bs { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.bs button { padding: 10px; border-radius: 9px; border: 1px solid var(--line2); background: var(--panel2); color: var(--ink2); font-weight: 700; font-family: Zen, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; font-size: 13px; }
.bs .buy.on { background: rgba(52, 211, 153, 0.14); border-color: var(--green); color: var(--green); }
.tfield { background: var(--panel2); border: 1px solid var(--line2); border-radius: 9px; padding: 10px 13px; display: flex; justify-content: space-between; font-size: 13.5px; font-weight: 600; }
.tfield .u { color: var(--ink3); font-size: 11px; font-weight: 700; }
.place { padding: 12px; border-radius: 9px; border: none; background: linear-gradient(135deg, #34d399, #0e9f6e); color: #01130c; font-weight: 800; font-size: 13.5px; font-family: Zen, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; transition: transform 0.15s ease, box-shadow 0.3s ease; }
.place.pressed { transform: scale(0.97); box-shadow: 0 0 24px rgba(52, 211, 153, 0.4); }
.fills { display: flex; flex-direction: column; gap: 6px; font-size: 12px; }
.fill-row { display: flex; justify-content: space-between; color: var(--ink2); font-variant-numeric: tabular-nums; opacity: 0; transform: translateY(6px); transition: all 0.4s ease; }
.fill-row.in { opacity: 1; transform: none; }
.fill-row b { color: var(--green); font-weight: 650; }
</style></head>
<body><div class="app">
<aside class="side">
<div class="brand"><div class="brand-mark">▼</div><div class="brand-name">Lux</div></div>
<div class="nav-item"><svg viewBox="0 0 24 24"><rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/></svg>Dashboard</div>
<div class="nav-item active"><svg viewBox="0 0 24 24"><path d="M3 17l6-6 4 4 8-8"/><path d="M14 7h7v7"/></svg>Exchange</div>
<div class="nav-item"><svg viewBox="0 0 24 24"><path d="M21 8v10a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8m18 0V6a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v2m18 0H3"/></svg>Wallets</div>
<div class="nav-item"><svg viewBox="0 0 24 24"><path d="M3 3v18h18"/><path d="M7 14l4-4 4 4 5-6"/></svg>Reports</div>
<div class="foot"><span class="env-dot"></span>CLOB · price-time priority</div>
</aside>
<main class="main">
<div class="topbar fx">
<div class="price-row"><h1>BTC-USD</h1><span class="price" id="px">$67,241.50</span><span class="tag ok" id="chg">+2.4%</span></div>
<div class="who"><span class="pill"><span class="env-dot"></span>16 venues · SOR</span><div class="avatar">LX</div></div>
</div>
<div class="ex-grid">
<div class="card chart-wrap fx">
<svg viewBox="0 0 640 300" preserveAspectRatio="none">
<defs>
<linearGradient id="goldgrad" x1="0" y1="0" x2="1" y2="0"><stop offset="0" stop-color="#8a6d1f"/><stop offset="1" stop-color="#e8cd6a"/></linearGradient>
<linearGradient id="goldfill" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="rgba(212,175,55,0.2)"/><stop offset="1" stop-color="rgba(212,175,55,0)"/></linearGradient>
</defs>
<path class="chart-fill fillfade" id="area"/>
<path class="chart-line draw" id="line"/>
</svg>
</div>
<div class="table fx" style="flex:none; overflow:hidden;">
<div class="thead" style="grid-template-columns:1fr 1fr 1fr;"><span>Price</span><span>Size</span><span>Total</span></div>
<div class="book" id="book"></div>
</div>
<div class="card ticket fx">
<div class="label">New order</div>
<div class="bs"><button class="buy on">Buy</button><button>Sell</button></div>
<div class="tfield"><span id="osz">0.2500</span><span class="u">BTC</span></div>
<div class="tfield"><span>Limit · $67,240.00</span><span class="u">GTC</span></div>
<button class="place" id="place">Place order</button>
<div class="label" style="margin-top:6px;">Fills</div>
<div class="fills" id="fills">
<div class="fill-row"><span>0.1000 @ 67,239.50</span><b>filled</b></div>
<div class="fill-row"><span>0.1000 @ 67,240.00</span><b>filled</b></div>
<div class="fill-row"><span>0.0500 @ 67,240.00</span><b>filled</b></div>
</div>
</div>
</div>
</main>
</div>
<script>
const $ = (id) => document.getElementById(id);
// chart path — deterministic walk ending on an uptick
const pts = []; let v = 95;
for (let i = 0; i <= 32; i++) { v += Math.sin(i * 1.3) * 11 + Math.cos(i * 0.6) * 7 + 1.4; pts.push([i * 20, 280 - v * 0.75]); }
$('line').setAttribute('d', 'M' + pts.map(p => p.map(n => n.toFixed(1)).join(',')).join(' L'));
$('area').setAttribute('d', 'M' + pts.map(p => p.map(n => n.toFixed(1)).join(',')).join(' L') + ' L640,300 L0,300 Z');
const mid = 67241.5;
function bookRow(side, px, sz, tot, depth) {
return `<div class="lvl ${side}"><div class="bar" style="width:${depth}%"></div><span>${px.toLocaleString('en-US',{minimumFractionDigits:1})}</span><span>${sz.toFixed(4)}</span><span>${tot.toFixed(4)}</span></div>`;
}
let tick = 0;
function renderBook() {
const j = (n) => (Math.sin(tick * 2.1 + n * 7.3) + 1) / 2; // deterministic jitter
let asks = '', bids = '', ta = 0, tb = 0;
for (let i = 7; i >= 0; i--) { const sz = 0.3 + j(i) * 1.9; ta += sz; asks += bookRow('ask', mid + 0.5 + i * 1.5, sz, ta, 20 + j(i) * 70); }
let midHtml = `<div class="mid" id="mid">${(mid + Math.sin(tick) * 0.8).toLocaleString('en-US',{minimumFractionDigits:1})}</div>`;
for (let i = 0; i < 8; i++) { const sz = 0.3 + j(i + 9) * 1.9; tb += sz; bids += bookRow('bid', mid - 0.5 - i * 1.5, sz, tb, 20 + j(i + 9) * 70); }
$('book').innerHTML = asks + midHtml + bids;
}
function count(el, from, to, ms, fmt) {
const t0 = performance.now();
(function tk(t) { const k = Math.min(1, (t - t0) / ms), val = from + (to - from) * (1 - Math.pow(1 - k, 3)); el.textContent = fmt(val); if (k < 1) requestAnimationFrame(tk); })(t0);
}
let bookTimer;
function cycle() {
document.querySelectorAll('.fx').forEach((el, i) => { el.classList.remove('in'); setTimeout(() => el.classList.add('in'), 80 + i * 110); });
const ln = $('line'), ar = $('area');
ln.classList.remove('go'); ar.classList.remove('go'); void ln.getBoundingClientRect();
setTimeout(() => { ln.classList.add('go'); ar.classList.add('go'); }, 400);
document.querySelectorAll('.fill-row').forEach(el => el.classList.remove('in'));
count($('px'), 67180.2, 67241.5, 2000, v2 => '$' + v2.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 }));
clearInterval(bookTimer);
renderBook();
bookTimer = setInterval(() => { tick++; renderBook(); }, 700);
setTimeout(() => { $('place').classList.add('pressed'); setTimeout(() => $('place').classList.remove('pressed'), 250); }, 3400);
document.querySelectorAll('.fill-row').forEach((el, i) => setTimeout(() => el.classList.add('in'), 3900 + i * 700));
}
cycle(); setInterval(cycle, 8000);
</script></body></html>