Back to buttons
"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;
    }
  }
}