Gift Card Voucher — Reveal & Copy HTML CSS JS Snippet

Gift Card Voucher · Cards · Plain HTML, CSS & JS · Live preview

Share & Support

What's included

Features

Premium card visual
A multi-stop gradient with a soft coloured shadow, brand mark, chip, and large amount make it read as a real gift card.
Animated shine sweep
A .gc-shine strip translates across the card on a loop using only transform, so the glint is smooth and export-safe.
Masked code by default
The code shows as FWDT-••••-•••• so screenshots or onlookers never see it until the recipient reveals it.
Two-step reveal then copy
revealOrCopy reveals on the first click (with a flip-in) and copies on the second — preventing accidental copies of a hidden value.
Clipboard with fallback
Uses navigator.clipboard.writeText and still confirms if it is unavailable, so the button always gives feedback.
Copied confirmation
The button switches to "Copied ✓" in green for 1.6s, then resets — clear feedback the code is on the clipboard.
Redemption details
Amount, balance, recipient, expiry, and a redeem note turn the visual into a usable voucher.
Single source of truth
The code lives in one CODE constant that the reveal and copy both read — easy to populate from real data.

About this UI Snippet

Gift Card Voucher — Animated Shine, Masked Code Reveal & Copy-With-Feedback

Screenshot of the Gift Card Voucher snippet rendered live

A digital gift card or voucher needs to feel valuable and be effortless to redeem. That means a premium card visual, a code that stays hidden until the recipient chooses to reveal it, and one-tap copying. This snippet implements exactly that in plain HTML, CSS, and vanilla JavaScript: a gradient voucher with an animated shine sweep, a masked code that reveals on click and then copies with confirmation, plus amount, balance, recipient, and expiry details.

A voucher that looks the part

The card uses a multi-stop gradient and a soft coloured shadow to read as a premium, physical-feeling gift card. A .gc-shine element sweeps a diagonal highlight across it on a loop — the light-glint effect that makes gift cards and credit cards feel tactile and special. The animation uses only transform (a translating, rotated gradient strip), so it runs on the compositor and exports cleanly. Brand mark, "GIFT CARD" chip, a large amount, and a to/from line complete the front.

Masked code with reveal-then-copy

The redemption code starts masked (FWDT-••••-••••) so a shared screenshot or an over-the-shoulder glance never exposes it. A single button drives a two-step flow handled by revealOrCopy: the first click reveals the real code (with a small flip-in animation) and changes the button to "Copy code"; the second click copies it to the clipboard and confirms with "Copied ✓" in green for 1.6 seconds before resetting. This reveal-first pattern is both a privacy nicety and a deliberate two-tap interaction that prevents accidental copies of a hidden value.

Robust clipboard copy

revealOrCopy uses the modern navigator.clipboard.writeText API and falls back gracefully (the confirmation still fires) if it is unavailable, so the button always gives feedback. The code is stored once in a CODE constant and revealed/copied from there — a single source of truth.

Redemption details

The footer shows an expiry date and the current balance, and a note clarifies how to redeem ("applies to your next order"). These details are what turn a pretty card into a usable voucher: the amount tells the recipient what it is worth, the balance handles partial redemptions, and the expiry sets expectations so the card does not quietly lapse.

Built to drop into a real flow

Because the whole card is plain markup with one CODE constant and CSS-driven visuals, it slots into an email template, an account "my gift cards" page, or a post-purchase confirmation with minimal work — and the reveal/copy logic is the only JavaScript, so it stays light and dependency-free.

Swap the amount, code, names, and expiry for real values (rendered server-side per gift card) and it is production-ready. Pair this with an order summary where the code applies, a scratch card reveal for a playful unlock, or a confetti celebration card for the gifting moment.

Step by step

How to Use

  1. 1
    Paste HTML, CSS, and JSA gradient $50 gift card appears with an animated shine sweep, a to/from line, a masked code, and a "Reveal code" button.
  2. 2
    Reveal the codeClick "Reveal code" — the masked dots flip to the real code and the button changes to "Copy code".
  3. 3
    Copy itClick "Copy code" — the code is copied to the clipboard and the button confirms "Copied ✓" in green.
  4. 4
    Watch the shineA diagonal light glint sweeps across the card on a loop, giving it a premium, physical feel.
  5. 5
    Read the detailsThe footer shows the expiry and balance, with a note on how to redeem.
  6. 6
    Plug in real valuesSet the CODE constant, amount, names, and expiry from your gift-card data (rendered per recipient).

Real-world uses

Common Use Cases

