Back to buttons
"use client";

import * as React from "react";
import { motion, AnimatePresence, type HTMLMotionProps } from "motion/react";
import { AlertTriangle, Check, ShieldAlert } from "lucide-react";
import { cn } from "@/lib/utils";

interface HoldToChargeButtonProps extends Omit<
  HTMLMotionProps<"button">,
  "children" | "onAnimationStart"
> {
  label?: string;
  holdingLabel?: string;
  completedLabel?: string;
  holdDuration?: number;
  autoResetDelay?: number;
  onComplete?: () => void;
  variant?: "destructive" | "default";
}

const HoldToChargeButton = React.forwardRef<
  HTMLButtonElement,
  HoldToChargeButtonProps
>(
  (
    {
      label = "Hold to Delete",
      holdingLabel = "Holding...",
      completedLabel = "Deleted Successfully!",
      holdDuration = 1500,
      autoResetDelay = 3000,
      onComplete,
      variant = "destructive",
      className,
      onMouseDown,
      onMouseUp,
      onMouseLeave,
      onTouchStart,
      onTouchEnd,
      ...props
    },
    ref,
  ) => {
    const [progress, setProgress] = React.useState(0);
    const [isHolding, setIsHolding] = React.useState(false);
    const [isCompleted, setIsCompleted] = React.useState(false);

    const animationFrameRef = React.useRef<number | null>(null);
    const startTimeRef = React.useRef<number | null>(null);

    const isDestructive = variant === "destructive";

    const stopHolding = React.useCallback(() => {
      setIsHolding(false);
      if (animationFrameRef.current) {
        cancelAnimationFrame(animationFrameRef.current);
        animationFrameRef.current = null;
      }
      startTimeRef.current = null;
      if (!isCompleted) {
        setProgress(0);
      }
    }, [isCompleted]);

    const startHolding = React.useCallback(() => {
      if (isCompleted) return;
      setIsHolding(true);
      startTimeRef.current = performance.now();

      const updateProgress = (now: number) => {
        if (!startTimeRef.current) return;
        const elapsed = now - startTimeRef.current;
        const currentProgress = Math.min((elapsed / holdDuration) * 100, 100);

        setProgress(currentProgress);

        if (currentProgress >= 100) {
          setIsCompleted(true);
          setIsHolding(false);
          if (onComplete) onComplete();

          if (autoResetDelay > 0) {
            setTimeout(() => {
              setIsCompleted(false);
              setProgress(0);
            }, autoResetDelay);
          }
        } else {
          animationFrameRef.current = requestAnimationFrame(updateProgress);
        }
      };

      animationFrameRef.current = requestAnimationFrame(updateProgress);
    }, [holdDuration, isCompleted, onComplete, autoResetDelay]);

    React.useEffect(() => {
      return () => {
        if (animationFrameRef.current) {
          cancelAnimationFrame(animationFrameRef.current);
        }
      };
    }, []);

    return (
      <motion.button
        ref={ref}
        onMouseDown={(e) => {
          startHolding();
          if (onMouseDown) onMouseDown(e);
        }}
        onMouseUp={(e) => {
          stopHolding();
          if (onMouseUp) onMouseUp(e);
        }}
        onMouseLeave={(e) => {
          stopHolding();
          if (onMouseLeave) onMouseLeave(e);
        }}
        onTouchStart={(e) => {
          startHolding();
          if (onTouchStart) onTouchStart(e);
        }}
        onTouchEnd={(e) => {
          stopHolding();
          if (onTouchEnd) onTouchEnd(e);
        }}
        whileHover={{ scale: isCompleted ? 1 : 1.02 }}
        whileTap={{ scale: isCompleted ? 1 : 0.97 }}
        className={cn(
          "group relative inline-flex items-center justify-center gap-2.5 overflow-hidden rounded-xl px-5 py-2.5 font-medium text-sm transition-all duration-300 select-none cursor-pointer outline-none min-h-10 border hover:bg-muted dark:hover:bg-input/50",
          isHolding || isCompleted
            ? isDestructive
              ? "border-destructive"
              : "border-primary"
            : isDestructive
              ? "border-border focus-visible:ring-destructive/30"
              : "border-border hover:border-primary/60 focus-visible:ring-primary/30",
          "bg-background text-foreground focus-visible:ring-2 focus-visible:ring-offset-2",
          className,
        )}
        {...props}
      >
        {/* Dynamic Progress Fill Bar */}
        <motion.span
          style={{ width: `${progress}%` }}
          className={cn(
            "absolute left-0 top-0 bottom-0 z-0 pointer-events-none transition-all duration-75",
            isDestructive ? "bg-destructive/15" : "bg-primary/15",
          )}
        />

        <AnimatePresence mode="wait">
          {isCompleted ? (
            <motion.span
              key="completed"
              initial={{ opacity: 0, scale: 0.9 }}
              animate={{ opacity: 1, scale: 1 }}
              exit={{ opacity: 0, scale: 0.9 }}
              className={cn(
                "relative z-10 flex items-center gap-2 font-medium",
                isDestructive ? "text-destructive" : "text-primary",
              )}
            >
              <Check className="size-4 stroke-[2.5]" />
              <span>{completedLabel}</span>
            </motion.span>
          ) : (
            <motion.span
              key="active"
              className="relative z-10 flex items-center gap-2.5"
            >
              {isHolding ? (
                <AlertTriangle className="size-4 animate-pulse text-foreground" />
              ) : (
                <ShieldAlert className="size-4 text-foreground transition-colors" />
              )}

              {/* Label */}
              <span className="font-medium tracking-wide transition-colors text-foreground">
                {isHolding ? holdingLabel : label}
              </span>
            </motion.span>
          )}
        </AnimatePresence>
      </motion.button>
    );
  },
);

