Back to buttons
"use client";

import { Button as ButtonPrimitive } from "@base-ui/react/button";
import { cva, type VariantProps } from "class-variance-authority";
import { Loader2 } from "lucide-react";
import { AnimatePresence, motion, useReducedMotion } from "motion/react";
import {
  type AnchorHTMLAttributes,
  type ButtonHTMLAttributes,
  cloneElement,
  type FocusEventHandler,
  forwardRef,
  isValidElement,
  type KeyboardEvent,
  type KeyboardEventHandler,
  memo,
  type PointerEvent,
  type PointerEventHandler,
  type ReactNode,
  useCallback,
  useEffect,
  useLayoutEffect,
  useMemo,
  useRef,
  useState,
} from "react";

import { cn } from "@/lib/utils";

const componentThemeClassName =
  "[--ic-background:#ffffff] [--ic-foreground:#111111] [--ic-primary:#111111] [--ic-secondary:#646b75] [--ic-surface-border:#e9edf2] [--ic-border:#e3e7ec] [--ic-card:#ffffff] [--ic-card-foreground:#111111] [--ic-muted:#f5f7fa] [--ic-muted-foreground:#6d7480] [--ic-accent:#f3f5f8] [--color-accent:var(--ic-accent)] [--color-accent-foreground:var(--ic-accent-foreground)] [--ic-accent-foreground:#111111] [--ic-input:#e3e7ec] [--ic-ring:rgba(17,17,17,0.16)] [--ic-destructive:#dc2626] [--ic-paper:#fcfcfd] [--ic-popover-foreground:#111111] [--ic-brand:#0ea5e9] [--ic-brand-soft:#bae6fd] [--ic-shadow-soft:0_18px_38px_-24px_rgba(15,23,42,0.35)] [--ic-chart-1:oklch(0.52_0.19_254)] [--ic-chart-2:oklch(0.74_0.11_232)] [--ic-chart-3:oklch(0.42_0.16_262)] [--ic-chart-4:oklch(0.84_0.07_228)] [--ic-chart-5:oklch(0.62_0.14_240)] [--color-background:var(--ic-background)] [--color-foreground:var(--ic-foreground)] [--color-primary:var(--ic-primary)] [--color-secondary:var(--ic-secondary)] [--color-border:var(--ic-border)] [--color-card:var(--ic-card)] [--color-card-foreground:var(--ic-card-foreground)] [--color-muted:var(--ic-muted)] [--color-muted-foreground:var(--ic-muted-foreground)] [--color-accent:var(--ic-accent)] [--color-accent-foreground:var(--ic-accent-foreground)] [--color-input:var(--ic-input)] [--color-ring:var(--ic-ring)] [--color-destructive:var(--ic-destructive)] [--color-paper:var(--ic-paper)] [--color-popover-foreground:var(--ic-popover-foreground)] [--color-brand:var(--ic-brand)] [--color-brand-soft:var(--ic-brand-soft)] [--color-chart-1:var(--ic-chart-1)] [--color-chart-2:var(--ic-chart-2)] [--color-chart-3:var(--ic-chart-3)] [--color-chart-4:var(--ic-chart-4)] [--color-chart-5:var(--ic-chart-5)] dark:[--ic-background:#111111] dark:[--ic-foreground:#f6f3ec] dark:[--ic-primary:#f6f3ec] dark:[--ic-secondary:#cbc6bb] dark:[--ic-surface-border:#2a2a25] dark:[--ic-border:#2b2a25] dark:[--ic-card:#111111] dark:[--ic-card-foreground:#f6f3ec] dark:[--ic-muted:#171716] dark:[--ic-muted-foreground:#9a958a] dark:[--ic-accent:#1a1a18] [--color-accent:var(--ic-accent)] [--color-accent-foreground:var(--ic-accent-foreground)] dark:[--ic-accent-foreground:#f6f3ec] dark:[--ic-input:#2b2a25] dark:[--ic-ring:rgba(246,243,236,0.18)] dark:[--ic-destructive:#f87171] dark:[--ic-paper:#171716] dark:[--ic-popover-foreground:#f6f3ec] dark:[--ic-brand:#38bdf8] dark:[--ic-brand-soft:#0c4a6e] dark:[--ic-shadow-soft:0_20px_44px_-28px_rgba(0,0,0,0.6)] dark:[--ic-chart-1:oklch(0.68_0.17_250)] dark:[--ic-chart-2:oklch(0.82_0.09_225)] dark:[--ic-chart-3:oklch(0.58_0.15_260)] dark:[--ic-chart-4:oklch(0.75_0.12_235)] dark:[--ic-chart-5:oklch(0.88_0.06_220)]";

const buttonThemeClassName =
  "[--button-destructive:#dc2626] [--button-input:#e3e7ec] [--button-primary-foreground:#ffffff] [--button-ring:rgba(17,17,17,0.16)] [--button-ripple:color-mix(in_oklch,var(--foreground),transparent_52%)] [--button-secondary:#eceff3] [--button-secondary-foreground:#111111] dark:[--button-destructive:#f87171] dark:[--button-input:#2b2a25] dark:[--button-primary-foreground:#111111] dark:[--button-ring:rgba(246,243,236,0.18)] dark:[--button-ripple:color-mix(in_oklch,var(--foreground),transparent_30%)] dark:[--button-secondary:#2a2a27] dark:[--button-secondary-foreground:#f6f3ec]";

const buttonCornerClassName =
  "rounded-lg supports-[corner-shape:squircle]:corner-squircle supports-[corner-shape:squircle]:rounded-[11px]";

const buttonCornerXsClassName =
  "rounded-[min(var(--radius-md),10px)] supports-[corner-shape:squircle]:corner-squircle supports-[corner-shape:squircle]:rounded-[12px]";

const buttonCornerSmClassName =
  "rounded-[min(var(--radius-md),12px)] supports-[corner-shape:squircle]:corner-squircle supports-[corner-shape:squircle]:rounded-[14px]";

const buttonGroupCornerClassName =
  "in-data-[slot=button-group]:rounded-lg in-data-[slot=button-group]:supports-[corner-shape:squircle]:corner-squircle in-data-[slot=button-group]:supports-[corner-shape:squircle]:rounded-[11px]";

const buttonVariants = cva(
  cn(
    componentThemeClassName,
    buttonThemeClassName,
    buttonCornerClassName,
    "group/button relative isolate inline-flex shrink-0 cursor-pointer touch-manipulation select-none items-center justify-center overflow-hidden whitespace-nowrap border border-transparent bg-clip-border font-medium text-sm no-underline outline-none transition-[background-color,border-color,color,box-shadow] focus-visible:border-[color:var(--button-ring)] focus-visible:ring-3 focus-visible:ring-[color:color-mix(in_oklch,var(--button-ring),transparent_50%)] active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-[color:var(--button-destructive)] aria-invalid:ring-3 aria-invalid:ring-[color:color-mix(in_oklch,var(--button-destructive),transparent_80%)] dark:aria-invalid:border-[color:color-mix(in_oklch,var(--button-destructive),transparent_50%)] dark:aria-invalid:ring-[color:color-mix(in_oklch,var(--button-destructive),transparent_60%)] [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0"
  ),
  {
    variants: {
      variant: {
        default:
          "visited:!text-primary-foreground hover:!text-primary-foreground focus-visible:!text-primary-foreground active:!text-primary-foreground bg-primary text-primary-foreground [--button-ripple:color-mix(in_oklch,var(--primary-foreground),transparent_45%)] hover:bg-primary dark:[--button-ripple:color-mix(in_oklch,#111111,transparent_10%)] [&_svg]:text-primary-foreground",
        outline:
          "visited:!text-foreground hover:!text-foreground focus-visible:!text-foreground active:!text-foreground aria-expanded:!text-foreground border-border bg-background text-foreground [--button-ripple:color-mix(in_oklch,var(--foreground),transparent_52%)] hover:bg-accent/60 aria-expanded:bg-muted dark:border-[color:var(--button-input)] dark:bg-[color-mix(in_oklch,var(--button-input),transparent_70%)] dark:hover:bg-[color-mix(in_oklch,var(--button-input),transparent_50%)] dark:[--button-ripple:color-mix(in_oklch,var(--foreground),transparent_28%)] [&_svg]:text-foreground",
        secondary:
          "visited:!text-[color:var(--button-secondary-foreground)] hover:!text-[color:var(--button-secondary-foreground)] focus-visible:!text-[color:var(--button-secondary-foreground)] active:!text-[color:var(--button-secondary-foreground)] aria-expanded:!text-[color:var(--button-secondary-foreground)] bg-[color:var(--button-secondary)] text-[color:var(--button-secondary-foreground)] [--button-ripple:color-mix(in_oklch,var(--button-secondary-foreground),transparent_52%)] hover:bg-[color-mix(in_oklch,var(--button-secondary),var(--foreground)_7%)] aria-expanded:bg-[color:var(--button-secondary)] dark:hover:bg-[color-mix(in_oklch,var(--button-secondary),var(--foreground)_11%)] dark:[--button-ripple:color-mix(in_oklch,var(--button-secondary-foreground),transparent_20%)] [&_svg]:text-[color:var(--button-secondary-foreground)]",
        ghost:
          "visited:!text-foreground hover:!text-foreground focus-visible:!text-foreground active:!text-foreground aria-expanded:!text-foreground text-foreground [--button-ripple:color-mix(in_oklch,var(--foreground),transparent_52%)] hover:bg-accent/60 aria-expanded:bg-muted dark:hover:bg-muted/50 dark:[--button-ripple:color-mix(in_oklch,var(--foreground),transparent_28%)] [&_svg]:text-foreground",
        destructive:
          "visited:!text-[color:var(--button-destructive)] hover:!text-[color:var(--button-destructive)] focus-visible:!text-[color:var(--button-destructive)] active:!text-[color:var(--button-destructive)] bg-[color-mix(in_oklch,var(--button-destructive),transparent_90%)] text-[color:var(--button-destructive)] [--button-ripple:color-mix(in_oklch,var(--button-destructive),transparent_40%)] hover:bg-[color-mix(in_oklch,var(--button-destructive),transparent_80%)] focus-visible:border-[color:color-mix(in_oklch,var(--button-destructive),transparent_60%)] focus-visible:ring-[color:color-mix(in_oklch,var(--button-destructive),transparent_80%)] dark:bg-[color-mix(in_oklch,var(--button-destructive),transparent_80%)] dark:focus-visible:ring-[color:color-mix(in_oklch,var(--button-destructive),transparent_60%)] dark:hover:bg-[color-mix(in_oklch,var(--button-destructive),transparent_70%)] dark:[--button-ripple:color-mix(in_oklch,var(--button-destructive),transparent_20%)] [&_svg]:text-[color:var(--button-destructive)]",
        link: "",
      },
      linkUnderline: {
        motion: "",
        static: "",
      },
      size: {
        default:
          "h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
        xs: cn(
          "h-6 gap-1 px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
          buttonGroupCornerClassName,
          buttonCornerXsClassName
        ),
        sm: cn(
          "h-7 gap-1 px-2.5 text-[0.8rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",
          buttonGroupCornerClassName,
          buttonCornerSmClassName
        ),
        lg: "h-10 gap-1.5 px-4 text-base leading-5 has-data-[icon=inline-end]:pr-3.5 has-data-[icon=inline-start]:pl-3.5",
        icon: "size-8",
        "icon-xs": cn(
          "size-6 [&_svg:not([class*='size-'])]:size-3",
          buttonGroupCornerClassName,
          buttonCornerXsClassName
        ),
        "icon-sm": cn(
          "size-7",
          buttonGroupCornerClassName,
          buttonCornerSmClassName
        ),
        "icon-lg": "size-9",
      },
    },
    compoundVariants: [
      {
        variant: "link",
        linkUnderline: "motion",
        class:
          "visited:!text-foreground hover:!text-foreground focus-visible:!text-foreground px-0 text-foreground shadow-none active:translate-y-0 [&_[data-link-label]]:font-medium [&_[data-link-label]]:text-[1em]",
      },
      {
        variant: "link",
        linkUnderline: "static",
        class:
          "visited:!text-foreground hover:!text-foreground focus-visible:!text-foreground px-0 text-foreground underline-offset-4 hover:underline [&_[data-link-label]]:font-medium [&_[data-link-label]]:text-[1em]",
      },
      {
        variant: "link",
        size: "default",
        class: "h-8 px-0 text-sm leading-5",
      },
      {
        variant: "link",
        size: "lg",
        class: "h-10 px-0 text-base leading-5",
      },
      {
        variant: "link",
        size: "sm",
        class: "h-7 px-0 text-[0.8rem]",
      },
      {
        variant: "link",
        size: "xs",
        class: "h-6 px-0 text-xs",
      },
    ],
    defaultVariants: {
      variant: "default",
      size: "default",
      linkUnderline: "motion",
    },
  }
);

const linkUnderlineFillTransition = {
  type: "spring" as const,
  stiffness: 420,
  damping: 36,
  mass: 0.65,
};

const MAX_RIPPLES = 3;
const REL_TOKEN_SPLIT = /\s+/;

const LinkUnderlineLabel = memo(function LinkUnderlineLabel({
  children,
  enabled,
}: {
  children: ReactNode;
  enabled: boolean;
}) {
  if (!enabled) {
    return <>{children}</>;
  }

  return (
    <span className="relative inline-block pb-px font-medium text-[1em] leading-[inherit]">
      <span className="relative z-10" data-link-label>
        {children}
      </span>
      <span
        aria-hidden
        className="pointer-events-none absolute inset-x-0 bottom-0 h-px bg-muted-foreground/50"
      />
      <motion.span
        aria-hidden
        className="pointer-events-none absolute inset-x-0 bottom-0 h-px origin-left bg-foreground"
        transition={linkUnderlineFillTransition}
        variants={{
          rest: { scaleX: 0 },
          hover: { scaleX: 1 },
        }}
      />
    </span>
  );
});

const buttonContentClassName =
  "relative z-10 inline-flex items-center justify-center [gap:inherit] whitespace-nowrap text-inherit";

type Ripple = { id: string; x: number; y: number; size: number };

type AnchorHTMLAttributesForMotion = Omit<
  AnchorHTMLAttributes<HTMLAnchorElement>,
  | "onAnimationEnd"
  | "onAnimationIteration"
  | "onAnimationStart"
  | "onDrag"
  | "onDragEnd"
  | "onDragEnter"
  | "onDragExit"
  | "onDragLeave"
  | "onDragOver"
  | "onDragStart"
  | "onDrop"
>;

type ButtonHTMLAttributesForMotion = Omit<
  ButtonHTMLAttributes<HTMLButtonElement>,
  | "onAnimationEnd"
  | "onAnimationIteration"
  | "onAnimationStart"
  | "onDrag"
  | "onDragEnd"
  | "onDragEnter"
  | "onDragExit"
  | "onDragLeave"
  | "onDragOver"
  | "onDragStart"
  | "onDrop"
>;

type ButtonRenderProps = React.HTMLAttributes<HTMLButtonElement> & {
  className?: string;
  ref?: React.Ref<HTMLButtonElement>;
  style?: React.CSSProperties;
};

function setRef<T>(ref: React.Ref<T> | undefined, value: T) {
  if (typeof ref === "function") {
    ref(value);
    return;
  }

  if (ref) {
    (ref as React.MutableRefObject<T>).current = value;
  }
}

function useMeasure<T extends HTMLElement = HTMLElement>(
  enabled: boolean
): [(node: T | null) => void, { width: number; height: number }] {
  const [element, setElement] = useState<T | null>(null);
  const [bounds, setBounds] = useState({ width: 0, height: 0 });

  const ref = useCallback((node: T | null) => {
    setElement(node);
  }, []);

  useEffect(() => {
    if (!(enabled && element && typeof ResizeObserver !== "undefined")) {
      return;
    }

    const observer = new ResizeObserver(([entry]) => {
      setBounds((current) => {
        const next = {
          width: entry.contentRect.width,
          height: entry.contentRect.height,
        };

        if (current.width === next.width && current.height === next.height) {
          return current;
        }

        return next;
      });
    });

    observer.observe(element);
    return () => observer.disconnect();
  }, [element, enabled]);

  return [ref, enabled ? bounds : { width: 0, height: 0 }];
}

