npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

spin-card-react-web

v0.2.0

Published

A composable React card with an organic tilt and cinematic spin transition.

Readme

Spin Card

A composable React card with an organic hover tilt and a cinematic whip-spin transition.

Spin Card owns the interaction. You own everything inside it.

npm · Live playground · GitHub · Created by Polo García

<SpinCard theme={{ background: "#234dde", foreground: "#f8f3e8" }}>
  <YourComposition />
</SpinCard>
  • Arbitrary React children—no prescribed card schema
  • Link and button semantics built in
  • Image and video backgrounds with custom reverse faces
  • Theme-aware media tinting with full manual control
  • Pointer-reactive holographic foil for image and video cards
  • Configurable tilt, spin, blur, and optional color burst
  • Router adapters for client-side navigation
  • Reduced-motion and touch-friendly defaults
  • TypeScript-first with no runtime dependencies

Playground

Open the interactive playground to try the motion controls, upload your own image or video, and switch between burst and spin-only transitions.

The included Vite demo also runs locally:

npm install
npm run dev

Install

npm install spin-card-react-web

Import the stylesheet once near the root of your app:

import "spin-card-react-web/styles.css";

Quick start

Wrap your cards in SpinTransitionProvider, then pass any composition to SpinCard:

import {
  SpinCard,
  SpinTransitionProvider,
} from "spin-card-react-web";
import "spin-card-react-web/styles.css";

export function ProjectGallery() {
  return (
    <SpinTransitionProvider>
      <SpinCard
        href="/projects/weather"
        theme={{
          background: "#c7e34a",
          foreground: "#17210e",
          accent: "#2d57dc",
        }}
      >
        <article className="project-card">
          <WeatherMap />
          <h2>Weather, composed your way</h2>
        </article>
      </SpinCard>
    </SpinTransitionProvider>
  );
}

With href, the card renders a native anchor. Without it, the card renders a button and calls onActivate at the transition's activation point:

<SpinCard onActivate={() => setSelected(true)}>
  <YourComposition />
</SpinCard>

href and onActivate are mutually exclusive: use href for navigation and onActivate for actions such as opening a modal or updating local state.

The card is itself interactive, so do not nest links or buttons inside it. For compositions with multiple controls, use useSpinTransition.

Your composition, your fonts

SpinCard renders your React tree directly. It does not prescribe a schema, add a content wrapper, or impose padding, typography, or layout.

<SpinCard className="brand-card">
  <article className="brand-composition">
    <BrandArtwork />
    <h2>Your layout and typography</h2>
    <Metrics />
  </article>
</SpinCard>

The shell uses font: inherit and does not load font files. Apply any local, hosted, variable, or design-system font through ordinary CSS:

.brand-card {
  font-family: "Your Sans", sans-serif;
}

.brand-composition h2 {
  font-family: "Your Display", serif;
  font-variation-settings: "wght" 720;
}

Font loading remains in your application, so Spin Card adds no font dependency or network request.

Image backgrounds

Use background for an image layer behind your content. Gradients and overlays remain regular CSS:

<SpinCard
  href="/stories/coast"
  background={{
    src: "/images/coast.jpg",
    overlay: "linear-gradient(180deg, transparent 35%, rgb(0 0 0 / 72%))",
    position: "center",
  }}
  theme={{ foreground: "#fff" }}
>
  <article className="story-card">
    <span>Field notes</span>
    <h2>The Atlantic coast</h2>
  </article>
</SpinCard>

Video backgrounds

Use video for a muted, autoplaying background. Include a poster so the card has an immediate fallback while loading and a static presentation when reduced motion is preferred:

<SpinCard
  video={{
    src: "/video/magic.mp4",
    poster: "/images/magic-poster.jpg",
    overlay: "linear-gradient(180deg, transparent, rgb(8 6 20 / 78%))",
    position: "center",
    loop: true,
  }}
  theme={{ foreground: "#fff" }}
>
  <article className="story-card">
    <span>Motion study</span>
    <h2>A little more magic</h2>
  </article>
</SpinCard>