HoldToChargeButton.displayName = "HoldToChargeButton";

export { HoldToChargeButton, type HoldToChargeButtonProps };

const HoldToChargeButtonDemo = () => {
  return (
    <HoldToChargeButton
      holdDuration={2000}
      label="Hold to Delete"
      holdingLabel="Holding..."
      completedLabel="Deleted Successfully!"
      variant="destructive"
    />
  );
};

export default HoldToChargeButtonDemo;

Button 28 - Hold to Charge

By ShadcnSpace

Try the interaction, copy the React source files, or download the complete example.

About Button 28 - Hold to Charge

Button 28 - Hold to Charge is an animated React and Tailwind CSS button by ShadcnSpace. 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

components/shadcn-space/button/button-28.tsx

"use client";

import * as React from "react";
import { motion, AnimatePresence, type HTMLMotionProps } from "motion/react";
import { AlertTriangle, Check, ShieldAlert } from "lucide-react";
import { cn } from "@/lib/utils";

interface HoldToChargeButtonProps extends Omit<
  HTMLMotionProps<"button">,
  "children" | "onAnimationStart"
> {
  label?: string;
  holdingLabel?: string;
  completedLabel?: string;
  holdDuration?: number;
  autoResetDelay?: number;
  onComplete?: () => void;
  variant?: "destructive" | "default";
}

const HoldToChargeButton = React.forwardRef<
  HTMLButtonElement,
  HoldToChargeButtonProps