const MotionAnchor = motion.create("a");

const buttonTransition = {
  scale: {
    type: "spring",
    stiffness: 640,
    damping: 38,
    mass: 0.85,
  },
  width: {
    type: "spring",
    stiffness: 420,
    damping: 34,
    mass: 0.9,
  },
  opacity: {
    type: "spring",
    stiffness: 380,
    damping: 32,
    mass: 0.8,
  },
  y: { type: "spring", stiffness: 340, damping: 28, mass: 0.8 },
} as const;

const loadingIconTransition = {
  type: "spring" as const,
  stiffness: 400,
  damping: 30,
  mass: 0.76,
};

const loadingLabelExitTransition = {
  type: "spring" as const,
  stiffness: 420,
  damping: 38,
  mass: 0.84,
};

const loadingLabelEnterTransition = {
  type: "spring" as const,
  stiffness: 280,
  damping: 30,
  mass: 0.98,
};

const loadingLayoutTransition = {
  type: "spring" as const,
  stiffness: 360,
  damping: 36,
  mass: 0.94,
};

const loadingLabelVariants = {
  animate: {
    filter: "blur(0px)",
    opacity: 1,
    scale: 1,
    transition: loadingLabelEnterTransition,
    y: 0,
  },
  exit: {
    filter: "blur(3px)",
    opacity: 0,
    scale: 0.96,
    transition: loadingLabelExitTransition,
    y: -4,
  },
  initial: {
    filter: "blur(3px)",
    opacity: 0,
    scale: 0.96,
    y: 4,
  },
} as const;

function getLabelMotionKey(children: ReactNode, loading: boolean) {
  if (typeof children === "string" || typeof children === "number") {
    return `${loading ? "busy" : "idle"}:${children}`;
  }

  return loading ? "busy" : "idle";
}

function withLoadingSpin(node: ReactNode, spinning: boolean) {
  if (!(spinning && isValidElement<{ className?: string }>(node))) {
    return node;
  }

  return cloneElement(node, {
    className: cn(node.props.className, "animate-spin"),
  });
}

function getButtonAnimate(
  canAnimate: boolean,
  isPressed: boolean,
  animatedWidth: number | undefined,
  reduceMotion: boolean | null
) {
  const shouldReduceMotion = reduceMotion === true;
  const pressScale = canAnimate && isPressed && !shouldReduceMotion ? 0.96 : 1;

  if (animatedWidth) {
    return {
      scale: pressScale,
      width: animatedWidth,
    };
  }

  return {
    scale: pressScale,
  };
}

function omitLinkMotionConflicts<T extends Record<string, unknown>>(props: T) {
  const {
    onAnimationEnd: _onAnimationEnd,
    onAnimationIteration: _onAnimationIteration,
    onAnimationStart: _onAnimationStart,
    onDrag: _onDrag,
    onDragEnd: _onDragEnd,
    onDragEnter: _onDragEnter,
    onDragExit: _onDragExit,
    onDragLeave: _onDragLeave,
    onDragOver: _onDragOver,
    onDragStart: _onDragStart,
    onDrop: _onDrop,
    ...rest
  } = props;

  return rest;
}

function resolvePrimitiveButtonProps(buttonProps: ButtonRenderProps) {
  const {
    children: _buttonChildren,
    className: primitiveClassName,
    onAnimationEnd: _onAnimationEnd,
    onAnimationIteration: _onAnimationIteration,
    onAnimationStart: _onAnimationStart,
    onDrag: _onDrag,
    onDragEnd: _onDragEnd,
    onDragEnter: _onDragEnter,
    onDragExit: _onDragExit,
    onDragLeave: _onDragLeave,
    onDragOver: _onDragOver,
    onDragStart: _onDragStart,
    onDrop: _onDrop,
    ref: primitiveRef,
    style: primitiveStyle,
    ...resolvedButtonProps
  } = buttonProps;

  return {
    primitiveClassName,
    primitiveRef,
    primitiveStyle,
    resolvedButtonProps,
  };
}

function resolveAnchorRel(target?: string, rel?: string) {
  if (!target?.includes("_blank")) {
    return rel;
  }

  const tokens = new Set((rel ?? "").split(REL_TOKEN_SPLIT).filter(Boolean));
  tokens.add("noopener");
  tokens.add("noreferrer");
  return [...tokens].join(" ");
}

function isIconOnlySize(
  size:
    | "default"
    | "xs"
    | "sm"
    | "lg"
    | "icon"
    | "icon-xs"
    | "icon-sm"
    | "icon-lg"
    | null
    | undefined
) {
  return (
    size === "icon" ||
    size === "icon-xs" ||
    size === "icon-sm" ||
    size === "icon-lg"
  );
}

function useIconOnlyA11yWarning(
  size:
    | "default"
    | "xs"
    | "sm"
    | "lg"
    | "icon"
    | "icon-xs"
    | "icon-sm"
    | "icon-lg"
    | null
    | undefined,
  children: ReactNode,
  iconLabel: string | undefined,
  ariaLabel: string | undefined,
  loading: boolean
) {
  useEffect(() => {
    if (process.env.NODE_ENV !== "development") {
      return;
    }

    const hasVisibleLabel =
      children != null && children !== false && children !== "";
    const needsAccessibleName =
      isIconOnlySize(size) || (loading && !hasVisibleLabel);

    if (!needsAccessibleName || iconLabel || ariaLabel) {
      return;
    }

    console.warn(
      "[Button] Icon-only and loading-only buttons need `iconLabel` or `aria-label` for accessibility."
    );
  }, [ariaLabel, children, iconLabel, loading, size]);
}

function getIconIdleAnimate(showSpinner: boolean, noMotion: boolean) {
  if (noMotion) {
    return { opacity: showSpinner ? 0 : 1 };
  }

  return {
    filter: showSpinner ? "blur(3px)" : "blur(0px)",
    opacity: showSpinner ? 0 : 1,
    rotate: showSpinner ? -56 : 0,
    scale: showSpinner ? 0.82 : 1,
  };
}

function getIconSpinnerAnimate(showSpinner: boolean, noMotion: boolean) {
  if (noMotion) {
    return { opacity: showSpinner ? 1 : 0 };
  }

  return {
    filter: showSpinner ? "blur(0px)" : "blur(3px)",
    opacity: showSpinner ? 1 : 0,
    rotate: showSpinner ? 0 : 56,
    scale: showSpinner ? 1 : 0.82,
  };
}

function buildMotionSurfaceProps({
  canAnimate,
  isPressed,
  motionAnimate,
  reduceMotion,
  useLinkMotionUnderline,
  variant,
}: {
  canAnimate: boolean;
  isPressed: boolean;
  motionAnimate: ReturnType<typeof getButtonAnimate>;
  reduceMotion: boolean | null;
  useLinkMotionUnderline: boolean;
  variant: VariantProps<typeof buttonVariants>["variant"];
}) {
  const shouldReduceMotion = reduceMotion === true;
  const surfaceTransition = shouldReduceMotion
    ? { duration: 0 }
    : buttonTransition;
  const shared = {
    animate: motionAnimate,
    "data-pressed": isPressed ? "true" : "false",
    transition: surfaceTransition,
  };

  if (useLinkMotionUnderline) {
    return {
      ...shared,
      initial: shouldReduceMotion ? (false as const) : ("rest" as const),
      variants: shouldReduceMotion ? undefined : { rest: {}, hover: {} },
      whileHover: shouldReduceMotion ? undefined : ("hover" as const),
    };
  }

  const hoverLift =
    canAnimate && variant !== "link" && !shouldReduceMotion
      ? { y: -1 }
      : undefined;

  return {
    ...shared,
    initial: false as const,
    whileHover: hoverLift,
  };
}

const ButtonIconSlot = memo(function ButtonIconSlot({
  accessibleName,
  dataIcon,
  hasIcon,
  hasLabel,
  icon,
  iconTransition,
  layoutTransition,
  noMotion,
  showSpinner,
  spinner,
}: {
  accessibleName?: string;
  dataIcon: "inline-start" | "inline-end";
  hasIcon: boolean;
  hasLabel: boolean;
  icon?: ReactNode;
  iconTransition: { duration: number } | typeof loadingIconTransition;
  layoutTransition: { duration: number } | typeof loadingLayoutTransition;
  noMotion: boolean;
  showSpinner: boolean;
  spinner: ReactNode;
}) {
  return (
    <motion.span
      aria-hidden={hasLabel || accessibleName ? true : undefined}
      className="relative inline-flex size-[1em] shrink-0 items-center justify-center"
      data-icon={dataIcon}
      layout={noMotion ? false : "position"}
      transition={layoutTransition}
    >
      {hasIcon ? (
        <motion.span
          animate={getIconIdleAnimate(showSpinner, noMotion)}
          className="absolute inset-0 inline-flex items-center justify-center"
          initial={false}
          transition={iconTransition}
        >
          {icon}
        </motion.span>
      ) : null}
      <motion.span
        animate={getIconSpinnerAnimate(showSpinner, noMotion)}
        className="absolute inset-0 inline-flex items-center justify-center"
        initial={false}
        transition={iconTransition}
      >
        {withLoadingSpin(spinner, showSpinner && !noMotion)}
      </motion.span>
    </motion.span>
  );
});

const ButtonLoadingLabel = memo(function ButtonLoadingLabel({
  children,
  layoutTransition,
  linkUnderlineEnabled,
  loading,
  noMotion,
}: {
  children: ReactNode;
  layoutTransition: { duration: number } | typeof loadingLayoutTransition;
  linkUnderlineEnabled: boolean;
  loading: boolean;
  noMotion: boolean;
}) {
  return (
    <motion.span
      className="relative inline-grid min-w-0 items-center leading-[inherit] [grid-template-columns:max-content] [grid-template-rows:minmax(0,1.25em)]"
      layout="position"
      transition={layoutTransition}
    >
      <AnimatePresence initial={false} mode="popLayout">
        <motion.span
          animate={noMotion ? { opacity: 1 } : "animate"}
          className="col-start-1 row-start-1 inline-flex min-w-0 items-center whitespace-nowrap"
          exit={noMotion ? { opacity: 0 } : "exit"}
          initial={noMotion ? false : "initial"}
          key={getLabelMotionKey(children, loading)}
          layout="position"
          transition={{ layout: layoutTransition }}
          variants={noMotion ? undefined : loadingLabelVariants}
        >
          <LinkUnderlineLabel enabled={linkUnderlineEnabled}>
            {children}
          </LinkUnderlineLabel>
        </motion.span>
      </AnimatePresence>
    </motion.span>
  );
});

const ButtonSpinnerPresence = memo(function ButtonSpinnerPresence({
  iconSlot,
  layoutTransition,
  noMotion,
  visible,
}: {
  iconSlot: ReactNode;
  layoutTransition: { duration: number } | typeof loadingLayoutTransition;
  noMotion: boolean;
  visible: boolean;
}) {
  return (
    <AnimatePresence initial={false} mode="popLayout">
      {visible ? (
        <motion.span
          animate={noMotion ? { opacity: 1 } : { opacity: 1, scale: 1 }}
          className="inline-flex shrink-0"
          exit={
            noMotion
              ? { opacity: 0 }
              : {
                  filter: "blur(2px)",
                  opacity: 0,
                  scale: 0.86,
                  transition: loadingLabelExitTransition,
                }
          }
          initial={noMotion ? false : { opacity: 0, scale: 0.86 }}
          key="loading-spinner"
          layout="position"
          transition={layoutTransition}
        >
          {iconSlot}
        </motion.span>
      ) : null}
    </AnimatePresence>
  );
});

const defaultLoadingIcon = <Loader2 aria-hidden strokeWidth={2.25} />;

const ButtonLoadingContent = memo(function ButtonLoadingContent({
  accessibleName,
  children,
  contentRef,
  icon,
  iconPosition,
  linkUnderlineEnabled,
  loading,
  loadingIcon,
  reduceMotion,
}: {
  accessibleName?: string;
  children: ReactNode;
  contentRef?: React.Ref<HTMLSpanElement>;
  icon?: ReactNode;
  iconPosition: "start" | "end";
  linkUnderlineEnabled: boolean;
  loading: boolean;
  loadingIcon?: ReactNode;
  reduceMotion: boolean | null;
}) {
  const hasIcon = Boolean(icon);
  const hasLabel = children != null && children !== false && children !== "";
  const noMotion = reduceMotion === true;
  const iconTransition = noMotion ? { duration: 0 } : loadingIconTransition;
  const layoutTransition = noMotion ? { duration: 0 } : loadingLayoutTransition;
  const spinner = loadingIcon ?? defaultLoadingIcon;

  const iconAtStart = loading || (hasIcon && iconPosition === "start");
  const iconAtEnd = hasIcon && !loading && iconPosition === "end";
  const spinnerOnlyAtStart = loading && !hasIcon;

  const iconSlotProps = {
    accessibleName,
    hasLabel,
    iconTransition,
    layoutTransition,
    noMotion,
    spinner,
  };

  return (
    <motion.span
      animate={loading && !noMotion ? { opacity: 0.96 } : { opacity: 1 }}
      className={buttonContentClassName}
      initial={false}
      layout={!noMotion}
      ref={contentRef}
      transition={layoutTransition}
    >
      <ButtonSpinnerPresence
        iconSlot={
          <ButtonIconSlot
            {...iconSlotProps}
            dataIcon="inline-start"
            hasIcon={hasIcon}
            icon={icon}
            showSpinner
          />
        }
        layoutTransition={layoutTransition}
        noMotion={noMotion}
        visible={spinnerOnlyAtStart}
      />
      {hasIcon && iconAtStart ? (
        <ButtonIconSlot
          {...iconSlotProps}
          dataIcon="inline-start"
          hasIcon={hasIcon}
          icon={icon}
          showSpinner={loading}
        />
      ) : null}
      {hasLabel ? (
        <ButtonLoadingLabel
          layoutTransition={layoutTransition}
          linkUnderlineEnabled={linkUnderlineEnabled}
          loading={loading}
          noMotion={noMotion}
        >
          {children}
        </ButtonLoadingLabel>
      ) : null}
      {hasIcon && iconAtEnd ? (
        <ButtonIconSlot
          {...iconSlotProps}
          dataIcon="inline-end"
          hasIcon={hasIcon}
          icon={icon}
          showSpinner={loading}
        />
      ) : null}
    </motion.span>
  );
});

const rippleTransition = {
  duration: 0.65,
  ease: [0.22, 1, 0.36, 1] as const,
  opacity: {
    duration: 0.65,
    ease: [0.4, 0, 0.2, 1] as const,
    times: [0, 0.2, 1] as const,
  },
} as const;

const RippleLayer = memo(function RippleLayer({
  onRippleComplete,
  ripples,
}: {
  ripples: Ripple[];
  onRippleComplete: (id: string) => void;
}) {
  return (
    <span aria-hidden className="pointer-events-none absolute inset-0 z-[1]">
      {ripples.map((ripple) => (
        <motion.span
          animate={{ opacity: [0.36, 0.2, 0], scale: 1 }}
          className="pointer-events-none absolute -translate-x-1/2 -translate-y-1/2 rounded-full bg-[var(--button-ripple)]"
          initial={{ opacity: 0.36, scale: 0 }}
          key={ripple.id}
          onAnimationComplete={() => onRippleComplete(ripple.id)}
          style={{
            height: ripple.size,
            left: ripple.x,
            top: ripple.y,
            width: ripple.size,
          }}
          transition={rippleTransition}
        />
      ))}
    </span>
  );
});

type ButtonInteractionElement = HTMLButtonElement | HTMLAnchorElement;

type ButtonInteractionHandlers = {
  onBlur?: FocusEventHandler<ButtonInteractionElement>;
  onKeyDown?: KeyboardEventHandler<ButtonInteractionElement>;
  onKeyUp?: KeyboardEventHandler<ButtonInteractionElement>;
  onPointerCancel?: PointerEventHandler<ButtonInteractionElement>;
  onPointerDown?: PointerEventHandler<ButtonInteractionElement>;
  onPointerLeave?: PointerEventHandler<ButtonInteractionElement>;
  onPointerUp?: PointerEventHandler<ButtonInteractionElement>;
};