E-commerce gift cards
Digital gift cards delivered by email or account. Apply the revealed code in an order summary at checkout.
Discount and promo vouchers
Single-use coupon codes presented as a branded voucher; pair with a sticky promo bar to surface offers.
Loyalty and reward redemptions
Redeemable reward codes from a loyalty points widget or referral program.
Event tickets and passes
Access codes or passes shown on a premium card with reveal-to-view privacy.
Prize and giveaway reveals
Pair with a scratch card reveal or confetti celebration card for a fun unlock moment.
Account credit and top-ups
Show applied credit with a balance and code for wallets and subscription credits.

Got questions?

Frequently Asked Questions

Render the amount, recipient/sender names, expiry, balance, and the CODE constant from your gift-card record (server-side, per recipient). Never hard-code real codes in shipped client source for production cards — fetch the code only for the authenticated owner, ideally revealing it via an authorised request rather than embedding it in the page.

Yes — masking the code until the user taps reveal protects it from shoulder-surfing and from being captured in screenshots shared for the card's design, and it makes the copy action deliberate. For sensitive codes, go further: fetch the code from the server only on the reveal click, so it is never present in the page source at all.

Append the code to the redeem flow: a "Redeem now" button can navigate to checkout with ?gift=CODE or call your apply-credit API directly. Validate and apply gift cards server-side (check balance, expiry, single-use), and reflect the new balance back on the card so partial redemptions show the remaining amount.

Make the reveal/copy a real <button> (it is) so it is keyboard-operable, and announce the reveal and copy via an aria-live="polite" region. Ensure the code, when revealed, is selectable text and has sufficient contrast on the gradient; the masked state should have an aria-label like "code hidden, activate to reveal" so screen-reader users understand the control.

In React, hold revealed in useState and the code/amount as props; the button toggles reveal then copies, with a setTimeout for the "Copied" flag. In Vue, use a ref for revealed and a method for the two-step action. In Angular, track revealed on the component. The shine keyframe and card CSS port unchanged.

Gift Card Voucher — Export as HTML, React, Vue, Angular & Tailwind