>(
  (
    {
      label = "Hold to Delete",
      holdingLabel = "Holding...",
      completedLabel = "Deleted Successfully!",
      holdDuration = 1500,
      autoResetDelay = 3000,
      onComplete,
      variant = "destructive",
      className,
      onMouseDown,
      onMouseUp,
      onMouseLeave,
      onTouchStart,
      onTouchEnd,
      ...props
    },
    ref,
  ) => {
    const [progress, setProgress] = React.useState(0);
    const [isHolding, setIsHolding] = React.useState(false);
    const [isCompleted, setIsCompleted] = React.useState(false);

    const animationFrameRef = React.useRef<number | null>(null);
    const startTimeRef = React.useRef<number | null>(null);

    const isDestructive = variant === "destructive";

    const stopHolding = React.useCallback(() => {
      setIsHolding(false);
      if (animationFrameRef.current) {
        cancelAnimationFrame(animationFrameRef.current);
        animationFrameRef.current = null;
      }
      startTimeRef.current = null;
      if (!isCompleted) {
        setProgress(0);
      }
    }, [isCompleted]);

    const startHolding = React.useCallback(() => {
      if (isCompleted) return;
      setIsHolding(true);
      startTimeRef.current = performance.now();

      const updateProgress = (now: number) => {
        if (!startTimeRef.current) return;
        const elapsed = now - startTimeRef.current;
        const currentProgress = Math.min((elapsed / holdDuration) * 100, 100);

        setProgress(currentProgress);

        if (currentProgress >= 100) {
          setIsCompleted(true);
          setIsHolding(false);
          if (onComplete) onComplete();

          if (autoResetDelay > 0) {
            setTimeout(() => {
              setIsCompleted(false);
              setProgress(0);
            }, autoResetDelay);
          }
        } else {
          animationFrameRef.current = requestAnimationFrame(updateProgress);
        }
      };

      animationFrameRef.current = requestAnimationFrame(updateProgress);
    }, [holdDuration, isCompleted, onComplete, autoResetDelay]);

    React.useEffect(() => {
      return () => {
        if (animationFrameRef.current) {
          cancelAnimationFrame(animationFrameRef.current);
        }
      };
    }, []);

    return (
      <motion.button
        ref={ref}
        onMouseDown={(e) => {
          startHolding();
          if (onMouseDown) onMouseDown(e);
        }}
        onMouseUp={(e) => {
          stopHolding();
          if (onMouseUp) onMouseUp(e);
        }}
        onMouseLeave={(e) => {
          stopHolding();
          if (onMouseLeave) onMouseLeave(e);
        }}
        onTouchStart={(e) => {
          startHolding();
          if (onTouchStart) onTouchStart(e);
        }}
        onTouchEnd={(e) => {
          stopHolding();
          if (onTouchEnd) onTouchEnd(e);
        }}
        whileHover={{ scale: isCompleted ? 1 : 1.02 }}
        whileTap={{ scale: isCompleted ? 1 : 0.97 }}
        className={cn(
          "group relative inline-flex items-center justify-center gap-2.5 overflow-hidden rounded-xl px-5 py-2.5 font-medium text-sm transition-all duration-300 select-none cursor-pointer outline-none min-h-10 border hover:bg-muted dark:hover:bg-input/50",
          isHolding || isCompleted
            ? isDestructive
              ? "border-destructive"
              : "border-primary"
            : isDestructive
              ? "border-border focus-visible:ring-destructive/30"
              : "border-border hover:border-primary/60 focus-visible:ring-primary/30",
          "bg-background text-foreground focus-visible:ring-2 focus-visible:ring-offset-2",
          className,
        )}
        {...props}
      >
        {/* Dynamic Progress Fill Bar */}
        <motion.span
          style={{ width: `${progress}%` }}
          className={cn(
            "absolute left-0 top-0 bottom-0 z-0 pointer-events-none transition-all duration-75",
            isDestructive ? "bg-destructive/15" : "bg-primary/15",
          )}
        />

        <AnimatePresence mode="wait">
          {isCompleted ? (
            <motion.span
              key="completed"
              initial={{ opacity: 0, scale: 0.9 }}
              animate={{ opacity: 1, scale: 1 }}
              exit={{ opacity: 0, scale: 0.9 }}
              className={cn(
                "relative z-10 flex items-center gap-2 font-medium",
                isDestructive ? "text-destructive" : "text-primary",
              )}
            >
              <Check className="size-4 stroke-[2.5]" />
              <span>{completedLabel}</span>
            </motion.span>
          ) : (
            <motion.span
              key="active"
              className="relative z-10 flex items-center gap-2.5"
            >
              {isHolding ? (
                <AlertTriangle className="size-4 animate-pulse text-foreground" />
              ) : (
                <ShieldAlert className="size-4 text-foreground transition-colors" />
              )}

              {/* Label */}
              <span className="font-medium tracking-wide transition-colors text-foreground">
                {isHolding ? holdingLabel : label}
              </span>
            </motion.span>
          )}
        </AnimatePresence>
      </motion.button>
    );
  },
);

HoldToChargeButton.displayName = "HoldToChargeButton";

export { HoldToChargeButton, type HoldToChargeButtonProps };

const HoldToChargeButtonDemo = () => {
  return (
    <HoldToChargeButton
      holdDuration={2000}
      label="Hold to Delete"
      holdingLabel="Holding..."
      completedLabel="Deleted Successfully!"
      variant="destructive"
    />
  );
};

export default HoldToChargeButtonDemo;