export type ButtonProps = Omit<
  ButtonHTMLAttributesForMotion,
  keyof ButtonInteractionHandlers
> &
  ButtonInteractionHandlers &
  VariantProps<typeof buttonVariants> & {
    animateSize?: boolean;
    children?: ReactNode;
    disableRipple?: boolean;
    href?: string;
    icon?: ReactNode;
    iconLabel?: string;
    iconPosition?: "start" | "end";
    loading?: boolean;
    loadingIcon?: ReactNode;
  } & Pick<
    AnchorHTMLAttributes<HTMLAnchorElement>,
    | "download"
    | "hrefLang"
    | "media"
    | "ping"
    | "referrerPolicy"
    | "rel"
    | "target"
  >;

const ButtonInner = forwardRef<
  HTMLButtonElement | HTMLAnchorElement,
  ButtonProps
>(
  (
    {
      animateSize = false,
      "aria-label": ariaLabel,
      children,
      className,
      disabled,
      disableRipple = false,
      href,
      icon,
      iconLabel,
      iconPosition = "start",
      linkUnderline,
      loading = false,
      loadingIcon,
      onBlur,
      onKeyDown,
      onKeyUp,
      onPointerCancel,
      onPointerDown,
      onPointerLeave,
      onPointerUp,
      rel,
      size,
      style,
      target,
      type = "button",
      variant,
      ...props
    },
    ref
  ) => {
    const isLocked = Boolean(disabled || loading);
    const accessibleName = iconLabel ?? ariaLabel;
    const reduceMotion = useReducedMotion();

    useIconOnlyA11yWarning(size, children, iconLabel, ariaLabel, loading);
    const buttonNodeRef = useRef<HTMLButtonElement | HTMLAnchorElement | null>(
      null
    );
    const rippleIdRef = useRef(0);
    const wasLoadingRef = useRef(loading);
    const [horizontalInset, setHorizontalInset] = useState(0);
    const [isPressed, setIsPressed] = useState(false);
    const [ripples, setRipples] = useState<Ripple[]>([]);
    const shouldAnimateSize = animateSize && style?.width == null;
    const [contentRef, bounds] = useMeasure<HTMLSpanElement>(shouldAnimateSize);
    const lastContentWidthRef = useRef(0);
    const resolvedLinkUnderline =
      variant === "link" ? (linkUnderline ?? "motion") : undefined;
    const useLinkMotionUnderline = resolvedLinkUnderline === "motion";

    const canAnimate = !isLocked && reduceMotion !== true;
    const allowsRipple =
      !(disableRipple || loading) &&
      variant !== "link" &&
      !isLocked &&
      reduceMotion !== true;
    useLayoutEffect(() => {
      if (bounds.width > 0) {
        lastContentWidthRef.current = bounds.width;
      }
    }, [bounds.width]);

    const contentWidth =
      bounds.width > 0 ? bounds.width : lastContentWidthRef.current;
    const animatedWidth =
      shouldAnimateSize && contentWidth > 0
        ? Math.ceil(contentWidth + horizontalInset)
        : undefined;

    const setButtonRef = useCallback(
      (node: HTMLButtonElement | HTMLAnchorElement | null) => {
        buttonNodeRef.current = node;
        setRef(ref, node);
      },
      [ref]
    );

    const measureHorizontalInset = useCallback(
      (measuredContentWidth: number) => {
        if (!(measuredContentWidth > 0)) {
          return;
        }

        const buttonNode = buttonNodeRef.current;
        if (!(buttonNode && animateSize)) {
          return;
        }

        const styles = window.getComputedStyle(buttonNode);
        const next =
          (Number.parseFloat(styles.borderLeftWidth) || 0) +
          (Number.parseFloat(styles.borderRightWidth) || 0) +
          (Number.parseFloat(styles.paddingLeft) || 0) +
          (Number.parseFloat(styles.paddingRight) || 0);

        setHorizontalInset((current) => (current === next ? current : next));
      },
      [animateSize]
    );

    useLayoutEffect(() => {
      measureHorizontalInset(contentWidth);
    }, [contentWidth, measureHorizontalInset]);

    const handleRippleComplete = useCallback((id: string) => {
      setRipples((current) => {
        if (!current.some((item) => item.id === id)) {
          return current;
        }

        return current.filter((item) => item.id !== id);
      });
    }, []);

    useEffect(() => {
      if (isLocked) {
        setIsPressed(false);
      }
    }, [isLocked]);

    useLayoutEffect(() => {
      const wasLoading = wasLoadingRef.current;
      if (loading || wasLoading) {
        setRipples([]);
      }
      if (loading) {
        setIsPressed(false);
      }
      wasLoadingRef.current = loading;
    }, [loading]);

    const handlePointerDown = (
      e: PointerEvent<HTMLButtonElement | HTMLAnchorElement>
    ) => {
      onPointerDown?.(e);

      if (e.defaultPrevented || isLocked) {
        return;
      }

      setIsPressed(true);

      if (!(allowsRipple && e.button === 0)) {
        return;
      }

      if (e.pointerType === "mouse" && e.buttons !== 1) {
        return;
      }

      const rect = e.currentTarget.getBoundingClientRect();
      const x = e.clientX - rect.left;
      const y = e.clientY - rect.top;
      const rippleSize =
        2 *
        Math.max(
          Math.hypot(x, y),
          Math.hypot(rect.width - x, y),
          Math.hypot(x, rect.height - y),
          Math.hypot(rect.width - x, rect.height - y)
        );
      rippleIdRef.current += 1;
      const id = `ripple-${rippleIdRef.current}`;

      setRipples((current) => {
        const next = [...current, { id, x, y, size: rippleSize }];
        return next.length > MAX_RIPPLES
          ? next.slice(next.length - MAX_RIPPLES)
          : next;
      });
    };

    const handlePointerUp = (
      e: PointerEvent<HTMLButtonElement | HTMLAnchorElement>
    ) => {
      onPointerUp?.(e);
      setIsPressed(false);
    };

    const handlePointerLeave = (
      e: PointerEvent<HTMLButtonElement | HTMLAnchorElement>
    ) => {
      onPointerLeave?.(e);
      setIsPressed(false);
    };

    const handlePointerCancel = (
      e: PointerEvent<HTMLButtonElement | HTMLAnchorElement>
    ) => {
      onPointerCancel?.(e);
      setIsPressed(false);
    };

    const handleKeyDown = (e: KeyboardEvent<ButtonInteractionElement>) => {
      onKeyDown?.(e);

      if (
        e.defaultPrevented ||
        isLocked ||
        e.repeat ||
        (e.key !== " " && e.key !== "Enter")
      ) {
        return;
      }

      setIsPressed(true);
    };

    const handleKeyUp = (e: KeyboardEvent<ButtonInteractionElement>) => {
      onKeyUp?.(e);

      if (e.key === " " || e.key === "Enter") {
        setIsPressed(false);
      }
    };

    const rootClassName = cn(
      buttonVariants({
        className,
        linkUnderline: resolvedLinkUnderline,
        size,
        variant,
      }),
      isLocked && "pointer-events-none",
      disabled && "opacity-50"
    );

    const buildButtonSurface = (labelContent: ReactNode) => (
      <>
        {allowsRipple ? (
          <RippleLayer
            onRippleComplete={handleRippleComplete}
            ripples={ripples}
          />
        ) : null}
        <ButtonLoadingContent
          accessibleName={accessibleName}
          contentRef={shouldAnimateSize ? contentRef : undefined}
          icon={icon}
          iconPosition={iconPosition}
          linkUnderlineEnabled={resolvedLinkUnderline === "motion"}
          loading={loading}
          loadingIcon={loadingIcon}
          reduceMotion={reduceMotion}
        >
          {labelContent}
        </ButtonLoadingContent>
      </>
    );

    const buttonSurface = buildButtonSurface(children);

    const motionAnimate = useMemo(
      () =>
        getButtonAnimate(canAnimate, isPressed, animatedWidth, reduceMotion),
      [animatedWidth, canAnimate, isPressed, reduceMotion]
    );

    const motionSurfaceProps = useMemo(
      () =>
        buildMotionSurfaceProps({
          canAnimate,
          isPressed,
          motionAnimate,
          reduceMotion,
          useLinkMotionUnderline,
          variant,
        }),
      [
        canAnimate,
        isPressed,
        motionAnimate,
        reduceMotion,
        useLinkMotionUnderline,
        variant,
      ]
    );

    const sharedRootProps = {
      "aria-busy": loading || undefined,
      "aria-label": accessibleName,
      className: rootClassName,
      "data-slot": "button" as const,
    };

    if (href?.trim()) {
      const safeHref = href.trim();
      const {
        onClick,
        type: _type,
        ...anchorProps
      } = omitLinkMotionConflicts(props as AnchorHTMLAttributesForMotion);
      const safeRel = resolveAnchorRel(target, rel);

      return (
        <MotionAnchor
          {...motionSurfaceProps}
          {...anchorProps}
          {...sharedRootProps}
          aria-disabled={isLocked || undefined}
          href={safeHref}
          onBlur={(e) => {
            onBlur?.(e);
            setIsPressed(false);
          }}
          onClick={(e) => {
            if (isLocked) {
              e.preventDefault();
            }
            onClick?.(e as React.MouseEvent<HTMLAnchorElement>);
          }}
          onKeyDown={(e) => {
            onKeyDown?.(e);

            if (
              e.defaultPrevented ||
              isLocked ||
              e.repeat ||
              e.key !== "Enter"
            ) {
              return;
            }

            setIsPressed(true);
          }}
          onKeyUp={(e) => {
            onKeyUp?.(e);

            if (e.key === "Enter") {
              setIsPressed(false);
            }
          }}
          onPointerCancel={handlePointerCancel}
          onPointerDown={handlePointerDown}
          onPointerLeave={handlePointerLeave}
          onPointerUp={handlePointerUp}
          ref={setButtonRef}
          rel={safeRel}
          style={style}
          tabIndex={isLocked ? -1 : undefined}
          target={target}
        >
          {buttonSurface}
        </MotionAnchor>
      );
    }

    return (
      <ButtonPrimitive
        {...sharedRootProps}
        disabled={isLocked}
        onBlur={(e) => {
          onBlur?.(e);
          setIsPressed(false);
        }}
        onKeyDown={handleKeyDown}
        onKeyUp={handleKeyUp}
        onPointerCancel={handlePointerCancel}
        onPointerDown={handlePointerDown}
        onPointerLeave={handlePointerLeave}
        onPointerUp={handlePointerUp}
        render={(buttonProps) => {
          const {
            primitiveClassName,
            primitiveRef,
            primitiveStyle,
            resolvedButtonProps,
          } = resolvePrimitiveButtonProps(buttonProps);

          return (
            <motion.button
              {...resolvedButtonProps}
              {...motionSurfaceProps}
              className={primitiveClassName}
              ref={(node) => {
                setButtonRef(node);
                setRef(primitiveRef, node);
              }}
              style={primitiveStyle}
              type={type}
            >
              {buttonSurface}
            </motion.button>
          );
        }}
        style={style}
        type={type}
        {...props}
      />
    );
  }
);

ButtonInner.displayName = "Button";

const Button = memo(ButtonInner);

export { Button, buttonVariants };

Base Button

By Edwin Vakayil

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

About Base Button

Base Button is an animated React and Tailwind CSS button by Edwin Vakayil. 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

import-examples/base-button.tsx

import {Button} from "@/registry/b-button";export default function Demo(){return <div className="flex flex-wrap gap-3"><Button>Continue</Button><Button variant="outline">Outline</Button><Button loading>Saving</Button></div>}

registry/b-button.tsx

"use client";

import { Button as ButtonPrimitive } from "@base-ui/react/button";
import { cva, type VariantProps } from "class-variance-authority";
import { Loader2 } from "lucide-react";
import { AnimatePresence, motion, useReducedMotion } from "motion/react";
import {
  type AnchorHTMLAttributes,
  type ButtonHTMLAttributes,
  cloneElement,
  type FocusEventHandler,
  forwardRef,
  isValidElement,
  type KeyboardEvent,
  type KeyboardEventHandler,
  memo,
  type PointerEvent,
  type PointerEventHandler,
  type ReactNode,
  useCallback,
  useEffect,
  useLayoutEffect,
  useMemo,
  useRef,
  useState,
} from "react";

import { cn } from "@/lib/utils";

const componentThemeClassName =
  "[--ic-background:#ffffff] [--ic-foreground:#111111] [--ic-primary:#111111] [--ic-secondary:#646b75] [--ic-surface-border:#e9edf2] [--ic-border:#e3e7ec] [--ic-card:#ffffff] [--ic-card-foreground:#111111] [--ic-muted:#f5f7fa] [--ic-muted-foreground:#6d7480] [--ic-accent:#f3f5f8] [--color-accent:var(--ic-accent)] [--color-accent-foreground:var(--ic-accent-foreground)] [--ic-accent-foreground:#111111] [--ic-input:#e3e7ec] [--ic-ring:rgba(17,17,17,0.16)] [--ic-destructive:#dc2626] [--ic-paper:#fcfcfd] [--ic-popover-foreground:#111111] [--ic-brand:#0ea5e9] [--ic-brand-soft:#bae6fd] [--ic-shadow-soft:0_18px_38px_-24px_rgba(15,23,42,0.35)] [--ic-chart-1:oklch(0.52_0.19_254)] [--ic-chart-2:oklch(0.74_0.11_232)] [--ic-chart-3:oklch(0.42_0.16_262)] [--ic-chart-4:oklch(0.84_0.07_228)] [--ic-chart-5:oklch(0.62_0.14_240)] [--color-background:var(--ic-background)] [--color-foreground:var(--ic-foreground)] [--color-primary:var(--ic-primary)] [--color-secondary:var(--ic-secondary)] [--color-border:var(--ic-border)] [--color-card:var(--ic-card)] [--color-card-foreground:var(--ic-card-foreground)] [--color-muted:var(--ic-muted)] [--color-muted-foreground:var(--ic-muted-foreground)] [--color-accent:var(--ic-accent)] [--color-accent-foreground:var(--ic-accent-foreground)] [--color-input:var(--ic-input)] [--color-ring:var(--ic-ring)] [--color-destructive:var(--ic-destructive)] [--color-paper:var(--ic-paper)] [--color-popover-foreground:var(--ic-popover-foreground)] [--color-brand:var(--ic-brand)] [--color-brand-soft:var(--ic-brand-soft)] [--color-chart-1:var(--ic-chart-1)] [--color-chart-2:var(--ic-chart-2)] [--color-chart-3:var(--ic-chart-3)] [--color-chart-4:var(--ic-chart-4)] [--color-chart-5:var(--ic-chart-5)] dark:[--ic-background:#111111] dark:[--ic-foreground:#f6f3ec] dark:[--ic-primary:#f6f3ec] dark:[--ic-secondary:#cbc6bb] dark:[--ic-surface-border:#2a2a25] dark:[--ic-border:#2b2a25] dark:[--ic-card:#111111] dark:[--ic-card-foreground:#f6f3ec] dark:[--ic-muted:#171716] dark:[--ic-muted-foreground:#9a958a] dark:[--ic-accent:#1a1a18] [--color-accent:var(--ic-accent)] [--color-accent-foreground:var(--ic-accent-foreground)] dark:[--ic-accent-foreground:#f6f3ec] dark:[--ic-input:#2b2a25] dark:[--ic-ring:rgba(246,243,236,0.18)] dark:[--ic-destructive:#f87171] dark:[--ic-paper:#171716] dark:[--ic-popover-foreground:#f6f3ec] dark:[--ic-brand:#38bdf8] dark:[--ic-brand-soft:#0c4a6e] dark:[--ic-shadow-soft:0_20px_44px_-28px_rgba(0,0,0,0.6)] dark:[--ic-chart-1:oklch(0.68_0.17_250)] dark:[--ic-chart-2:oklch(0.82_0.09_225)] dark:[--ic-chart-3:oklch(0.58_0.15_260)] dark:[--ic-chart-4:oklch(0.75_0.12_235)] dark:[--ic-chart-5:oklch(0.88_0.06_220)]";

