Payment Card
payment-carduiPremium metallic credit-card visual with embossed type, holographic foil, and per-digit typing animation. Auto-detects brand (Visa, Mastercard, Amex, Discover), flips in 3D, and supports opt-in mouse-parallax tilt and shimmer. Pure presentational — pass typed values in via props.
Installation
$pnpm dlx shadcn@latest add https://uipkge.dev/r/lit/payment-card.json$npx shadcn@latest add https://uipkge.dev/r/lit/payment-card.json$yarn dlx shadcn@latest add https://uipkge.dev/r/lit/payment-card.json$bunx shadcn@latest add https://uipkge.dev/r/lit/payment-card.jsonManifest:
https://uipkge.dev/r/lit/payment-card.jsonInstalls to:src/uip/components/payment-card/Examples
Loading interactive previews…
Loading interactive previews…
Props
| Name | Type / Values | Default | Required |
|---|---|---|---|
number | string | — | optional |
name | string | — | optional |
expiry | string | — | optional |
cvc | string | — | optional |
brand | CardBrand | 'auto' | auto | optional |
flipped | boolean | false | optional |
variant | 'default' | 'compact' | default | optional |
tilt | boolean | false | optional |
shimmer | boolean | false | optional |
flip | string | true | optional |
size | 'sm' | 'md' | 'lg' | md | optional |
Schema
Type aliases from this item's source — use them to shape the data you pass in.
BrandVisualinterface BrandVisual {
cardClass: string
accentGlow: string
foilSheen: string
guillocheColor: string
textColor: string
subtextColor: string
embossStyle: string
chipType: 'gold' | 'silver'
}HTMLElementTagNameMapinterface HTMLElementTagNameMap {
'uip-payment-card': UipPaymentCard
}
}Used by
Files installed (1)
src/uip/components/payment-card/payment-card.ts22.3 kB
import { LitElement, css, html, nothing, svg } from 'lit' import { styleMap } from 'lit/directives/style-map.js' import { cn } from '../../lib/utils' import { tailwind } from '../../lib/styles' import { ThemeController } from '../../lib/theme' export type CardBrand = 'visa' | 'mastercard' | 'amex' | 'discover' | 'unknown' // --- verbatim from packages/registry-react/components/payment-card/payment-card.tsx --- function detectBrand(raw: string): CardBrand { const n = raw.replace(/\D/g, '') if (!n) return 'unknown' if (/^4/.test(n)) return 'visa' if (/^(5[1-5]|2[2-7])/.test(n)) return 'mastercard' if (/^3[47]/.test(n)) return 'amex' if (/^(6011|65|64[4-9])/.test(n)) return 'discover' return 'unknown' } function formatMasked(raw: string, brand: CardBrand): string { const digits = raw.replace(/\D/g, '').slice(0, brand === 'amex' ? 15 : 16) const groups = brand === 'amex' ? [4, 6, 5] : [4, 4, 4, 4] const total = groups.reduce((a, b) => a + b, 0) const padded = (digits + '••••••••••••••••').slice(0, total) let i = 0 return groups .map((g) => { const slice = padded.slice(i, i + g) i += g return slice }) .join(' ') } interface BrandVisual { cardClass: string accentGlow: string foilSheen: string guillocheColor: string textColor: string subtextColor: string embossStyle: string chipType: 'gold' | 'silver' } const BRAND_VISUALS: Record<CardBrand, BrandVisual> = { visa: { cardClass: 'bg-[radial-gradient(ellipse_at_top_right,#1e3a8a_0%,#0f2252_45%,#08122c_85%,#040a18_100%)] text-white shadow-[0_22px_45px_-12px_rgba(15,34,82,0.65),0_8px_16px_-8px_rgba(0,0,0,0.5)]', accentGlow: 'from-blue-400/20 via-indigo-500/10 to-transparent', foilSheen: 'from-white/20 via-blue-200/5 to-transparent', guillocheColor: 'rgba(96, 165, 250, 0.12)', textColor: 'text-white', subtextColor: 'text-blue-200/60', embossStyle: 'text-white drop-shadow-[0_1px_1px_rgba(0,0,0,0.8)]', chipType: 'gold', }, mastercard: { cardClass: 'bg-[radial-gradient(ellipse_at_top_right,#2a0808_0%,#180608_35%,#0f0506_70%,#09090b_100%)] text-white shadow-[0_22px_45px_-12px_rgba(220,38,38,0.35),0_8px_16px_-8px_rgba(0,0,0,0.6)]', accentGlow: 'from-red-500/25 via-amber-500/15 to-transparent', foilSheen: 'from-white/20 via-orange-300/10 to-transparent', guillocheColor: 'rgba(239, 68, 68, 0.1)', textColor: 'text-white', subtextColor: 'text-zinc-400', embossStyle: 'text-zinc-100 drop-shadow-[0_1px_2px_rgba(0,0,0,0.9)]', chipType: 'gold', }, amex: { cardClass: 'bg-[radial-gradient(ellipse_at_top_right,#1e293b_0%,#0f172a_40%,#090d16_80%,#020617_100%)] text-slate-100 shadow-[0_22px_45px_-12px_rgba(15,23,42,0.7),0_8px_16px_-8px_rgba(0,0,0,0.6)]', accentGlow: 'from-slate-300/20 via-teal-500/10 to-transparent', foilSheen: 'from-white/25 via-cyan-100/10 to-transparent', guillocheColor: 'rgba(148, 163, 184, 0.12)', textColor: 'text-slate-100', subtextColor: 'text-slate-400', embossStyle: 'text-slate-100 drop-shadow-[0_1px_2px_rgba(0,0,0,0.9)]', chipType: 'silver', }, discover: { cardClass: 'bg-[radial-gradient(ellipse_at_top_right,#431407_0%,#270d04_45%,#1c0903_80%,#0c0401_100%)] text-white shadow-[0_22px_45px_-12px_rgba(194,65,12,0.45),0_8px_16px_-8px_rgba(0,0,0,0.6)]', accentGlow: 'from-orange-400/25 via-amber-600/15 to-transparent', foilSheen: 'from-white/20 via-amber-200/10 to-transparent', guillocheColor: 'rgba(251, 146, 60, 0.12)', textColor: 'text-white', subtextColor: 'text-amber-200/60', embossStyle: 'text-white drop-shadow-[0_1px_2px_rgba(0,0,0,0.9)]', chipType: 'gold', }, unknown: { cardClass: 'bg-[radial-gradient(ellipse_at_top_right,#27272a_0%,#18181b_40%,#09090b_85%,#000000_100%)] text-white shadow-[0_22px_45px_-12px_rgba(0,0,0,0.7),0_8px_16px_-8px_rgba(0,0,0,0.5)]', accentGlow: 'from-zinc-400/15 via-zinc-600/10 to-transparent', foilSheen: 'from-white/18 via-zinc-300/5 to-transparent', guillocheColor: 'rgba(255, 255, 255, 0.06)', textColor: 'text-white', subtextColor: 'text-zinc-400', embossStyle: 'text-zinc-100 drop-shadow-[0_1px_2px_rgba(0,0,0,0.9)]', chipType: 'silver', }, } // --- end verbatim --- // Boolean props whose React default is `true`: `attr="false"` turns them off. const trueByDefault = { fromAttribute: (v: string | null) => v !== 'false', toAttribute: () => null } /** * <uip-payment-card> — the registry PaymentCard (a display-only credit-card * preview, `role="img"`) as a web component. Not a form control in React, so * not form-associated here either; pair it with inputs that format the values. * * Class strings, brand visuals, masking (`formatMasked`), brand detection and * the tilt maths are React's verbatim. The React root <div> (role=img, size * width, perspective, mouse handlers) is rendered inside the shadow root; the * host is `display: inline-block` like that root's `inline-block`. * * Properties / attributes (React prop names): `number`, `name`, `expiry`, * `cvc`, `brand` ('auto' | 'visa' | 'mastercard' | 'amex' | 'discover' | * 'unknown'), `flipped`, `variant` ('default' | 'compact'), `tilt`, `shimmer`, * `flip` (default true; `flip="false"` for the short 300ms transition), * `size` ('sm' | 'md' | 'lg'). * * No events (React has no callbacks). */ export class UipPaymentCard extends LitElement { static styles = [tailwind, css`:host { display: inline-block; }`] static properties = { number: {}, name: {}, expiry: {}, cvc: {}, brand: { reflect: true }, flipped: { type: Boolean, reflect: true }, variant: { reflect: true }, tilt: { type: Boolean }, shimmer: { type: Boolean }, flip: { converter: trueByDefault }, size: { reflect: true }, tiltState: { state: true }, } number = '' name = '' expiry = '' cvc = '' brand: CardBrand | 'auto' = 'auto' flipped = false variant: 'default' | 'compact' = 'default' tilt = false shimmer = false flip = true size: 'sm' | 'md' | 'lg' = 'md' private tiltState = { x: 0, y: 0, active: false } constructor() { super() new ThemeController(this) } connectedCallback() { super.connectedCallback() this.setAttribute('data-uipkge', '') this.setAttribute('data-slot', 'payment-card') } private get cardEl() { return this.renderRoot?.querySelector<HTMLDivElement>('[role=img]') } private onMouseMove = (e: MouseEvent) => { const el = this.cardEl if (!this.tilt || !el) return const rect = el.getBoundingClientRect() const x = e.clientX - rect.left const y = e.clientY - rect.top const px = (x / rect.width - 0.5) * 2 const py = (y / rect.height - 0.5) * 2 this.tiltState = { x: -py * 10, y: px * 12, active: true } } private onMouseLeave = () => { if (!this.tilt) return this.tiltState = { x: 0, y: 0, active: false } } private renderBrandLogo(detectedBrand: CardBrand) { const compact = this.variant === 'compact' switch (detectedBrand) { case 'visa': return html`<svg class=${compact ? 'h-3 w-auto' : 'h-6 w-auto'} viewBox="0 0 78 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-label="Visa" > <path d="M29.5 23.2H23.9L27.4 1.8H33L29.5 23.2ZM52.4 2.3C51.3 1.9 49.5 1.5 47.3 1.5C41.8 1.5 37.9 4.4 37.8 8.6C37.7 11.7 40.5 13.4 42.6 14.4C44.8 15.5 45.5 16.2 45.5 17.1C45.5 18.6 43.7 19.2 42.1 19.2C39.8 19.2 38.5 18.9 36.8 18.1L36 17.7L35.1 23C36.6 23.7 39.2 24.2 41.8 24.2C47.7 24.2 51.5 21.3 51.6 16.9C51.7 13.4 49.6 11.7 47 10.4C45.2 9.5 44.1 8.9 44.1 8C44.1 7.2 45 6.4 46.8 6.4C48.3 6.4 49.6 6.7 50.8 7.2L51.3 7.5L52.4 2.3ZM67.5 1.8H63.2C61.8 1.8 60.8 2.2 60.2 3.6L51.4 23.2H57.3L58.5 19.9H65.7L66.4 23.2H71.6L67.5 1.8ZM60.1 15.5C60.6 14.2 62.5 8.9 62.5 8.9C62.5 8.9 63 7.6 63.3 6.7L63.8 8.7C63.8 8.7 64.9 14.1 65.2 15.5H60.1ZM18.7 1.8L13.2 16.4L12.6 13.4C11.6 10 8.7 6.3 5.4 4.5L10.3 23.2H16.3L25.1 1.8H18.7ZM8.3 1.8H0.2L0 2.3C6.3 3.9 10.4 7.7 12.1 12.4L10.4 3.7C10.1 2.4 9.3 1.9 8.3 1.8Z" fill="white" /> <path d="M12.1 12.4L10.4 3.7C10.1 2.4 9.3 1.9 8.3 1.8H0.2L0 2.3C6.3 3.9 10.4 7.7 12.1 12.4Z" fill="#F9A01B" /> </svg>` case 'mastercard': return html`<div class="flex items-center gap-1.5"> <svg class=${compact ? 'h-3.5 w-auto' : 'h-7 w-auto'} viewBox="0 0 48 30" fill="none" xmlns="http://www.w3.org/2000/svg" aria-label="Mastercard" > <circle cx="15" cy="15" r="15" fill="#EB001B" /> <circle cx="33" cy="15" r="15" fill="#F79E1B" fill-opacity="0.92" /> <path d="M24 4.5C27.5 7.1 29.8 11.2 29.8 16C29.8 20.8 27.5 24.9 24 27.5C20.5 24.9 18.2 20.8 18.2 16C18.2 11.2 20.5 7.1 24 4.5Z" fill="#FF5F00" /> </svg> ${!compact ? // React sets letter-spacing -0.05em inline; a static inline style is // expressed as an arbitrary-property utility here. html`<span class="font-mono text-xs font-bold tracking-tight text-white lowercase [letter-spacing:-0.05em]">mastercard</span>` : nothing} </div>` case 'amex': return html`<div class=${cn( 'flex items-center justify-center rounded-sm bg-[#006fcf] font-sans font-bold tracking-tighter text-white shadow-xs', compact ? 'px-1 py-0.5 text-xs' : 'px-2 py-1 text-xs', )} > AMEX </div>` case 'discover': return html`<div class=${cn('flex items-center gap-0.5 font-bold tracking-wider text-white', compact ? 'text-xs' : 'text-xs')}> <span>DISC</span> <span class=${cn( 'inline-block rounded-full bg-gradient-to-br from-amber-400 to-orange-600 shadow-xs', compact ? 'size-2' : 'size-3.5', )} ></span> <span>VER</span> </div>` default: return html`<div class="flex items-center gap-1 opacity-80"> <div class="size-2 rounded-full bg-white/60"></div> <div class="size-2 rounded-full bg-white/30"></div> <span class=${cn('font-mono font-bold tracking-widest text-white/80 uppercase', compact ? 'text-xs' : 'text-xs')}>PREMIUM</span> </div>` } } render() { const compact = this.variant === 'compact' const detectedBrand: CardBrand = this.brand && this.brand !== 'auto' ? this.brand : detectBrand(this.number ?? '') const theme = BRAND_VISUALS[detectedBrand] ?? BRAND_VISUALS.unknown const displayNumber = formatMasked(this.number ?? '', detectedBrand) const displayName = ((this.name ?? '').trim() || 'CARDHOLDER NAME').toUpperCase() const displayExpiry = (() => { const raw = (this.expiry ?? '').replace(/\D/g, '').slice(0, 4) if (!raw) return 'MM/YY' if (raw.length <= 2) return raw + '/YY' return `${raw.slice(0, 2)}/${raw.slice(2, 4)}` })() const displayCvc = (() => { const max = detectedBrand === 'amex' ? 4 : 3 const digits = (this.cvc ?? '').replace(/\D/g, '').slice(0, max) return digits || '•••' })() const displayChars = displayNumber.split('') const sizeClass = compact ? 'w-[120px]' : this.size === 'sm' ? 'w-[280px]' : this.size === 'lg' ? 'w-[420px]' : 'w-[360px]' const flipDeg = this.flipped ? 180 : 0 const innerTransform = this.tilt && this.tiltState.active ? `perspective(1200px) rotateX(${this.tiltState.x}deg) rotateY(${this.tiltState.y + flipDeg}deg) scale3d(1.02, 1.02, 1.02)` : `perspective(1200px) rotateY(${flipDeg}deg)` const bevel = html`<div class="pointer-events-none absolute inset-0 rounded-[16px] shadow-[inset_0_1px_1px_rgba(255,255,255,0.35),inset_0_-1px_1px_rgba(0,0,0,0.5)]" ></div>` return html`<div part="base" role="img" aria-label=${`${detectedBrand === 'unknown' ? 'Card' : detectedBrand} ending ${displayNumber.replace(/[•\s]/g, '').slice(-4) || '••••'}, ${displayName}, expires ${displayExpiry}`} class=${cn('group relative inline-block select-none [perspective:1200px]', sizeClass)} @mousemove=${this.onMouseMove} @mouseleave=${this.onMouseLeave} > <div class=${cn( 'relative aspect-[85.6/53.98] w-full transition-transform [transform-style:preserve-3d]', this.flip ? 'duration-[650ms] ease-[cubic-bezier(0.22,1,0.36,1)]' : 'duration-300', )} style=${styleMap({ transform: innerTransform })} > <!-- FRONT FACE --> <div class=${cn( 'pay-card-face absolute inset-0 overflow-hidden rounded-[16px] border border-white/15 [backface-visibility:hidden]', theme.cardClass, )} > <svg class="pointer-events-none absolute inset-0 size-full opacity-60 mix-blend-screen" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 360 227" fill="none" aria-hidden="true" > ${svg`<g stroke=${theme.guillocheColor} stroke-width="0.75" stroke-opacity="0.7"> <path d="M-40 40 C 60 180, 180 -40, 400 120" /> <path d="M-40 55 C 60 195, 180 -25, 400 135" /> <path d="M-40 70 C 60 210, 180 -10, 400 150" /> <path d="M-40 85 C 60 225, 180 5, 400 165" /> <path d="M-40 100 C 60 240, 180 20, 400 180" /> <path d="M-40 115 C 60 255, 180 35, 400 195" /> <circle cx="280" cy="80" r="90" stroke-width="0.5" stroke-dasharray="2 3" /> <circle cx="280" cy="80" r="60" stroke-width="0.5" stroke-dasharray="1 2" /> </g>`} </svg> <div class="pointer-events-none absolute inset-0 bg-gradient-to-tr from-transparent via-white/[0.07] to-transparent opacity-80"></div> <div class=${cn('pointer-events-none absolute -inset-y-8 -left-1/3 w-1/2 rotate-12 bg-gradient-to-r opacity-70', theme.foilSheen)}></div> <div class=${cn('pointer-events-none absolute -top-12 -right-12 h-44 w-44 rounded-full bg-gradient-to-br blur-2xl', theme.accentGlow)}></div> <div class="pointer-events-none absolute -bottom-10 -left-10 h-36 w-36 rounded-full bg-black/40 blur-2xl"></div> ${bevel} ${this.shimmer ? // React sets animation: payment-card-shimmer 2.5s linear infinite inline // (the keyframes are not defined in the React registry either). html`<div class="pointer-events-none absolute -inset-x-full inset-y-0 bg-gradient-to-r from-transparent via-white/25 to-transparent [animation:payment-card-shimmer_2.5s_linear_infinite]" ></div>` : nothing} <div class=${cn('absolute inset-0 flex flex-col justify-between', compact ? 'p-2' : 'p-5')}> <!-- TOP ROW --> <div class="flex items-center justify-between"> <div class="flex items-center gap-1.5"> ${!compact ? html`<span class="font-mono text-xs font-semibold tracking-[0.25em] text-white/70 uppercase">${detectedBrand === 'amex' ? 'PLATINUM' : detectedBrand === 'mastercard' ? 'WORLD ELITE' : 'SIGNATURE'}</span>` : html`<span class="font-mono text-xs font-bold tracking-widest text-white/70 uppercase">CARD</span>`} </div> <div class="flex items-center justify-end">${this.renderBrandLogo(detectedBrand)}</div> </div> <!-- MIDDLE ROW --> <div class="flex items-center justify-between"> <div class="flex items-center gap-3"> <div class=${cn( 'relative overflow-hidden rounded-[5px] border border-amber-950/40 shadow-[0_2px_5px_rgba(0,0,0,0.5),inset_0_1px_0_rgba(255,255,255,0.6)]', theme.chipType === 'silver' ? 'bg-gradient-to-br from-slate-200 via-slate-300 to-slate-400' : 'bg-gradient-to-br from-[#ffe082] via-[#ffb300] to-[#ff8f00]', compact ? 'h-3.5 w-5' : 'h-8 w-11', )} > <div class="absolute inset-[2px] rounded-[3px] border border-black/25"></div> <div class="absolute inset-x-0 top-1/2 h-px -translate-y-1/2 bg-black/30"></div> <div class="absolute inset-y-0 left-1/3 w-px bg-black/30"></div> <div class="absolute inset-y-0 right-1/3 w-px bg-black/30"></div> <div class="absolute top-1/4 right-1/3 left-1/3 h-1/2 rounded-[2px] border border-black/25 bg-black/10"></div> <div class="pointer-events-none absolute inset-0 bg-gradient-to-tr from-transparent via-white/40 to-transparent"></div> </div> ${!compact ? html`<svg viewBox="0 0 24 24" class="h-5 w-auto text-white/70 drop-shadow-sm" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" aria-hidden="true" > <path d="M6.5 8a6 6 0 0 1 0 8" /> <path d="M10 6a9.5 9.5 0 0 1 0 12" /> <path d="M13.5 4a13 13 0 0 1 0 16" /> </svg>` : nothing} </div> ${!compact ? html`<div class="pay-card-holo h-7 w-9 rounded-[4px] opacity-75 shadow-xs"></div>` : nothing} </div> <!-- CARD NUMBER --> <div class=${cn( 'flex flex-nowrap items-center font-mono font-bold tracking-[0.18em] tabular-nums', theme.embossStyle, compact ? 'my-0.5 text-xs' : 'my-1 text-base sm:text-lg', )} > <span class="sr-only">${displayNumber}</span> ${displayChars.map( (ch) => html`<span class=${cn('inline-block origin-bottom tabular-nums transition-transform', ch === '•' && 'text-[0.85em] opacity-50')} aria-hidden="true" >${ch === ' ' ? ' ' : ch}</span>`, )} </div> <!-- BOTTOM ROW --> <div class="flex items-end justify-between gap-3"> <div class="min-w-0 flex-1"> ${!compact ? html`<div class=${cn('text-xs font-semibold tracking-[0.2em] uppercase', theme.subtextColor)}>CARDHOLDER NAME</div>` : nothing} <div class=${cn('truncate font-semibold tracking-wider uppercase', theme.embossStyle, compact ? 'text-xs' : 'text-xs')}>${displayName}</div> </div> <div class="shrink-0 text-right"> ${!compact ? html`<div class=${cn('flex items-center justify-end gap-1 text-xs font-semibold tracking-wider uppercase', theme.subtextColor)}> <span class="text-[6px] leading-tight">GOOD<br />THRU</span> <span>MONTH/YEAR</span> </div>` : nothing} <div class=${cn('font-mono font-semibold tracking-wider whitespace-nowrap', theme.embossStyle, compact ? 'text-xs' : 'text-xs')}>${displayExpiry}</div> </div> </div> </div> </div> <!-- BACK FACE --> <div class=${cn( 'pay-card-face absolute inset-0 [transform:rotateY(180deg)] overflow-hidden rounded-[16px] border border-white/15 [backface-visibility:hidden]', theme.cardClass, )} > ${bevel} <div class=${cn( 'w-full bg-gradient-to-b from-neutral-950 via-black to-neutral-900 shadow-[inset_0_1px_0_rgba(255,255,255,0.1),inset_0_-1px_0_rgba(0,0,0,0.8)]', compact ? 'mt-2 h-4' : 'mt-4 h-10', )} ></div> <div class=${cn('relative flex flex-col justify-between', compact ? 'p-2' : 'p-4')}> <div class=${cn('flex items-stretch gap-2', compact ? 'mt-1' : 'mt-2')}> <!-- React's inline backgroundImage as an arbitrary-value utility. --> <div class=${cn( 'relative flex-1 overflow-hidden rounded-[3px] border border-slate-300/80 bg-white/95 px-2 py-1', compact ? 'h-4' : 'h-8', 'bg-[repeating-linear-gradient(-45deg,rgba(0,0,0,0.05)_0px,rgba(0,0,0,0.05)_2px,transparent_2px,transparent_6px)]', )} > ${!compact ? html`<div class="font-mono text-xs font-semibold tracking-wider text-slate-400 uppercase">AUTHORIZED SIGNATURE</div>` : nothing} </div> <div class=${cn( 'flex flex-col items-center justify-center rounded-[3px] border border-slate-300 bg-white font-mono font-bold text-slate-950 shadow-inner', compact ? 'px-1 text-xs' : 'px-3 text-xs', )} > ${!compact ? html`<span class="text-[6px] font-bold tracking-widest text-slate-400 uppercase">CVV</span>` : nothing} <span class="leading-none tracking-widest italic">${displayCvc}</span> </div> </div> ${!compact ? html`<div class="mt-3 space-y-1.5"> <p class="text-[7.5px] leading-snug text-white/50"> This card is property of the issuing bank and must be returned upon request. Use is subject to the cardholder agreement. </p> <div class="flex items-center justify-between border-t border-white/10 pt-2 text-xs font-medium text-white/60"> <span>24/7 Support: 1-800-555-0199</span> <span class="font-mono font-bold tracking-wider text-white/75">CIRRUS · PLUS · STAR</span> </div> </div>` : nothing} </div> </div> </div> </div>` } } customElements.get('uip-payment-card') || customElements.define('uip-payment-card', UipPaymentCard) declare global { interface HTMLElementTagNameMap { 'uip-payment-card': UipPaymentCard } }
Raw manifest:https://uipkge.dev/r/lit/payment-card.json