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

109 lines
6.4 KiB
HTML

<!doctype html>
<html><head><meta charset="utf-8"><link rel="stylesheet" href="lib.css">
<style>
.cards-grid { display: grid; grid-template-columns: 460px 1fr; gap: 20px; flex: 1; min-height: 0; }
.issue { display: flex; flex-direction: column; gap: 14px; }
.vcard {
aspect-ratio: 1.586; border-radius: 18px; padding: 24px;
background: linear-gradient(135deg, #1a1a1e 0%, #000 60%), radial-gradient(420px 220px at 80% 0%, rgba(212,175,55,0.32), transparent);
background-blend-mode: screen; border: 1px solid var(--line2);
display: flex; flex-direction: column; justify-content: space-between;
position: relative; overflow: hidden;
opacity: 0; transform: rotateY(14deg) scale(0.94); transform-origin: left center;
transition: opacity 0.7s ease, transform 0.7s cubic-bezier(0.22, 1, 0.36, 1);
}
.vcard.in { opacity: 1; transform: none; }
.vcard::after { content: ''; position: absolute; inset: 0; background: linear-gradient(110deg, transparent 35%, rgba(255,255,255,0.07) 50%, transparent 65%); background-size: 220% 100%; animation: shimmer 3.2s linear infinite; }
.vcard .top { display: flex; justify-content: space-between; align-items: center; }
.vcard .net { font-weight: 800; font-style: italic; color: var(--ink2); }
.vcard .num { font-family: Zen Mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace; font-size: 20px; letter-spacing: 0.16em; min-height: 26px; }
.vcard .meta { display: flex; justify-content: space-between; font-size: 11.5px; color: var(--ink2); }
.issue-btn {
padding: 13px; border-radius: 10px; border: none;
background: linear-gradient(135deg, #ffffff, #d8c37a); color: #000;
font-size: 14px; font-weight: 700; font-family: Zen, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
transition: transform 0.15s ease, box-shadow 0.3s ease;
}
.issue-btn.pressed { transform: scale(0.97); box-shadow: 0 0 26px rgba(212, 175, 55, 0.45); }
.kyc { display: flex; flex-direction: column; gap: 8px; }
.kyc .k { display: flex; justify-content: space-between; align-items: center; font-size: 12.5px; color: var(--ink2); padding: 7px 2px; }
.row { grid-template-columns: 1.7fr 1fr 1fr 1fr; }
.thead { grid-template-columns: 1.7fr 1fr 1fr 1fr; }
</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>Home</div>
<div class="nav-item"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M3 12h18"/></svg>Payments</div>
<div class="nav-item active"><svg viewBox="0 0 24 24"><rect x="2" y="6" width="20" height="13" rx="2"/><path d="M2 10h20"/></svg>Cards</div>
<div class="nav-item"><svg viewBox="0 0 24 24"><path d="M12 2v20M2 12h20"/></svg>Earn</div>
<div class="foot"><span class="env-dot"></span>Issuing · provider KYC clear</div>
</aside>
<main class="main">
<div class="topbar fx"><h1>Cards</h1><div class="who"><span class="pill"><span class="env-dot"></span>Program active</span><div class="avatar">LX</div></div></div>
<div class="cards-grid">
<div class="card issue fx">
<div class="label">Issue virtual card</div>
<div class="kyc">
<div class="k"><span>Platform KYC</span><span class="tag ok">Clear</span></div>
<div class="k"><span>Issuer KYC — provider-scoped</span><span class="tag ok" id="ikyc">Clear</span></div>
<div class="k"><span>Consent agreement</span><span class="tag ok">Signed</span></div>
</div>
<button class="issue-btn" id="issue">Issue card</button>
<div class="vcard" id="vc">
<div class="top"><span style="font-size:15px;">▼ <b>Lux</b></span><span class="net">VISA</span></div>
<div class="num" id="pan"></div>
<div class="meta"><span>LUX DEMO</span><span>EXP 09/29</span><span id="ctag">VIRTUAL</span></div>
</div>
</div>
<div class="table fx">
<div class="thead"><span>Merchant</span><span>Amount</span><span>Card</span><span>Auth</span></div>
<div id="feed"></div>
</div>
</div>
</main>
</div>
<script>
const $ = (id) => document.getElementById(id);
const PAN = '4127 88•• •••• 0392';
const auths = [
['Figma', '$15.00', '•• 0392', 'ok', 'Approved'],
['AWS', '$482.19', '•• 0392', 'ok', 'Approved'],
['Uber', '$23.40', '•• 7715', 'ok', 'Approved'],
['Unknown merchant · RU', '$890.00', '•• 7715', 'bad', 'Declined'],
['GitHub', '$21.00', '•• 0392', 'ok', 'Approved'],
['Notion', '$96.00', '•• 7715', 'ok', 'Approved'],
['Linear', '$8.00', '•• 7715', 'ok', 'Approved'],
['Google Cloud', '$1,240.55', '•• 0392', 'ok', 'Approved'],
['Blue Bottle', '$11.75', '•• 7715', 'ok', 'Approved'],
['Datadog', '$310.00', '•• 0392', 'ok', 'Approved'],
];
function addRow([m, amt, card, cls, st], flash) {
const d = document.createElement('div');
d.className = 'row' + (flash ? ' flash' : '');
d.style.gridTemplateColumns = '1.7fr 1fr 1fr 1fr';
d.innerHTML = `<span>${m}</span><span style="font-variant-numeric:tabular-nums;">${amt}</span><span class="mono" style="color:var(--ink2)">${card}</span><span class="tag ${cls}">${st}</span>`;
$('feed').prepend(d);
while ($('feed').children.length > 10) $('feed').lastChild.remove();
}
function typePan(done) {
let i = 0;
const t = setInterval(() => {
i++; $('pan').textContent = PAN.slice(0, i);
if (i >= PAN.length) { clearInterval(t); done && done(); }
}, 45);
}
function cycle() {
document.querySelectorAll('.fx').forEach((el, i) => { el.classList.remove('in'); setTimeout(() => el.classList.add('in'), 80 + i * 110); });
$('vc').classList.remove('in');
$('pan').textContent = '';
$('feed').innerHTML = '';
auths.slice(2).reverse().forEach(r => addRow(r, false));
setTimeout(() => { $('issue').classList.add('pressed'); setTimeout(() => $('issue').classList.remove('pressed'), 250); }, 1600);
setTimeout(() => { $('vc').classList.add('in'); typePan(); }, 2000);
setTimeout(() => addRow(auths[1], true), 4800);
setTimeout(() => addRow(auths[0], true), 6200);
}
cycle(); setInterval(cycle, 8000);
</script></body></html>