const buttonThemeClassName =
  "[--button-destructive:#dc2626] [--button-input:#e3e7ec] [--button-primary-foreground:#ffffff] [--button-ring:rgba(17,17,17,0.16)] [--button-ripple:color-mix(in_oklch,var(--foreground),transparent_52%)] [--button-secondary:#eceff3] [--button-secondary-foreground:#111111] dark:[--button-destructive:#f87171] dark:[--button-input:#2b2a25] dark:[--button-primary-foreground:#111111] dark:[--button-ring:rgba(246,243,236,0.18)] dark:[--button-ripple:color-mix(in_oklch,var(--foreground),transparent_30%)] dark:[--button-secondary:#2a2a27] dark:[--button-secondary-foreground:#f6f3ec]";

const buttonCornerClassName =
  "rounded-lg supports-[corner-shape:squircle]:corner-squircle supports-[corner-shape:squircle]:rounded-[11px]";

const buttonCornerXsClassName =
  "rounded-[min(var(--radius-md),10px)] supports-[corner-shape:squircle]:corner-squircle supports-[corner-shape:squircle]:rounded-[12px]";

const buttonCornerSmClassName =
  "rounded-[min(var(--radius-md),12px)] supports-[corner-shape:squircle]:corner-squircle supports-[corner-shape:squircle]:rounded-[14px]";

const buttonGroupCornerClassName =
  "in-data-[slot=button-group]:rounded-lg in-data-[slot=button-group]:supports-[corner-shape:squircle]:corner-squircle in-data-[slot=button-group]:supports-[corner-shape:squircle]:rounded-[11px]";

const buttonVariants = cva(
  cn(
    componentThemeClassName,
    buttonThemeClassName,
    buttonCornerClassName,
    "group/button relative isolate inline-flex shrink-0 cursor-pointer touch-manipulation select-none items-center justify-center overflow-hidden whitespace-nowrap border border-transparent bg-clip-border font-medium text-sm no-underline outline-none transition-[background-color,border-color,color,box-shadow] focus-visible:border-[color:var(--button-ring)] focus-visible:ring-3 focus-visible:ring-[color:color-mix(in_oklch,var(--button-ring),transparent_50%)] active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-[color:var(--button-destructive)] aria-invalid:ring-3 aria-invalid:ring-[color:color-mix(in_oklch,var(--button-destructive),transparent_80%)] dark:aria-invalid:border-[color:color-mix(in_oklch,var(--button-destructive),transparent_50%)] dark:aria-invalid:ring-[color:color-mix(in_oklch,var(--button-destructive),transparent_60%)] [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0"
  ),
  {
    variants: {
      variant: {
        default:
          "visited:!text-primary-foreground hover:!text-primary-foreground focus-visible:!text-primary-foreground active:!text-primary-foreground bg-primary text-primary-foreground [--button-ripple:color-mix(in_oklch,var(--primary-foreground),transparent_45%)] hover:bg-primary dark:[--button-ripple:color-mix(in_oklch,#111111,transparent_10%)] [&_svg]:text-primary-foreground",
        outline:
          "visited:!text-foreground hover:!text-foreground focus-visible:!text-foreground active:!text-foreground aria-expanded:!text-foreground border-border bg-background text-foreground [--button-ripple:color-mix(in_oklch,var(--foreground),transparent_52%)] hover:bg-accent/60 aria-expanded:bg-muted dark:border-[color:var(--button-input)] dark:bg-[color-mix(in_oklch,var(--button-input),transparent_70%)] dark:hover:bg-[color-mix(in_oklch,var(--button-input),transparent_50%)] dark:[--button-ripple:color-mix(in_oklch,var(--foreground),transparent_28%)] [&_svg]:text-foreground",
        secondary:
          "visited:!text-[color:var(--button-secondary-foreground)] hover:!text-[color:var(--button-secondary-foreground)] focus-visible:!text-[color:var(--button-secondary-foreground)] active:!text-[color:var(--button-secondary-foreground)] aria-expanded:!text-[color:var(--button-secondary-foreground)] bg-[color:var(--button-secondary)] text-[color:var(--button-secondary-foreground)] [--button-ripple:color-mix(in_oklch,var(--button-secondary-foreground),transparent_52%)] hover:bg-[color-mix(in_oklch,var(--button-secondary),var(--foreground)_7%)] aria-expanded:bg-[color:var(--button-secondary)] dark:hover:bg-[color-mix(in_oklch,var(--button-secondary),var(--foreground)_11%)] dark:[--button-ripple:color-mix(in_oklch,var(--button-secondary-foreground),transparent_20%)] [&_svg]:text-[color:var(--button-secondary-foreground)]",
        ghost:
          "visited:!text-foreground hover:!text-foreground focus-visible:!text-foreground active:!text-foreground aria-expanded:!text-foreground text-foreground [--button-ripple:color-mix(in_oklch,var(--foreground),transparent_52%)] hover:bg-accent/60 aria-expanded:bg-muted dark:hover:bg-muted/50 dark:[--button-ripple:color-mix(in_oklch,var(--foreground),transparent_28%)] [&_svg]:text-foreground",
        destructive:
          "visited:!text-[color:var(--button-destructive)] hover:!text-[color:var(--button-destructive)] focus-visible:!text-[color:var(--button-destructive)] active:!text-[color:var(--button-destructive)] bg-[color-mix(in_oklch,var(--button-destructive),transparent_90%)] text-[color:var(--button-destructive)] [--button-ripple:color-mix(in_oklch,var(--button-destructive),transparent_40%)] hover:bg-[color-mix(in_oklch,var(--button-destructive),transparent_80%)] focus-visible:border-[color:color-mix(in_oklch,var(--button-destructive),transparent_60%)] focus-visible:ring-[color:color-mix(in_oklch,var(--button-destructive),transparent_80%)] dark:bg-[color-mix(in_oklch,var(--button-destructive),transparent_80%)] dark:focus-visible:ring-[color:color-mix(in_oklch,var(--button-destructive),transparent_60%)] dark:hover:bg-[color-mix(in_oklch,var(--button-destructive),transparent_70%)] dark:[--button-ripple:color-mix(in_oklch,var(--button-destructive),transparent_20%)] [&_svg]:text-[color:var(--button-destructive)]",
        link: "",
      },
      linkUnderline: {
        motion: "",
        static: "",
      },
      size: {
        default:
          "h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
        xs: cn(
          "h-6 gap-1 px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
          buttonGroupCornerClassName,
          buttonCornerXsClassName
        ),
        sm: cn(
          "h-7 gap-1 px-2.5 text-[0.8rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",
          buttonGroupCornerClassName,
          buttonCornerSmClassName
        ),
        lg: "h-10 gap-1.5 px-4 text-base leading-5 has-data-[icon=inline-end]:pr-3.5 has-data-[icon=inline-start]:pl-3.5",
        icon: "size-8",
        "icon-xs": cn(
          "size-6 [&_svg:not([class*='size-'])]:size-3",
          buttonGroupCornerClassName,
          buttonCornerXsClassName
        ),
        "icon-sm": cn(
          "size-7",
          buttonGroupCornerClassName,
          buttonCornerSmClassName
        ),
        "icon-lg": "size-9",
      },
    },
    compoundVariants: [
      {
        variant: "link",
        linkUnderline: "motion",
        class:
          "visited:!text-foreground hover:!text-foreground focus-visible:!text-foreground px-0 text-foreground shadow-none active:translate-y-0 [&_[data-link-label]]:font-medium [&_[data-link-label]]:text-[1em]",
      },
      {
        variant: "link",
        linkUnderline: "static",
        class:
          "visited:!text-foreground hover:!text-foreground focus-visible:!text-foreground px-0 text-foreground underline-offset-4 hover:underline [&_[data-link-label]]:font-medium [&_[data-link-label]]:text-[1em]",
      },
      {
        variant: "link",
        size: "default",
        class: "h-8 px-0 text-sm leading-5",
      },
      {
        variant: "link",
        size: "lg",
        class: "h-10 px-0 text-base leading-5",
      },
      {
        variant: "link",
        size: "sm",
        class: "h-7 px-0 text-[0.8rem]",
      },
      {
        variant: "link",
        size: "xs",
        class: "h-6 px-0 text-xs",
      },
    ],
    defaultVariants: {
      variant: "default",
      size: "default",
      linkUnderline: "motion",
    },
  }
);

const linkUnderlineFillTransition = {
  type: "spring" as const,
  stiffness: 420,
  damping: 36,
  mass: 0.65,
};

const MAX_RIPPLES = 3;
const REL_TOKEN_SPLIT = /\s+/;

const LinkUnderlineLabel = memo(function LinkUnderlineLabel({
  children,
  enabled,
}: {
  children: ReactNode;
  enabled: boolean;
}) {
  if (!enabled) {
    return <>{children}</>;
  }

  return (
    <span className="relative inline-block pb-px font-medium text-[1em] leading-[inherit]">
      <span className="relative z-10" data-link-label>
        {children}
      </span>
      <span
        aria-hidden
        className="pointer-events-none absolute inset-x-0 bottom-0 h-px bg-muted-foreground/50"
      />
      <motion.span
        aria-hidden
        className="pointer-events-none absolute inset-x-0 bottom-0 h-px origin-left bg-foreground"
        transition={linkUnderlineFillTransition}
        variants={{
          rest: { scaleX: 0 },
          hover: { scaleX: 1 },
        }}
      />
    </span>
  );
});

const buttonContentClassName =
  "relative z-10 inline-flex items-center justify-center [gap:inherit] whitespace-nowrap text-inherit";

type Ripple = { id: string; x: number; y: number; size: number };

type AnchorHTMLAttributesForMotion = Omit<
  AnchorHTMLAttributes<HTMLAnchorElement>,
  | "onAnimationEnd"
  | "onAnimationIteration"
  | "onAnimationStart"
  | "onDrag"
  | "onDragEnd"
  | "onDragEnter"
  | "onDragExit"
  | "onDragLeave"
  | "onDragOver"
  | "onDragStart"
  | "onDrop"
>;

type ButtonHTMLAttributesForMotion = Omit<
  ButtonHTMLAttributes<HTMLButtonElement>,
  | "onAnimationEnd"
  | "onAnimationIteration"
  | "onAnimationStart"
  | "onDrag"
  | "onDragEnd"
  | "onDragEnter"
  | "onDragExit"
  | "onDragLeave"
  | "onDragOver"
  | "onDragStart"
  | "onDrop"
>;

type ButtonRenderProps = React.HTMLAttributes<HTMLButtonElement> & {
  className?: string;
  ref?: React.Ref<HTMLButtonElement>;
  style?: React.CSSProperties;
};

function setRef<T>(ref: React.Ref<T> | undefined, value: T) {
  if (typeof ref === "function") {
    ref(value);
    return;
  }

  if (ref) {
    (ref as React.MutableRefObject<T>).current = value;
  }
}

function useMeasure<T extends HTMLElement = HTMLElement>(
  enabled: boolean
): [(node: T | null) => void, { width: number; height: number }] {
  const [element, setElement] = useState<T | null>(null);
  const [bounds, setBounds] = useState({ width: 0, height: 0 });

  const ref = useCallback((node: T | null) => {
    setElement(node);
  }, []);

  useEffect(() => {
    if (!(enabled && element && typeof ResizeObserver !== "undefined")) {
      return;
    }

    const observer = new ResizeObserver(([entry]) => {
      setBounds((current) => {
        const next = {
          width: entry.contentRect.width,
          height: entry.contentRect.height,
        };

        if (current.width === next.width && current.height === next.height) {
          return current;
        }

        return next;
      });
    });

    observer.observe(element);
    return () => observer.disconnect();
  }, [element, enabled]);

  return [ref, enabled ? bounds : { width: 0, height: 0 }];
}

const MotionAnchor = motion.create("a");

const buttonTransition = {
  scale: {
    type: "spring",
    stiffness: 640,
    damping: 38,
    mass: 0.85,
  },
  width: {
    type: "spring",
    stiffness: 420,
    damping: 34,
    mass: 0.9,
  },
  opacity: {
    type: "spring",
    stiffness: 380,
    damping: 32,
    mass: 0.8,
  },
  y: { type: "spring", stiffness: 340, damping: 28, mass: 0.8 },
} as const;

const loadingIconTransition = {
  type: "spring" as const,
  stiffness: 400,
  damping: 30,
  mass: 0.76,
};

const loadingLabelExitTransition = {
  type: "spring" as const,
  stiffness: 420,
  damping: 38,
  mass: 0.84,
};

const loadingLabelEnterTransition = {
  type: "spring" as const,
  stiffness: 280,
  damping: 30,
  mass: 0.98,
};

const loadingLayoutTransition = {
  type: "spring" as const,
  stiffness: 360,
  damping: 36,
  mass: 0.94,
};

const loadingLabelVariants = {
  animate: {
    filter: "blur(0px)",
    opacity: 1,
    scale: 1,
    transition: loadingLabelEnterTransition,
    y: 0,
  },
  exit: {
    filter: "blur(3px)",
    opacity: 0,
    scale: 0.96,
    transition: loadingLabelExitTransition,
    y: -4,
  },
  initial: {
    filter: "blur(3px)",
    opacity: 0,
    scale: 0.96,
    y: 4,
  },
} as const;

function getLabelMotionKey(children: ReactNode, loading: boolean) {
  if (typeof children === "string" || typeof children === "number") {
    return `${loading ? "busy" : "idle"}:${children}`;
  }

  return loading ? "busy" : "idle";
}

function withLoadingSpin(node: ReactNode, spinning: boolean) {
  if (!(spinning && isValidElement<{ className?: string }>(node))) {
    return node;
  }

  return cloneElement(node, {
    className: cn(node.props.className, "animate-spin"),
  });
}

function getButtonAnimate(
  canAnimate: boolean,
  isPressed: boolean,
  animatedWidth: number | undefined,
  reduceMotion: boolean | null
) {
  const shouldReduceMotion = reduceMotion === true;
  const pressScale = canAnimate && isPressed && !shouldReduceMotion ? 0.96 : 1;

  if (animatedWidth) {
    return {
      scale: pressScale,
      width: animatedWidth,
    };
  }

  return {
    scale: pressScale,
  };
}

function omitLinkMotionConflicts<T extends Record<string, unknown>>(props: T) {
  const {
    onAnimationEnd: _onAnimationEnd,
    onAnimationIteration: _onAnimationIteration,
    onAnimationStart: _onAnimationStart,
    onDrag: _onDrag,
    onDragEnd: _onDragEnd,
    onDragEnter: _onDragEnter,
    onDragExit: _onDragExit,
    onDragLeave: _onDragLeave,
    onDragOver: _onDragOver,
    onDragStart: _onDragStart,
    onDrop: _onDrop,
    ...rest
  } = props;

  return rest;
}

function resolvePrimitiveButtonProps(buttonProps: ButtonRenderProps) {
  const {
    children: _buttonChildren,
    className: primitiveClassName,
    onAnimationEnd: _onAnimationEnd,
    onAnimationIteration: _onAnimationIteration,
    onAnimationStart: _onAnimationStart,
    onDrag: _onDrag,
    onDragEnd: _onDragEnd,
    onDragEnter: _onDragEnter,
    onDragExit: _onDragExit,
    onDragLeave: _onDragLeave,
    onDragOver: _onDragOver,
    onDragStart: _onDragStart,
    onDrop: _onDrop,
    ref: primitiveRef,
    style: primitiveStyle,
    ...resolvedButtonProps
  } = buttonProps;

  return {
    primitiveClassName,
    primitiveRef,
    primitiveStyle,
    resolvedButtonProps,
  };
}

function resolveAnchorRel(target?: string, rel?: string) {
  if (!target?.includes("_blank")) {
    return rel;
  }

  const tokens = new Set((rel ?? "").split(REL_TOKEN_SPLIT).filter(Boolean));
  tokens.add("noopener");
  tokens.add("noreferrer");
  return [...tokens].join(" ");
}

function isIconOnlySize(
  size:
    | "default"
    | "xs"
    | "sm"
    | "lg"
    | "icon"
    | "icon-xs"
    | "icon-sm"
    | "icon-lg"
    | null
    | undefined
) {
  return (
    size === "icon" ||
    size === "icon-xs" ||
    size === "icon-sm" ||
    size === "icon-lg"
  );
}