lib/utils.ts

import { config } from "@/config";
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs));
}

export function capitalize(str: string) {
  return str.charAt(0).toUpperCase() + str.slice(1);
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
type GroupBy<T, K extends keyof T> = Record<string, T[]>;

export function groupBy<T, K extends keyof T>(
  array: T[],
  key: K
): GroupBy<T, K> {
  return array.reduce((acc, item) => {
    const keyValue = String(item[key]);
    if (!acc[keyValue]) {
      acc[keyValue] = [];
    }
    acc[keyValue].push(item);
    return acc;
  }, {} as GroupBy<T, K>);
}

export function absoluteUrl(path: string) {
  return process.env.NODE_ENV === "development"
    ? `http://localhost:3000/${path}`
    : `${config.appUrl}/${path}`;
}

config.ts

export const config = {
  appUrl:
    process.env.NODE_ENV === "production"
      ? process.env.VERCEL_PROJECT_PRODUCTION_URL ??
      process.env.NEXT_PUBLIC_APP_URL!
      : "http://localhost:3000",
  social: {
    github: "https://github.com/shadcnspace/shadcnspace",
    twitter: "https://twitter.com/",
  },
};

demo.tsx

export { default as default } from "@/components/shadcn-space/button/button-28";

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;
    --text-sm: 0.875rem;
    --text-sm--line-height: calc(1.25 / 0.875);
    --font-weight-medium: 500;
    --tracking-wide: 0.025em;
    --animate-pulse: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
    --default-transition-duration: 150ms;
    --default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
    --default-font-family: var(--font-sans);
    --default-font-feature-settings: "cv02", "cv03", "cv04", "cv11";
    --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;
  }
  .top-0 {
    top: 0px;
  }
  .bottom-0 {
    bottom: 0px;
  }
  .left-0 {
    left: 0px;
  }
  .z-0 {
    z-index: 0;
  }
  .z-10 {
    z-index: 10;
  }
  .flex {
    display: flex;
  }
  .inline-flex {
    display: inline-flex;
  }
  .size-4 {
    width: calc(var(--spacing) * 4);
    height: calc(var(--spacing) * 4);
  }
  .min-h-10 {
    min-height: calc(var(--spacing) * 10);
  }
  .animate-pulse {
    animation: var(--animate-pulse);
  }
  .cursor-pointer {
    cursor: pointer;
  }
  .items-center {
    align-items: center;
  }
  .justify-center {
    justify-content: center;
  }
  .gap-2 {
    gap: calc(var(--spacing) * 2);
  }
  .gap-2\.5 {
    gap: calc(var(--spacing) * 2.5);
  }
  .overflow-hidden {
    overflow: hidden;
  }
  .rounded-xl {
    border-radius: calc(var(--radius) + 4px);
  }
  .border {
    border-style: var(--tw-border-style);
    border-width: 1px;
  }
  .border-border {
    border-color: var(--border);
  }
  .border-destructive {
    border-color: var(--destructive);
  }
  .border-primary {
    border-color: var(--primary);
  }
  .bg-background {
    background-color: var(--background);
  }
  .bg-destructive\/15 {
    background-color: var(--destructive);
    @supports (color: color-mix(in lab, red, red)) {
      background-color: color-mix(in oklab, var(--destructive) 15%, transparent);
    }
  }
  .bg-primary\/15 {
    background-color: var(--primary);
    @supports (color: color-mix(in lab, red, red)) {
      background-color: color-mix(in oklab, var(--primary) 15%, transparent);
    }
  }
  .stroke-\[2\.5\] {
    stroke-width: 2.5;
  }
  .px-5 {
    padding-inline: calc(var(--spacing) * 5);
  }
  .py-2\.5 {
    padding-block: calc(var(--spacing) * 2.5);
  }
  .text-sm {
    font-size: var(--text-sm);
    line-height: var(--tw-leading, var(--text-sm--line-height));
  }
  .font-medium {
    --tw-font-weight: var(--font-weight-medium);
    font-weight: var(--font-weight-medium);
  }
  .tracking-wide {
    --tw-tracking: var(--tracking-wide);
    letter-spacing: var(--tracking-wide);
  }
  .text-destructive {
    color: var(--destructive);
  }
  .text-foreground {
    color: var(--foreground);
  }
  .text-primary {
    color: var(--primary);
  }
  .transition-all {
    transition-property: all;
    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-75 {
    --tw-duration: 75ms;
    transition-duration: 75ms;
  }
  .duration-300 {
    --tw-duration: 300ms;
    transition-duration: 300ms;
  }
  .outline-none {
    --tw-outline-style: none;
    outline-style: none;
  }
  .select-none {
    -webkit-user-select: none;
    user-select: none;
  }
  @media (hover: hover) {
    .hover\:border-primary\/60:hover {
      border-color: var(--primary);
    }
    @supports (color: color-mix(in lab, red, red)) {
      .hover\:border-primary\/60:hover {
        border-color: color-mix(in oklab, var(--primary) 60%, transparent);
      }
    }
    .hover\:bg-muted:hover {
      background-color: var(--muted);
    }
  }
  .focus-visible\:ring-2:focus-visible {
    --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
    box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
  }
  .focus-visible\:ring-destructive\/30:focus-visible {
    --tw-ring-color: var(--destructive);
    @supports (color: color-mix(in lab, red, red)) {
      --tw-ring-color: color-mix(in oklab, var(--destructive) 30%, transparent);
    }
  }
  .focus-visible\:ring-primary\/30:focus-visible {
    --tw-ring-color: var(--primary);
    @supports (color: color-mix(in lab, red, red)) {
      --tw-ring-color: color-mix(in oklab, var(--primary) 30%, transparent);
    }
  }
  .focus-visible\:ring-offset-2:focus-visible {
    --tw-ring-offset-width: 2px;
    --tw-ring-offset-shadow: var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
  }
  @media (hover: hover) {
    .dark\:hover\:bg-input\/50:where(.dark, .dark *):hover {
      background-color: var(--input);
    }
    @supports (color: color-mix(in lab, red, red)) {
      .dark\:hover\:bg-input\/50:where(.dark, .dark *):hover {
        background-color: color-mix(in oklab, var(--input) 50%, transparent);
      }
    }
  }
}
: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;
}
:root {
  --radius: 0.5rem;
  --background: oklch(1 0 0);
  --foreground: oklch(0.1296 0.0274 261.69);
  --card: oklch(1 0 0);
  --card-foreground: oklch(0.1296 0.0274 261.69);
  --popover: oklch(1 0 0);
  --popover-foreground: oklch(0.1296 0.0274 261.69);
  --primary: oklch(0.1296 0.0274 261.69);
  --primary-foreground: oklch(0.9276 0.0058 264.53);
  --secondary: oklch(0.967 0.0029 264.54);
  --secondary-foreground: oklch(0.1296 0.0274 261.69);
  --muted: oklch(0.967 0.0029 264.54);
  --muted-foreground: oklch(0.4461 0.0263 256.8);
  --accent: oklch(0.967 0.0029 264.54);
  --accent-foreground: oklch(0.1296 0.0274 261.69);
  --destructive: oklch(0.6368 0.2078 25.33);
  --border: oklch(0.9276 0.0058 264.53);
  --input: oklch(0.922 0 0);
  --ring: oklch(0.7137 0.0192 261.32);
  --chart-1: oklch(63.681% 0.20784 25.315);
  --chart-2: oklch(78.453% 0.13254 181.95);
  --chart-3: oklch(62.31% 0.1881 259.83);
  --chart-4: oklch(87.896% 0.15335 91.611);
  --chart-5: oklch(75.763% 0.15899 55.922);
  --sidebar: oklch(0.985 0 0);
  --sidebar-foreground: oklch(0.1296 0.0274 261.69);
  --sidebar-primary: oklch(0.1296 0.0274 261.69);
  --sidebar-primary-foreground: oklch(0.9276 0.0058 264.53);
  --sidebar-accent: oklch(0.967 0.0029 264.54);
  --sidebar-accent-foreground: oklch(0.1296 0.0274 261.69);
  --sidebar-border: oklch(0.9276 0.0058 264.53);
  --sidebar-ring: oklch(0.7137 0.0192 261.32);
}
.dark {
  --background: oklch(0.1296 0.0274 261.69);
  --foreground: oklch(0.9276 0.0058 264.53);
  --card: oklch(0.1296 0.0274 261.69);
  --card-foreground: oklch(0.9276 0.0058 264.53);
  --popover: oklch(0.1296 0.0274 261.69);
  --popover-foreground: oklch(0.9276 0.0058 264.53);
  --primary: oklch(0.9276 0.0058 264.53);
  --primary-foreground: oklch(0.1296 0.0274 261.69);
  --secondary: oklch(0.3729 0.0306 259.73);
  --secondary-foreground: oklch(0.9276 0.0058 264.53);
  --muted: oklch(0.2781 0.0296 256.85);
  --muted-foreground: oklch(0.8717 0.0093 258.34);
  --accent: oklch(0.2781 0.0296 256.85);
  --accent-foreground: oklch(0.9276 0.0058 264.53);
  --destructive: oklch(0.6368 0.2078 25.33);
  --border: oklch(27.808% 0.02963 256.871 / 70%);
  --input: oklch(1 0 0 / 15%);
  --ring: oklch(0.4461 0.0263 256.8);
  --chart-1: oklch(63.681% 0.20784 25.315);
  --chart-2: oklch(78.453% 0.13254 181.95);
  --chart-3: oklch(62.31% 0.1881 259.83);
  --chart-4: oklch(87.896% 0.15335 91.611);
  --chart-5: oklch(75.763% 0.15899 55.922);
  --sidebar: oklch(0.205 0 0);
  --sidebar-foreground: oklch(0.9276 0.0058 264.53);
  --sidebar-primary: oklch(0.9276 0.0058 264.53);
  --sidebar-primary-foreground: oklch(0.1296 0.0274 261.69);
  --sidebar-accent: oklch(0.2781 0.0296 256.85);
  --sidebar-accent-foreground: oklch(0.9276 0.0058 264.53);
  --sidebar-border: var(--border);
  --sidebar-ring: oklch(0.4461 0.0263 256.8);
}
@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-duration {
  syntax: "*";
  inherits: false;
}
@property --tw-shadow {
  syntax: "*";
  inherits: false;
  initial-value: 0 0 #0000;
}
@property --tw-shadow-color {
  syntax: "*";
  inherits: false;
}
@property --tw-shadow-alpha {
  syntax: "<percentage>";
  inherits: false;
  initial-value: 100%;
}
@property --tw-inset-shadow {
  syntax: "*";
  inherits: false;
  initial-value: 0 0 #0000;
}
@property --tw-inset-shadow-color {
  syntax: "*";
  inherits: false;
}
@property --tw-inset-shadow-alpha {
  syntax: "<percentage>";
  inherits: false;
  initial-value: 100%;
}
@property --tw-ring-color {
  syntax: "*";
  inherits: false;
}
@property --tw-ring-shadow {
  syntax: "*";
  inherits: false;
  initial-value: 0 0 #0000;
}
@property --tw-inset-ring-color {
  syntax: "*";
  inherits: false;
}
@property --tw-inset-ring-shadow {
  syntax: "*";
  inherits: false;
  initial-value: 0 0 #0000;
}
@property --tw-ring-inset {
  syntax: "*";
  inherits: false;
}
@property --tw-ring-offset-width {
  syntax: "<length>";
  inherits: false;
  initial-value: 0px;
}
@property --tw-ring-offset-color {
  syntax: "*";
  inherits: false;
  initial-value: #fff;
}
@property --tw-ring-offset-shadow {
  syntax: "*";
  inherits: false;
  initial-value: 0 0 #0000;
}
@keyframes pulse {
  50% {
    opacity: 0.5;
  }
}
@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-duration: initial;
      --tw-shadow: 0 0 #0000;
      --tw-shadow-color: initial;
      --tw-shadow-alpha: 100%;
      --tw-inset-shadow: 0 0 #0000;
      --tw-inset-shadow-color: initial;
      --tw-inset-shadow-alpha: 100%;
      --tw-ring-color: initial;
      --tw-ring-shadow: 0 0 #0000;
      --tw-inset-ring-color: initial;
      --tw-inset-ring-shadow: 0 0 #0000;
      --tw-ring-inset: initial;
      --tw-ring-offset-width: 0px;
      --tw-ring-offset-color: #fff;
      --tw-ring-offset-shadow: 0 0 #0000;
    }
  }
}