empowered.house ↗

payments/credit-card.ex

Payment card

A generic payment card with a chip, a masked number, the holder's name and the expiry.

Use it
# in the module that renders the template
use EHVisuals

<div class="h-96 rounded-xl border border-border">
  <.payments_credit_card animated />
</div>
default
isometric
custom copy
no mark · hover · no glow
light surface
isometric · fade out · still

Props

Prop Type Default What it does
kind string "Company card"
issuer string "Northwind" The name on the top left. Up to 22 characters.
holder string "Marta Nowak" Up to 20 characters.
last4 string "4471" The four digits left readable.
expiry string "08/29"
mark boolean true Shows the EmpoweredHouse mark where a card brand would stand.
Frame props, shared by every composition
Prop Type Default What it does
animated boolean false Turns motion on. Without it the composition renders in its final, still state.
trigger "mount" | "in-view" | "in-view-repeat" "in-view" When the entrance plays: on page load, once when scrolled into view, or every time it comes back into view.
hover boolean false Enables the hover micro-interactions the composition supports.
glow boolean true The warm incandescent halo behind the composition.
fade_out boolean false Fades the bottom edge into the surface underneath.
isometric boolean false Tilts the composition into the isometric plane of the platform stack.
theme "dark" | "light" "dark" `dark` for the site canvas, `light` for the cream reversed surface.
class string nil Extra classes for the frame.
rest global — Any other HTML attribute, set on the frame.

Source

Two files. Copy them into your app if you would rather own the code than depend on the library.

lib/eh_visuals/payments/credit-card.ex
credit-card.ex
defmodule EHVisuals.Payments.CreditCard do
  @moduledoc """
  A payment card: a chip, a masked number, the holder and the expiry. It
  carries no card brand; the EmpoweredHouse mark may stand where one would.
  """

  use EHVisuals.Composition,
    name: "payments/credit-card",
    title: "Payment card",
    description:
      "A generic payment card with a chip, a masked number, the holder's name and the expiry."

  attr(:kind, :string, default: "Company card")

  attr(:issuer, :string,
    default: "Northwind",
    doc: "The name on the top left. Up to 22 characters."
  )

  attr(:holder, :string, default: "Marta Nowak", doc: "Up to 20 characters.")
  attr(:last4, :string, default: "4471", doc: "The four digits left readable.")
  attr(:expiry, :string, default: "08/29")

  attr(:mark, :boolean,
    default: true,
    doc: "Shows the EmpoweredHouse mark where a card brand would stand."
  )

  attr(:label, :string, default: "A payment card for Marta Nowak ending in 4471")
  frame_attrs()

  @spec payments_credit_card(map()) :: Phoenix.LiveView.Rendered.t()
  def payments_credit_card(assigns) do
    dots =
      for group <- 0..2, dot <- 0..3 do
        %{cx: 27 + group * 56 + dot * 10, stagger: 5 + group * 2}
      end

    assigns = assign(assigns, :dots, dots)

    ~H"""
    <.frame name="payments/credit-card" label={@label} {frame_opts(assigns)}>
      <svg viewBox="0 0 340 214">
        <g class="ehv-h-lift">
          <rect
            class="ehv-payments-credit-card__body ehv-a-rise"
            x="0.5"
            y="0.5"
            width="339"
            height="213"
            rx="16"
          />
          <g class="ehv-a-fade ehv-i-1">
            <text class="ehv-mono" x="24" y="33" font-size="8.5">{String.upcase(@kind)}</text>
            <text class="ehv-t-strong" x="24" y="52" font-size="14">{@issuer}</text>
          </g>
          <g :if={@mark} class="ehv-a-pop ehv-i-2">
            <.mark x="279" y="9" size="50" />
          </g>
          <g class="ehv-a-pop ehv-i-3">
            <rect
              class="ehv-payments-credit-card__chip"
              x="24.5"
              y="82.5"
              width="42"
              height="31"
              rx="7"
            />
            <path class="ehv-line" d="M38.5 83 V113 M52.5 83 V113 M25 98 H38.5 M52.5 98 H66" />
          </g>
          <path
            class="ehv-line-ink ehv-round ehv-a-fade ehv-i-4"
            d="M80 92 a8.5 8.5 0 0 1 0 12 M85.5 87.5 a14.5 14.5 0 0 1 0 21 M91 83 a20.5 20.5 0 0 1 0 30"
          />
          <g :for={dot <- @dots} class={"ehv-a-pop ehv-i-#{dot.stagger}"}>
            <circle class="ehv-fill-2" cx={dot.cx} cy="149" r="2.6" />
          </g>
          <text class="ehv-code ehv-a-fade ehv-i-11" x="192" y="155" font-size="17">{@last4}</text>
          <g class="ehv-a-fade ehv-i-12">
            <text class="ehv-mono ehv-t-4" x="24" y="180" font-size="7">CARDHOLDER</text>
            <text x="24" y="196" font-size="12.5">{@holder}</text>
            <text class="ehv-mono ehv-t-4" x="192" y="180" font-size="7">VALID THRU</text>
            <text class="ehv-code" x="192" y="196" font-size="12">{@expiry}</text>
          </g>
        </g>
      </svg>
    </.frame>
    """
  end
end
assets/css/payments/credit-card.css
credit-card.css
/* payments/credit-card */
.ehv-payments-credit-card { --ehv-max-w: 370px; }

.ehv-payments-credit-card__body { fill: var(--ehv-surface-2); stroke: var(--ehv-border-strong); stroke-width: 1; }
.ehv-payments-credit-card__chip { fill: var(--ehv-surface-3); stroke: var(--ehv-ink-3); stroke-width: 1; }

.ehv--hover .ehv-payments-credit-card__chip { transition: stroke 200ms var(--ehv-ease); }
.ehv--hover:hover .ehv-payments-credit-card__chip { stroke: var(--ehv-lime); }