A card form that is drawn as the card. Number, expiry, code, and a button that pays.
The prompt33,624 characters
Add the "Payment" component from notboring to my project.
It is licensed for use in my projects — notboring.dev/license. Please:
1. Create the component at a sensible path for this
project, named Payment, from the source below.
2. Install what it needs: npm i framer-motion
3. Add the CSS to the project's stylesheet.
4. THE PART THAT NEEDS YOUR JUDGMENT. The CSS reads
these custom properties and does not define them:
--card
--font-ui
--ink
--ink-3
--ink-5
--ink-rgb
--on-ink
They are notboring's design tokens. Map each one to
whatever this project already uses for the same
job — its own ink, ground, surface and UI font —
rather than defining them as new globals. If the
project has no equivalent, define it locally on
the component's own root so nothing leaks out.
Anything ending -rgb wants three bare numbers,
because the CSS builds rgba() from it.
Keep the comments. They say why the numbers are what
they are, and they are most of what makes this worth
copying rather than rewriting.
--- Payment.tsx ---
import { useEffect, useLayoutEffect, useRef, useState } from "react";
import { AnimatePresence, motion, useSpring, type MotionValue } from "framer-motion";
/* ══ Payment ══════════════════════════════════════════════
A card form, drawn as the card.
THE FORM IS THE CARD. The usual build is a stack of labeled
fields with a picture of a card above them that fills in as
you type — two things saying the same thing, and one of them
is decoration. Here the fields sit where they sit on the
card in your hand: the number across the middle, the name
and the date along the foot, the brand in a corner. Nothing
has to be kept in step, because there is one object, and
nothing needs a label, because the shape is the label.
ONE RING, NOT FOUR FOCUS STYLES. A ring per field is four
rings blinking on and off as you tab; one ring that TRAVELS
between them reads as your attention moving across the
card, which is what tabbing is. It is placed by layout
arithmetic — offsetLeft and offsetTop, in the block's own
pixels — so it lands right at any zoom and under any
ancestor transform, which a screen rectangle would not.
THE DOTS ARE EATEN, NOT REPLACED. The number's placeholder
The rest — the source and its comments — is what you are buying.