mirror of
https://github.com/luxfi/financial.git
synced 2026-10-30 16:55:56 +00:00
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.
109 lines
6.4 KiB
HTML
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>
|