Background videos are always muted and play inline. Supported formats depend on the browser; MP4 and WebM cover modern browsers well.

Spin Card sets both the live and default muted states so autoplay works with Safari's media policy. A poster is still recommended for loading, reduced-motion, and low-power fallbacks.

Theme-aware media tint

Image and video cards receive a subtle tint derived from theme.background by default. This keeps media aligned with the card palette without changing the source asset.

Tune the tint when the artwork needs a different treatment:

<SpinCard
  background={{ src: "/images/coast.jpg" }}
  theme={{ background: "#153b45", foreground: "#fff" }}
  tint={{
    color: "#123a46",
    opacity: 0.28,
    blendMode: "multiply",
  }}
>
  <YourComposition />
</SpinCard>

Set tint={false} to preserve the original media colors. Use background.overlay or video.overlay for directional gradients; use tint for the overall color wash.

Holographic foil

Add a refractor-style spectrum, glint, and foil grain above image or video media without changing the source asset:

<SpinCard
  background={{ src: "/images/collector-card.jpg" }}
  holographic={{ intensity: 0.58 }}
>
  <YourComposition />
</SpinCard>

Pass holographic for the default treatment or tune intensity from 0 to 1. The surface combines mathematically generated guilloché waves and offset rosettes with an aligned relief copy of the background image, so bright and dark details shape the reflection beneath precise security-print linework. Fine pointers move the reflected light without triggering React renders. Touch devices receive a quiet static foil, and reduced-motion preferences disable the shimmer movement.

Custom motion

The original two-turn whip spin is the default. Override only the values your design needs:

<SpinCard
  motion={{
    duration: 500,
    turns: 1.5,
    blur: 12,
    burstDuration: 460,
  }}
  tilt={{ rotate: 0.5, x: 1, y: [-4, -2] }}
  backFace={<BrandMonogram />}
>
  <YourComposition />
</SpinCard>

Set tilt={false} to disable the hover response. Motion blur is clamped between 0 and 20px.

With the fullscreen burst enabled, the animated card swells gently and dissolves directly into a borderless viewport veil; there is no expanding circle, rectangle, or stacked color wash to track. Image and video clones keep the current playback position so media does not restart during the handoff.

On Safari and other Apple WebKit browsers, Spin Card automatically approximates the centered-axis turn with two flat faces, omits the blur stage, gives fast multi-turn spins enough time to remain visible, and hands the burst to a sibling veil. The configured turn count, custom backFace, opacity, and burst are preserved. This avoids WebKit compositor artifacts that can enlarge, split, or flatten nested 3D card faces.

For a self-contained interaction, turn off the fullscreen burst. The card spins in place, removes its motion blur, and settles back into the layout before activation:

<SpinCard burst={false} onActivate={openDetails}>
  <YourComposition />
</SpinCard>

Client-side routing

Spin Card is router-agnostic. Inject your router's navigation functions and pass a routeKey that changes when navigation finishes:

// Next.js App Router
const router = useRouter();
const pathname = usePathname();

<SpinTransitionProvider
  navigate={(href) => router.push(href)}
  prefetch={(href) => router.prefetch(href)}
  routeKey={pathname}
>
  {children}
</SpinTransitionProvider>

Without navigate, links use normal browser navigation. If routeKey does not change, the provider reveals the page after its safety timeout.

API

SpinCard

Accepts standard HTML attributes in addition to these props.

| Prop | Type | Default | Description | | --- | --- | --- | --- | | children | ReactNode | required | Any React composition. | | href | string | — | Renders an anchor and navigates after the transition. | | onActivate | () => void \| Promise<void> | — | Runs a non-navigation action at the transition's activation point. Cannot be combined with href. | | burst | boolean | true | Set to false to spin and settle without covering the viewport. | | theme | SpinCardTheme | coral theme | Transition and card colors. | | background | SpinCardBackground | — | Image, overlay, position, size, and repeat settings. | | video | SpinCardVideoBackground | — | Muted video, poster, overlay, fit, position, loop, and preload settings. | | tint | SpinCardMediaTint \| false | theme color at 0.18 | Media color, opacity, and blend mode, or false to disable. | | holographic | SpinCardHolographic \| boolean | false | Pointer-reactive refractor foil over image or video media. | | motion | SpinMotion | provider defaults | Per-card motion overrides. | | tilt | SpinTilt \| false | enabled | Hover rotation and drift, or false to disable. | | backFace | ReactNode | themed reverse | Custom reverse-side composition. | | disabled | boolean | false | Disables activation and tilt. |