function useIconOnlyA11yWarning(
  size:
    | "default"
    | "xs"
    | "sm"
    | "lg"
    | "icon"
    | "icon-xs"
    | "icon-sm"
    | "icon-lg"
    | null
    | undefined,
  children: ReactNode,
  iconLabel: string | undefined,
  ariaLabel: string | undefined,
  loading: boolean
) {
  useEffect(() => {
    if (process.env.NODE_ENV !== "development") {
      return;
    }

    const hasVisibleLabel =
      children != null && children !== false && children !== "";
    const needsAccessibleName =
      isIconOnlySize(size) || (loading && !hasVisibleLabel);

    if (!needsAccessibleName || iconLabel || ariaLabel) {
      return;
    }

    console.warn(
      "[Button] Icon-only and loading-only buttons need `iconLabel` or `aria-label` for accessibility."
    );
  }, [ariaLabel, children, iconLabel, loading, size]);
}

function getIconIdleAnimate(showSpinner: boolean, noMotion: boolean) {
  if (noMotion) {
    return { opacity: showSpinner ? 0 : 1 };
  }

  return {
    filter: showSpinner ? "blur(3px)" : "blur(0px)",
    opacity: showSpinner ? 0 : 1,
    rotate: showSpinner ? -56 : 0,
    scale: showSpinner ? 0.82 : 1,
  };
}

function getIconSpinnerAnimate(showSpinner: boolean, noMotion: boolean) {
  if (noMotion) {
    return { opacity: showSpinner ? 1 : 0 };
  }

  return {
    filter: showSpinner ? "blur(0px)" : "blur(3px)",
    opacity: showSpinner ? 1 : 0,
    rotate: showSpinner ? 0 : 56,
    scale: showSpinner ? 1 : 0.82,
  };
}

function buildMotionSurfaceProps({
  canAnimate,
  isPressed,
  motionAnimate,
  reduceMotion,
  useLinkMotionUnderline,
  variant,
}: {
  canAnimate: boolean;
  isPressed: boolean;
  motionAnimate: ReturnType<typeof getButtonAnimate>;
  reduceMotion: boolean | null;
  useLinkMotionUnderline: boolean;
  variant: VariantProps<typeof buttonVariants>["variant"];
}) {
  const shouldReduceMotion = reduceMotion === true;
  const surfaceTransition = shouldReduceMotion
    ? { duration: 0 }
    : buttonTransition;
  const shared = {
    animate: motionAnimate,
    "data-pressed": isPressed ? "true" : "false",
    transition: surfaceTransition,
  };

  if (useLinkMotionUnderline) {
    return {
      ...shared,
      initial: shouldReduceMotion ? (false as const) : ("rest" as const),
      variants: shouldReduceMotion ? undefined : { rest: {}, hover: {} },
      whileHover: shouldReduceMotion ? undefined : ("hover" as const),
    };
  }

  const hoverLift =
    canAnimate && variant !== "link" && !shouldReduceMotion
      ? { y: -1 }
      : undefined;

  return {
    ...shared,
    initial: false as const,
    whileHover: hoverLift,
  };
}

const ButtonIconSlot = memo(function ButtonIconSlot({
  accessibleName,
  dataIcon,
  hasIcon,
  hasLabel,
  icon,
  iconTransition,
  layoutTransition,
  noMotion,
  showSpinner,
  spinner,
}: {
  accessibleName?: string;
  dataIcon: "inline-start" | "inline-end";
  hasIcon: boolean;
  hasLabel: boolean;
  icon?: ReactNode;
  iconTransition: { duration: number } | typeof loadingIconTransition;
  layoutTransition: { duration: number } | typeof loadingLayoutTransition;
  noMotion: boolean;
  showSpinner: boolean;
  spinner: ReactNode;
}) {
  return (
    <motion.span
      aria-hidden={hasLabel || accessibleName ? true : undefined}
      className="relative inline-flex size-[1em] shrink-0 items-center justify-center"
      data-icon={dataIcon}
      layout={noMotion ? false : "position"}
      transition={layoutTransition}
    >
      {hasIcon ? (
        <motion.span
          animate={getIconIdleAnimate(showSpinner, noMotion)}
          className="absolute inset-0 inline-flex items-center justify-center"
          initial={false}
          transition={iconTransition}
        >
          {icon}
        </motion.span>
      ) : null}
      <motion.span
        animate={getIconSpinnerAnimate(showSpinner, noMotion)}
        className="absolute inset-0 inline-flex items-center justify-center"
        initial={false}
        transition={iconTransition}
      >
        {withLoadingSpin(spinner, showSpinner && !noMotion)}
      </motion.span>
    </motion.span>
  );
});

const ButtonLoadingLabel = memo(function ButtonLoadingLabel({
  children,
  layoutTransition,
  linkUnderlineEnabled,
  loading,
  noMotion,
}: {
  children: ReactNode;
  layoutTransition: { duration: number } | typeof loadingLayoutTransition;
  linkUnderlineEnabled: boolean;
  loading: boolean;
  noMotion: boolean;
}) {
  return (
    <motion.span
      className="relative inline-grid min-w-0 items-center leading-[inherit] [grid-template-columns:max-content] [grid-template-rows:minmax(0,1.25em)]"
      layout="position"
      transition={layoutTransition}
    >
      <AnimatePresence initial={false} mode="popLayout">
        <motion.span
          animate={noMotion ? { opacity: 1 } : "animate"}
          className="col-start-1 row-start-1 inline-flex min-w-0 items-center whitespace-nowrap"
          exit={noMotion ? { opacity: 0 } : "exit"}
          initial={noMotion ? false : "initial"}
          key={getLabelMotionKey(children, loading)}
          layout="position"
          transition={{ layout: layoutTransition }}
          variants={noMotion ? undefined : loadingLabelVariants}
        >
          <LinkUnderlineLabel enabled={linkUnderlineEnabled}>
            {children}
          </LinkUnderlineLabel>
        </motion.span>
      </AnimatePresence>
    </motion.span>
  );
});

const ButtonSpinnerPresence = memo(function ButtonSpinnerPresence({
  iconSlot,
  layoutTransition,
  noMotion,
  visible,
}: {
  iconSlot: ReactNode;
  layoutTransition: { duration: number } | typeof loadingLayoutTransition;
  noMotion: boolean;
  visible: boolean;
}) {
  return (
    <AnimatePresence initial={false} mode="popLayout">
      {visible ? (
        <motion.span
          animate={noMotion ? { opacity: 1 } : { opacity: 1, scale: 1 }}
          className="inline-flex shrink-0"
          exit={
            noMotion
              ? { opacity: 0 }
              : {
                  filter: "blur(2px)",
                  opacity: 0,
                  scale: 0.86,
                  transition: loadingLabelExitTransition,
                }
          }
          initial={noMotion ? false : { opacity: 0, scale: 0.86 }}
          key="loading-spinner"
          layout="position"
          transition={layoutTransition}
        >
          {iconSlot}
        </motion.span>
      ) : null}
    </AnimatePresence>
  );
});

const defaultLoadingIcon = <Loader2 aria-hidden strokeWidth={2.25} />;

const ButtonLoadingContent = memo(function ButtonLoadingContent({
  accessibleName,
  children,
  contentRef,
  icon,
  iconPosition,
  linkUnderlineEnabled,
  loading,
  loadingIcon,
  reduceMotion,
}: {
  accessibleName?: string;
  children: ReactNode;
  contentRef?: React.Ref<HTMLSpanElement>;
  icon?: ReactNode;
  iconPosition: "start" | "end";
  linkUnderlineEnabled: boolean;
  loading: boolean;
  loadingIcon?: ReactNode;
  reduceMotion: boolean | null;
}) {
  const hasIcon = Boolean(icon);
  const hasLabel = children != null && children !== false && children !== "";
  const noMotion = reduceMotion === true;
  const iconTransition = noMotion ? { duration: 0 } : loadingIconTransition;
  const layoutTransition = noMotion ? { duration: 0 } : loadingLayoutTransition;
  const spinner = loadingIcon ?? defaultLoadingIcon;

  const iconAtStart = loading || (hasIcon && iconPosition === "start");
  const iconAtEnd = hasIcon && !loading && iconPosition === "end";
  const spinnerOnlyAtStart = loading && !hasIcon;

  const iconSlotProps = {
    accessibleName,
    hasLabel,
    iconTransition,
    layoutTransition,
    noMotion,
    spinner,
  };

  return (
    <motion.span
      animate={loading && !noMotion ? { opacity: 0.96 } : { opacity: 1 }}
      className={buttonContentClassName}
      initial={false}
      layout={!noMotion}
      ref={contentRef}
      transition={layoutTransition}
    >
      <ButtonSpinnerPresence
        iconSlot={
          <ButtonIconSlot
            {...iconSlotProps}
            dataIcon="inline-start"
            hasIcon={hasIcon}
            icon={icon}
            showSpinner
          />
        }
        layoutTransition={layoutTransition}
        noMotion={noMotion}
        visible={spinnerOnlyAtStart}
      />
      {hasIcon && iconAtStart ? (
        <ButtonIconSlot
          {...iconSlotProps}
          dataIcon="inline-start"
          hasIcon={hasIcon}
          icon={icon}
          showSpinner={loading}
        />
      ) : null}
      {hasLabel ? (
        <ButtonLoadingLabel
          layoutTransition={layoutTransition}
          linkUnderlineEnabled={linkUnderlineEnabled}
          loading={loading}
          noMotion={noMotion}
        >
          {children}
        </ButtonLoadingLabel>
      ) : null}
      {hasIcon && iconAtEnd ? (
        <ButtonIconSlot
          {...iconSlotProps}
          dataIcon="inline-end"
          hasIcon={hasIcon}
          icon={icon}
          showSpinner={loading}
        />
      ) : null}
    </motion.span>
  );
});

const rippleTransition = {
  duration: 0.65,
  ease: [0.22, 1, 0.36, 1] as const,
  opacity: {
    duration: 0.65,
    ease: [0.4, 0, 0.2, 1] as const,
    times: [0, 0.2, 1] as const,
  },
} as const;

const RippleLayer = memo(function RippleLayer({
  onRippleComplete,
  ripples,
}: {
  ripples: Ripple[];
  onRippleComplete: (id: string) => void;
}) {
  return (
    <span aria-hidden className="pointer-events-none absolute inset-0 z-[1]">
      {ripples.map((ripple) => (
        <motion.span
          animate={{ opacity: [0.36, 0.2, 0], scale: 1 }}
          className="pointer-events-none absolute -translate-x-1/2 -translate-y-1/2 rounded-full bg-[var(--button-ripple)]"
          initial={{ opacity: 0.36, scale: 0 }}
          key={ripple.id}
          onAnimationComplete={() => onRippleComplete(ripple.id)}
          style={{
            height: ripple.size,
            left: ripple.x,
            top: ripple.y,
            width: ripple.size,
          }}
          transition={rippleTransition}
        />
      ))}
    </span>
  );
});

type ButtonInteractionElement = HTMLButtonElement | HTMLAnchorElement;

type ButtonInteractionHandlers = {
  onBlur?: FocusEventHandler<ButtonInteractionElement>;
  onKeyDown?: KeyboardEventHandler<ButtonInteractionElement>;
  onKeyUp?: KeyboardEventHandler<ButtonInteractionElement>;
  onPointerCancel?: PointerEventHandler<ButtonInteractionElement>;
  onPointerDown?: PointerEventHandler<ButtonInteractionElement>;
  onPointerLeave?: PointerEventHandler<ButtonInteractionElement>;
  onPointerUp?: PointerEventHandler<ButtonInteractionElement>;
};

export type ButtonProps = Omit<
  ButtonHTMLAttributesForMotion,
  keyof ButtonInteractionHandlers
> &
  ButtonInteractionHandlers &
  VariantProps<typeof buttonVariants> & {
    animateSize?: boolean;
    children?: ReactNode;
    disableRipple?: boolean;
    href?: string;
    icon?: ReactNode;
    iconLabel?: string;
    iconPosition?: "start" | "end";
    loading?: boolean;
    loadingIcon?: ReactNode;
  } & Pick<
    AnchorHTMLAttributes<HTMLAnchorElement>,
    | "download"
    | "hrefLang"
    | "media"
    | "ping"
    | "referrerPolicy"
    | "rel"
    | "target"
  >;

const ButtonInner = forwardRef<
  HTMLButtonElement | HTMLAnchorElement,
  ButtonProps
