"use client"
import { useEffect, useRef, useState, type ReactNode } from "react"
import { cn } from "@/registry/default/lib/utils"
interface FlipCoinButtonProps {
children: ReactNode
}
export default function FlipCoinButton({ children }: FlipCoinButtonProps) {
const [clicked, setClicked] = useState(false)
const [shrinkLanding, setShrinkLanding] = useState(false)
const [coinLanded, setCoinLanded] = useState(false)
const coinRef = useRef<HTMLDivElement>(null)
const requestRef = useRef<number | undefined>(undefined)
const handleClick = () => {
if (clicked) return
setClicked(true)
// Wait to start flipping the coin because of the button tilt animation
setTimeout(() => {
if (coinRef.current) {
// Randomize the flipping speeds just for fun
coinRef.current.style.setProperty("--coin-side-rotation-count", `${Math.floor(Math.random() * 5) * 90}`)
coinRef.current.style.setProperty("--coin-max-flip-angle", `${(Math.floor(Math.random() * 4) + 3) * Math.PI}`)
flipCoin()
}
}, 50)
}
const flipCoin = () => {
if (!coinRef.current) return
let moveLoopCount = 0
const maxMoveLoopCount = 90 // The larger the number, the slower the animation
const sideRotationCount = Number.parseInt(
coinRef.current.style.getPropertyValue("--coin-side-rotation-count") || "0",
)
const maxFlipAngle = Number.parseFloat(coinRef.current.style.getPropertyValue("--coin-max-flip-angle") || "0")
const flipCoinLoop = () => {
if (!coinRef.current) return
moveLoopCount++
const percentageCompleted = moveLoopCount / maxMoveLoopCount
const angle = -maxFlipAngle * Math.pow(percentageCompleted - 1, 2) + maxFlipAngle
// Calculate the scale and position of the coin moving through the air
coinRef.current.style.setProperty("--coin-y-multiplier", `${-11 * Math.pow(percentageCompleted * 2 - 1, 4) + 11}`)
coinRef.current.style.setProperty("--coin-x-multiplier", `${percentageCompleted}`)
coinRef.current.style.setProperty("--coin-scale-multiplier", `${percentageCompleted * 0.6}`)
coinRef.current.style.setProperty("--coin-rotation-multiplier", `${percentageCompleted * sideRotationCount}`)
// Calculate the scale and position values for the different coin faces
coinRef.current.style.setProperty("--front-scale-multiplier", `${Math.max(Math.cos(angle), 0)}`)
coinRef.current.style.setProperty("--front-y-multiplier", `${Math.sin(angle)}`)
coinRef.current.style.setProperty("--middle-scale-multiplier", `${Math.abs(Math.cos(angle))}`)
coinRef.current.style.setProperty("--middle-y-multiplier", `${Math.cos((angle + Math.PI / 2) % Math.PI)}`)
coinRef.current.style.setProperty("--back-scale-multiplier", `${Math.max(Math.cos(angle - Math.PI), 0)}`)
coinRef.current.style.setProperty("--back-y-multiplier", `${Math.sin(angle - Math.PI)}`)
coinRef.current.style.setProperty(
"--shine-opacity-multiplier",
`${4 * Math.sin((angle + Math.PI / 2) % Math.PI) - 3.2}`,
)
coinRef.current.style.setProperty(
"--shine-bg-multiplier",
`${-40 * (Math.cos((angle + Math.PI / 2) % Math.PI) - 0.5)}%`,
)
// Repeat animation loop
if (moveLoopCount < maxMoveLoopCount) {
if (moveLoopCount === maxMoveLoopCount - 6) {
setShrinkLanding(true)
}
requestRef.current = requestAnimationFrame(flipCoinLoop)
} else {
setCoinLanded(true)
if (coinRef.current) {
coinRef.current.style.setProperty("opacity", "0")
}
setTimeout(() => {
setClicked(false)
setShrinkLanding(false)
setCoinLanded(false)
setTimeout(() => {
resetCoin()
}, 300)
}, 1500)
}
}
requestRef.current = requestAnimationFrame(flipCoinLoop)
}
const resetCoin = () => {
if (!coinRef.current) return
coinRef.current.style.setProperty("--coin-x-multiplier", "0")
coinRef.current.style.setProperty("--coin-scale-multiplier", "0")
coinRef.current.style.setProperty("--coin-rotation-multiplier", "0")
coinRef.current.style.setProperty("--shine-opacity-multiplier", "0.4")
coinRef.current.style.setProperty("--shine-bg-multiplier", "50%")
coinRef.current.style.setProperty("opacity", "1")
}
useEffect(() => {
return () => {
if (requestRef.current) {
cancelAnimationFrame(requestRef.current)
}
}
}, [])
return (
<button
className={cn("flipCoinButton", {
clicked: clicked,
shrinkLanding: shrinkLanding,
coinLanded: coinLanded,
})}
onClick={handleClick}
>
<span className="buttonText">{children}</span>
<div className="coinWrapper">
<div className="coin" ref={coinRef}>
<div className="coinMiddle"></div>
<div className="coinBack"></div>
<div className="coinFront"></div>
</div>
</div>
</button>
)
}
// Define all CSS as a template string
const styles = `
.flipCoinButton {
background: none;
border: 0;
border-radius: 0.25rem 0.25rem 0 0;
cursor: pointer;
font-family: "Quicksand", sans-serif;
font-size: 0.75rem;
font-weight: 600;
height: 2.6rem;
margin-bottom: -4rem;
outline: 0;
position: relative;
top: 0;
transform-origin: 0% 100%;
transition: transform 50ms ease-in-out;
width: 9.5rem;
-webkit-tap-highlight-color: transparent;
}
.flipCoinButton:active {
transform: rotate(4deg);
}
.flipCoinButton.clicked {
animation: 150ms ease-in-out 1 shake;
pointer-events: none;
}
.flipCoinButton.clicked .buttonText {
opacity: 0;
transition: opacity 100ms linear 200ms;
}
.flipCoinButton.clicked::before {
height: 0.5rem;
width: 60%;
}
.flipCoinButton.clicked .coin {
transition: margin-bottom 1s linear 200ms;
margin-bottom: 0;
}
.flipCoinButton.shrinkLanding::before {
transition: width 200ms ease-in;
width: 0;
}
.flipCoinButton.coinLanded::after {
opacity: 1;
transform: scale(1);
transform-origin: 50% 100%;
}
.flipCoinButton.coinLanded .coinWrapper {
background: radial-gradient(circle at 35% 97%, rgba(3, 16, 50, 0.4) 0.04rem, transparent 0.04rem),
radial-gradient(circle at 45% 92%, rgba(3, 16, 50, 0.4) 0.04rem, transparent 0.02rem),
radial-gradient(circle at 55% 98%, rgba(3, 16, 50, 0.4) 0.04rem, transparent 0.04rem),
radial-gradient(circle at 65% 96%, rgba(3, 16, 50, 0.4) 0.06rem, transparent 0.06rem);
background-position: center bottom;
background-size: 100%;
bottom: -1rem;
opacity: 0;
transform: scale(2) translateY(-10px);
}
.buttonText {
color: #fff;
margin-right: 1.8rem;
opacity: 1;
position: relative;
transition: opacity 100ms linear 500ms;
z-index: 3;
}
.flipCoinButton::before {
background: #031032;
border-radius: 0.25rem;
bottom: 0;
content: "";
display: block;
height: 100%;
left: 50%;
position: absolute;
transform: translateX(-50%);
transition: height 250ms ease-in-out 400ms, width 250ms ease-in-out 300ms;
width: 100%;
z-index: 2;
}
.flipCoinButton::after {
bottom: -1rem;
color: #808080;
content: "Thank you!";
height: 110%;
left: 0;
opacity: 0;
position: absolute;
pointer-events: none;
text-align: center;
transform: scale(0);
transform-origin: 50% 20%;
transition: transform 200ms cubic-bezier(0, 0, 0.35, 1.43);
width: 100%;
z-index: 1;
}
.coinWrapper {
background: none;
bottom: 0;
height: 18rem;
left: 0;
opacity: 1;
overflow: hidden;
pointer-events: none;
position: absolute;
transform: none;
transform-origin: 50% 100%;
transition: opacity 200ms linear 100ms, transform 300ms ease-out;
width: 100%;
}
.coin {
--front-y-multiplier: 0;
--back-y-multiplier: 0;
--coin-y-multiplier: 0;
--coin-x-multiplier: 0;
--coin-scale-multiplier: 0;
--coin-rotation-multiplier: 0;
--shine-opacity-multiplier: 0.4;
--shine-bg-multiplier: 50%;
bottom: calc(var(--coin-y-multiplier) * 1rem - 3.5rem);
height: 3.5rem;
margin-bottom: 3.05rem;
position: absolute;
right: calc(var(--coin-x-multiplier) * 34% + 16%);
transform: translateX(50%) scale(calc(0.4 + var(--coin-scale-multiplier)))
rotate(calc(var(--coin-rotation-multiplier) * -1deg));
transition: opacity 100ms linear 200ms;
width: 3.5rem;
z-index: 3;
}
.coinFront,
.coinMiddle,
.coinBack,
.coin::before,
.coinFront::after,
.coinBack::after {
border-radius: 50%;
box-sizing: border-box;
height: 100%;
left: 0;
position: absolute;
width: 100%;
z-index: 3;
}
.coinFront {
background: radial-gradient(circle at 50% 50%, transparent 50%, rgba(115, 124, 153, 0.4) 54%, #c2cadf 54%),
linear-gradient(210deg, #8590b3 32%, transparent 32%), linear-gradient(150deg, #8590b3 32%, transparent 32%),
linear-gradient(to right, #8590b3 22%, transparent 22%, transparent 78%, #8590b3 78%),
linear-gradient(to bottom, #fcfaf9 44%, transparent 44%, transparent 65%, #fcfaf9 65%, #fcfaf9 71%, #8590b3 71%),
linear-gradient(
to right,
transparent 28%,
#fcfaf9 28%,
#fcfaf9 34%,
#8590b3 34%,
#8590b3 40%,
#fcfaf9 40%,
#fcfaf9 47%,
#8590b3 47%,
#8590b3 53%,
#fcfaf9 53%,
#fcfaf9 60%,
#8590b3 60%,
#8590b3 66%,
#fcfaf9 66%,
#fcfaf9 72%,
transparent 72%
);
background-color: #8590b3;
background-size: 100% 100%;
transform: translateY(calc(var(--front-y-multiplier) * 0.3181818182rem / 2)) scaleY(var(--front-scale-multiplier));
}
.coinFront::after {
background: rgba(0, 0, 0, 0.2);
content: "";
opacity: var(--front-y-multiplier);
}
.coinMiddle {
background: #737c99;
transform: translateY(calc(var(--middle-y-multiplier) * 0.3181818182rem / 2)) scaleY(var(--middle-scale-multiplier));
}
.coinBack {
background: radial-gradient(circle at 50% 50%, transparent 50%, rgba(115, 124, 153, 0.4) 54%, #c2cadf 54%),
radial-gradient(circle at 50% 40%, #fcfaf9 23%, transparent 23%),
radial-gradient(circle at 50% 100%, #fcfaf9 35%, transparent 35%);
background-color: #8590b3;
background-size: 100% 100%;
transform: translateY(calc(var(--back-y-multiplier) * 0.3181818182rem / 2)) scaleY(var(--back-scale-multiplier));
}
.coinBack::after {
background: rgba(0, 0, 0, 0.2);
content: "";
opacity: var(--back-y-multiplier);
}
.coin::before {
background: radial-gradient(circle at 25% 65%, transparent 50%, rgba(255, 255, 255, 0.9) 90%),
linear-gradient(
55deg,
transparent calc(var(--shine-bg-multiplier) + 0%),
#e9f4ff calc(var(--shine-bg-multiplier) + 0%),
transparent calc(var(--shine-bg-multiplier) + 50%)
);
content: "";
opacity: var(--shine-opacity-multiplier);
transform: translateY(calc(var(--middle-y-multiplier) * 0.3181818182rem / -2)) scaleY(var(--middle-scale-multiplier))
rotate(calc(var(--coin-rotation-multiplier) * 1deg));
z-index: 10;
}
.coin::after {
background: #737c99;
content: "";
height: 0.3181818182rem;
left: 0;
position: absolute;
top: 50%;
transform: translateY(-50%);
width: 100%;
z-index: 2;
}
@keyframes shake {
0% {
transform: rotate(4deg);
}
66% {
transform: rotate(-4deg);
}
100% {
transform: rotate();
}
}
`
// Function to inject styles into the document head
const injectStyles = () => {
if (typeof document !== "undefined") {
const styleSheet = document.createElement("style")
styleSheet.type = "text/css"
styleSheet.innerText = styles
document.head.appendChild(styleSheet)
}
}
// Inject styles
injectStyles()
Usage
By Voxlet UI
Try the interaction, copy the React source files, or download the complete example.
About Usage
Usage is an animated React and Tailwind CSS button by Voxlet UI. Try the interaction, copy the React source, or download the example with creator credit.
- Effects and categories
- Hover effects, Animated
- Using this example
- Copy a TSX file or download the original source and standalone demo. Test keyboard focus, motion and contrast in your own project.
Usage, Tailwind and licensing questions
Read the source files
registry/default/components/flip-coin-button/usage.tsx
import FlipCoinButton from "@/registry/default/ui/flip-coin-button"
export default function FlipCoinButtonDemo() {
return (
<div className="flex min-h-[10rem] flex-col items-center justify-center p-4 relative">
<p className="text-muted-foreground absolute top-2">Click to flip the coin</p>
<FlipCoinButton>Support Us</FlipCoinButton>
</div>
)
}
registry/default/ui/flip-coin-button.tsx
"use client"
import { useEffect, useRef, useState, type ReactNode } from "react"
import { cn } from "@/registry/default/lib/utils"
interface FlipCoinButtonProps {
children: ReactNode
}
export default function FlipCoinButton({ children }: FlipCoinButtonProps) {
const [clicked, setClicked] = useState(false)
const [shrinkLanding, setShrinkLanding] = useState(false)
const [coinLanded, setCoinLanded] = useState(false)
const coinRef = useRef<HTMLDivElement>(null)
const requestRef = useRef<number | undefined>(undefined)
const handleClick = () => {
if (clicked) return
setClicked(true)
// Wait to start flipping the coin because of the button tilt animation
setTimeout(() => {
if (coinRef.current) {
// Randomize the flipping speeds just for fun
coinRef.current.style.setProperty("--coin-side-rotation-count", `${Math.floor(Math.random() * 5) * 90}`)
coinRef.current.style.setProperty("--coin-max-flip-angle", `${(Math.floor(Math.random() * 4) + 3) * Math.PI}`)
flipCoin()
}
}, 50)
}
const flipCoin = () => {
if (!coinRef.current) return
let moveLoopCount = 0
const maxMoveLoopCount = 90 // The larger the number, the slower the animation
const sideRotationCount = Number.parseInt(
coinRef.current.style.getPropertyValue("--coin-side-rotation-count") || "0",
)
const maxFlipAngle = Number.parseFloat(coinRef.current.style.getPropertyValue("--coin-max-flip-angle") || "0")
const flipCoinLoop = () => {
if (!coinRef.current) return
moveLoopCount++
const percentageCompleted = moveLoopCount / maxMoveLoopCount
const angle = -maxFlipAngle * Math.pow(percentageCompleted - 1, 2) + maxFlipAngle
// Calculate the scale and position of the coin moving through the air
coinRef.current.style.setProperty("--coin-y-multiplier", `${-11 * Math.pow(percentageCompleted * 2 - 1, 4) + 11}`)
coinRef.current.style.setProperty("--coin-x-multiplier", `${percentageCompleted}`)
coinRef.current.style.setProperty("--coin-scale-multiplier", `${percentageCompleted * 0.6}`)
coinRef.current.style.setProperty("--coin-rotation-multiplier", `${percentageCompleted * sideRotationCount}`)
// Calculate the scale and position values for the different coin faces
coinRef.current.style.setProperty("--front-scale-multiplier", `${Math.max(Math.cos(angle), 0)}`)
coinRef.current.style.setProperty("--front-y-multiplier", `${Math.sin(angle)}`)
coinRef.current.style.setProperty("--middle-scale-multiplier", `${Math.abs(Math.cos(angle))}`)
coinRef.current.style.setProperty("--middle-y-multiplier", `${Math.cos((angle + Math.PI / 2) % Math.PI)}`)
coinRef.current.style.setProperty("--back-scale-multiplier", `${Math.max(Math.cos(angle - Math.PI), 0)}`)
coinRef.current.style.setProperty("--back-y-multiplier", `${Math.sin(angle - Math.PI)}`)
coinRef.current.style.setProperty(
"--shine-opacity-multiplier",
`${4 * Math.sin((angle + Math.PI / 2) % Math.PI) - 3.2}`,
)
coinRef.current.style.setProperty(
"--shine-bg-multiplier",
`${-40 * (Math.cos((angle + Math.PI / 2) % Math.PI) - 0.5)}%`,
)
// Repeat animation loop
if (moveLoopCount < maxMoveLoopCount) {
if (moveLoopCount === maxMoveLoopCount - 6) {
setShrinkLanding(true)
}
requestRef.current = requestAnimationFrame(flipCoinLoop)
} else {
setCoinLanded(true)
if (coinRef.current) {
coinRef.current.style.setProperty("opacity", "0")
}
setTimeout(() => {
setClicked(false)
setShrinkLanding(false)
setCoinLanded(false)
setTimeout(() => {
resetCoin()
}, 300)
}, 1500)
}
}
requestRef.current = requestAnimationFrame(flipCoinLoop)
}
const resetCoin = () => {
if (!coinRef.current) return
coinRef.current.style.setProperty("--coin-x-multiplier", "0")
coinRef.current.style.setProperty("--coin-scale-multiplier", "0")
coinRef.current.style.setProperty("--coin-rotation-multiplier", "0")
coinRef.current.style.setProperty("--shine-opacity-multiplier", "0.4")
coinRef.current.style.setProperty("--shine-bg-multiplier", "50%")
coinRef.current.style.setProperty("opacity", "1")
}
useEffect(() => {
return () => {
if (requestRef.current) {
cancelAnimationFrame(requestRef.current)
}
}
}, [])
return (
<button
className={cn("flipCoinButton", {
clicked: clicked,
shrinkLanding: shrinkLanding,
coinLanded: coinLanded,
})}
onClick={handleClick}
>
<span className="buttonText">{children}</span>
<div className="coinWrapper">
<div className="coin" ref={coinRef}>
<div className="coinMiddle"></div>
<div className="coinBack"></div>
<div className="coinFront"></div>
</div>
</div>
</button>
)
}
// Define all CSS as a template string
const styles = `
.flipCoinButton {
background: none;
border: 0;
border-radius: 0.25rem 0.25rem 0 0;
cursor: pointer;
font-family: "Quicksand", sans-serif;
font-size: 0.75rem;
font-weight: 600;
height: 2.6rem;
margin-bottom: -4rem;
outline: 0;
position: relative;
top: 0;
transform-origin: 0% 100%;
transition: transform 50ms ease-in-out;
width: 9.5rem;
-webkit-tap-highlight-color: transparent;
}
.flipCoinButton:active {
transform: rotate(4deg);
}
.flipCoinButton.clicked {
animation: 150ms ease-in-out 1 shake;
pointer-events: none;
}
.flipCoinButton.clicked .buttonText {
opacity: 0;
transition: opacity 100ms linear 200ms;
}
.flipCoinButton.clicked::before {
height: 0.5rem;
width: 60%;
}
.flipCoinButton.clicked .coin {
transition: margin-bottom 1s linear 200ms;
margin-bottom: 0;
}
.flipCoinButton.shrinkLanding::before {
transition: width 200ms ease-in;
width: 0;
}
.flipCoinButton.coinLanded::after {
opacity: 1;
transform: scale(1);
transform-origin: 50% 100%;
}
.flipCoinButton.coinLanded .coinWrapper {
background: radial-gradient(circle at 35% 97%, rgba(3, 16, 50, 0.4) 0.04rem, transparent 0.04rem),
radial-gradient(circle at 45% 92%, rgba(3, 16, 50, 0.4) 0.04rem, transparent 0.02rem),
radial-gradient(circle at 55% 98%, rgba(3, 16, 50, 0.4) 0.04rem, transparent 0.04rem),
radial-gradient(circle at 65% 96%, rgba(3, 16, 50, 0.4) 0.06rem, transparent 0.06rem);
background-position: center bottom;
background-size: 100%;
bottom: -1rem;
opacity: 0;
transform: scale(2) translateY(-10px);
}
.buttonText {
color: #fff;
margin-right: 1.8rem;
opacity: 1;
position: relative;
transition: opacity 100ms linear 500ms;
z-index: 3;
}
.flipCoinButton::before {
background: #031032;
border-radius: 0.25rem;
bottom: 0;
content: "";
display: block;
height: 100%;
left: 50%;
position: absolute;
transform: translateX(-50%);
transition: height 250ms ease-in-out 400ms, width 250ms ease-in-out 300ms;
width: 100%;
z-index: 2;
}
.flipCoinButton::after {
bottom: -1rem;
color: #808080;
content: "Thank you!";
height: 110%;
left: 0;
opacity: 0;
position: absolute;
pointer-events: none;
text-align: center;
transform: scale(0);
transform-origin: 50% 20%;
transition: transform 200ms cubic-bezier(0, 0, 0.35, 1.43);
width: 100%;
z-index: 1;
}
.coinWrapper {
background: none;
bottom: 0;
height: 18rem;
left: 0;
opacity: 1;
overflow: hidden;
pointer-events: none;
position: absolute;
transform: none;
transform-origin: 50% 100%;
transition: opacity 200ms linear 100ms, transform 300ms ease-out;
width: 100%;
}
.coin {
--front-y-multiplier: 0;
--back-y-multiplier: 0;
--coin-y-multiplier: 0;
--coin-x-multiplier: 0;
--coin-scale-multiplier: 0;
--coin-rotation-multiplier: 0;
--shine-opacity-multiplier: 0.4;
--shine-bg-multiplier: 50%;
bottom: calc(var(--coin-y-multiplier) * 1rem - 3.5rem);
height: 3.5rem;
margin-bottom: 3.05rem;
position: absolute;
right: calc(var(--coin-x-multiplier) * 34% + 16%);
transform: translateX(50%) scale(calc(0.4 + var(--coin-scale-multiplier)))
rotate(calc(var(--coin-rotation-multiplier) * -1deg));
transition: opacity 100ms linear 200ms;
width: 3.5rem;
z-index: 3;
}
.coinFront,
.coinMiddle,
.coinBack,
.coin::before,
.coinFront::after,
.coinBack::after {
border-radius: 50%;
box-sizing: border-box;
height: 100%;
left: 0;
position: absolute;
width: 100%;
z-index: 3;
}
.coinFront {
background: radial-gradient(circle at 50% 50%, transparent 50%, rgba(115, 124, 153, 0.4) 54%, #c2cadf 54%),
linear-gradient(210deg, #8590b3 32%, transparent 32%), linear-gradient(150deg, #8590b3 32%, transparent 32%),
linear-gradient(to right, #8590b3 22%, transparent 22%, transparent 78%, #8590b3 78%),
linear-gradient(to bottom, #fcfaf9 44%, transparent 44%, transparent 65%, #fcfaf9 65%, #fcfaf9 71%, #8590b3 71%),
linear-gradient(
to right,
transparent 28%,
#fcfaf9 28%,
#fcfaf9 34%,
#8590b3 34%,
#8590b3 40%,
#fcfaf9 40%,
#fcfaf9 47%,
#8590b3 47%,
#8590b3 53%,
#fcfaf9 53%,
#fcfaf9 60%,
#8590b3 60%,
#8590b3 66%,
#fcfaf9 66%,
#fcfaf9 72%,
transparent 72%
);
background-color: #8590b3;
background-size: 100% 100%;
transform: translateY(calc(var(--front-y-multiplier) * 0.3181818182rem / 2)) scaleY(var(--front-scale-multiplier));
}
.coinFront::after {
background: rgba(0, 0, 0, 0.2);
content: "";
opacity: var(--front-y-multiplier);
}
.coinMiddle {
background: #737c99;
transform: translateY(calc(var(--middle-y-multiplier) * 0.3181818182rem / 2)) scaleY(var(--middle-scale-multiplier));
}
.coinBack {
background: radial-gradient(circle at 50% 50%, transparent 50%, rgba(115, 124, 153, 0.4) 54%, #c2cadf 54%),
radial-gradient(circle at 50% 40%, #fcfaf9 23%, transparent 23%),
radial-gradient(circle at 50% 100%, #fcfaf9 35%, transparent 35%);
background-color: #8590b3;
background-size: 100% 100%;
transform: translateY(calc(var(--back-y-multiplier) * 0.3181818182rem / 2)) scaleY(var(--back-scale-multiplier));
}
.coinBack::after {
background: rgba(0, 0, 0, 0.2);
content: "";
opacity: var(--back-y-multiplier);
}
.coin::before {
background: radial-gradient(circle at 25% 65%, transparent 50%, rgba(255, 255, 255, 0.9) 90%),
linear-gradient(
55deg,
transparent calc(var(--shine-bg-multiplier) + 0%),
#e9f4ff calc(var(--shine-bg-multiplier) + 0%),
transparent calc(var(--shine-bg-multiplier) + 50%)
);
content: "";
opacity: var(--shine-opacity-multiplier);
transform: translateY(calc(var(--middle-y-multiplier) * 0.3181818182rem / -2)) scaleY(var(--middle-scale-multiplier))
rotate(calc(var(--coin-rotation-multiplier) * 1deg));
z-index: 10;
}
.coin::after {
background: #737c99;
content: "";
height: 0.3181818182rem;
left: 0;
position: absolute;
top: 50%;
transform: translateY(-50%);
width: 100%;
z-index: 2;
}
@keyframes shake {
0% {
transform: rotate(4deg);
}
66% {
transform: rotate(-4deg);
}
100% {
transform: rotate();
}
}
`
// Function to inject styles into the document head
const injectStyles = () => {
if (typeof document !== "undefined") {
const styleSheet = document.createElement("style")
styleSheet.type = "text/css"
styleSheet.innerText = styles
document.head.appendChild(styleSheet)
}
}
// Inject styles
injectStyles()
registry/default/lib/utils.ts
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
demo.tsx
export { default as default } from "@/registry/default/components/flip-coin-button/usage";CSS
/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */
@layer properties;
@layer theme, base, components, utilities;
@layer theme {
:root, :host {
--font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue",
"Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji",
"Segoe UI Symbol", "Noto Color Emoji";
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono",
"Courier New", monospace;
--spacing: 0.25rem;
--ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
--default-transition-duration: 150ms;
--default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
--default-font-family: var(--font-sans);
--default-mono-font-family: var(--font-mono);
}
}
@layer base {
*, ::after, ::before, ::backdrop, ::file-selector-button {
box-sizing: border-box;
margin: 0;
padding: 0;
border: 0 solid;
}
html, :host {
line-height: 1.5;
-webkit-text-size-adjust: 100%;
tab-size: 4;
font-family: var(--default-font-family, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");
font-feature-settings: var(--default-font-feature-settings, normal);
font-variation-settings: var(--default-font-variation-settings, normal);
-webkit-tap-highlight-color: transparent;
}
hr {
height: 0;
color: inherit;
border-top-width: 1px;
}
abbr:where([title]) {
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
}
h1, h2, h3, h4, h5, h6 {
font-size: inherit;
font-weight: inherit;
}
a {
color: inherit;
-webkit-text-decoration: inherit;
text-decoration: inherit;
}
b, strong {
font-weight: bolder;
}
code, kbd, samp, pre {
font-family: var(--default-mono-font-family, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);
font-feature-settings: var(--default-mono-font-feature-settings, normal);
font-variation-settings: var(--default-mono-font-variation-settings, normal);
font-size: 1em;
}
small {
font-size: 80%;
}
sub, sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
table {
text-indent: 0;
border-color: inherit;
border-collapse: collapse;
}
:-moz-focusring:where(:not(iframe)) {
outline: auto;
}
progress {
vertical-align: baseline;
}
summary {
display: list-item;
}
ol, ul, menu {
list-style: none;
}
img, svg, video, canvas, audio, iframe, embed, object {
display: block;
vertical-align: middle;
}
img, video {
max-width: 100%;
height: auto;
}
button, input, select, optgroup, textarea, ::file-selector-button {
font: inherit;
font-feature-settings: inherit;
font-variation-settings: inherit;
letter-spacing: inherit;
color: inherit;
border-radius: 0;
background-color: transparent;
opacity: 1;
}
:where(select:is([multiple], [size])) optgroup {
font-weight: bolder;
}
:where(select:is([multiple], [size])) optgroup option {
padding-inline-start: 20px;
}
::file-selector-button {
margin-inline-end: 4px;
}
::placeholder {
opacity: 1;
}
@supports (not (-webkit-appearance: -apple-pay-button)) or (contain-intrinsic-size: 1px) {
::placeholder {
color: currentcolor;
@supports (color: color-mix(in lab, red, red)) {
color: color-mix(in oklab, currentcolor 50%, transparent);
}
}
}
textarea {
resize: vertical;
}
::-webkit-search-decoration {
-webkit-appearance: none;
}
::-webkit-date-and-time-value {
min-height: 1lh;
text-align: inherit;
}
::-webkit-datetime-edit {
display: inline-flex;
}
::-webkit-datetime-edit-fields-wrapper {
padding: 0;
}
::-webkit-datetime-edit, ::-webkit-datetime-edit-year-field, ::-webkit-datetime-edit-month-field, ::-webkit-datetime-edit-day-field, ::-webkit-datetime-edit-hour-field, ::-webkit-datetime-edit-minute-field, ::-webkit-datetime-edit-second-field, ::-webkit-datetime-edit-millisecond-field, ::-webkit-datetime-edit-meridiem-field {
padding-block: 0;
}
::-webkit-calendar-picker-indicator {
line-height: 1;
}
:-moz-ui-invalid {
box-shadow: none;
}
button, input:where([type="button"], [type="reset"], [type="submit"]), ::file-selector-button {
appearance: button;
}
::-webkit-inner-spin-button, ::-webkit-outer-spin-button {
height: auto;
}
[hidden]:where(:not([hidden="until-found"])) {
display: none !important;
}
}
@layer utilities {
.absolute {
position: absolute;
}
.relative {
position: relative;
}
.top-2 {
top: calc(var(--spacing) * 2);
}
.flex {
display: flex;
}
.min-h-\[10rem\] {
min-height: 10rem;
}
.transform {
transform: var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,);
}
.flex-col {
flex-direction: column;
}
.items-center {
align-items: center;
}
.justify-center {
justify-content: center;
}
.border {
border-style: var(--tw-border-style);
border-width: 1px;
}
.p-4 {
padding: calc(var(--spacing) * 4);
}
.text-muted-foreground {
color: var(--muted-foreground);
}
.outline {
outline-style: var(--tw-outline-style);
outline-width: 1px;
}
.transition {
transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to, opacity, box-shadow, transform, translate, scale, rotate, filter, -webkit-backdrop-filter, backdrop-filter, display, content-visibility, overlay, pointer-events;
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
transition-duration: var(--tw-duration, var(--default-transition-duration));
}
.ease-in-out {
--tw-ease: var(--ease-in-out);
transition-timing-function: var(--ease-in-out);
}
}
:root {
--background: #fff;
--foreground: #111;
--primary: #171717;
--primary-foreground: #fff;
--secondary: #eee;
--secondary-foreground: #171717;
--muted: #eee;
--muted-foreground: #666;
--accent: #eee;
--accent-foreground: #171717;
--border: #ddd;
--input: #ddd;
--ring: #777;
--destructive: #dc2626;
--destructive-foreground: #fff;
--popover: #fff;
--popover-foreground: #111;
--success: #15803d;
--success-foreground: #fff;
--radius: .625rem;
--hairline: 1px;
}
.dark {
--background: #090909;
--foreground: #fafafa;
--primary: #fafafa;
--primary-foreground: #171717;
--secondary: #262626;
--secondary-foreground: #fafafa;
--muted: #262626;
--muted-foreground: #a3a3a3;
--accent: #262626;
--accent-foreground: #fafafa;
--border: #333;
--input: #333;
--popover: #171717;
--popover-foreground: #fafafa;
}
@property --tw-rotate-x {
syntax: "*";
inherits: false;
}
@property --tw-rotate-y {
syntax: "*";
inherits: false;
}
@property --tw-rotate-z {
syntax: "*";
inherits: false;
}
@property --tw-skew-x {
syntax: "*";
inherits: false;
}
@property --tw-skew-y {
syntax: "*";
inherits: false;
}
@property --tw-border-style {
syntax: "*";
inherits: false;
initial-value: solid;
}
@property --tw-outline-style {
syntax: "*";
inherits: false;
initial-value: solid;
}
@property --tw-ease {
syntax: "*";
inherits: false;
}
@layer properties {
@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) {
*, ::before, ::after, ::backdrop {
--tw-rotate-x: initial;
--tw-rotate-y: initial;
--tw-rotate-z: initial;
--tw-skew-x: initial;
--tw-skew-y: initial;
--tw-border-style: solid;
--tw-outline-style: solid;
--tw-ease: initial;
}
}
}