"use client";
import { motion, AnimatePresence } from "motion/react";
import { useState, useEffect, useCallback, useRef } from "react";
/**
* Share Button — Rauno Freiberg craft.
*
* One surface, four phases, choreographed transitions.
* Idle → Open (staggered cascade) → Closing (reverse cascade) → Done.
* Icons exit BEFORE state swaps. Container holds shape during exit.
* Cursor glow under glass. Per-role springs. Blur materialization.
*/
/* ── Springs — tuned per visual weight ── */
const spring = {
morph: { type: "spring" as const, stiffness: 400, damping: 30 },
content: { type: "spring" as const, stiffness: 340, damping: 28 },
icon: { type: "spring" as const, stiffness: 440, damping: 24 },
press: { type: "spring" as const, stiffness: 600, damping: 32 },
};
/* ── Types ── */
type Phase = "idle" | "open" | "closing" | "done";
interface ShareButtonProps {
onShare?: (target: string) => void;
}
/* ── Share targets ── */
const TARGETS = [
{ id: "link", label: "Copy link" },
{ id: "email", label: "Email" },
{ id: "x", label: "Share on X" },
];
/* ── Component ── */
export function ShareButton({ onShare }: ShareButtonProps) {
const [phase, setPhase] = useState<Phase>("idle");
const [locked, setLocked] = useState(false);
const [glowX, setGlowX] = useState(0);
const [glowOn, setGlowOn] = useState(false);
const closeTimer = useRef<ReturnType<typeof setTimeout>>();
const lock = useCallback(() => {
setLocked(true);
setTimeout(() => setLocked(false), 450);
}, []);
/* Clean up timers */
useEffect(() => {
return () => {
if (closeTimer.current) clearTimeout(closeTimer.current);
};
}, []);
/* Auto-revert from done */
useEffect(() => {
if (phase !== "done") return;
const t = setTimeout(() => {
setPhase("idle");
lock();
}, 2200);
return () => clearTimeout(t);
}, [phase, lock]);
/**
* Choreographed close:
* 1. Set "closing" — icons reverse-stagger out, container holds shape
* 2. After 300ms (icons invisible) — swap to target phase
*/
const startClose = (nextPhase: "idle" | "done") => {
setPhase("closing");
closeTimer.current = setTimeout(() => setPhase(nextPhase), 300);
};
const toggle = () => {
if (locked || phase === "closing") return;
lock();
if (phase === "idle") {
setPhase("open");
} else {
startClose("idle");
}
};
const share = (target: string) => {
if (locked || phase !== "open") return;
lock();
onShare?.(target);
startClose("done");
};
const closing = phase === "closing";
return (
<div className="flex items-center justify-center">
{/* One surface — layout-animated shell */}
<motion.div
layout
transition={spring.morph}
style={{ borderRadius: 16 }}
className="relative flex items-center bg-neutral-900 border select-none"
animate={{
borderColor:
phase === "done"
? "rgba(52, 211, 153, 0.2)"
: "rgba(255, 255, 255, 0.08)",
boxShadow:
phase === "done"
? "inset 0 1px 0 rgba(52,211,153,0.06), 0 1px 3px rgba(0,0,0,0.4), 0 4px 14px rgba(0,0,0,0.2), 0 0 20px rgba(52,211,153,0.04)"
: "inset 0 1px 0 rgba(255,255,255,0.04), 0 1px 3px rgba(0,0,0,0.4), 0 4px 14px rgba(0,0,0,0.2)",
}}
onMouseMove={(e) => {
const r = e.currentTarget.getBoundingClientRect();
setGlowX(e.clientX - r.left);
if (!glowOn) setGlowOn(true);
}}
onMouseLeave={() => setGlowOn(false)}
>
{/* ── Cursor glow — light-under-glass ── */}
<div
className="absolute inset-0 pointer-events-none z-0"
style={{
borderRadius: 16,
opacity: glowOn ? 1 : 0,
transition: "opacity 0.3s ease",
background: `radial-gradient(100px circle at ${glowX}px 50%, rgba(255,255,255,0.05), transparent)`,
}}
/>
<AnimatePresence mode="popLayout" initial={false}>
{/* ── IDLE ── */}
{phase === "idle" && (
<motion.button
key="idle"
onClick={toggle}
disabled={locked}
whileHover={{ scale: 1.025 }}
whileTap={{ scale: 0.97 }}
className="relative z-10 flex items-center gap-2.5 px-5 h-11 text-[14px] font-medium text-white outline-none cursor-pointer disabled:cursor-default"
initial={{ opacity: 0, y: 8, filter: "blur(3px)" }}
animate={{ opacity: 1, y: 0, filter: "blur(0px)" }}
exit={{ opacity: 0, y: -8, filter: "blur(3px)" }}
transition={{
...spring.content,
filter: { duration: 0.2, ease: "easeOut" },
}}
>
<span className="tracking-[-0.01em]">Share</span>
<ArrowIcon />
</motion.button>
)}
{/* ── OPEN + CLOSING (same key — icons animate within, no AnimatePresence swap) ── */}
{(phase === "open" || closing) && (
<motion.div
key="open"
className="relative z-10 flex items-center h-11"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.08 }}
>
{/* Share anchor — dimmed, acts as close */}
<motion.button
onClick={!closing ? toggle : undefined}
whileTap={!closing ? { scale: 0.96 } : undefined}
animate={{ opacity: closing ? 0 : 0.45 }}
transition={spring.content}
className="px-4 text-[14px] font-medium tracking-[-0.01em] text-white outline-none cursor-pointer"
style={{ pointerEvents: closing ? "none" : "auto" }}
>
Share
</motion.button>
{/* Divider */}
<motion.div
className="w-px h-5 bg-white/[0.08] shrink-0"
initial={{ scaleY: 0, opacity: 0 }}
animate={{
scaleY: closing ? 0 : 1,
opacity: closing ? 0 : 1,
}}
transition={{ ...spring.content, delay: closing ? 0 : 0.02 }}
/>
{/* Target icons — forward stagger in, REVERSE stagger out */}
{TARGETS.map((target, i) => {
const reverseI = TARGETS.length - 1 - i;
return (
<motion.button
key={target.id}
onClick={() => share(target.id)}
whileHover={!closing ? { scale: 1.12 } : undefined}
whileTap={!closing ? { scale: 0.88 } : undefined}
initial={{ opacity: 0, scale: 0.4, filter: "blur(4px)" }}
animate={{
opacity: closing ? 0 : 1,
scale: closing ? 0.4 : 1,
filter: closing ? "blur(4px)" : "blur(0px)",
}}
transition={{
...spring.icon,
delay: closing ? reverseI * 0.04 : 0.04 + i * 0.05,
filter: {
duration: 0.2,
delay: closing ? reverseI * 0.04 : 0.04 + i * 0.05,
},
}}
className="flex items-center justify-center w-9 h-9 mx-0.5 rounded-xl text-neutral-400 hover:text-white hover:bg-white/[0.06] transition-colors duration-150 outline-none cursor-pointer"
aria-label={target.label}
style={{ pointerEvents: closing ? "none" : "auto" }}
>
{target.id === "link" && <LinkIcon />}
{target.id === "email" && <EmailIcon />}
{target.id === "x" && <XIcon />}
</motion.button>
);
})}
<div className="w-1.5 shrink-0" />
</motion.div>
)}
{/* ── DONE — checkmark draws on ── */}
{phase === "done" && (
<motion.div
key="done"
className="relative z-10 flex items-center gap-2.5 px-5 h-11 text-[14px] font-medium"
initial={{ opacity: 0, y: 8, filter: "blur(3px)" }}
animate={{ opacity: 1, y: 0, filter: "blur(0px)" }}
exit={{ opacity: 0, y: -8, filter: "blur(3px)" }}
transition={{
...spring.content,
filter: { duration: 0.2, ease: "easeOut" },
}}
>
<span className="text-emerald-300/90 tracking-[-0.01em]">
Copied
</span>
<CheckIcon />
</motion.div>
)}
</AnimatePresence>
</motion.div>
</div>
);
}
/* ── Icons ── */
function ArrowIcon() {
return (
<svg
width="15"
height="15"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="text-neutral-400"
>
<path d="M5 12h14" />
<path d="m12 5 7 7-7 7" />
</svg>
);
}
function LinkIcon() {
return (
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
</svg>
);
}
function EmailIcon() {
return (
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<rect width="20" height="16" x="2" y="4" rx="2" />
<path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7" />
</svg>
);
}
function XIcon() {
return (
<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor">
<path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z" />
</svg>
);
}
function CheckIcon() {
return (
<motion.svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2.5"
strokeLinecap="round"
strokeLinejoin="round"
className="text-emerald-400"
>
<motion.path
d="M5 13l4 4L19 7"
initial={{ pathLength: 0, opacity: 0 }}
animate={{ pathLength: 1, opacity: 1 }}
transition={{
pathLength: {
type: "spring",
stiffness: 280,
damping: 18,
delay: 0.15,
},
opacity: { duration: 0.15, delay: 0.15 },
}}
/>
</motion.svg>
);
}
export default ShareButton;
Share Button Demo
By Ruixen
Try the interaction, copy the React source files, or download the complete example.
About Share Button Demo
Share Button Demo is an animated React and Tailwind CSS button by Ruixen. 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/example/share-button-demo.tsx
import { ShareButton } from "@/registry/ruixenui/share-button";
export default function ShareButtonDemo() {
return (
<div className="flex min-h-[200px] w-full items-center justify-center">
<ShareButton />
</div>
);
}
registry/ruixenui/share-button.tsx
"use client";
import { motion, AnimatePresence } from "motion/react";
import { useState, useEffect, useCallback, useRef } from "react";
/**
* Share Button — Rauno Freiberg craft.
*
* One surface, four phases, choreographed transitions.
* Idle → Open (staggered cascade) → Closing (reverse cascade) → Done.
* Icons exit BEFORE state swaps. Container holds shape during exit.
* Cursor glow under glass. Per-role springs. Blur materialization.
*/
/* ── Springs — tuned per visual weight ── */
const spring = {
morph: { type: "spring" as const, stiffness: 400, damping: 30 },
content: { type: "spring" as const, stiffness: 340, damping: 28 },
icon: { type: "spring" as const, stiffness: 440, damping: 24 },
press: { type: "spring" as const, stiffness: 600, damping: 32 },
};
/* ── Types ── */
type Phase = "idle" | "open" | "closing" | "done";
interface ShareButtonProps {
onShare?: (target: string) => void;
}
/* ── Share targets ── */
const TARGETS = [
{ id: "link", label: "Copy link" },
{ id: "email", label: "Email" },
{ id: "x", label: "Share on X" },
];
/* ── Component ── */
export function ShareButton({ onShare }: ShareButtonProps) {
const [phase, setPhase] = useState<Phase>("idle");
const [locked, setLocked] = useState(false);
const [glowX, setGlowX] = useState(0);
const [glowOn, setGlowOn] = useState(false);
const closeTimer = useRef<ReturnType<typeof setTimeout>>();
const lock = useCallback(() => {
setLocked(true);
setTimeout(() => setLocked(false), 450);
}, []);
/* Clean up timers */
useEffect(() => {
return () => {
if (closeTimer.current) clearTimeout(closeTimer.current);
};
}, []);
/* Auto-revert from done */
useEffect(() => {
if (phase !== "done") return;
const t = setTimeout(() => {
setPhase("idle");
lock();
}, 2200);
return () => clearTimeout(t);
}, [phase, lock]);
/**
* Choreographed close:
* 1. Set "closing" — icons reverse-stagger out, container holds shape
* 2. After 300ms (icons invisible) — swap to target phase
*/
const startClose = (nextPhase: "idle" | "done") => {
setPhase("closing");
closeTimer.current = setTimeout(() => setPhase(nextPhase), 300);
};
const toggle = () => {
if (locked || phase === "closing") return;
lock();
if (phase === "idle") {
setPhase("open");
} else {
startClose("idle");
}
};
const share = (target: string) => {
if (locked || phase !== "open") return;
lock();
onShare?.(target);
startClose("done");
};
const closing = phase === "closing";
return (
<div className="flex items-center justify-center">
{/* One surface — layout-animated shell */}
<motion.div
layout
transition={spring.morph}
style={{ borderRadius: 16 }}
className="relative flex items-center bg-neutral-900 border select-none"
animate={{
borderColor:
phase === "done"
? "rgba(52, 211, 153, 0.2)"
: "rgba(255, 255, 255, 0.08)",
boxShadow:
phase === "done"
? "inset 0 1px 0 rgba(52,211,153,0.06), 0 1px 3px rgba(0,0,0,0.4), 0 4px 14px rgba(0,0,0,0.2), 0 0 20px rgba(52,211,153,0.04)"
: "inset 0 1px 0 rgba(255,255,255,0.04), 0 1px 3px rgba(0,0,0,0.4), 0 4px 14px rgba(0,0,0,0.2)",
}}
onMouseMove={(e) => {
const r = e.currentTarget.getBoundingClientRect();
setGlowX(e.clientX - r.left);
if (!glowOn) setGlowOn(true);
}}
onMouseLeave={() => setGlowOn(false)}
>
{/* ── Cursor glow — light-under-glass ── */}
<div
className="absolute inset-0 pointer-events-none z-0"
style={{
borderRadius: 16,
opacity: glowOn ? 1 : 0,
transition: "opacity 0.3s ease",
background: `radial-gradient(100px circle at ${glowX}px 50%, rgba(255,255,255,0.05), transparent)`,
}}
/>
<AnimatePresence mode="popLayout" initial={false}>
{/* ── IDLE ── */}
{phase === "idle" && (
<motion.button
key="idle"
onClick={toggle}
disabled={locked}
whileHover={{ scale: 1.025 }}
whileTap={{ scale: 0.97 }}
className="relative z-10 flex items-center gap-2.5 px-5 h-11 text-[14px] font-medium text-white outline-none cursor-pointer disabled:cursor-default"
initial={{ opacity: 0, y: 8, filter: "blur(3px)" }}
animate={{ opacity: 1, y: 0, filter: "blur(0px)" }}
exit={{ opacity: 0, y: -8, filter: "blur(3px)" }}
transition={{
...spring.content,
filter: { duration: 0.2, ease: "easeOut" },
}}
>
<span className="tracking-[-0.01em]">Share</span>
<ArrowIcon />
</motion.button>
)}
{/* ── OPEN + CLOSING (same key — icons animate within, no AnimatePresence swap) ── */}
{(phase === "open" || closing) && (
<motion.div
key="open"
className="relative z-10 flex items-center h-11"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.08 }}
>
{/* Share anchor — dimmed, acts as close */}
<motion.button
onClick={!closing ? toggle : undefined}
whileTap={!closing ? { scale: 0.96 } : undefined}
animate={{ opacity: closing ? 0 : 0.45 }}
transition={spring.content}
className="px-4 text-[14px] font-medium tracking-[-0.01em] text-white outline-none cursor-pointer"
style={{ pointerEvents: closing ? "none" : "auto" }}
>
Share
</motion.button>
{/* Divider */}
<motion.div
className="w-px h-5 bg-white/[0.08] shrink-0"
initial={{ scaleY: 0, opacity: 0 }}
animate={{
scaleY: closing ? 0 : 1,
opacity: closing ? 0 : 1,
}}
transition={{ ...spring.content, delay: closing ? 0 : 0.02 }}
/>
{/* Target icons — forward stagger in, REVERSE stagger out */}
{TARGETS.map((target, i) => {
const reverseI = TARGETS.length - 1 - i;
return (
<motion.button
key={target.id}
onClick={() => share(target.id)}
whileHover={!closing ? { scale: 1.12 } : undefined}
whileTap={!closing ? { scale: 0.88 } : undefined}
initial={{ opacity: 0, scale: 0.4, filter: "blur(4px)" }}
animate={{
opacity: closing ? 0 : 1,
scale: closing ? 0.4 : 1,
filter: closing ? "blur(4px)" : "blur(0px)",
}}
transition={{
...spring.icon,
delay: closing ? reverseI * 0.04 : 0.04 + i * 0.05,
filter: {
duration: 0.2,
delay: closing ? reverseI * 0.04 : 0.04 + i * 0.05,
},
}}
className="flex items-center justify-center w-9 h-9 mx-0.5 rounded-xl text-neutral-400 hover:text-white hover:bg-white/[0.06] transition-colors duration-150 outline-none cursor-pointer"
aria-label={target.label}
style={{ pointerEvents: closing ? "none" : "auto" }}
>
{target.id === "link" && <LinkIcon />}
{target.id === "email" && <EmailIcon />}
{target.id === "x" && <XIcon />}
</motion.button>
);
})}
<div className="w-1.5 shrink-0" />
</motion.div>
)}
{/* ── DONE — checkmark draws on ── */}
{phase === "done" && (
<motion.div
key="done"
className="relative z-10 flex items-center gap-2.5 px-5 h-11 text-[14px] font-medium"
initial={{ opacity: 0, y: 8, filter: "blur(3px)" }}
animate={{ opacity: 1, y: 0, filter: "blur(0px)" }}
exit={{ opacity: 0, y: -8, filter: "blur(3px)" }}
transition={{
...spring.content,
filter: { duration: 0.2, ease: "easeOut" },
}}
>
<span className="text-emerald-300/90 tracking-[-0.01em]">
Copied
</span>
<CheckIcon />
</motion.div>
)}
</AnimatePresence>
</motion.div>
</div>
);
}
/* ── Icons ── */
function ArrowIcon() {
return (
<svg
width="15"
height="15"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="text-neutral-400"
>
<path d="M5 12h14" />
<path d="m12 5 7 7-7 7" />
</svg>
);
}
function LinkIcon() {
return (
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
</svg>
);
}
function EmailIcon() {
return (
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<rect width="20" height="16" x="2" y="4" rx="2" />
<path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7" />
</svg>
);
}
function XIcon() {
return (
<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor">
<path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z" />
</svg>
);
}
function CheckIcon() {
return (
<motion.svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2.5"
strokeLinecap="round"
strokeLinejoin="round"
className="text-emerald-400"
>
<motion.path
d="M5 13l4 4L19 7"
initial={{ pathLength: 0, opacity: 0 }}
animate={{ pathLength: 1, opacity: 1 }}
transition={{
pathLength: {
type: "spring",
stiffness: 280,
damping: 18,
delay: 0.15,
},
opacity: { duration: 0.15, delay: 0.15 },
}}
/>
</motion.svg>
);
}
export default ShareButton;
demo.tsx
export { default as default } from "@/registry/example/share-button-demo";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;
--color-emerald-300: oklch(84.5% 0.143 164.978);
--color-emerald-400: oklch(76.5% 0.177 163.223);
--color-neutral-400: oklch(70.8% 0 none);
--color-neutral-900: oklch(20.5% 0 none);
--color-white: #fff;
--spacing: 0.25rem;
--font-weight-medium: 500;
--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 {
.pointer-events-none {
pointer-events: none;
}
.absolute {
position: absolute;
}
.relative {
position: relative;
}
.inset-0 {
inset: 0px;
}
.z-0 {
z-index: 0;
}
.z-10 {
z-index: 10;
}
.container {
width: 100%;
@media (width >= 40rem) {
max-width: 40rem;
}
@media (width >= 48rem) {
max-width: 48rem;
}
@media (width >= 64rem) {
max-width: 64rem;
}
@media (width >= 80rem) {
max-width: 80rem;
}
@media (width >= 96rem) {
max-width: 96rem;
}
}
.mx-0\.5 {
margin-inline: calc(var(--spacing) * 0.5);
}
.flex {
display: flex;
}
.h-5 {
height: calc(var(--spacing) * 5);
}
.h-9 {
height: calc(var(--spacing) * 9);
}
.h-11 {
height: calc(var(--spacing) * 11);
}
.min-h-\[200px\] {
min-height: 200px;
}
.w-1\.5 {
width: calc(var(--spacing) * 1.5);
}
.w-9 {
width: calc(var(--spacing) * 9);
}
.w-full {
width: 100%;
}
.w-px {
width: 1px;
}
.shrink-0 {
flex-shrink: 0;
}
.cursor-pointer {
cursor: pointer;
}
.items-center {
align-items: center;
}
.justify-center {
justify-content: center;
}
.gap-2\.5 {
gap: calc(var(--spacing) * 2.5);
}
.rounded-xl {
border-radius: calc(var(--radius) + 4px);
}
.border {
border-style: var(--tw-border-style);
border-width: 1px;
}
.bg-neutral-900 {
background-color: var(--color-neutral-900);
}
.bg-white\/\[0\.08\] {
background-color: color-mix(in srgb, #fff 8%, transparent);
@supports (color: color-mix(in lab, red, red)) {
background-color: color-mix(in oklab, var(--color-white) 8%, transparent);
}
}
.px-4 {
padding-inline: calc(var(--spacing) * 4);
}
.px-5 {
padding-inline: calc(var(--spacing) * 5);
}
.text-\[14px\] {
font-size: 14px;
}
.font-medium {
--tw-font-weight: var(--font-weight-medium);
font-weight: var(--font-weight-medium);
}
.tracking-\[-0\.01em\] {
--tw-tracking: -0.01em;
letter-spacing: -0.01em;
}
.text-emerald-300\/90 {
color: color-mix(in srgb, oklch(84.5% 0.143 164.978) 90%, transparent);
@supports (color: color-mix(in lab, red, red)) {
color: color-mix(in oklab, var(--color-emerald-300) 90%, transparent);
}
}
.text-emerald-400 {
color: var(--color-emerald-400);
}
.text-neutral-400 {
color: var(--color-neutral-400);
}
.text-white {
color: var(--color-white);
}
.filter {
filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);
}
.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));
}
.transition-colors {
transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to;
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
transition-duration: var(--tw-duration, var(--default-transition-duration));
}
.duration-150 {
--tw-duration: 150ms;
transition-duration: 150ms;
}
.outline-none {
--tw-outline-style: none;
outline-style: none;
}
.select-none {
-webkit-user-select: none;
user-select: none;
}
@media (hover: hover) {
.hover\:bg-white\/\[0\.06\]:hover {
background-color: color-mix(in srgb, #fff 6%, transparent);
}
@supports (color: color-mix(in lab, red, red)) {
.hover\:bg-white\/\[0\.06\]:hover {
background-color: color-mix(in oklab, var(--color-white) 6%, transparent);
}
}
.hover\:text-white:hover {
color: var(--color-white);
}
}
.disabled\:cursor-default:disabled {
cursor: default;
}
}
: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-border-style {
syntax: "*";
inherits: false;
initial-value: solid;
}
@property --tw-font-weight {
syntax: "*";
inherits: false;
}
@property --tw-tracking {
syntax: "*";
inherits: false;
}
@property --tw-blur {
syntax: "*";
inherits: false;
}
@property --tw-brightness {
syntax: "*";
inherits: false;
}
@property --tw-contrast {
syntax: "*";
inherits: false;
}
@property --tw-grayscale {
syntax: "*";
inherits: false;
}
@property --tw-hue-rotate {
syntax: "*";
inherits: false;
}
@property --tw-invert {
syntax: "*";
inherits: false;
}
@property --tw-opacity {
syntax: "*";
inherits: false;
}
@property --tw-saturate {
syntax: "*";
inherits: false;
}
@property --tw-sepia {
syntax: "*";
inherits: false;
}
@property --tw-drop-shadow {
syntax: "*";
inherits: false;
}
@property --tw-drop-shadow-color {
syntax: "*";
inherits: false;
}
@property --tw-drop-shadow-alpha {
syntax: "<percentage>";
inherits: false;
initial-value: 100%;
}
@property --tw-drop-shadow-size {
syntax: "*";
inherits: false;
}
@property --tw-duration {
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-border-style: solid;
--tw-font-weight: initial;
--tw-tracking: initial;
--tw-blur: initial;
--tw-brightness: initial;
--tw-contrast: initial;
--tw-grayscale: initial;
--tw-hue-rotate: initial;
--tw-invert: initial;
--tw-opacity: initial;
--tw-saturate: initial;
--tw-sepia: initial;
--tw-drop-shadow: initial;
--tw-drop-shadow-color: initial;
--tw-drop-shadow-alpha: 100%;
--tw-drop-shadow-size: initial;
--tw-duration: initial;
}
}
}