>(
  (
    {
      animateSize = false,
      "aria-label": ariaLabel,
      children,
      className,
      disabled,
      disableRipple = false,
      href,
      icon,
      iconLabel,
      iconPosition = "start",
      linkUnderline,
      loading = false,
      loadingIcon,
      onBlur,
      onKeyDown,
      onKeyUp,
      onPointerCancel,
      onPointerDown,
      onPointerLeave,
      onPointerUp,
      rel,
      size,
      style,
      target,
      type = "button",
      variant,
      ...props
    },
    ref
  ) => {
    const isLocked = Boolean(disabled || loading);
    const accessibleName = iconLabel ?? ariaLabel;
    const reduceMotion = useReducedMotion();

    useIconOnlyA11yWarning(size, children, iconLabel, ariaLabel, loading);
    const buttonNodeRef = useRef<HTMLButtonElement | HTMLAnchorElement | null>(
      null
    );
    const rippleIdRef = useRef(0);
    const wasLoadingRef = useRef(loading);
    const [horizontalInset, setHorizontalInset] = useState(0);
    const [isPressed, setIsPressed] = useState(false);
    const [ripples, setRipples] = useState<Ripple[]>([]);
    const shouldAnimateSize = animateSize && style?.width == null;
    const [contentRef, bounds] = useMeasure<HTMLSpanElement>(shouldAnimateSize);
    const lastContentWidthRef = useRef(0);
    const resolvedLinkUnderline =
      variant === "link" ? (linkUnderline ?? "motion") : undefined;
    const useLinkMotionUnderline = resolvedLinkUnderline === "motion";

    const canAnimate = !isLocked && reduceMotion !== true;
    const allowsRipple =
      !(disableRipple || loading) &&
      variant !== "link" &&
      !isLocked &&
      reduceMotion !== true;
    useLayoutEffect(() => {
      if (bounds.width > 0) {
        lastContentWidthRef.current = bounds.width;
      }
    }, [bounds.width]);

    const contentWidth =
      bounds.width > 0 ? bounds.width : lastContentWidthRef.current;
    const animatedWidth =
      shouldAnimateSize && contentWidth > 0
        ? Math.ceil(contentWidth + horizontalInset)
        : undefined;

    const setButtonRef = useCallback(
      (node: HTMLButtonElement | HTMLAnchorElement | null) => {
        buttonNodeRef.current = node;
        setRef(ref, node);
      },
      [ref]
    );

    const measureHorizontalInset = useCallback(
      (measuredContentWidth: number) => {
        if (!(measuredContentWidth > 0)) {
          return;
        }

        const buttonNode = buttonNodeRef.current;
        if (!(buttonNode && animateSize)) {
          return;
        }

        const styles = window.getComputedStyle(buttonNode);
        const next =
          (Number.parseFloat(styles.borderLeftWidth) || 0) +
          (Number.parseFloat(styles.borderRightWidth) || 0) +
          (Number.parseFloat(styles.paddingLeft) || 0) +
          (Number.parseFloat(styles.paddingRight) || 0);

        setHorizontalInset((current) => (current === next ? current : next));
      },
      [animateSize]
    );

    useLayoutEffect(() => {
      measureHorizontalInset(contentWidth);
    }, [contentWidth, measureHorizontalInset]);

    const handleRippleComplete = useCallback((id: string) => {
      setRipples((current) => {
        if (!current.some((item) => item.id === id)) {
          return current;
        }

        return current.filter((item) => item.id !== id);
      });
    }, []);

    useEffect(() => {
      if (isLocked) {
        setIsPressed(false);
      }
    }, [isLocked]);

    useLayoutEffect(() => {
      const wasLoading = wasLoadingRef.current;
      if (loading || wasLoading) {
        setRipples([]);
      }
      if (loading) {
        setIsPressed(false);
      }
      wasLoadingRef.current = loading;
    }, [loading]);

    const handlePointerDown = (
      e: PointerEvent<HTMLButtonElement | HTMLAnchorElement>
    ) => {
      onPointerDown?.(e);

      if (e.defaultPrevented || isLocked) {
        return;
      }

      setIsPressed(true);

      if (!(allowsRipple && e.button === 0)) {
        return;
      }

      if (e.pointerType === "mouse" && e.buttons !== 1) {
        return;
      }

      const rect = e.currentTarget.getBoundingClientRect();
      const x = e.clientX - rect.left;
      const y = e.clientY - rect.top;
      const rippleSize =
        2 *
        Math.max(
          Math.hypot(x, y),
          Math.hypot(rect.width - x, y),
          Math.hypot(x, rect.height - y),
          Math.hypot(rect.width - x, rect.height - y)
        );
      rippleIdRef.current += 1;
      const id = `ripple-${rippleIdRef.current}`;

      setRipples((current) => {
        const next = [...current, { id, x, y, size: rippleSize }];
        return next.length > MAX_RIPPLES
          ? next.slice(next.length - MAX_RIPPLES)
          : next;
      });
    };

    const handlePointerUp = (
      e: PointerEvent<HTMLButtonElement | HTMLAnchorElement>
    ) => {
      onPointerUp?.(e);
      setIsPressed(false);
    };

    const handlePointerLeave = (
      e: PointerEvent<HTMLButtonElement | HTMLAnchorElement>
    ) => {
      onPointerLeave?.(e);
      setIsPressed(false);
    };

    const handlePointerCancel = (
      e: PointerEvent<HTMLButtonElement | HTMLAnchorElement>
    ) => {
      onPointerCancel?.(e);
      setIsPressed(false);
    };

    const handleKeyDown = (e: KeyboardEvent<ButtonInteractionElement>) => {
      onKeyDown?.(e);

      if (
        e.defaultPrevented ||
        isLocked ||
        e.repeat ||
        (e.key !== " " && e.key !== "Enter")
      ) {
        return;
      }

      setIsPressed(true);
    };

    const handleKeyUp = (e: KeyboardEvent<ButtonInteractionElement>) => {
      onKeyUp?.(e);

      if (e.key === " " || e.key === "Enter") {
        setIsPressed(false);
      }
    };

    const rootClassName = cn(
      buttonVariants({
        className,
        linkUnderline: resolvedLinkUnderline,
        size,
        variant,
      }),
      isLocked && "pointer-events-none",
      disabled && "opacity-50"
    );

    const buildButtonSurface = (labelContent: ReactNode) => (
      <>
        {allowsRipple ? (
          <RippleLayer
            onRippleComplete={handleRippleComplete}
            ripples={ripples}
          />
        ) : null}
        <ButtonLoadingContent
          accessibleName={accessibleName}
          contentRef={shouldAnimateSize ? contentRef : undefined}
          icon={icon}
          iconPosition={iconPosition}
          linkUnderlineEnabled={resolvedLinkUnderline === "motion"}
          loading={loading}
          loadingIcon={loadingIcon}
          reduceMotion={reduceMotion}
        >
          {labelContent}
        </ButtonLoadingContent>
      </>
    );

    const buttonSurface = buildButtonSurface(children);

    const motionAnimate = useMemo(
      () =>
        getButtonAnimate(canAnimate, isPressed, animatedWidth, reduceMotion),
      [animatedWidth, canAnimate, isPressed, reduceMotion]
    );

    const motionSurfaceProps = useMemo(
      () =>
        buildMotionSurfaceProps({
          canAnimate,
          isPressed,
          motionAnimate,
          reduceMotion,
          useLinkMotionUnderline,
          variant,
        }),
      [
        canAnimate,
        isPressed,
        motionAnimate,
        reduceMotion,
        useLinkMotionUnderline,
        variant,
      ]
    );

    const sharedRootProps = {
      "aria-busy": loading || undefined,
      "aria-label": accessibleName,
      className: rootClassName,
      "data-slot": "button" as const,
    };

    if (href?.trim()) {
      const safeHref = href.trim();
      const {
        onClick,
        type: _type,
        ...anchorProps
      } = omitLinkMotionConflicts(props as AnchorHTMLAttributesForMotion);
      const safeRel = resolveAnchorRel(target, rel);

      return (
        <MotionAnchor
          {...motionSurfaceProps}
          {...anchorProps}
          {...sharedRootProps}
          aria-disabled={isLocked || undefined}
          href={safeHref}
          onBlur={(e) => {
            onBlur?.(e);
            setIsPressed(false);
          }}
          onClick={(e) => {
            if (isLocked) {
              e.preventDefault();
            }
            onClick?.(e as React.MouseEvent<HTMLAnchorElement>);
          }}
          onKeyDown={(e) => {
            onKeyDown?.(e);

            if (
              e.defaultPrevented ||
              isLocked ||
              e.repeat ||
              e.key !== "Enter"
            ) {
              return;
            }

            setIsPressed(true);
          }}
          onKeyUp={(e) => {
            onKeyUp?.(e);

            if (e.key === "Enter") {
              setIsPressed(false);
            }
          }}
          onPointerCancel={handlePointerCancel}
          onPointerDown={handlePointerDown}
          onPointerLeave={handlePointerLeave}
          onPointerUp={handlePointerUp}
          ref={setButtonRef}
          rel={safeRel}
          style={style}
          tabIndex={isLocked ? -1 : undefined}
          target={target}
        >
          {buttonSurface}
        </MotionAnchor>
      );
    }

    return (
      <ButtonPrimitive
        {...sharedRootProps}
        disabled={isLocked}
        onBlur={(e) => {
          onBlur?.(e);
          setIsPressed(false);
        }}
        onKeyDown={handleKeyDown}
        onKeyUp={handleKeyUp}
        onPointerCancel={handlePointerCancel}
        onPointerDown={handlePointerDown}
        onPointerLeave={handlePointerLeave}
        onPointerUp={handlePointerUp}
        render={(buttonProps) => {
          const {
            primitiveClassName,
            primitiveRef,
            primitiveStyle,
            resolvedButtonProps,
          } = resolvePrimitiveButtonProps(buttonProps);

          return (
            <motion.button
              {...resolvedButtonProps}
              {...motionSurfaceProps}
              className={primitiveClassName}
              ref={(node) => {
                setButtonRef(node);
                setRef(primitiveRef, node);
              }}
              style={primitiveStyle}
              type={type}
            >
              {buttonSurface}
            </motion.button>
          );
        }}
        style={style}
        type={type}
        {...props}
      />
    );
  }
);

ButtonInner.displayName = "Button";

const Button = memo(ButtonInner);

export { Button, buttonVariants };

lib/utils.ts

import type { ClassValue } from "clsx";
import { clsx } from "clsx";
import { twMerge } from "tailwind-merge";

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

demo.tsx

