Banking — Virtual Card Visual
A polished, 3D flippable virtual bank card component built in the trust-first fintech style with Inter typography and tabular figures. The brand face shows a metallic chip, contactless glyph, masked PAN, holder and expiry, then flips in 3D to reveal the CVV on a magnetic-stripe back. Tap to flip, reveal or hide the number, and copy it to the clipboard, and switch live between three designs — metal black, aurora gradient and minimal white — all in vanilla JavaScript with a small toast helper.
MCP
Code
:root {
--navy: #0e1b3a;
--navy-2: #16264d;
--ink: #0e1726;
--ink-2: #3a4660;
--muted: #697089;
--accent: #3b6ef6;
--accent-d: #2a55cc;
--accent-50: #eaf0ff;
--teal: #0fb5a6;
--violet: #7c5cff;
--bg: #f5f7fb;
--surface: #ffffff;
--line: rgba(14, 27, 58, 0.10);
--line-2: rgba(14, 27, 58, 0.18);
--ok: #1f9d62;
--warn: #d9982b;
--danger: #d4493e;
--credit: #1f9d62;
--debit: #0e1726;
--r-sm: 8px;
--r-md: 14px;
--r-lg: 20px;
--shadow-sm: 0 1px 2px rgba(14, 27, 58, 0.06), 0 2px 8px rgba(14, 27, 58, 0.05);
--shadow-card: 0 18px 40px -16px rgba(14, 27, 58, 0.45), 0 6px 16px -8px rgba(14, 27, 58, 0.30);
}
* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
margin: 0;
background: var(--bg);
color: var(--ink);
font-family: "Inter", system-ui, -apple-system, sans-serif;
line-height: 1.5;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
font-variant-numeric: tabular-nums;
}
.wrap {
max-width: 560px;
margin: 0 auto;
padding: 32px 20px 56px;
}
/* Header */
.head {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 16px;
margin-bottom: 22px;
}
.eyebrow {
margin: 0 0 4px;
font-size: 12px;
font-weight: 600;
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--muted);
}
.head h1 {
margin: 0;
font-size: 26px;
font-weight: 800;
letter-spacing: -0.02em;
color: var(--navy);
}
.verified {
display: inline-flex;
align-items: center;
gap: 5px;
padding: 5px 11px;
border-radius: 999px;
background: rgba(31, 157, 98, 0.10);
color: var(--ok);
font-size: 12px;
font-weight: 700;
white-space: nowrap;
}
/* Stage */
.stage { margin-bottom: 30px; }
.card-scene {
perspective: 1400px;
margin: 0 auto 22px;
max-width: 380px;
}
/* Card */
.card {
position: relative;
width: 100%;
aspect-ratio: 1.586 / 1;
border: 0;
padding: 0;
background: transparent;
cursor: pointer;
transform-style: preserve-3d;
transition: transform 0.7s cubic-bezier(0.4, 0.15, 0.2, 1);
border-radius: var(--r-lg);
outline: none;
}
.card.is-flipped { transform: rotateY(180deg); }
.card:focus-visible { outline: 3px solid var(--accent); outline-offset: 4px; border-radius: var(--r-lg); }
.card-face {
position: absolute;
inset: 0;
border-radius: var(--r-lg);
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
box-shadow: var(--shadow-card);
overflow: hidden;
display: flex;
flex-direction: column;
padding: 22px;
color: #fff;
}
/* ---- Front ---- */
.card-front { z-index: 2; }
.card-top {
display: flex;
align-items: center;
justify-content: space-between;
}
.brand {
font-size: 16px;
font-weight: 800;
letter-spacing: 0.16em;
}
.card-type {
font-size: 10px;
font-weight: 700;
letter-spacing: 0.14em;
padding: 3px 8px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.16);
backdrop-filter: blur(4px);
}
.chip {
width: 44px;
height: 33px;
margin-top: 16px;
border-radius: 7px;
background: linear-gradient(135deg, #f4d27a, #c79a3c 55%, #e7c264);
display: flex;
flex-direction: column;
justify-content: space-evenly;
padding: 5px 6px;
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.18);
}
.chip span {
display: block;
height: 2px;
border-radius: 2px;
background: rgba(120, 85, 20, 0.55);
}
.wireless {
position: absolute;
top: 56px;
left: 76px;
color: rgba(255, 255, 255, 0.85);
}
.pan {
display: flex;
gap: 16px;
margin-top: auto;
font-size: clamp(17px, 5.4vw, 21px);
font-weight: 600;
letter-spacing: 0.06em;
font-variant-numeric: tabular-nums;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.25);
}
.pan .mask { letter-spacing: 0.02em; }
.card-bottom {
display: flex;
align-items: flex-end;
gap: 18px;
margin-top: 16px;
}
.field { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.field label {
font-size: 8.5px;
font-weight: 600;
letter-spacing: 0.12em;
text-transform: uppercase;
opacity: 0.7;
}
.field span {
font-size: 13px;
font-weight: 600;
letter-spacing: 0.04em;
white-space: nowrap;
}
.field.expiry { margin-left: auto; }
.network {
display: flex;
align-items: center;
flex-shrink: 0;
}
.network .dot {
width: 26px;
height: 26px;
border-radius: 50%;
}
.network .dot-a { background: #eb6f3a; }
.network .dot-b { background: rgba(245, 180, 35, 0.85); margin-left: -11px; mix-blend-mode: screen; }
/* ---- Back ---- */
.card-back {
transform: rotateY(180deg);
padding-left: 0;
padding-right: 0;
}
.magstripe {
height: 42px;
margin: 8px 0 18px;
background: rgba(0, 0, 0, 0.78);
}
.signature {
display: flex;
align-items: center;
gap: 10px;
margin: 0 22px 14px;
}
.sig-line {
flex: 1;
height: 34px;
border-radius: 5px;
background: repeating-linear-gradient(-45deg, #f6f6f6, #f6f6f6 8px, #e7e7e7 8px, #e7e7e7 16px);
color: rgba(14, 23, 38, 0.55);
font-size: 8px;
font-weight: 500;
display: flex;
align-items: center;
padding: 0 10px;
letter-spacing: 0.01em;
}
.cvv-box {
display: flex;
flex-direction: column;
align-items: center;
gap: 2px;
}
.cvv-label {
font-size: 8px;
font-weight: 700;
letter-spacing: 0.12em;
opacity: 0.75;
}
.cvv-value {
background: #fff;
color: var(--ink);
font-weight: 700;
font-size: 15px;
letter-spacing: 0.18em;
padding: 5px 12px;
border-radius: var(--r-sm);
font-variant-numeric: tabular-nums;
min-width: 62px;
text-align: center;
}
.back-note {
margin: auto 22px 2px;
display: flex;
align-items: center;
gap: 6px;
font-size: 10px;
font-weight: 500;
color: rgba(255, 255, 255, 0.78);
}
/* ---- Themes (applied to .card) ---- */
.card.theme-metal .card-face {
background:
radial-gradient(120% 140% at 80% -10%, rgba(123, 92, 255, 0.22), transparent 55%),
linear-gradient(140deg, #1c1f29 0%, #0c0d12 60%, #15171f 100%);
}
.card.theme-gradient .card-face {
background: linear-gradient(135deg, #3b6ef6 0%, #7c5cff 48%, #0fb5a6 100%);
}
.card.theme-minimal .card-face {
background: linear-gradient(155deg, #ffffff 0%, #eef1f7 100%);
color: var(--ink);
}
.card.theme-minimal .card-type { background: rgba(14, 27, 58, 0.07); }
.card.theme-minimal .pan { text-shadow: none; }
.card.theme-minimal .wireless { color: rgba(14, 27, 58, 0.55); }
.card.theme-minimal .back-note { color: rgba(14, 23, 38, 0.6); }
.card.theme-minimal .magstripe { background: #1c2232; }
/* Controls */
.controls {
display: flex;
gap: 10px;
justify-content: center;
flex-wrap: wrap;
}
.btn {
display: inline-flex;
align-items: center;
gap: 7px;
padding: 10px 16px;
border-radius: var(--r-md);
border: 1px solid var(--line);
background: var(--surface);
color: var(--ink);
font: inherit;
font-size: 13.5px;
font-weight: 600;
cursor: pointer;
box-shadow: var(--shadow-sm);
transition: transform 0.12s, border-color 0.16s, background 0.16s, color 0.16s;
}
.btn:hover { border-color: var(--line-2); transform: translateY(-1px); }
.btn:active { transform: translateY(0); }
.btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.btn[aria-pressed="true"] {
background: var(--accent);
border-color: var(--accent);
color: #fff;
}
.btn[aria-pressed="true"] svg { color: #fff; }
/* Themes section */
.themes { margin-bottom: 26px; }
.section-label {
margin: 0 0 12px;
font-size: 13px;
font-weight: 700;
color: var(--navy);
}
.theme-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 12px;
}
.swatch {
display: flex;
flex-direction: column;
gap: 9px;
padding: 11px;
border-radius: var(--r-md);
border: 1.5px solid var(--line);
background: var(--surface);
cursor: pointer;
font: inherit;
text-align: left;
transition: border-color 0.16s, box-shadow 0.16s, transform 0.12s;
}
.swatch:hover { transform: translateY(-2px); box-shadow: var(--shadow-sm); }
.swatch:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.swatch.is-active {
border-color: var(--accent);
box-shadow: 0 0 0 3px var(--accent-50);
}
.sw-preview {
height: 46px;
border-radius: var(--r-sm);
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.08);
}
.pv-metal {
background:
radial-gradient(120% 140% at 80% -10%, rgba(123, 92, 255, 0.3), transparent 55%),
linear-gradient(140deg, #1c1f29, #0c0d12 60%, #15171f);
}
.pv-gradient { background: linear-gradient(135deg, #3b6ef6, #7c5cff 48%, #0fb5a6); }
.pv-minimal { background: linear-gradient(155deg, #ffffff, #eef1f7); border: 1px solid var(--line); }
.sw-name {
font-size: 12.5px;
font-weight: 600;
color: var(--ink-2);
}
.swatch.is-active .sw-name { color: var(--navy); }
/* Legal */
.legal {
display: flex;
align-items: center;
gap: 7px;
margin: 0;
padding: 12px 14px;
border-radius: var(--r-md);
background: var(--surface);
border: 1px solid var(--line);
color: var(--muted);
font-size: 12px;
font-weight: 500;
}
.legal svg { color: var(--ink-2); flex-shrink: 0; }
/* Toast */
.toast {
position: fixed;
left: 50%;
bottom: 26px;
transform: translate(-50%, 16px);
background: var(--navy);
color: #fff;
padding: 11px 18px;
border-radius: 999px;
font-size: 13px;
font-weight: 600;
box-shadow: 0 12px 30px -8px rgba(14, 27, 58, 0.5);
opacity: 0;
pointer-events: none;
transition: opacity 0.24s, transform 0.24s;
z-index: 50;
}
.toast.show { opacity: 1; transform: translate(-50%, 0); }
@media (max-width: 520px) {
.wrap { padding: 24px 16px 48px; }
.head h1 { font-size: 23px; }
.controls .btn { flex: 1 1 calc(50% - 5px); justify-content: center; }
.theme-grid { gap: 9px; }
.swatch { padding: 9px; }
.sw-name { font-size: 11.5px; }
}(function () {
"use strict";
// Fictional but realistic card data.
var CARD = {
pan: "4242 8810 5673 4242",
cvv: "318",
holder: "ANA M. REYES",
expiry: "08 / 29"
};
var card = document.getElementById("card");
var panEl = document.getElementById("pan");
var cvvEl = document.getElementById("cvv");
var flipBtn = document.getElementById("flip");
var revealBtn = document.getElementById("reveal");
var revealLabel = document.getElementById("reveal-label");
var copyBtn = document.getElementById("copy");
var themeGrid = document.getElementById("themeGrid");
var toastEl = document.getElementById("toast");
var revealed = false;
var flipped = false;
var toastTimer = null;
function toast(msg) {
toastEl.textContent = msg;
toastEl.classList.add("show");
clearTimeout(toastTimer);
toastTimer = setTimeout(function () {
toastEl.classList.remove("show");
}, 2000);
}
var groups = CARD.pan.split(" ");
function renderPan() {
if (revealed) {
panEl.innerHTML = groups
.map(function (g) { return "<span>" + g + "</span>"; })
.join("");
} else {
panEl.innerHTML =
"<span>" + groups[0] + "</span>" +
'<span class="mask">••••</span>' +
'<span class="mask">••••</span>' +
"<span>" + groups[3] + "</span>";
}
}
function setRevealed(state) {
revealed = state;
renderPan();
cvvEl.textContent = revealed ? CARD.cvv : "•••";
revealBtn.setAttribute("aria-pressed", String(revealed));
revealLabel.textContent = revealed ? "Hide number" : "Reveal number";
}
function setFlipped(state) {
flipped = state;
card.classList.toggle("is-flipped", flipped);
card.setAttribute("aria-pressed", String(flipped));
}
// Flip on card tap.
card.addEventListener("click", function () {
setFlipped(!flipped);
});
flipBtn.addEventListener("click", function () {
setFlipped(!flipped);
});
revealBtn.addEventListener("click", function () {
setRevealed(!revealed);
if (revealed) {
toast("Card number revealed — keep it private");
} else {
toast("Card number hidden");
}
});
copyBtn.addEventListener("click", function () {
var text = CARD.pan.replace(/\s+/g, "");
function done() { toast("Card number copied to clipboard"); }
function fallback() {
var ta = document.createElement("textarea");
ta.value = text;
ta.setAttribute("readonly", "");
ta.style.position = "absolute";
ta.style.left = "-9999px";
document.body.appendChild(ta);
ta.select();
try { document.execCommand("copy"); done(); }
catch (e) { toast("Couldn't copy — please copy manually"); }
document.body.removeChild(ta);
}
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(text).then(done, fallback);
} else {
fallback();
}
});
// Theme switching.
var THEME_CLASSES = ["theme-metal", "theme-gradient", "theme-minimal"];
var swatches = Array.prototype.slice.call(themeGrid.querySelectorAll(".swatch"));
function applyTheme(theme, source) {
THEME_CLASSES.forEach(function (c) { card.classList.remove(c); });
card.classList.add("theme-" + theme);
swatches.forEach(function (sw) {
var active = sw.getAttribute("data-theme") === theme;
sw.classList.toggle("is-active", active);
sw.setAttribute("aria-checked", String(active));
});
if (source) toast(source.querySelector(".sw-name").textContent + " applied");
}
themeGrid.addEventListener("click", function (e) {
var sw = e.target.closest(".swatch");
if (!sw) return;
applyTheme(sw.getAttribute("data-theme"), sw);
});
// Keyboard nav for the radiogroup (arrow keys).
themeGrid.addEventListener("keydown", function (e) {
if (e.key !== "ArrowRight" && e.key !== "ArrowLeft") return;
var current = swatches.findIndex(function (s) {
return s.classList.contains("is-active");
});
var next = e.key === "ArrowRight"
? (current + 1) % swatches.length
: (current - 1 + swatches.length) % swatches.length;
e.preventDefault();
var sw = swatches[next];
applyTheme(sw.getAttribute("data-theme"), sw);
sw.focus();
});
// Init: start on the gradient theme to match initial markup class.
applyTheme("gradient");
document.getElementById("holder").textContent = CARD.holder;
document.getElementById("expiry").textContent = CARD.expiry;
setRevealed(false);
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Banking — Virtual Card Visual</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="style.css" />
</head>
<body>
<main class="wrap">
<header class="head">
<div>
<p class="eyebrow">Wallet · Virtual cards</p>
<h1>Your cards</h1>
</div>
<span class="verified" title="Identity verified">
<svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true"><path fill="currentColor" d="m9.55 17.05-4.6-4.6 1.4-1.4 3.2 3.2 7.7-7.7 1.4 1.4z"/></svg>
Verified
</span>
</header>
<section class="stage" aria-label="Virtual card preview">
<div class="card-scene">
<button
id="card"
class="card theme-gradient"
type="button"
aria-pressed="false"
aria-label="Bank card, tap to flip and reveal security code"
>
<div class="card-face card-front">
<div class="card-top">
<span class="brand">STEALTHIS</span>
<span class="card-type">VIRTUAL</span>
</div>
<div class="chip" aria-hidden="true">
<span></span><span></span><span></span>
</div>
<div class="wireless" aria-hidden="true">
<svg viewBox="0 0 24 24" width="22" height="22"><path fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" d="M8 7a7 7 0 0 1 0 10M12 4a11 11 0 0 1 0 16M4 10a3 3 0 0 1 0 4"/></svg>
</div>
<div class="pan" id="pan" aria-label="Card number">
<span>4242</span><span class="mask">••••</span><span class="mask">••••</span><span>4242</span>
</div>
<div class="card-bottom">
<div class="field">
<label>Card holder</label>
<span id="holder">ANA M. REYES</span>
</div>
<div class="field expiry">
<label>Expires</label>
<span id="expiry">08 / 29</span>
</div>
<div class="network" aria-hidden="true">
<span class="dot dot-a"></span><span class="dot dot-b"></span>
</div>
</div>
</div>
<div class="card-face card-back">
<div class="magstripe" aria-hidden="true"></div>
<div class="signature">
<span class="sig-line">Authorized signature — not valid unless signed</span>
<span class="cvv-box">
<span class="cvv-label">CVV</span>
<span id="cvv" class="cvv-value">•••</span>
</span>
</div>
<p class="back-note">
<svg viewBox="0 0 24 24" width="13" height="13" aria-hidden="true"><path fill="currentColor" d="M12 1 3 5v6c0 5 3.8 9.7 9 11 5.2-1.3 9-6 9-11V5z"/></svg>
Secured by 3-D Secure 2.0 · Issued by Stealthis Bank N.A.
</p>
</div>
</button>
</div>
<div class="controls">
<button id="flip" class="btn" type="button">
<svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true"><path fill="currentColor" d="M12 6V3L8 7l4 4V8a4 4 0 1 1-4 4H6a6 6 0 1 0 6-6"/></svg>
Flip card
</button>
<button id="reveal" class="btn" type="button" aria-pressed="false">
<svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true"><path fill="currentColor" d="M12 5c-5 0-9 4.5-10 7 1 2.5 5 7 10 7s9-4.5 10-7c-1-2.5-5-7-10-7m0 11a4 4 0 1 1 0-8 4 4 0 0 1 0 8"/></svg>
<span id="reveal-label">Reveal number</span>
</button>
<button id="copy" class="btn" type="button">
<svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true"><path fill="currentColor" d="M16 1H4a2 2 0 0 0-2 2v12h2V3h12zm3 4H8a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h11a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2m0 16H8V7h11z"/></svg>
Copy number
</button>
</div>
</section>
<section class="themes" aria-label="Card themes">
<p class="section-label">Card design</p>
<div class="theme-grid" id="themeGrid" role="radiogroup" aria-label="Select card theme">
<button class="swatch sw-metal" type="button" role="radio" aria-checked="false" data-theme="metal">
<span class="sw-preview pv-metal"></span>
<span class="sw-name">Metal Black</span>
</button>
<button class="swatch sw-gradient is-active" type="button" role="radio" aria-checked="true" data-theme="gradient">
<span class="sw-preview pv-gradient"></span>
<span class="sw-name">Aurora Gradient</span>
</button>
<button class="swatch sw-minimal" type="button" role="radio" aria-checked="false" data-theme="minimal">
<span class="sw-preview pv-minimal"></span>
<span class="sw-name">Minimal White</span>
</button>
</div>
</section>
<p class="legal">
<svg viewBox="0 0 24 24" width="13" height="13" aria-hidden="true"><path fill="currentColor" d="M18 8h-1V6a5 5 0 0 0-10 0v2H6a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2M9 6a3 3 0 0 1 6 0v2H9zm3 11a2 2 0 1 1 0-4 2 2 0 0 1 0 4"/></svg>
Card details are tokenized. We never display your full PAN on shared screens.
</p>
</main>
<div id="toast" class="toast" role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>Virtual Card Visual
A self-contained payment-card component rendered in calm, trust-first fintech style: Inter typography, tabular figures for the masked PAN and expiry, a gilded chip, a contactless glyph and a soft layered shadow that lifts the card off the page. Tap the card and it rotates a full 180° in 3D to reveal a magnetic-stripe back, signature panel and a boxed CVV, complete with a 3-D Secure issuer note.
Three controls sit below the card. Flip mirrors the tap gesture, Reveal number unmasks the full PAN and CVV (with a privacy toast) and toggles back to a hidden state, and Copy number writes the digits to the clipboard with a graceful execCommand fallback. A theme picker behaves as a proper radiogroup — click or arrow-key between Metal Black, Aurora Gradient and Minimal White to restyle the live card instantly, including the minimal theme that flips text to ink for contrast.
Everything is vanilla JavaScript with a tiny toast(msg) helper — no frameworks, no build step. The card is a real <button> with aria-pressed, swatches expose aria-checked, focus rings are visible throughout, and the layout stays comfortable down to 360px for a mobile-first wallet feel.
Illustrative UI only — not real banking software or financial advice.