HTML
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Gift Card Voucher</title>
  <style>
    *{box-sizing:border-box;margin:0;padding:0}
    body{font-family:system-ui,-apple-system,sans-serif;background:#0f172a;min-height:100vh;display:flex;align-items:center;justify-content:center;padding:24px}
    .gc-wrap{width:100%;max-width:360px;text-align:center}
    
    .gc-card{position:relative;border-radius:20px;padding:24px;color:#fff;overflow:hidden;text-align:left;background:linear-gradient(135deg,#7c3aed,#6366f1 45%,#0ea5e9);box-shadow:0 22px 50px rgba(99,102,241,.4)}
    .gc-shine{position:absolute;top:-60%;left:-30%;width:80%;height:220%;background:linear-gradient(100deg,transparent,rgba(255,255,255,.25),transparent);transform:rotate(18deg);animation:gc-shine 4s ease-in-out infinite}
    @keyframes gc-shine{0%,60%{transform:translateX(-60%) rotate(18deg)}100%{transform:translateX(320%) rotate(18deg)}}
    
    .gc-top{display:flex;align-items:center;justify-content:space-between;position:relative}
    .gc-brand{font-size:13px;font-weight:800;letter-spacing:.04em}
    .gc-chip{font-size:10px;font-weight:800;letter-spacing:.08em;background:rgba(255,255,255,.2);border:1px solid rgba(255,255,255,.3);border-radius:6px;padding:3px 8px}
    .gc-amount{font-size:46px;font-weight:800;margin:16px 0 4px;position:relative;line-height:1}
    .gc-bal{font-size:22px;font-weight:700;opacity:.8}
    .gc-to{font-size:12px;opacity:.85;position:relative;margin-bottom:20px}
    
    .gc-code-row{display:flex;align-items:center;gap:8px;background:rgba(15,23,42,.25);border:1px dashed rgba(255,255,255,.35);border-radius:12px;padding:6px 6px 6px 14px;position:relative}
    .gc-code{flex:1;font-family:ui-monospace,monospace;font-size:15px;font-weight:700;letter-spacing:.12em;color:#fff}
    .gc-code.revealed{animation:gc-flip .35s ease}
    @keyframes gc-flip{from{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:translateY(0)}}
    .gc-btn{background:#fff;color:#4f46e5;border:none;border-radius:9px;padding:9px 14px;font-size:12px;font-weight:800;cursor:pointer;font-family:inherit;white-space:nowrap;transition:background .15s,transform .1s}
    .gc-btn:hover{background:#f1f5f9}
    .gc-btn:active{transform:scale(.95)}
    .gc-btn.done{background:#dcfce7;color:#16a34a}
    
    .gc-foot{display:flex;justify-content:space-between;font-size:11px;opacity:.85;margin-top:14px;position:relative}
    .gc-bal-tag{font-weight:700}
    .gc-note{font-size:12px;color:#64748b;margin-top:16px}
  </style>
</head>
<body>
  <div class="gc-wrap">
    <div class="gc-card">
      <div class="gc-shine"></div>
      <div class="gc-top">
        <span class="gc-brand">✦ FWD STORE</span>
        <span class="gc-chip">GIFT CARD</span>
      </div>
      <div class="gc-amount">$50<span class="gc-bal">.00</span></div>
      <div class="gc-to">To: <strong>Alex</strong> · From: <strong>Sam</strong></div>
  
      <div class="gc-code-row">
        <code class="gc-code" id="gcCode">FWDT-••••-••••</code>
        <button class="gc-btn" id="gcBtn" onclick="revealOrCopy()">Reveal code</button>
      </div>
      <div class="gc-foot">
        <span>Expires Dec 2027</span>
        <span class="gc-bal-tag">Balance $50.00</span>
      </div>
    </div>
    <p class="gc-note">Redeem at checkout — applies to your next order.</p>
  </div>
  <script>
    var CODE = 'FWDT-9X4K-7Q2M';
    var revealed = false;
    var copyTimer;
    
    function revealOrCopy() {
      var codeEl = document.getElementById('gcCode');
      var btn = document.getElementById('gcBtn');
    
      if (!revealed) {
        revealed = true;
        codeEl.textContent = CODE;
        codeEl.classList.remove('revealed');
        void codeEl.offsetWidth;
        codeEl.classList.add('revealed');
        btn.textContent = 'Copy code';
        return;
      }
    
      var done = function () {
        btn.textContent = 'Copied ✓';
        btn.classList.add('done');
        clearTimeout(copyTimer);
        copyTimer = setTimeout(function () { btn.textContent = 'Copy code'; btn.classList.remove('done'); }, 1600);
      };
      if (navigator.clipboard && navigator.clipboard.writeText) {
        navigator.clipboard.writeText(CODE).then(done).catch(done);
      } else { done(); }
    }
  </script>
</body>
</html>
Tailwind
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Gift Card Voucher</title>
  <!-- Tailwind CSS v3+ — arbitrary value classes (e.g. bg-[#0f172a]) are valid -->
  <script src="https://cdn.tailwindcss.com"></script>
  <style>
    @keyframes gc-shine{0%,60%{transform:translateX(-60%) rotate(18deg)}100%{transform:translateX(320%) rotate(18deg)}}

    @keyframes gc-flip{from{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:translateY(0)}}

    * {
      box-sizing:border-box;margin:0;padding:0
    }

    body {
      font-family:system-ui,-apple-system,sans-serif;background:#0f172a;min-height:100vh;display:flex;align-items:center;justify-content:center;padding:24px
    }

    .gc-code.revealed {
      animation:gc-flip .35s ease
    }

    .gc-btn.done {
      background:#dcfce7;color:#16a34a
    }
  </style>
</head>
<body>
  <div class="w-full max-w-[360px] text-center">
    <div class="relative rounded-[20px] p-6 text-[#fff] overflow-hidden text-left [background:linear-gradient(135deg,#7c3aed,#6366f1_45%,#0ea5e9)] shadow-[0_22px_50px_rgba(99,102,241,.4)]">
      <div class="absolute top-[-60%] left-[-30%] w-4/5 h-[220%] [background:linear-gradient(100deg,transparent,rgba(255,255,255,.25),transparent)] [transform:rotate(18deg)] [animation:gc-shine_4s_ease-in-out_infinite]"></div>
      <div class="flex items-center justify-between relative">
        <span class="text-[13px] font-extrabold tracking-[.04em]">✦ FWD STORE</span>
        <span class="text-xs font-extrabold tracking-[.08em] bg-[rgba(255,255,255,.2)] border border-[rgba(255,255,255,.3)] rounded-md py-[3px] px-2">GIFT CARD</span>
      </div>
      <div class="text-[46px] font-extrabold mt-4 mx-0 mb-1 relative leading-none">$50<span class="text-[22px] font-bold opacity-80">.00</span></div>
      <div class="text-xs opacity-85 relative mb-5">To: <strong>Alex</strong> · From: <strong>Sam</strong></div>
  
      <div class="flex items-center gap-2 bg-[rgba(15,23,42,.25)] border border-dashed rounded-xl pt-1.5 pr-1.5 pb-1.5 pl-3.5 relative">
        <code class="gc-code flex-1 font-mono text-[15px] font-bold tracking-[.12em] text-[#fff]" id="gcCode">FWDT-••••-••••</code>
        <button class="gc-btn bg-[#fff] text-[#4f46e5] border-0 rounded-[9px] py-[9px] px-3.5 text-xs font-extrabold cursor-pointer font-[inherit] whitespace-nowrap [transition:background_.15s,transform_.1s] hover:bg-[#f1f5f9] active:[transform:scale(.95)]" id="gcBtn" onclick="revealOrCopy()">Reveal code</button>
      </div>
      <div class="flex justify-between text-[11px] opacity-85 mt-3.5 relative">
        <span>Expires Dec 2027</span>
        <span class="font-bold">Balance $50.00</span>
      </div>
    </div>
    <p class="text-xs text-[#64748b] mt-4">Redeem at checkout — applies to your next order.</p>
  </div>
  <script>
    var CODE = 'FWDT-9X4K-7Q2M';
    var revealed = false;
    var copyTimer;
    
    function revealOrCopy() {
      var codeEl = document.getElementById('gcCode');
      var btn = document.getElementById('gcBtn');
    
      if (!revealed) {
        revealed = true;
        codeEl.textContent = CODE;
        codeEl.classList.remove('revealed');
        void codeEl.offsetWidth;
        codeEl.classList.add('revealed');
        btn.textContent = 'Copy code';
        return;
      }
    
      var done = function () {
        btn.textContent = 'Copied ✓';
        btn.classList.add('done');
        clearTimeout(copyTimer);
        copyTimer = setTimeout(function () { btn.textContent = 'Copy code'; btn.classList.remove('done'); }, 1600);
      };
      if (navigator.clipboard && navigator.clipboard.writeText) {
        navigator.clipboard.writeText(CODE).then(done).catch(done);
      } else { done(); }
    }
  </script>
</body>
</html>
React
import React, { useEffect } from 'react';

// CSS — optionally move to GiftCardVoucher.module.css
const css = `
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:system-ui,-apple-system,sans-serif;background:#0f172a;min-height:100vh;display:flex;align-items:center;justify-content:center;padding:24px}
.gc-wrap{width:100%;max-width:360px;text-align:center}

.gc-card{position:relative;border-radius:20px;padding:24px;color:#fff;overflow:hidden;text-align:left;background:linear-gradient(135deg,#7c3aed,#6366f1 45%,#0ea5e9);box-shadow:0 22px 50px rgba(99,102,241,.4)}
.gc-shine{position:absolute;top:-60%;left:-30%;width:80%;height:220%;background:linear-gradient(100deg,transparent,rgba(255,255,255,.25),transparent);transform:rotate(18deg);animation:gc-shine 4s ease-in-out infinite}
@keyframes gc-shine{0%,60%{transform:translateX(-60%) rotate(18deg)}100%{transform:translateX(320%) rotate(18deg)}}

.gc-top{display:flex;align-items:center;justify-content:space-between;position:relative}
.gc-brand{font-size:13px;font-weight:800;letter-spacing:.04em}
.gc-chip{font-size:10px;font-weight:800;letter-spacing:.08em;background:rgba(255,255,255,.2);border:1px solid rgba(255,255,255,.3);border-radius:6px;padding:3px 8px}
.gc-amount{font-size:46px;font-weight:800;margin:16px 0 4px;position:relative;line-height:1}
.gc-bal{font-size:22px;font-weight:700;opacity:.8}
.gc-to{font-size:12px;opacity:.85;position:relative;margin-bottom:20px}

.gc-code-row{display:flex;align-items:center;gap:8px;background:rgba(15,23,42,.25);border:1px dashed rgba(255,255,255,.35);border-radius:12px;padding:6px 6px 6px 14px;position:relative}
.gc-code{flex:1;font-family:ui-monospace,monospace;font-size:15px;font-weight:700;letter-spacing:.12em;color:#fff}
.gc-code.revealed{animation:gc-flip .35s ease}
@keyframes gc-flip{from{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:translateY(0)}}
.gc-btn{background:#fff;color:#4f46e5;border:none;border-radius:9px;padding:9px 14px;font-size:12px;font-weight:800;cursor:pointer;font-family:inherit;white-space:nowrap;transition:background .15s,transform .1s}
.gc-btn:hover{background:#f1f5f9}
.gc-btn:active{transform:scale(.95)}
.gc-btn.done{background:#dcfce7;color:#16a34a}

.gc-foot{display:flex;justify-content:space-between;font-size:11px;opacity:.85;margin-top:14px;position:relative}
.gc-bal-tag{font-weight:700}
.gc-note{font-size:12px;color:#64748b;margin-top:16px}
`;

export default function GiftCardVoucher() {
  // Auto-generated escape hatch: the original snippet's vanilla JS runs once
  // after mount and queries the rendered DOM. For idiomatic React, lift this
  // into state + handlers.
  useEffect(() => {
    const _listeners = [];
    const _originalAddEventListener = EventTarget.prototype.addEventListener;
    EventTarget.prototype.addEventListener = function(type, listener, options) {
      _listeners.push({ target: this, type, listener, options });
      return _originalAddEventListener.call(this, type, listener, options);
    };
    var CODE = 'FWDT-9X4K-7Q2M';
    var revealed = false;
    var copyTimer;
    
    function revealOrCopy() {
      var codeEl = document.getElementById('gcCode');
      var btn = document.getElementById('gcBtn');
    
      if (!revealed) {
        revealed = true;
        codeEl.textContent = CODE;
        codeEl.classList.remove('revealed');
        void codeEl.offsetWidth;
        codeEl.classList.add('revealed');
        btn.textContent = 'Copy code';
        return;
      }
    
      var done = function () {
        btn.textContent = 'Copied ✓';
        btn.classList.add('done');
        clearTimeout(copyTimer);
        copyTimer = setTimeout(function () { btn.textContent = 'Copy code'; btn.classList.remove('done'); }, 1600);
      };
      if (navigator.clipboard && navigator.clipboard.writeText) {
        navigator.clipboard.writeText(CODE).then(done).catch(done);
      } else { done(); }
    }
    // Cleanup: restore addEventListener and remove all listeners
    return () => {
      EventTarget.prototype.addEventListener = _originalAddEventListener;
      for (const { target, type, listener, options } of _listeners) {
        target.removeEventListener(type, listener, options);
      }
    };

    // Expose handlers used by inline JSX events to the global scope
    if (typeof revealOrCopy === 'function') window.revealOrCopy = revealOrCopy;
  }, []);

  return (
    <>
      <style>{css}</style>
      <div className="gc-wrap">
        <div className="gc-card">
          <div className="gc-shine"></div>
          <div className="gc-top">
            <span className="gc-brand">✦ FWD STORE</span>
            <span className="gc-chip">GIFT CARD</span>
          </div>
          <div className="gc-amount">$50<span className="gc-bal">.00</span></div>
          <div className="gc-to">To: <strong>Alex</strong> · From: <strong>Sam</strong></div>
      
          <div className="gc-code-row">
            <code className="gc-code" id="gcCode">FWDT-••••-••••</code>
            <button className="gc-btn" id="gcBtn" onClick={(event) => { revealOrCopy() }}>Reveal code</button>
          </div>
          <div className="gc-foot">
            <span>Expires Dec 2027</span>
            <span className="gc-bal-tag">Balance $50.00</span>
          </div>
        </div>
        <p className="gc-note">Redeem at checkout — applies to your next order.</p>
      </div>
    </>
  );
}
React + Tailwind
import React, { useEffect } from 'react';
// Requires Tailwind CSS v3+ — https://tailwindcss.com/docs/installation
// Arbitrary value classes (e.g. bg-[#0f172a]) are valid Tailwind v3+

export default function GiftCardVoucher() {
  // Auto-generated escape hatch: the original snippet's vanilla JS runs once
  // after mount and queries the rendered DOM. For idiomatic React, lift this
  // into state + handlers.
  useEffect(() => {
    const _listeners = [];
    const _originalAddEventListener = EventTarget.prototype.addEventListener;
    EventTarget.prototype.addEventListener = function(type, listener, options) {
      _listeners.push({ target: this, type, listener, options });
      return _originalAddEventListener.call(this, type, listener, options);
    };
    var CODE = 'FWDT-9X4K-7Q2M';
    var revealed = false;
    var copyTimer;
    
    function revealOrCopy() {
      var codeEl = document.getElementById('gcCode');
      var btn = document.getElementById('gcBtn');
    
      if (!revealed) {
        revealed = true;
        codeEl.textContent = CODE;
        codeEl.classList.remove('revealed');
        void codeEl.offsetWidth;
        codeEl.classList.add('revealed');
        btn.textContent = 'Copy code';
        return;
      }
    
      var done = function () {
        btn.textContent = 'Copied ✓';
        btn.classList.add('done');
        clearTimeout(copyTimer);
        copyTimer = setTimeout(function () { btn.textContent = 'Copy code'; btn.classList.remove('done'); }, 1600);
      };
      if (navigator.clipboard && navigator.clipboard.writeText) {
        navigator.clipboard.writeText(CODE).then(done).catch(done);
      } else { done(); }
    }
    // Cleanup: restore addEventListener and remove all listeners
    return () => {
      EventTarget.prototype.addEventListener = _originalAddEventListener;
      for (const { target, type, listener, options } of _listeners) {
        target.removeEventListener(type, listener, options);
      }
    };

    // Expose handlers used by inline JSX events to the global scope
    if (typeof revealOrCopy === 'function') window.revealOrCopy = revealOrCopy;
  }, []);

  return (
    <>
      <style>{`
@keyframes gc-shine{0%,60%{transform:translateX(-60%) rotate(18deg)}100%{transform:translateX(320%) rotate(18deg)}}

@keyframes gc-flip{from{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:translateY(0)}}

* {
  box-sizing:border-box;margin:0;padding:0
}

body {
  font-family:system-ui,-apple-system,sans-serif;background:#0f172a;min-height:100vh;display:flex;align-items:center;justify-content:center;padding:24px
}

.gc-code.revealed {
  animation:gc-flip .35s ease
}

.gc-btn.done {
  background:#dcfce7;color:#16a34a
}
      `}</style>
      <div className="w-full max-w-[360px] text-center">
        <div className="relative rounded-[20px] p-6 text-[#fff] overflow-hidden text-left [background:linear-gradient(135deg,#7c3aed,#6366f1_45%,#0ea5e9)] shadow-[0_22px_50px_rgba(99,102,241,.4)]">
          <div className="absolute top-[-60%] left-[-30%] w-4/5 h-[220%] [background:linear-gradient(100deg,transparent,rgba(255,255,255,.25),transparent)] [transform:rotate(18deg)] [animation:gc-shine_4s_ease-in-out_infinite]"></div>
          <div className="flex items-center justify-between relative">
            <span className="text-[13px] font-extrabold tracking-[.04em]">✦ FWD STORE</span>
            <span className="text-xs font-extrabold tracking-[.08em] bg-[rgba(255,255,255,.2)] border border-[rgba(255,255,255,.3)] rounded-md py-[3px] px-2">GIFT CARD</span>
          </div>
          <div className="text-[46px] font-extrabold mt-4 mx-0 mb-1 relative leading-none">$50<span className="text-[22px] font-bold opacity-80">.00</span></div>
          <div className="text-xs opacity-85 relative mb-5">To: <strong>Alex</strong> · From: <strong>Sam</strong></div>
      
          <div className="flex items-center gap-2 bg-[rgba(15,23,42,.25)] border border-dashed rounded-xl pt-1.5 pr-1.5 pb-1.5 pl-3.5 relative">
            <code className="gc-code flex-1 font-mono text-[15px] font-bold tracking-[.12em] text-[#fff]" id="gcCode">FWDT-••••-••••</code>
            <button className="gc-btn bg-[#fff] text-[#4f46e5] border-0 rounded-[9px] py-[9px] px-3.5 text-xs font-extrabold cursor-pointer font-[inherit] whitespace-nowrap [transition:background_.15s,transform_.1s] hover:bg-[#f1f5f9] active:[transform:scale(.95)]" id="gcBtn" onClick={(event) => { revealOrCopy() }}>Reveal code</button>
          </div>
          <div className="flex justify-between text-[11px] opacity-85 mt-3.5 relative">
            <span>Expires Dec 2027</span>
            <span className="font-bold">Balance $50.00</span>
          </div>
        </div>
        <p className="text-xs text-[#64748b] mt-4">Redeem at checkout — applies to your next order.</p>
      </div>
    </>
  );
}
Vue
<template>
  <div class="gc-wrap">
    <div class="gc-card">
      <div class="gc-shine"></div>
      <div class="gc-top">
        <span class="gc-brand">✦ FWD STORE</span>
        <span class="gc-chip">GIFT CARD</span>
      </div>
      <div class="gc-amount">$50<span class="gc-bal">.00</span></div>
      <div class="gc-to">To: <strong>Alex</strong> · From: <strong>Sam</strong></div>
  
      <div class="gc-code-row">
        <code class="gc-code" id="gcCode">FWDT-••••-••••</code>
        <button class="gc-btn" id="gcBtn" @click="revealOrCopy()">Reveal code</button>
      </div>
      <div class="gc-foot">
        <span>Expires Dec 2027</span>
        <span class="gc-bal-tag">Balance $50.00</span>
      </div>
    </div>
    <p class="gc-note">Redeem at checkout — applies to your next order.</p>
  </div>
</template>

<script setup>
import { onMounted } from 'vue';

var CODE = 'FWDT-9X4K-7Q2M';
var revealed = false;
var copyTimer;
function revealOrCopy() {
  var codeEl = document.getElementById('gcCode');
  var btn = document.getElementById('gcBtn');

  if (!revealed) {
    revealed = true;
    codeEl.textContent = CODE;
    codeEl.classList.remove('revealed');
    void codeEl.offsetWidth;
    codeEl.classList.add('revealed');
    btn.textContent = 'Copy code';
    return;
  }

  var done = function () {
    btn.textContent = 'Copied ✓';
    btn.classList.add('done');
    clearTimeout(copyTimer);
    copyTimer = setTimeout(function () { btn.textContent = 'Copy code'; btn.classList.remove('done'); }, 1600);
  };
  if (navigator.clipboard && navigator.clipboard.writeText) {
    navigator.clipboard.writeText(CODE).then(done).catch(done);
  } else { done(); }
}

</script>

<style scoped>
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:system-ui,-apple-system,sans-serif;background:#0f172a;min-height:100vh;display:flex;align-items:center;justify-content:center;padding:24px}
.gc-wrap{width:100%;max-width:360px;text-align:center}

.gc-card{position:relative;border-radius:20px;padding:24px;color:#fff;overflow:hidden;text-align:left;background:linear-gradient(135deg,#7c3aed,#6366f1 45%,#0ea5e9);box-shadow:0 22px 50px rgba(99,102,241,.4)}
.gc-shine{position:absolute;top:-60%;left:-30%;width:80%;height:220%;background:linear-gradient(100deg,transparent,rgba(255,255,255,.25),transparent);transform:rotate(18deg);animation:gc-shine 4s ease-in-out infinite}
@keyframes gc-shine{0%,60%{transform:translateX(-60%) rotate(18deg)}100%{transform:translateX(320%) rotate(18deg)}}

.gc-top{display:flex;align-items:center;justify-content:space-between;position:relative}
.gc-brand{font-size:13px;font-weight:800;letter-spacing:.04em}
.gc-chip{font-size:10px;font-weight:800;letter-spacing:.08em;background:rgba(255,255,255,.2);border:1px solid rgba(255,255,255,.3);border-radius:6px;padding:3px 8px}
.gc-amount{font-size:46px;font-weight:800;margin:16px 0 4px;position:relative;line-height:1}
.gc-bal{font-size:22px;font-weight:700;opacity:.8}
.gc-to{font-size:12px;opacity:.85;position:relative;margin-bottom:20px}

.gc-code-row{display:flex;align-items:center;gap:8px;background:rgba(15,23,42,.25);border:1px dashed rgba(255,255,255,.35);border-radius:12px;padding:6px 6px 6px 14px;position:relative}
.gc-code{flex:1;font-family:ui-monospace,monospace;font-size:15px;font-weight:700;letter-spacing:.12em;color:#fff}
.gc-code.revealed{animation:gc-flip .35s ease}
@keyframes gc-flip{from{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:translateY(0)}}
.gc-btn{background:#fff;color:#4f46e5;border:none;border-radius:9px;padding:9px 14px;font-size:12px;font-weight:800;cursor:pointer;font-family:inherit;white-space:nowrap;transition:background .15s,transform .1s}
.gc-btn:hover{background:#f1f5f9}
.gc-btn:active{transform:scale(.95)}
.gc-btn.done{background:#dcfce7;color:#16a34a}

.gc-foot{display:flex;justify-content:space-between;font-size:11px;opacity:.85;margin-top:14px;position:relative}
.gc-bal-tag{font-weight:700}
.gc-note{font-size:12px;color:#64748b;margin-top:16px}
</style>
Angular
// @ts-nocheck
// Note: vanilla JS DOM manipulation is preserved as-is inside ngAfterViewInit().
// For idiomatic Angular, replace document.getElementById() with @ViewChild() refs
// and move state into component properties with two-way binding.
import { Component, AfterViewInit, ViewEncapsulation } from '@angular/core';
import { CommonModule } from '@angular/common';

@Component({
  selector: 'app-gift-card-voucher',
  standalone: true,
  imports: [CommonModule],
  encapsulation: ViewEncapsulation.None,
  template: `
    <div class="gc-wrap">
      <div class="gc-card">
        <div class="gc-shine"></div>
        <div class="gc-top">
          <span class="gc-brand">✦ FWD STORE</span>
          <span class="gc-chip">GIFT CARD</span>
        </div>
        <div class="gc-amount">$50<span class="gc-bal">.00</span></div>
        <div class="gc-to">To: <strong>Alex</strong> · From: <strong>Sam</strong></div>
    
        <div class="gc-code-row">
          <code class="gc-code" id="gcCode">FWDT-••••-••••</code>
          <button class="gc-btn" id="gcBtn" (click)="revealOrCopy()">Reveal code</button>
        </div>
        <div class="gc-foot">
          <span>Expires Dec 2027</span>
          <span class="gc-bal-tag">Balance $50.00</span>
        </div>
      </div>
      <p class="gc-note">Redeem at checkout — applies to your next order.</p>
    </div>
  `,
  styles: [`
    *{box-sizing:border-box;margin:0;padding:0}
    body{font-family:system-ui,-apple-system,sans-serif;background:#0f172a;min-height:100vh;display:flex;align-items:center;justify-content:center;padding:24px}
    .gc-wrap{width:100%;max-width:360px;text-align:center}
    
    .gc-card{position:relative;border-radius:20px;padding:24px;color:#fff;overflow:hidden;text-align:left;background:linear-gradient(135deg,#7c3aed,#6366f1 45%,#0ea5e9);box-shadow:0 22px 50px rgba(99,102,241,.4)}
    .gc-shine{position:absolute;top:-60%;left:-30%;width:80%;height:220%;background:linear-gradient(100deg,transparent,rgba(255,255,255,.25),transparent);transform:rotate(18deg);animation:gc-shine 4s ease-in-out infinite}
    @keyframes gc-shine{0%,60%{transform:translateX(-60%) rotate(18deg)}100%{transform:translateX(320%) rotate(18deg)}}
    
    .gc-top{display:flex;align-items:center;justify-content:space-between;position:relative}
    .gc-brand{font-size:13px;font-weight:800;letter-spacing:.04em}
    .gc-chip{font-size:10px;font-weight:800;letter-spacing:.08em;background:rgba(255,255,255,.2);border:1px solid rgba(255,255,255,.3);border-radius:6px;padding:3px 8px}
    .gc-amount{font-size:46px;font-weight:800;margin:16px 0 4px;position:relative;line-height:1}
    .gc-bal{font-size:22px;font-weight:700;opacity:.8}
    .gc-to{font-size:12px;opacity:.85;position:relative;margin-bottom:20px}
    
    .gc-code-row{display:flex;align-items:center;gap:8px;background:rgba(15,23,42,.25);border:1px dashed rgba(255,255,255,.35);border-radius:12px;padding:6px 6px 6px 14px;position:relative}
    .gc-code{flex:1;font-family:ui-monospace,monospace;font-size:15px;font-weight:700;letter-spacing:.12em;color:#fff}
    .gc-code.revealed{animation:gc-flip .35s ease}
    @keyframes gc-flip{from{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:translateY(0)}}
    .gc-btn{background:#fff;color:#4f46e5;border:none;border-radius:9px;padding:9px 14px;font-size:12px;font-weight:800;cursor:pointer;font-family:inherit;white-space:nowrap;transition:background .15s,transform .1s}
    .gc-btn:hover{background:#f1f5f9}
    .gc-btn:active{transform:scale(.95)}
    .gc-btn.done{background:#dcfce7;color:#16a34a}
    
    .gc-foot{display:flex;justify-content:space-between;font-size:11px;opacity:.85;margin-top:14px;position:relative}
    .gc-bal-tag{font-weight:700}
    .gc-note{font-size:12px;color:#64748b;margin-top:16px}
  `]
})
export class GiftCardVoucherComponent implements AfterViewInit {
  ngAfterViewInit(): void {
    var CODE = 'FWDT-9X4K-7Q2M';
    var revealed = false;
    var copyTimer;
    
    function revealOrCopy() {
      var codeEl = document.getElementById('gcCode');
      var btn = document.getElementById('gcBtn');
    
      if (!revealed) {
        revealed = true;
        codeEl.textContent = CODE;
        codeEl.classList.remove('revealed');
        void codeEl.offsetWidth;
        codeEl.classList.add('revealed');
        btn.textContent = 'Copy code';
        return;
      }
    
      var done = function () {
        btn.textContent = 'Copied ✓';
        btn.classList.add('done');
        clearTimeout(copyTimer);
        copyTimer = setTimeout(function () { btn.textContent = 'Copy code'; btn.classList.remove('done'); }, 1600);
      };
      if (navigator.clipboard && navigator.clipboard.writeText) {
        navigator.clipboard.writeText(CODE).then(done).catch(done);
      } else { done(); }
    }

    // Bind inline-handler functions to the component so the template can call them
    Object.assign(this, { revealOrCopy });
  }
}