SpinTransitionProvider

| Prop | Type | Default | Description | | --- | --- | --- | --- | | children | ReactNode | required | The subtree containing spin triggers. | | navigate | (href: string) => void \| Promise<void> | browser navigation | Client-router adapter. | | prefetch | (href: string) => void \| Promise<void> | — | Called when a link card is hovered. | | routeKey | unknown | — | Changing value starts the destination reveal. | | motion | SpinMotion | built-in defaults | Motion defaults inherited by every card. | | burst | boolean | true | Default burst behavior inherited by every card. | | onCover | () => void | — | Runs when the burst covers the viewport. | | onReveal | () => void | — | Runs after the destination is revealed. | | navigationTimeout | number | 4000 | Safety reveal timeout in milliseconds. |

Motion defaults

{
  duration: 430,
  turns: 2,
  blur: 16,
  exitScale: 1.06,
  burstDelay: 280,
  burstDuration: 420,
  revealDelay: 120,
  revealDuration: 240,
}

Motion limits

Motion values are normalized so every supported combination remains legible and returns to a clean final state:

| Value | Supported behavior | | --- | --- | | turns | Clamped to -4…4; negative values reverse direction. | | duration | Clamped to 160…2000ms and raised to at least 180ms per turn. | | blur | Clamped to 0…20px. | | exitScale | Clamped to 0.9…1.5. | | burstDelay | Kept within the final 58–78% of the effective spin duration. | | burstDuration | Clamped to 160…1200ms. | | revealDelay | Clamped to 0…2000ms. | | revealDuration | Clamped to 0…1000ms. |

Fractional turns are supported during fullscreen bursts. In spin-only mode, they settle on the nearest whole rotation so the card returns front-facing without a visible snap. Apple WebKit receives at least 320ms per turn because its transform-only fallback cannot rely on motion blur to communicate speed.

Headless trigger

Use useSpinTransition when another element should start the effect:

const { begin } = useSpinTransition();

function openFrom(element: HTMLElement) {
  begin({
    source: element,
    onActivate: openProject,
    theme: { background: "#234dde" },
    backFace: <Logo />,
  });
}

Styling

The shell has no padding, typography, aspect ratio, or content layout. Style your composition normally and use CSS custom properties for the shell:

.my-spin-card {
  --spn-radius: 2rem;
  --spn-focus: #ffffff;
  aspect-ratio: 4 / 5;
}

During the transition, Spin Card clones the rendered DOM so CSS, images, SVG, video, and nested React output stay visually intact. Provide a video poster for the cleanest loading and transition fallback.

Accessibility

  • Native anchors for links and native buttons for actions
  • Normal behavior for modified clicks and target="_blank"
  • Visible keyboard focus with :focus-visible
  • Immediate activation when reduced motion is preferred
  • Video posters replace autoplaying backgrounds when reduced motion is preferred
  • Holographic foil stays static when reduced motion is preferred
  • No hover animation for touch pointers

Browser support

Spin Card supports current Chrome, Edge, Firefox, and Safari releases. It uses the Web Animations API when available and falls back to immediate activation when it is not, so navigation and actions never depend on animation support.

Safari-specific compatibility includes legacy reduced-motion listeners, autoplay-safe muted video, timeout-safe animation cleanup, and a flat-face axis-spin fallback that avoids WebKit's nested-3D compositor issue.

Development

npm install
npm run dev
npm test
npm run typecheck
npm run build

The playground includes six compositions and a shadcn/ui motion lab with local image and video uploads. Uploaded media stays in the browser and is never sent to a server.

License

MIT © Polo García