Content Card
component / marketing / layout
One card in five shapes — copy, media on top, media beside, figure-first stat and quote — plus a grid that lays them out, with every state drawn and five opt-in effects.
- type
- component
- files
- 2
- registry deps
- badge, card, skeleton, @automindz/render-link
- npm deps
- lucide-react
installs
- components/automindz/content-card.tsx
- styles/automindz-card.css
- Five shapes: copy, media, horizontal, stat, quote.
- emphasis spends the consumer's primary token, not ours.
- A stat's series reads out; sr-only carries it on touch.
variant changes the markup, not a flag: outline, media, horizontal, stat, quote. href makes the card one anchor, action puts your control in the footer, and the types reject both. emphasis and fx are opt-in and spend your own tokens: fx takes any of beam, spotlight, glare, tilt and glow, and needs styles/automindz-card.css imported once in your root layout or the effects are inert. A stat's sparkline formats no number: pass sparklineFormat.
Variations
one state per section
- 01Beam, spotlight and tilt at once
- 02Beam: a lit arc on the perimeter
- 03Spotlight: light the edge, pay for the face
- 04Glare: one crossing per hover
- 05Tilt: four rates, no z-axis
- 06Glow: a bloom, on hover and on focus
- 07A section, not a row of equals
- 08Figure between two edges
- 09Quote with an actual mark
- 10Media beside inverted copy
- 11The whole card is one link
- 12Pending, blocked, and no artwork
- 13Divided grid
Beam, spotlight and tilt at once
The loudest this card gets, and every bit of it is opt-in: fx={["beam", "spotlight", "tilt"]} on a card that is otherwise the same card as the last variant. **Hover it, then move the cursor around inside it.** An arc of the consumer's primary is already lapping the perimeter before you arrive — that one runs whether or not anybody is pointing at the card, over a faint static rail of the same colour. Under the cursor, the edge nearest it lights up and the card turns to face you, capped at 5°, while the badge, the copy, the footer and the picture move at four different rates — the marks fastest and with you, the picture slowest and against you. There is no translateZ anywhere in it: the card is overflow-hidden, and any overflow but visible forces transform-style: flat, so the depth is four 2D speeds rather than four z-offsets. Nothing here is a new dependency — a registered <angle>, a conic gradient, two masks composited with exclude, and the one pointermove handler the card already had.
Beam: a lit arc on the perimeter
**Nothing to hover — this one is already running.** A conic gradient painted into a ring the width of one property: padding on one overlay pseudo-element makes the frame, then the content box and the whole box are masked and composited with exclude, which keeps the difference between them. That difference is the border. The angle is a registered @property, which is the only reason it can be interpolated at all, and it is also the fallback — without @property the var() inside the gradient is invalid, the whole background is dropped, and what is left is the static rail underneath rather than a beam frozen in one corner. This card sets --card-beam-duration: 3.4s and --card-beam-arc: 26% in its className; the shipped defaults are 6s and 16%. The cost is honest: an animated gradient repaints every frame instead of compositing, so this is one or two cards on a screen, not twelve.
Spotlight: light the edge, pay for the face
**Move the cursor slowly across the card.** Two layers, and the split between them is arithmetic rather than taste. The **edge** takes the light at full strength — the same masked ring the beam uses, so it paints no pixel a glyph sits on and costs nobody any contrast. The **face** takes a 12% wash, which is not free: muted-foreground on card is 4.73:1 with stock tokens, the card's own hover ground already spends part of that (4.61:1), and a primary wash over the description lands at 3.60:1. The previous pass answered that by keeping the light inside the media frame. This one pays instead: while a face-painting effect is installed, the muted copy is raised to foreground at 70%, measured at 6.71:1 on stock tokens and 6.62:1 with primary pushed to black — the worst a light theme can do to it. Tab into the card and the same light parks at its centre; a keyboard has no cursor to follow. An emphasis="fill" card gets the edge and no wash at all: its ground is already primary and its copy already primary-foreground at full strength, so there is no headroom left to spend.
Glare: one crossing per hover
**Hover it once, move away, hover again.** A single diagonal band travels the card and stops — it rests off the left edge, so before and after the crossing there is nothing to see and nothing to undo. The card's own overflow-hidden and radius trim it to the corners for free; nothing here clips anything on purpose. This card is emphasis="fill", which is where the token the effects spend becomes visible: every effect reads --card-ink, and on a fill card that is primary-foreground rather than primary, because the ground is already primary and a beam of it would be invisible. Exactly one declaration of --card-ink is ever on the element, so which token gets spent never depends on stylesheet source order. 10% is the band's whole budget, and it is the number the spotlight's contrast table was measured with — a glare you have to read through has to be cheap. Under reduced motion the sweep is replaced by a still highlight that fades in where it would have crossed, which is the same trade the card's hover rule already makes.
Tilt: four rates, no z-axis
**Move the cursor to each corner in turn.** The card faces the pointer — the corner under it comes toward you, the opposite one goes back — and the four things inside it move at four different speeds: the badge most and with the cursor, then the copy, then the footer, and the picture least and *against* it. Opposed directions at four speeds is what makes this read as depth rather than as one plane turning. 5° at the edge is a ceiling rather than a default; past about 7° the type on the far side smears and a card that flops is worse than a flat one. --card-tilt will let you do it anyway. The rotation comes from --card-nx / --card-ny, the pointer's position as a signed unitless fraction — an angle needs a number to multiply and CSS cannot turn a percentage into one — written by the same coalesced handler that positions the light, so this is still one listener per card and still nothing React ever hears about. Keyboard focus gets no rotation and that is correct rather than missing: with no pointer the fraction is 0 and the transform is the identity, so focus gets every other cue plus the ring.
Glow: a bloom, on hover and on focus
**Hover it, then tab to it.** No offset, a wide blur, a negative spread — the card's own colour in the air around it rather than a sheet lifted off the page, which is the one place in this family a shadow earns itself. It is also the one effect with no reduced-motion branch: a bloom is a state, not a gesture, so only the transition into it is behind the guard. The mechanism is worth a line, because box-shadow is a trap here: the card's ring, its inset-ring and its focus ring are all that same property, and a rule in the stylesheet would win the cascade and take all three with it. So the stylesheet hands over a *value* as --card-bloom and a Tailwind shadow-(--card-bloom) utility composes it with the rest — which is also why an unimported stylesheet leaves no glow rather than a broken ring: the variable never resolves and --tw-shadow falls back to its registered initial. The figure keeps its readout: run the pointer along the series at the card's floor while the bloom is up, and the bubble still tracks the viewport rather than the card.
A section, not a row of equals
featured claims two tracks and — once it has media to fill them — two rows, so one card plus two ordinary ones closes a 3×2 block. The lead card is emphasis="fill": the ground becomes the consumer's own primary and every neutral in it flips to primary-foreground at full opacity. The two figures beside it are emphasis="accent", which spends the same token on marks only — the rule at the top edge and the series at the floor. The lead card is also the only link here, so it is the only one that answers a hover at all — and the only one with a picture, so it is the only one whose artwork the pointer can light. The light stays primary even on a fill: over that frame's primary-foreground/10 it pulls back toward the base ground, which *raises* the label contrast inside it rather than spending it.
Figure between two edges
The stat card is three bands, not a stack of small type: a full-bleed spec strip carrying the icon, eyebrow and badge on one line; the figure at 60px with its first digit's sidebearing hung past the copy below it; and the series as the card's floor, bleeding to both edges. The delta's direction is a glyph and a word — accent colours the trace, never the reading. Run the pointer along the floor: it snaps to the nearest week, a dot marks it, and one bubble prints “w/c 21 Jul · 421ms”. The unit is yours — this card formats no number, so sparklineFormat is the seam it refuses to guess, and the band prints the same twelve readings as sr-only text for touch and for a screen reader.
Quote with an actual mark
The mark is a display-size serif glyph in the flow, leading the quotation instead of sitting behind it — a watermark under copy is contrast spent on decoration, and on a filled card at 25% it drags the quotation to 3.90:1. In front of the text it owes nothing, so it can be strong. Under it, a real cite at 24px over a hairline that thickens with the card.
Media beside inverted copy
Two fifths media, three fifths copy, stacking below 640px — on a fill, where the hairline between the columns is cut from primary-foreground. Now hover it: **nothing happens.** This card holds an action rather than an href, so it is not a target, and a card that lit up under the cursor while only its button was clickable would be lying about where to click. The button answers instead. That is also the seam the card refuses to close: a default Button here is primary on primary and vanishes, so the action is variant="secondary".
The whole card is one link
Hover the copy: the card lifts, the ring darkens, the ground shifts, the hairline under the picture thickens to 3px, the title underlines and a 2px rule wipes across the bottom edge from the left. Now move the pointer onto the picture — a soft circle of the consumer's primary follows the cursor across it and the picture leans a couple of pixels *against* the movement, inside a frame that does not shift by one. That is one pointermove listener writing two percentages per frame, so the browser repaints a background and never relays anything. The light stops at the frame's edge on purpose: muted-foreground on card is 4.73:1 with stock tokens, and the same wash under the description would read 3.60:1. Tab into the card for every cue plus the focus ring, with the light parked at the frame's centre — a keyboard has no cursor to follow. Reduced motion drops the light and the lean outright and keeps everything that carries information.
Pending, blocked, and no artwork
Left: the skeleton reserves the figure at display height, the delta as the same pill and the series as the same 56px band — nothing moves when the numbers land. Middle: emphasis="fill" is passed and deliberately ignored, because a blocked card shouting the brand colour reads as available; what you get is a dashed outline, a lock and the reason in words. **Both of them are also passed all five fx effects, and both ignore every one.** Run the cursor over all three: no beam, no bloom, no tilt, nothing — and the blocked one is the point, because a card that lights up under the pointer and then refuses the click is the worst version of this. The overlay the effects paint into is not even rendered, so a skeleton is not quietly wearing a light show either. The series readout follows the same rule: the loading band is a skeleton at the same 56px and a disabled card's band never listens for a pointer at all, though it keeps its sr-only readings either way. Right: no media, so the frame keeps its ratio and says which of the two it is.
Divided grid
One shared hairline between cells rather than a border per card: each cell draws its top and left edge and is pulled back a pixel. No eyebrow and no badge on these, so no spec strip is drawn and the figure is the first thing in the cell. Five figures in a three-up grid, so the short last row proves it leaves no stray fill behind. The fourth cell carries a series: hover its left-most and right-most points and the bubble stays whole, which is the cheapest proof that it is one fixed node on the body rather than something absolutely positioned inside a square, overflow-hidden cell.