export { default as default } from "@/import-examples/base-button";

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-xs: 0.75rem;
    --text-xs--line-height: calc(1 / 0.75);
    --text-sm: 0.875rem;
    --text-sm--line-height: calc(1.25 / 0.875);
    --text-base: 1rem;
    --text-base--line-height: calc(1.5 / 1);
    --font-weight-medium: 500;
    --radius-md: calc(var(--radius) - 2px);
    --animate-spin: spin 1s linear infinite;
    --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);
    --color-background: var(--background);
    --color-foreground: var(--foreground);
    --color-primary: var(--primary);
    --color-secondary: var(--secondary);
    --color-muted: var(--muted);
    --color-muted-foreground: var(--muted-foreground);
    --color-accent: var(--accent);
    --color-accent-foreground: var(--accent-foreground);
    --color-border: var(--border);
    --color-input: var(--input);
    --color-ring: var(--ring);
    --color-destructive: var(--destructive);
    --color-popover-foreground: var(--popover-foreground);
    --color-card: var(--card);
    --color-card-foreground: var(--card-foreground);
  }
}
@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;
  }
  .visible {
    visibility: visible;
  }
  .absolute {
    position: absolute;
  }
  .relative {
    position: relative;
  }
  .static {
    position: static;
  }
  .inset-0 {
    inset: 0px;
  }
  .inset-x-0 {
    inset-inline: 0px;
  }
  .bottom-0 {
    bottom: 0px;
  }
  .isolate {
    isolation: isolate;
  }
  .z-10 {
    z-index: 10;
  }
  .z-\[1\] {
    z-index: 1;
  }
  .col-start-1 {
    grid-column-start: 1;
  }
  .row-start-1 {
    grid-row-start: 1;
  }
  .flex {
    display: flex;
  }
  .inline-block {
    display: inline-block;
  }
  .inline-flex {
    display: inline-flex;
  }
  .inline-grid {
    display: inline-grid;
  }
  .size-6 {
    width: calc(var(--spacing) * 6);
    height: calc(var(--spacing) * 6);
  }
  .size-7 {
    width: calc(var(--spacing) * 7);
    height: calc(var(--spacing) * 7);
  }
  .size-8 {
    width: calc(var(--spacing) * 8);
    height: calc(var(--spacing) * 8);
  }
  .size-9 {
    width: calc(var(--spacing) * 9);
    height: calc(var(--spacing) * 9);
  }
  .size-\[1em\] {
    width: 1em;
    height: 1em;
  }
  .h-6 {
    height: calc(var(--spacing) * 6);
  }
  .h-7 {
    height: calc(var(--spacing) * 7);
  }
  .h-8 {
    height: calc(var(--spacing) * 8);
  }
  .h-10 {
    height: calc(var(--spacing) * 10);
  }
  .h-px {
    height: 1px;
  }
  .min-w-0 {
    min-width: 0px;
  }
  .shrink-0 {
    flex-shrink: 0;
  }
  .origin-left {
    transform-origin: 0;
  }
  .-translate-x-1\/2 {
    --tw-translate-x: calc(calc(1 / 2 * 100%) * -1);
    translate: var(--tw-translate-x) var(--tw-translate-y);
  }
  .-translate-y-1\/2 {
    --tw-translate-y: calc(calc(1 / 2 * 100%) * -1);
    translate: var(--tw-translate-x) var(--tw-translate-y);
  }
  .animate-spin {
    animation: var(--animate-spin);
  }
  .cursor-pointer {
    cursor: pointer;
  }
  .touch-manipulation {
    touch-action: manipulation;
  }
  .\[grid-template-columns\:max-content\] {
    grid-template-columns: max-content;
  }
  .\[grid-template-rows\:minmax\(0\,1\.25em\)\] {
    grid-template-rows: minmax(0,1.25em);
  }
  .flex-wrap {
    flex-wrap: wrap;
  }
  .items-center {
    align-items: center;
  }
  .justify-center {
    justify-content: center;
  }
  .\[gap\:inherit\] {
    gap: inherit;
  }
  .gap-1 {
    gap: var(--spacing);
  }
  .gap-1\.5 {
    gap: calc(var(--spacing) * 1.5);
  }
  .gap-3 {
    gap: calc(var(--spacing) * 3);
  }
  .overflow-hidden {
    overflow: hidden;
  }
  .rounded-\[min\(var\(--radius-md\)\,10px\)\] {
    border-radius: min(var(--radius-md), 10px);
  }
  .rounded-\[min\(var\(--radius-md\)\,12px\)\] {
    border-radius: min(var(--radius-md), 12px);
  }
  .rounded-full {
    border-radius: calc(infinity * 1px);
  }
  .rounded-lg {
    border-radius: var(--radius);
  }
  .border {
    border-style: var(--tw-border-style);
    border-width: 1px;
  }
  .border-border {
    border-color: var(--border);
  }
  .border-transparent {
    border-color: transparent;
  }
  .bg-\[color-mix\(in_oklch\,var\(--button-destructive\)\,transparent_90\%\)\] {
    background-color: var(--button-destructive);
    @supports (color: color-mix(in lab, red, red)) {
      background-color: color-mix(in oklch,var(--button-destructive),transparent 90%);
    }
  }
  .bg-\[color\:var\(--button-secondary\)\] {
    background-color: var(--button-secondary);
  }
  .bg-\[var\(--button-ripple\)\] {
    background-color: var(--button-ripple);
  }
  .bg-background {
    background-color: var(--background);
  }
  .bg-foreground {
    background-color: var(--foreground);
  }
  .bg-muted-foreground\/50 {
    background-color: var(--muted-foreground);
    @supports (color: color-mix(in lab, red, red)) {
      background-color: color-mix(in oklab, var(--muted-foreground) 50%, transparent);
    }
  }
  .bg-primary {
    background-color: var(--primary);
  }
  .bg-clip-border {
    background-clip: border-box;
  }
  .px-0 {
    padding-inline: 0px;
  }
  .px-2 {
    padding-inline: calc(var(--spacing) * 2);
  }
  .px-2\.5 {
    padding-inline: calc(var(--spacing) * 2.5);
  }
  .px-4 {
    padding-inline: calc(var(--spacing) * 4);
  }
  .pb-px {
    padding-bottom: 1px;
  }
  .text-base {
    font-size: var(--text-base);
    line-height: var(--tw-leading, var(--text-base--line-height));
  }
  .text-sm {
    font-size: var(--text-sm);
    line-height: var(--tw-leading, var(--text-sm--line-height));
  }
  .text-xs {
    font-size: var(--text-xs);
    line-height: var(--tw-leading, var(--text-xs--line-height));
  }
  .text-\[0\.8rem\] {
    font-size: 0.8rem;
  }
  .text-\[1em\] {
    font-size: 1em;
  }
  .leading-5 {
    --tw-leading: calc(var(--spacing) * 5);
    line-height: calc(var(--spacing) * 5);
  }
  .leading-\[inherit\] {
    --tw-leading: inherit;
    line-height: inherit;
  }
  .font-medium {
    --tw-font-weight: var(--font-weight-medium);
    font-weight: var(--font-weight-medium);
  }
  .whitespace-nowrap {
    white-space: nowrap;
  }
  .text-\[color\:var\(--button-destructive\)\] {
    color: var(--button-destructive);
  }
  .text-\[color\:var\(--button-secondary-foreground\)\] {
    color: var(--button-secondary-foreground);
  }
  .text-foreground {
    color: var(--foreground);
  }
  .text-inherit {
    color: inherit;
  }
  .text-primary-foreground {
    color: var(--primary-foreground);
  }
  .no-underline {
    text-decoration-line: none;
  }
  .underline-offset-4 {
    text-underline-offset: 4px;
  }
  .opacity-50 {
    opacity: 50%;
  }
  .shadow-none {
    --tw-shadow: 0 0 #0000;
    box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
  }
  .outline {
    outline-style: var(--tw-outline-style);
    outline-width: 1px;
  }
  .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-\[background-color\,border-color\,color\,box-shadow\] {
    transition-property: background-color,border-color,color,box-shadow;
    transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
    transition-duration: var(--tw-duration, var(--default-transition-duration));
  }
  .outline-none {
    --tw-outline-style: none;
    outline-style: none;
  }
  .select-none {
    -webkit-user-select: none;
    user-select: none;
  }
  .\[--button-destructive\:\#dc2626\] {
    --button-destructive: #dc2626;
  }
  .\[--button-input\:\#e3e7ec\] {
    --button-input: #e3e7ec;
  }
  .\[--button-primary-foreground\:\#ffffff\] {
    --button-primary-foreground: #ffffff;
  }
  .\[--button-ring\:rgba\(17\,17\,17\,0\.16\)\] {
    --button-ring: rgba(17,17,17,0.16);
  }
  .\[--button-ripple\:color-mix\(in_oklch\,var\(--button-destructive\)\,transparent_40\%\)\] {
    --button-ripple: var(--button-destructive);
    @supports (color: color-mix(in lab, red, red)) {
      --button-ripple: color-mix(in oklch,var(--button-destructive),transparent 40%);
    }
  }
  .\[--button-ripple\:color-mix\(in_oklch\,var\(--button-secondary-foreground\)\,transparent_52\%\)\] {
    --button-ripple: var(--button-secondary-foreground);
    @supports (color: color-mix(in lab, red, red)) {
      --button-ripple: color-mix(in oklch,var(--button-secondary-foreground),transparent 52%);
    }
  }
  .\[--button-ripple\:color-mix\(in_oklch\,var\(--foreground\)\,transparent_52\%\)\] {
    --button-ripple: var(--foreground);
    @supports (color: color-mix(in lab, red, red)) {
      --button-ripple: color-mix(in oklch,var(--foreground),transparent 52%);
    }
  }
  .\[--button-ripple\:color-mix\(in_oklch\,var\(--primary-foreground\)\,transparent_45\%\)\] {
    --button-ripple: var(--primary-foreground);
    @supports (color: color-mix(in lab, red, red)) {
      --button-ripple: color-mix(in oklch,var(--primary-foreground),transparent 45%);
    }
  }
  .\[--button-secondary-foreground\:\#111111\] {
    --button-secondary-foreground: #111111;
  }
  .\[--button-secondary\:\#eceff3\] {
    --button-secondary: #eceff3;
  }
  .\[--color-accent-foreground\:var\(--ic-accent-foreground\)\] {
    --color-accent-foreground: var(--ic-accent-foreground);
  }
  .\[--color-accent\:var\(--ic-accent\)\] {
    --color-accent: var(--ic-accent);
  }
  .\[--color-background\:var\(--ic-background\)\] {
    --color-background: var(--ic-background);
  }
  .\[--color-border\:var\(--ic-border\)\] {
    --color-border: var(--ic-border);
  }
  .\[--color-brand-soft\:var\(--ic-brand-soft\)\] {
    --color-brand-soft: var(--ic-brand-soft);
  }
  .\[--color-brand\:var\(--ic-brand\)\] {
    --color-brand: var(--ic-brand);
  }
  .\[--color-card-foreground\:var\(--ic-card-foreground\)\] {
    --color-card-foreground: var(--ic-card-foreground);
  }
  .\[--color-card\:var\(--ic-card\)\] {
    --color-card: var(--ic-card);
  }
  .\[--color-chart-1\:var\(--ic-chart-1\)\] {
    --color-chart-1: var(--ic-chart-1);
  }
  .\[--color-chart-2\:var\(--ic-chart-2\)\] {
    --color-chart-2: var(--ic-chart-2);
  }
  .\[--color-chart-3\:var\(--ic-chart-3\)\] {
    --color-chart-3: var(--ic-chart-3);
  }
  .\[--color-chart-4\:var\(--ic-chart-4\)\] {
    --color-chart-4: var(--ic-chart-4);
  }
  .\[--color-chart-5\:var\(--ic-chart-5\)\] {
    --color-chart-5: var(--ic-chart-5);
  }
  .\[--color-destructive\:var\(--ic-destructive\)\] {
    --color-destructive: var(--ic-destructive);
  }
  .\[--color-foreground\:var\(--ic-foreground\)\] {
    --color-foreground: var(--ic-foreground);
  }
  .\[--color-input\:var\(--ic-input\)\] {
    --color-input: var(--ic-input);
  }
  .\[--color-muted-foreground\:var\(--ic-muted-foreground\)\] {
    --color-muted-foreground: var(--ic-muted-foreground);
  }
  .\[--color-muted\:var\(--ic-muted\)\] {
    --color-muted: var(--ic-muted);
  }
  .\[--color-paper\:var\(--ic-paper\)\] {
    --color-paper: var(--ic-paper);
  }
  .\[--color-popover-foreground\:var\(--ic-popover-foreground\)\] {
    --color-popover-foreground: var(--ic-popover-foreground);
  }
  .\[--color-primary\:var\(--ic-primary\)\] {
    --color-primary: var(--ic-primary);
  }
  .\[--color-ring\:var\(--ic-ring\)\] {
    --color-ring: var(--ic-ring);
  }
  .\[--color-secondary\:var\(--ic-secondary\)\] {
    --color-secondary: var(--ic-secondary);
  }
  .\[--ic-accent-foreground\:\#111111\] {
    --ic-accent-foreground: #111111;
  }
  .\[--ic-accent\:\#f3f5f8\] {
    --ic-accent: #f3f5f8;
  }
  .\[--ic-background\:\#ffffff\] {
    --ic-background: #ffffff;
  }
  .\[--ic-border\:\#e3e7ec\] {
    --ic-border: #e3e7ec;
  }
  .\[--ic-brand-soft\:\#bae6fd\] {
    --ic-brand-soft: #bae6fd;
  }
  .\[--ic-brand\:\#0ea5e9\] {
    --ic-brand: #0ea5e9;
  }
  .\[--ic-card-foreground\:\#111111\] {
    --ic-card-foreground: #111111;
  }
  .\[--ic-card\:\#ffffff\] {
    --ic-card: #ffffff;
  }
  .\[--ic-chart-1\:oklch\(0\.52_0\.19_254\)\] {
    --ic-chart-1: oklch(0.52 0.19 254);
  }
  .\[--ic-chart-2\:oklch\(0\.74_0\.11_232\)\] {
    --ic-chart-2: oklch(0.74 0.11 232);
  }
  .\[--ic-chart-3\:oklch\(0\.42_0\.16_262\)\] {
    --ic-chart-3: oklch(0.42 0.16 262);
  }
  .\[--ic-chart-4\:oklch\(0\.84_0\.07_228\)\] {
    --ic-chart-4: oklch(0.84 0.07 228);
  }
  .\[--ic-chart-5\:oklch\(0\.62_0\.14_240\)\] {
    --ic-chart-5: oklch(0.62 0.14 240);
  }
  .\[--ic-destructive\:\#dc2626\] {
    --ic-destructive: #dc2626;
  }
  .\[--ic-foreground\:\#111111\] {
    --ic-foreground: #111111;
  }
  .\[--ic-input\:\#e3e7ec\] {
    --ic-input: #e3e7ec;
  }
  .\[--ic-muted-foreground\:\#6d7480\] {
    --ic-muted-foreground: #6d7480;
  }
  .\[--ic-muted\:\#f5f7fa\] {
    --ic-muted: #f5f7fa;
  }
  .\[--ic-paper\:\#fcfcfd\] {
    --ic-paper: #fcfcfd;
  }
  .\[--ic-popover-foreground\:\#111111\] {
    --ic-popover-foreground: #111111;
  }
  .\[--ic-primary\:\#111111\] {
    --ic-primary: #111111;
  }
  .\[--ic-ring\:rgba\(17\,17\,17\,0\.16\)\] {
    --ic-ring: rgba(17,17,17,0.16);
  }
  .\[--ic-secondary\:\#646b75\] {
    --ic-secondary: #646b75;
  }
  .\[--ic-shadow-soft\:0_18px_38px_-24px_rgba\(15\,23\,42\,0\.35\)\] {
    --ic-shadow-soft: 0 18px 38px -24px rgba(15,23,42,0.35);
  }
  .\[--ic-surface-border\:\#e9edf2\] {
    --ic-surface-border: #e9edf2;
  }
  .visited\:\!text-\[color\:var\(--button-destructive\)\]:visited {
    color: var(--button-destructive) !important;
  }
  .visited\:\!text-\[color\:var\(--button-secondary-foreground\)\]:visited {
    color: var(--button-secondary-foreground) !important;
  }
  .visited\:\!text-foreground:visited {
    color: var(--foreground) !important;
  }
  .visited\:\!text-primary-foreground:visited {
    color: var(--primary-foreground) !important;
  }
  @media (hover: hover) {
    .hover\:bg-\[color-mix\(in_oklch\,var\(--button-destructive\)\,transparent_80\%\)\]:hover {
      background-color: var(--button-destructive);
    }
    @supports (color: color-mix(in lab, red, red)) {
      .hover\:bg-\[color-mix\(in_oklch\,var\(--button-destructive\)\,transparent_80\%\)\]:hover {
        background-color: color-mix(in oklch,var(--button-destructive),transparent 80%);
      }
    }
    .hover\:bg-\[color-mix\(in_oklch\,var\(--button-secondary\)\,var\(--foreground\)_7\%\)\]:hover {
      background-color: var(--button-secondary);
    }
    @supports (color: color-mix(in lab, red, red)) {
      .hover\:bg-\[color-mix\(in_oklch\,var\(--button-secondary\)\,var\(--foreground\)_7\%\)\]:hover {
        background-color: color-mix(in oklch,var(--button-secondary),var(--foreground) 7%);
      }
    }
    .hover\:bg-accent\/60:hover {
      background-color: var(--accent);
    }
    @supports (color: color-mix(in lab, red, red)) {
      .hover\:bg-accent\/60:hover {
        background-color: color-mix(in oklab, var(--accent) 60%, transparent);
      }
    }
    .hover\:bg-primary:hover {
      background-color: var(--primary);
    }
    .hover\:\!text-\[color\:var\(--button-destructive\)\]:hover {
      color: var(--button-destructive) !important;
    }
    .hover\:\!text-\[color\:var\(--button-secondary-foreground\)\]:hover {
      color: var(--button-secondary-foreground) !important;
    }
    .hover\:\!text-foreground:hover {
      color: var(--foreground) !important;
    }
    .hover\:\!text-primary-foreground:hover {
      color: var(--primary-foreground) !important;
    }
    .hover\:underline:hover {
      text-decoration-line: underline;
    }
  }
  .focus-visible\:border-\[color\:color-mix\(in_oklch\,var\(--button-destructive\)\,transparent_60\%\)\]:focus-visible {
    border-color: var(--button-destructive);
    @supports (color: color-mix(in lab, red, red)) {
      border-color: color-mix(in oklch,var(--button-destructive),transparent 60%);
    }
  }
  .focus-visible\:border-\[color\:var\(--button-ring\)\]:focus-visible {
    border-color: var(--button-ring);
  }
  .focus-visible\:\!text-\[color\:var\(--button-destructive\)\]:focus-visible {
    color: var(--button-destructive) !important;
  }
  .focus-visible\:\!text-\[color\:var\(--button-secondary-foreground\)\]:focus-visible {
    color: var(--button-secondary-foreground) !important;
  }
  .focus-visible\:\!text-foreground:focus-visible {
    color: var(--foreground) !important;
  }
  .focus-visible\:\!text-primary-foreground:focus-visible {
    color: var(--primary-foreground) !important;
  }
  .focus-visible\:ring-3:focus-visible {
    --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(3px + 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-\[color\:color-mix\(in_oklch\,var\(--button-destructive\)\,transparent_80\%\)\]:focus-visible {
    --tw-ring-color: var(--button-destructive);
    @supports (color: color-mix(in lab, red, red)) {
      --tw-ring-color: color-mix(in oklch,var(--button-destructive),transparent 80%);
    }
  }
  .focus-visible\:ring-\[color\:color-mix\(in_oklch\,var\(--button-ring\)\,transparent_50\%\)\]:focus-visible {
    --tw-ring-color: var(--button-ring);
    @supports (color: color-mix(in lab, red, red)) {
      --tw-ring-color: color-mix(in oklch,var(--button-ring),transparent 50%);
    }
  }
  .active\:translate-y-0:active {
    --tw-translate-y: 0px;
    translate: var(--tw-translate-x) var(--tw-translate-y);
  }
  .active\:\!text-\[color\:var\(--button-destructive\)\]:active {
    color: var(--button-destructive) !important;
  }
  .active\:\!text-\[color\:var\(--button-secondary-foreground\)\]:active {
    color: var(--button-secondary-foreground) !important;
  }
  .active\:\!text-foreground:active {
    color: var(--foreground) !important;
  }
  .active\:\!text-primary-foreground:active {
    color: var(--primary-foreground) !important;
  }
  .active\:not-aria-\[haspopup\]\:translate-y-px:active:not([aria-haspopup]) {
    --tw-translate-y: 1px;
    translate: var(--tw-translate-x) var(--tw-translate-y);
  }
  .disabled\:pointer-events-none:disabled {
    pointer-events: none;
  }
  .disabled\:opacity-50:disabled {
    opacity: 50%;
  }
  :where([data-slot="button-group"]) .in-data-\[slot\=button-group\]\:rounded-lg {
    border-radius: var(--radius);
  }
  .has-data-\[icon\=inline-end\]\:pr-1\.5:has([data-icon="inline-end"]) {
    padding-right: calc(var(--spacing) * 1.5);
  }
  .has-data-\[icon\=inline-end\]\:pr-2:has([data-icon="inline-end"]) {
    padding-right: calc(var(--spacing) * 2);
  }
  .has-data-\[icon\=inline-end\]\:pr-3\.5:has([data-icon="inline-end"]) {
    padding-right: calc(var(--spacing) * 3.5);
  }
  .has-data-\[icon\=inline-start\]\:pl-1\.5:has([data-icon="inline-start"]) {
    padding-left: calc(var(--spacing) * 1.5);
  }
  .has-data-\[icon\=inline-start\]\:pl-2:has([data-icon="inline-start"]) {
    padding-left: calc(var(--spacing) * 2);
  }
  .has-data-\[icon\=inline-start\]\:pl-3\.5:has([data-icon="inline-start"]) {
    padding-left: calc(var(--spacing) * 3.5);
  }
  .aria-expanded\:bg-\[color\:var\(--button-secondary\)\][aria-expanded="true"] {
    background-color: var(--button-secondary);
  }
  .aria-expanded\:bg-muted[aria-expanded="true"] {
    background-color: var(--muted);
  }
  .aria-expanded\:\!text-\[color\:var\(--button-secondary-foreground\)\][aria-expanded="true"] {
    color: var(--button-secondary-foreground) !important;
  }
  .aria-expanded\:\!text-foreground[aria-expanded="true"] {
    color: var(--foreground) !important;
  }
  .aria-invalid\:border-\[color\:var\(--button-destructive\)\][aria-invalid="true"] {
    border-color: var(--button-destructive);
  }
  .aria-invalid\:ring-3[aria-invalid="true"] {
    --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(3px + 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);
  }
  .aria-invalid\:ring-\[color\:color-mix\(in_oklch\,var\(--button-destructive\)\,transparent_80\%\)\][aria-invalid="true"] {
    --tw-ring-color: var(--button-destructive);
    @supports (color: color-mix(in lab, red, red)) {
      --tw-ring-color: color-mix(in oklch,var(--button-destructive),transparent 80%);
    }
  }
  @supports (corner-shape:squircle) {
    .supports-\[corner-shape\:squircle\]\:rounded-\[11px\] {
      border-radius: 11px;
    }
    .supports-\[corner-shape\:squircle\]\:rounded-\[12px\] {
      border-radius: 12px;
    }
    .supports-\[corner-shape\:squircle\]\:rounded-\[14px\] {
      border-radius: 14px;
    }
    :where([data-slot="button-group"]) .in-data-\[slot\=button-group\]\:supports-\[corner-shape\:squircle\]\:rounded-\[11px\] {
      border-radius: 11px;
    }
  }
  .dark\:border-\[color\:var\(--button-input\)\]:where(.dark, .dark *) {
    border-color: var(--button-input);
  }
  .dark\:bg-\[color-mix\(in_oklch\,var\(--button-destructive\)\,transparent_80\%\)\]:where(.dark, .dark *) {
    background-color: var(--button-destructive);
    @supports (color: color-mix(in lab, red, red)) {
      background-color: color-mix(in oklch,var(--button-destructive),transparent 80%);
    }
  }
  .dark\:bg-\[color-mix\(in_oklch\,var\(--button-input\)\,transparent_70\%\)\]:where(.dark, .dark *) {
    background-color: var(--button-input);
    @supports (color: color-mix(in lab, red, red)) {
      background-color: color-mix(in oklch,var(--button-input),transparent 70%);
    }
  }
  .dark\:\[--button-destructive\:\#f87171\]:where(.dark, .dark *) {
    --button-destructive: #f87171;
  }
  .dark\:\[--button-input\:\#2b2a25\]:where(.dark, .dark *) {
    --button-input: #2b2a25;
  }
  .dark\:\[--button-primary-foreground\:\#111111\]:where(.dark, .dark *) {
    --button-primary-foreground: #111111;
  }
  .dark\:\[--button-ring\:rgba\(246\,243\,236\,0\.18\)\]:where(.dark, .dark *) {
    --button-ring: rgba(246,243,236,0.18);
  }
  .dark\:\[--button-ripple\:color-mix\(in_oklch\,\#111111\,transparent_10\%\)\]:where(.dark, .dark *) {
    --button-ripple: color-mix(in oklch,#111111,transparent 10%);
  }
  .dark\:\[--button-ripple\:color-mix\(in_oklch\,var\(--button-destructive\)\,transparent_20\%\)\]:where(.dark, .dark *) {
    --button-ripple: var(--button-destructive);
    @supports (color: color-mix(in lab, red, red)) {
      --button-ripple: color-mix(in oklch,var(--button-destructive),transparent 20%);
    }
  }
  .dark\:\[--button-ripple\:color-mix\(in_oklch\,var\(--button-secondary-foreground\)\,transparent_20\%\)\]:where(.dark, .dark *) {
    --button-ripple: var(--button-secondary-foreground);
    @supports (color: color-mix(in lab, red, red)) {
      --button-ripple: color-mix(in oklch,var(--button-secondary-foreground),transparent 20%);
    }
  }
  .dark\:\[--button-ripple\:color-mix\(in_oklch\,var\(--foreground\)\,transparent_28\%\)\]:where(.dark, .dark *) {
    --button-ripple: var(--foreground);
    @supports (color: color-mix(in lab, red, red)) {
      --button-ripple: color-mix(in oklch,var(--foreground),transparent 28%);
    }
  }
  .dark\:\[--button-ripple\:color-mix\(in_oklch\,var\(--foreground\)\,transparent_30\%\)\]:where(.dark, .dark *) {
    --button-ripple: var(--foreground);
    @supports (color: color-mix(in lab, red, red)) {
      --button-ripple: color-mix(in oklch,var(--foreground),transparent 30%);
    }
  }
  .dark\:\[--button-secondary-foreground\:\#f6f3ec\]:where(.dark, .dark *) {
    --button-secondary-foreground: #f6f3ec;
  }
  .dark\:\[--button-secondary\:\#2a2a27\]:where(.dark, .dark *) {
    --button-secondary: #2a2a27;
  }
  .dark\:\[--ic-accent-foreground\:\#f6f3ec\]:where(.dark, .dark *) {
    --ic-accent-foreground: #f6f3ec;
  }
  .dark\:\[--ic-accent\:\#1a1a18\]:where(.dark, .dark *) {
    --ic-accent: #1a1a18;
  }
  .dark\:\[--ic-background\:\#111111\]:where(.dark, .dark *) {
    --ic-background: #111111;
  }
  .dark\:\[--ic-border\:\#2b2a25\]:where(.dark, .dark *) {
    --ic-border: #2b2a25;
  }
  .dark\:\[--ic-brand-soft\:\#0c4a6e\]:where(.dark, .dark *) {
    --ic-brand-soft: #0c4a6e;
  }
  .dark\:\[--ic-brand\:\#38bdf8\]:where(.dark, .dark *) {
    --ic-brand: #38bdf8;
  }
  .dark\:\[--ic-card-foreground\:\#f6f3ec\]:where(.dark, .dark *) {
    --ic-card-foreground: #f6f3ec;
  }
  .dark\:\[--ic-card\:\#111111\]:where(.dark, .dark *) {
    --ic-card: #111111;
  }
  .dark\:\[--ic-chart-1\:oklch\(0\.68_0\.17_250\)\]:where(.dark, .dark *) {
    --ic-chart-1: oklch(0.68 0.17 250);
  }
  .dark\:\[--ic-chart-2\:oklch\(0\.82_0\.09_225\)\]:where(.dark, .dark *) {
    --ic-chart-2: oklch(0.82 0.09 225);
  }
  .dark\:\[--ic-chart-3\:oklch\(0\.58_0\.15_260\)\]:where(.dark, .dark *) {
    --ic-chart-3: oklch(0.58 0.15 260);
  }
  .dark\:\[--ic-chart-4\:oklch\(0\.75_0\.12_235\)\]:where(.dark, .dark *) {
    --ic-chart-4: oklch(0.75 0.12 235);
  }
  .dark\:\[--ic-chart-5\:oklch\(0\.88_0\.06_220\)\]:where(.dark, .dark *) {
    --ic-chart-5: oklch(0.88 0.06 220);
  }
  .dark\:\[--ic-destructive\:\#f87171\]:where(.dark, .dark *) {
    --ic-destructive: #f87171;
  }
  .dark\:\[--ic-foreground\:\#f6f3ec\]:where(.dark, .dark *) {
    --ic-foreground: #f6f3ec;
  }
  .dark\:\[--ic-input\:\#2b2a25\]:where(.dark, .dark *) {
    --ic-input: #2b2a25;
  }
  .dark\:\[--ic-muted-foreground\:\#9a958a\]:where(.dark, .dark *) {
    --ic-muted-foreground: #9a958a;
  }
  .dark\:\[--ic-muted\:\#171716\]:where(.dark, .dark *) {
    --ic-muted: #171716;
  }
  .dark\:\[--ic-paper\:\#171716\]:where(.dark, .dark *) {
    --ic-paper: #171716;
  }
  .dark\:\[--ic-popover-foreground\:\#f6f3ec\]:where(.dark, .dark *) {
    --ic-popover-foreground: #f6f3ec;
  }
  .dark\:\[--ic-primary\:\#f6f3ec\]:where(.dark, .dark *) {
    --ic-primary: #f6f3ec;
  }
  .dark\:\[--ic-ring\:rgba\(246\,243\,236\,0\.18\)\]:where(.dark, .dark *) {
    --ic-ring: rgba(246,243,236,0.18);
  }
  .dark\:\[--ic-secondary\:\#cbc6bb\]:where(.dark, .dark *) {
    --ic-secondary: #cbc6bb;
  }
  .dark\:\[--ic-shadow-soft\:0_20px_44px_-28px_rgba\(0\,0\,0\,0\.6\)\]:where(.dark, .dark *) {
    --ic-shadow-soft: 0 20px 44px -28px rgba(0,0,0,0.6);
  }
  .dark\:\[--ic-surface-border\:\#2a2a25\]:where(.dark, .dark *) {
    --ic-surface-border: #2a2a25;
  }
  @media (hover: hover) {
    .dark\:hover\:bg-\[color-mix\(in_oklch\,var\(--button-destructive\)\,transparent_70\%\)\]:where(.dark, .dark *):hover {
      background-color: var(--button-destructive);
    }
    @supports (color: color-mix(in lab, red, red)) {
      .dark\:hover\:bg-\[color-mix\(in_oklch\,var\(--button-destructive\)\,transparent_70\%\)\]:where(.dark, .dark *):hover {
        background-color: color-mix(in oklch,var(--button-destructive),transparent 70%);
      }
    }
    .dark\:hover\:bg-\[color-mix\(in_oklch\,var\(--button-input\)\,transparent_50\%\)\]:where(.dark, .dark *):hover {
      background-color: var(--button-input);
    }
    @supports (color: color-mix(in lab, red, red)) {
      .dark\:hover\:bg-\[color-mix\(in_oklch\,var\(--button-input\)\,transparent_50\%\)\]:where(.dark, .dark *):hover {
        background-color: color-mix(in oklch,var(--button-input),transparent 50%);
      }
    }
    .dark\:hover\:bg-\[color-mix\(in_oklch\,var\(--button-secondary\)\,var\(--foreground\)_11\%\)\]:where(.dark, .dark *):hover {
      background-color: var(--button-secondary);
    }
    @supports (color: color-mix(in lab, red, red)) {
      .dark\:hover\:bg-\[color-mix\(in_oklch\,var\(--button-secondary\)\,var\(--foreground\)_11\%\)\]:where(.dark, .dark *):hover {
        background-color: color-mix(in oklch,var(--button-secondary),var(--foreground) 11%);
      }
    }
    .dark\:hover\:bg-muted\/50:where(.dark, .dark *):hover {
      background-color: var(--muted);
    }
    @supports (color: color-mix(in lab, red, red)) {
      .dark\:hover\:bg-muted\/50:where(.dark, .dark *):hover {
        background-color: color-mix(in oklab, var(--muted) 50%, transparent);
      }
    }
  }
  .dark\:focus-visible\:ring-\[color\:color-mix\(in_oklch\,var\(--button-destructive\)\,transparent_60\%\)\]:where(.dark, .dark *):focus-visible {
    --tw-ring-color: var(--button-destructive);
    @supports (color: color-mix(in lab, red, red)) {
      --tw-ring-color: color-mix(in oklch,var(--button-destructive),transparent 60%);
    }
  }
  .dark\:aria-invalid\:border-\[color\:color-mix\(in_oklch\,var\(--button-destructive\)\,transparent_50\%\)\]:where(.dark, .dark *)[aria-invalid="true"] {
    border-color: var(--button-destructive);
    @supports (color: color-mix(in lab, red, red)) {
      border-color: color-mix(in oklch,var(--button-destructive),transparent 50%);
    }
  }
  .dark\:aria-invalid\:ring-\[color\:color-mix\(in_oklch\,var\(--button-destructive\)\,transparent_60\%\)\]:where(.dark, .dark *)[aria-invalid="true"] {
    --tw-ring-color: var(--button-destructive);
    @supports (color: color-mix(in lab, red, red)) {
      --tw-ring-color: color-mix(in oklch,var(--button-destructive),transparent 60%);
    }
  }
  .\[\&_\[data-link-label\]\]\:text-\[1em\] [data-link-label] {
    font-size: 1em;
  }
  .\[\&_\[data-link-label\]\]\:font-medium [data-link-label] {
    --tw-font-weight: var(--font-weight-medium);
    font-weight: var(--font-weight-medium);
  }
  .\[\&_svg\]\:pointer-events-none svg {
    pointer-events: none;
  }
  .\[\&_svg\]\:shrink-0 svg {
    flex-shrink: 0;
  }
  .\[\&_svg\]\:text-\[color\:var\(--button-destructive\)\] svg {
    color: var(--button-destructive);
  }
  .\[\&_svg\]\:text-\[color\:var\(--button-secondary-foreground\)\] svg {
    color: var(--button-secondary-foreground);
  }
  .\[\&_svg\]\:text-foreground svg {
    color: var(--foreground);
  }
  .\[\&_svg\]\:text-primary-foreground svg {
    color: var(--primary-foreground);
  }
  .\[\&_svg\:not\(\[class\*\=\'size-\'\]\)\]\:size-3 svg:not([class*='size-']) {
    width: calc(var(--spacing) * 3);
    height: calc(var(--spacing) * 3);
  }
  .\[\&_svg\:not\(\[class\*\=\'size-\'\]\)\]\:size-3\.5 svg:not([class*='size-']) {
    width: calc(var(--spacing) * 3.5);
    height: calc(var(--spacing) * 3.5);
  }
  .\[\&_svg\:not\(\[class\*\=\'size-\'\]\)\]\:size-4 svg:not([class*='size-']) {
    width: calc(var(--spacing) * 4);
    height: calc(var(--spacing) * 4);
  }
}
: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 {
  --header-height-mobile: 3.5rem;
  --header-height-desktop: 3.5rem;
  --nav-stack-height-mobile: var(--header-height-mobile);
  --nav-stack-height-desktop: var(--header-height-desktop);
  --background: #ffffff;
  --foreground: #111111;
  --primary: #111111;
  --primary-foreground: #ffffff;
  --secondary: #646b75;
  --secondary-foreground: #ffffff;
  --surface-border: #e9edf2;
  --border: #e3e7ec;
  --card: #ffffff;
  --card-foreground: #111111;
  --muted: #f5f7fa;
  --muted-foreground: #6d7480;
  --accent: #f3f5f8;
  --accent-foreground: #111111;
  --input: #e3e7ec;
  --ring: rgba(17, 17, 17, 0.16);
  --destructive: #dc2626;
  --accent-pro: #0ea5e9;
  --sidebar-accent: #f5f7fa;
  --sidebar-accent-foreground: #111111;
  --separator-dash: rgb(227 231 236 / 1);
  --selection-bg: #cfeeff;
  --selection-fg: #0f172a;
  --nav-menu-surface: #f4f4f5;
  --nav-menu-item-hover: #e4e4e7;
  --shadow-card: inset 0 1px 0 rgb(255 255 255 / 88%), inset 0 -1px 0 rgb(0 0 0 / 5%), 0 1px
    1px rgb(0 0 0 / 3%), 0 8px 24px rgb(0 0 0 / 4%);
  --radius: 0.4rem;
}
.dark {
  --background: #111111;
  --foreground: #f6f3ec;
  --secondary-bg: #171716;
  --secondary: #cbc6bb;
  --secondary-foreground: #111111;
  --primary: #f6f3ec;
  --primary-foreground: #111111;
  --surface-border: #2a2a25;
  --border: #2b2a25;
  --card: #111111;
  --card-foreground: #f6f3ec;
  --muted: #171716;
  --muted-foreground: #9a958a;
  --accent: #1a1a18;
  --accent-foreground: #f6f3ec;
  --input: #2b2a25;
  --ring: rgba(246, 243, 236, 0.18);
  --destructive: #f87171;
  --accent-pro: #38bdf8;
  --sidebar-accent: #262626;
  --sidebar-accent-foreground: #fff;
  --separator-dash: rgb(92 91 84 / 1);
  --selection-bg: #cfeeff;
  --selection-fg: #0f172a;
  --nav-menu-surface: #1a1a1c;
  --nav-menu-item-hover: #262628;
}
.dark .hero-brush-underline::after {
  background: #16a34a;
}
@keyframes status-blink {
  0%, 100% {
    opacity: 1;
    transform: scale(1);
  }
  50% {
    opacity: 0.28;
    transform: scale(0.82);
  }
}
@keyframes iconiq-accordion-down {
  from {
    height: 0;
  }
  to {
    height: var(--radix-accordion-content-height);
  }
}
@keyframes iconiq-accordion-up {
  from {
    height: var(--radix-accordion-content-height);
  }
  to {
    height: 0;
  }
}
.dark main .text-neutral-500 {
  color: rgb(163 163 163);
}
.dark main .text-neutral-600 {
  color: rgb(212 212 212);
}
.dark main .text-neutral-700 {
  color: rgb(230 230 230);
}
.dark main .text-neutral-800 {
  color: rgb(245 245 245);
}
.dark main .text-neutral-900 {
  color: rgb(255 255 255);
}
.dark main .text-neutral-400 {
  color: rgb(163 163 163);
}
.dark main code {
  color: rgb(212 212 212);
}
.dark main code.rounded, .dark main code.rounded-md, .dark main code[class*="bg-neutral-200"], .dark main code[class*="bg-neutral-50"] {
  background-color: rgb(38 38 38);
  color: rgb(230 230 230);
}
.dark code.rounded, .dark code.rounded-md, .dark code[class*="bg-neutral-200"] {
  background-color: rgb(38 38 38);
  color: rgb(230 230 230);
}
.dark code[class*="bg-amber-200"] {
  background-color: rgb(69 69 40);
  color: rgb(253 253 230);
}
.dark main a.text-neutral-700 {
  color: rgb(230 230 230);
}
.dark main a:hover {
  color: rgb(255 255 255);
}
.dark html[data-route-split-docs] [data-code-block] .shiki, .dark html[data-route-split-docs] [data-code-block] .shiki span {
  color: var(--shiki-dark) !important;
}
@property --tw-translate-x {
  syntax: "*";
  inherits: false;
  initial-value: 0;
}
@property --tw-translate-y {
  syntax: "*";
  inherits: false;
  initial-value: 0;
}
@property --tw-translate-z {
  syntax: "*";
  inherits: false;
  initial-value: 0;
}
@property --tw-border-style {
  syntax: "*";
  inherits: false;
  initial-value: solid;
}
@property --tw-leading {
  syntax: "*";
  inherits: false;
}
@property --tw-font-weight {
  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;
}
@property --tw-outline-style {
  syntax: "*";
  inherits: false;
  initial-value: solid;
}
@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;
}
@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}
@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-translate-x: 0;
      --tw-translate-y: 0;
      --tw-translate-z: 0;
      --tw-border-style: solid;
      --tw-leading: initial;
      --tw-font-weight: 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;
      --tw-outline-style: solid;
      --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;
    }
  }
}