Back to buttons
"use client";

import * as React from "react";
import {
  AnimatePresence,
  motion,
  useReducedMotion,
  type Variants,
} from "motion/react";
import { cn } from "@/lib/utils";

type DoubtState = "idle" | "doubting" | "success";

export interface DoubtButtonProps
  extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "onClick"> {
  /** Label shown before any doubt has been cast. Falls back to `label`. */
  children?: React.ReactNode;
  /** Label shown in the idle state when no children are provided. */
  label?: React.ReactNode;
  /**
   * Escalating confirmation prompts shown one per click. The final prompt, when
   * clicked, fires `onConfirm`. The user must click `confirmations.length + 1`
   * times total before the real action runs.
   */
  confirmations?: React.ReactNode[];
  /** Label flashed once the user finally pushes through every doubt. */
  successLabel?: React.ReactNode;
  /** Fired only after the last confirmation is clicked. */
  onConfirm?: () => void;
  /** Milliseconds to stay in the success state before resetting. Set to 0 to stay. */
  resetAfter?: number;
}

const DEFAULT_CONFIRMATIONS: string[] = [
  "Are you sure?",
  "Really, truly sure?",
  "There is no undo. Still?",
  "Think of the consequences.",
  "I am obligated to ask again.",
  "Last chance. Absolutely certain?",
];

const CheckIcon = () => (
  <svg
    xmlns="http://www.w3.org/2000/svg"
    viewBox="0 0 24 24"
    fill="none"
    stroke="currentColor"
    strokeWidth="2.25"
    strokeLinecap="round"
    strokeLinejoin="round"
    className="size-3.5 shrink-0"
    aria-hidden
  >
    <path d="M20 6 9 17l-5-5" />
  </svg>
);

const labelVariants: Variants = {
  enter: { y: 8, opacity: 0 },
  center: { y: 0, opacity: 1 },
  exit: { y: -8, opacity: 0 },
};

export const DoubtButton = React.forwardRef<
  HTMLButtonElement,
  DoubtButtonProps
>(
  (
    {
      children,
      label = "Delete everything",
      confirmations = DEFAULT_CONFIRMATIONS,
      successLabel = "Too late.",
      onConfirm,
      resetAfter = 1600,
      className,
      disabled,
      type = "button",
      ...props
    },
    ref,
  ) => {
    const reduceMotion = useReducedMotion();

    // 0 = idle; 1..steps show confirmations[index - 1]; clicking the last fires.
    const [index, setIndex] = React.useState(0);
    const [state, setState] = React.useState<DoubtState>("idle");
    const [shakeKey, setShakeKey] = React.useState(0);

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

    const steps = confirmations.length;

    const clearResetTimeout = () => {
      if (resetTimeoutRef.current !== null) {
        window.clearTimeout(resetTimeoutRef.current);
        resetTimeoutRef.current = null;
      }
    };

    React.useEffect(() => () => clearResetTimeout(), []);

    const handleClick = () => {
      if (disabled || state === "success") return;

      // No confirmations configured: behave like a normal one-shot button.
      if (steps === 0) {
        fire();
        return;
      }

      if (index < steps) {
        setIndex((i) => i + 1);
        setState("doubting");
        setShakeKey((k) => k + 1);
        return;
      }

      fire();
    };

    const fire = () => {
      clearResetTimeout();
      setState("success");
      setIndex(0);
      onConfirm?.();
      if (resetAfter > 0) {
        resetTimeoutRef.current = window.setTimeout(() => {
          setState("idle");
        }, resetAfter);
      }
    };

    const isSuccess = state === "success";

    // Share of doubts answered, drawn as a hairline along the bottom edge.
    const progress = isSuccess ? 1 : steps === 0 ? 0 : index / steps;

    const currentLabel: React.ReactNode = isSuccess
      ? successLabel
      : index === 0
        ? (children ?? label)
        : confirmations[index - 1];

    // A stable key so AnimatePresence animates between distinct prompts.
    const labelKey = isSuccess ? "success" : `step-${index}`;

    return (
      <button
        ref={ref}
        type={type}
        disabled={disabled}
        aria-live="polite"
        data-state={state}
        onClick={handleClick}
        className={cn(
          "relative inline-flex h-9 min-w-48 cursor-pointer items-center justify-center overflow-hidden rounded-[10px] px-4 text-sm font-medium text-neutral-50 outline-none select-none",
          // Graded dark surface matching MinimalButton: dark outer hairline, faint inner ring, top highlight, soft drop.
          "bg-linear-to-b from-[#353535] to-[#272727] shadow-[0_0_0_1px_rgb(0_0_0/0.9),inset_0_0_0_1px_rgb(255_255_255/0.06),inset_0_1px_0_rgb(255_255_255/0.14),0_1px_2px_rgb(0_0_0/0.25),0_4px_12px_-4px_rgb(0_0_0/0.4)]",
          "transition-[scale,filter,box-shadow] duration-300 ease-[cubic-bezier(0.34,1.35,0.64,1)] hover:brightness-110",
          "active:scale-[0.97] active:brightness-95 active:duration-100 active:ease-out active:shadow-[0_0_0_1px_rgb(0_0_0/0.9),inset_0_0_0_1px_rgb(255_255_255/0.05),inset_0_1px_0_rgb(255_255_255/0.08),0_0_1px_rgb(0_0_0/0.2),0_1px_3px_-2px_rgb(0_0_0/0.3)] motion-reduce:active:scale-100",
          "focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50",
          className,
        )}
        {...props}
      >
        <motion.span
          key={shakeKey}
          animate={
            reduceMotion || shakeKey === 0 || isSuccess
              ? undefined
              : { x: [0, -4, 4, -2, 2, 0] }
          }
          transition={{ duration: 0.3, ease: "easeInOut" }}
          className="inline-flex items-center gap-1.5"
        >
          {isSuccess ? <CheckIcon /> : null}
          <span className="relative inline-grid">
            {/* Invisible widest prompt reserves width so the button never jumps. */}
            <span
              aria-hidden
              className="invisible col-start-1 row-start-1 whitespace-nowrap"
            >
              {[label, successLabel, ...confirmations].reduce<React.ReactNode>(
                (widest, candidate) =>
                  String(extractText(candidate)).length >
                  String(extractText(widest)).length
                    ? candidate
                    : widest,
                "",
              )}
            </span>
            <AnimatePresence mode="popLayout" initial={false}>
              <motion.span
                key={labelKey}
                variants={reduceMotion ? undefined : labelVariants}
                initial="enter"
                animate="center"
                exit="exit"
                transition={{ duration: 0.18, ease: "easeOut" }}
                className="col-start-1 row-start-1 whitespace-nowrap text-center"
              >
                {currentLabel}
              </motion.span>
            </AnimatePresence>
          </span>
        </motion.span>

        {steps > 0 && (
          <span
            aria-hidden
            className="absolute inset-x-0 bottom-0 h-0.5 origin-left bg-current opacity-50 transition-transform duration-300 ease-out"
            style={{ transform: `scaleX(${progress})` }}
          />
        )}
      </button>
    );
  },
);

function extractText(node: React.ReactNode): string {
  if (node === null || node === undefined || typeof node === "boolean") {
    return "";
  }
  if (typeof node === "string" || typeof node === "number") {
    return String(node);
  }
  if (Array.isArray(node)) {
    return node.map(extractText).join("");
  }
  if (React.isValidElement<{ children?: React.ReactNode }>(node)) {
    return extractText(node.props.children);
  }
  return "";
}

DoubtButton.displayName = "DoubtButton";

export default DoubtButton;

DoubtButton

By jay sharma

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

About DoubtButton

DoubtButton is an animated React and Tailwind CSS button by jay sharma. 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/doubt-button.tsx

import { DoubtButton } from "@/components/evil-buttons/doubt-button";

export function ButtonDemo() {
  return (
    <DoubtButton
      label="Delete everything"
      successLabel="Too late."
      onConfirm={() => wipeDatabase()}
    />
  );
}

components/evil-buttons/doubt-button.tsx

"use client";

import * as React from "react";
import {
  AnimatePresence,
  motion,
  useReducedMotion,
  type Variants,
} from "motion/react";
import { cn } from "@/lib/utils";

type DoubtState = "idle" | "doubting" | "success";

export interface DoubtButtonProps
  extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "onClick"> {
  /** Label shown before any doubt has been cast. Falls back to `label`. */
  children?: React.ReactNode;
  /** Label shown in the idle state when no children are provided. */
  label?: React.ReactNode;
  /**
   * Escalating confirmation prompts shown one per click. The final prompt, when
   * clicked, fires `onConfirm`. The user must click `confirmations.length + 1`
   * times total before the real action runs.
   */
  confirmations?: React.ReactNode[];
  /** Label flashed once the user finally pushes through every doubt. */
  successLabel?: React.ReactNode;
  /** Fired only after the last confirmation is clicked. */
  onConfirm?: () => void;
  /** Milliseconds to stay in the success state before resetting. Set to 0 to stay. */
  resetAfter?: number;
}

const DEFAULT_CONFIRMATIONS: string[] = [
  "Are you sure?",
  "Really, truly sure?",
  "There is no undo. Still?",
  "Think of the consequences.",
  "I am obligated to ask again.",
  "Last chance. Absolutely certain?",
];

const CheckIcon = () => (
  <svg
    xmlns="http://www.w3.org/2000/svg"
    viewBox="0 0 24 24"
    fill="none"
    stroke="currentColor"
    strokeWidth="2.25"
    strokeLinecap="round"
    strokeLinejoin="round"
    className="size-3.5 shrink-0"
    aria-hidden
  >
    <path d="M20 6 9 17l-5-5" />
  </svg>
);

const labelVariants: Variants = {
  enter: { y: 8, opacity: 0 },
  center: { y: 0, opacity: 1 },
  exit: { y: -8, opacity: 0 },
};

export const DoubtButton = React.forwardRef<
  HTMLButtonElement,
  DoubtButtonProps
>(
  (
    {
      children,
      label = "Delete everything",
      confirmations = DEFAULT_CONFIRMATIONS,
      successLabel = "Too late.",
      onConfirm,
      resetAfter = 1600,
      className,
      disabled,
      type = "button",
      ...props
    },
    ref,
  ) => {
    const reduceMotion = useReducedMotion();

    // 0 = idle; 1..steps show confirmations[index - 1]; clicking the last fires.
    const [index, setIndex] = React.useState(0);
    const [state, setState] = React.useState<DoubtState>("idle");
    const [shakeKey, setShakeKey] = React.useState(0);

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

    const steps = confirmations.length;

    const clearResetTimeout = () => {
      if (resetTimeoutRef.current !== null) {
        window.clearTimeout(resetTimeoutRef.current);
        resetTimeoutRef.current = null;
      }
    };

    React.useEffect(() => () => clearResetTimeout(), []);

    const handleClick = () => {
      if (disabled || state === "success") return;

      // No confirmations configured: behave like a normal one-shot button.
      if (steps === 0) {
        fire();
        return;
      }

      if (index < steps) {
        setIndex((i) => i + 1);
        setState("doubting");
        setShakeKey((k) => k + 1);
        return;
      }

      fire();
    };

    const fire = () => {
      clearResetTimeout();
      setState("success");
      setIndex(0);
      onConfirm?.();
      if (resetAfter > 0) {
        resetTimeoutRef.current = window.setTimeout(() => {
          setState("idle");
        }, resetAfter);
      }
    };

    const isSuccess = state === "success";

    // Share of doubts answered, drawn as a hairline along the bottom edge.
    const progress = isSuccess ? 1 : steps === 0 ? 0 : index / steps;

    const currentLabel: React.ReactNode = isSuccess
      ? successLabel
      : index === 0
        ? (children ?? label)
        : confirmations[index - 1];

    // A stable key so AnimatePresence animates between distinct prompts.
    const labelKey = isSuccess ? "success" : `step-${index}`;

    return (
      <button
        ref={ref}
        type={type}
        disabled={disabled}
        aria-live="polite"
        data-state={state}
        onClick={handleClick}
        className={cn(
          "relative inline-flex h-9 min-w-48 cursor-pointer items-center justify-center overflow-hidden rounded-[10px] px-4 text-sm font-medium text-neutral-50 outline-none select-none",
          // Graded dark surface matching MinimalButton: dark outer hairline, faint inner ring, top highlight, soft drop.
          "bg-linear-to-b from-[#353535] to-[#272727] shadow-[0_0_0_1px_rgb(0_0_0/0.9),inset_0_0_0_1px_rgb(255_255_255/0.06),inset_0_1px_0_rgb(255_255_255/0.14),0_1px_2px_rgb(0_0_0/0.25),0_4px_12px_-4px_rgb(0_0_0/0.4)]",
          "transition-[scale,filter,box-shadow] duration-300 ease-[cubic-bezier(0.34,1.35,0.64,1)] hover:brightness-110",
          "active:scale-[0.97] active:brightness-95 active:duration-100 active:ease-out active:shadow-[0_0_0_1px_rgb(0_0_0/0.9),inset_0_0_0_1px_rgb(255_255_255/0.05),inset_0_1px_0_rgb(255_255_255/0.08),0_0_1px_rgb(0_0_0/0.2),0_1px_3px_-2px_rgb(0_0_0/0.3)] motion-reduce:active:scale-100",
          "focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50",
          className,
        )}
        {...props}
      >
        <motion.span
          key={shakeKey}
          animate={
            reduceMotion || shakeKey === 0 || isSuccess
              ? undefined
              : { x: [0, -4, 4, -2, 2, 0] }
          }
          transition={{ duration: 0.3, ease: "easeInOut" }}
          className="inline-flex items-center gap-1.5"
        >
          {isSuccess ? <CheckIcon /> : null}
          <span className="relative inline-grid">
            {/* Invisible widest prompt reserves width so the button never jumps. */}
            <span
              aria-hidden
              className="invisible col-start-1 row-start-1 whitespace-nowrap"
            >
              {[label, successLabel, ...confirmations].reduce<React.ReactNode>(
                (widest, candidate) =>
                  String(extractText(candidate)).length >
                  String(extractText(widest)).length
                    ? candidate
                    : widest,
                "",
              )}
            </span>
            <AnimatePresence mode="popLayout" initial={false}>
              <motion.span
                key={labelKey}
                variants={reduceMotion ? undefined : labelVariants}
                initial="enter"
                animate="center"
                exit="exit"
                transition={{ duration: 0.18, ease: "easeOut" }}
                className="col-start-1 row-start-1 whitespace-nowrap text-center"
              >
                {currentLabel}
              </motion.span>
            </AnimatePresence>
          </span>
        </motion.span>

        {steps > 0 && (
          <span
            aria-hidden
            className="absolute inset-x-0 bottom-0 h-0.5 origin-left bg-current opacity-50 transition-transform duration-300 ease-out"
            style={{ transform: `scaleX(${progress})` }}
          />
        )}
      </button>
    );
  },
);

function extractText(node: React.ReactNode): string {
  if (node === null || node === undefined || typeof node === "boolean") {
    return "";
  }
  if (typeof node === "string" || typeof node === "number") {
    return String(node);
  }
  if (Array.isArray(node)) {
    return node.map(extractText).join("");
  }
  if (React.isValidElement<{ children?: React.ReactNode }>(node)) {
    return extractText(node.props.children);
  }
  return "";
}

DoubtButton.displayName = "DoubtButton";

export default DoubtButton;

lib/utils.ts

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

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

demo.tsx

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

CSS

/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */
@layer properties;
@layer theme, base, components, utilities;
@layer theme {
  :root, :host {
    --color-neutral-50: oklch(98.5% 0 none);
    --spacing: 0.25rem;
    --text-sm: 0.875rem;
    --text-sm--line-height: calc(1.25 / 0.875);
    --font-weight-medium: 500;
    --ease-out: cubic-bezier(0, 0, 0.2, 1);
    --default-transition-duration: 150ms;
    --default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
    --default-font-family: var(--font-geist-sans), ui-sans-serif, system-ui, sans-serif;
    --default-mono-font-family: var(--font-geist-mono);
  }
}
@layer base {
  *, ::after, ::before, ::backdrop, ::file-selector-button {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
    border: 0 solid;
  }
  html, :host {
    line-height: 1.5;
    -webkit-text-size-adjust: 100%;
    tab-size: 4;
    font-family: var(--default-font-family, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");
    font-feature-settings: var(--default-font-feature-settings, normal);
    font-variation-settings: var(--default-font-variation-settings, normal);
    -webkit-tap-highlight-color: transparent;
  }
  hr {
    height: 0;
    color: inherit;
    border-top-width: 1px;
  }
  abbr:where([title]) {
    -webkit-text-decoration: underline dotted;
    text-decoration: underline dotted;
  }
  h1, h2, h3, h4, h5, h6 {
    font-size: inherit;
    font-weight: inherit;
  }
  a {
    color: inherit;
    -webkit-text-decoration: inherit;
    text-decoration: inherit;
  }
  b, strong {
    font-weight: bolder;
  }
  code, kbd, samp, pre {
    font-family: var(--default-mono-font-family, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);
    font-feature-settings: var(--default-mono-font-feature-settings, normal);
    font-variation-settings: var(--default-mono-font-variation-settings, normal);
    font-size: 1em;
  }
  small {
    font-size: 80%;
  }
  sub, sup {
    font-size: 75%;
    line-height: 0;
    position: relative;
    vertical-align: baseline;
  }
  sub {
    bottom: -0.25em;
  }
  sup {
    top: -0.5em;
  }
  table {
    text-indent: 0;
    border-color: inherit;
    border-collapse: collapse;
  }
  :-moz-focusring:where(:not(iframe)) {
    outline: auto;
  }
  progress {
    vertical-align: baseline;
  }
  summary {
    display: list-item;
  }
  ol, ul, menu {
    list-style: none;
  }
  img, svg, video, canvas, audio, iframe, embed, object {
    display: block;
    vertical-align: middle;
  }
  img, video {
    max-width: 100%;
    height: auto;
  }
  button, input, select, optgroup, textarea, ::file-selector-button {
    font: inherit;
    font-feature-settings: inherit;
    font-variation-settings: inherit;
    letter-spacing: inherit;
    color: inherit;
    border-radius: 0;
    background-color: transparent;
    opacity: 1;
  }
  :where(select:is([multiple], [size])) optgroup {
    font-weight: bolder;
  }
  :where(select:is([multiple], [size])) optgroup option {
    padding-inline-start: 20px;
  }
  ::file-selector-button {
    margin-inline-end: 4px;
  }
  ::placeholder {
    opacity: 1;
  }
  @supports (not (-webkit-appearance: -apple-pay-button))  or (contain-intrinsic-size: 1px) {
    ::placeholder {
      color: currentcolor;
      @supports (color: color-mix(in lab, red, red)) {
        color: color-mix(in oklab, currentcolor 50%, transparent);
      }
    }
  }
  textarea {
    resize: vertical;
  }
  ::-webkit-search-decoration {
    -webkit-appearance: none;
  }
  ::-webkit-date-and-time-value {
    min-height: 1lh;
    text-align: inherit;
  }
  ::-webkit-datetime-edit {
    display: inline-flex;
  }
  ::-webkit-datetime-edit-fields-wrapper {
    padding: 0;
  }
  ::-webkit-datetime-edit, ::-webkit-datetime-edit-year-field, ::-webkit-datetime-edit-month-field, ::-webkit-datetime-edit-day-field, ::-webkit-datetime-edit-hour-field, ::-webkit-datetime-edit-minute-field, ::-webkit-datetime-edit-second-field, ::-webkit-datetime-edit-millisecond-field, ::-webkit-datetime-edit-meridiem-field {
    padding-block: 0;
  }
  ::-webkit-calendar-picker-indicator {
    line-height: 1;
  }
  :-moz-ui-invalid {
    box-shadow: none;
  }
  button, input:where([type="button"], [type="reset"], [type="submit"]), ::file-selector-button {
    appearance: button;
  }
  ::-webkit-inner-spin-button, ::-webkit-outer-spin-button {
    height: auto;
  }
  [hidden]:where(:not([hidden="until-found"])) {
    display: none !important;
  }
}
@layer utilities {
  .invisible {
    visibility: hidden;
  }
  .absolute {
    position: absolute;
  }
  .relative {
    position: relative;
  }
  .inset-x-0 {
    inset-inline: 0px;
  }
  .bottom-0 {
    bottom: 0px;
  }
  .col-start-1 {
    grid-column-start: 1;
  }
  .row-start-1 {
    grid-row-start: 1;
  }
  .inline-flex {
    display: inline-flex;
  }
  .inline-grid {
    display: inline-grid;
  }
  .size-3\.5 {
    width: calc(var(--spacing) * 3.5);
    height: calc(var(--spacing) * 3.5);
  }
  .h-0\.5 {
    height: calc(var(--spacing) * 0.5);
  }
  .h-9 {
    height: calc(var(--spacing) * 9);
  }
  .min-w-48 {
    min-width: calc(var(--spacing) * 48);
  }
  .shrink-0 {
    flex-shrink: 0;
  }
  .origin-left {
    transform-origin: 0;
  }
  .transform {
    transform: var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,);
  }
  .cursor-pointer {
    cursor: pointer;
  }
  .items-center {
    align-items: center;
  }
  .justify-center {
    justify-content: center;
  }
  .gap-1\.5 {
    gap: calc(var(--spacing) * 1.5);
  }
  .overflow-hidden {
    overflow: hidden;
  }
  .rounded-\[10px\] {
    border-radius: 10px;
  }
  .bg-current {
    background-color: currentcolor;
  }
  .bg-linear-to-b {
    --tw-gradient-position: to bottom;
    @supports (background-image: linear-gradient(in lab, red, red)) {
      --tw-gradient-position: to bottom in oklab;
    }
    background-image: linear-gradient(var(--tw-gradient-stops));
  }
  .from-\[\#353535\] {
    --tw-gradient-from: #353535;
    --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
  }
  .to-\[\#272727\] {
    --tw-gradient-to: #272727;
    --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
  }
  .px-4 {
    padding-inline: calc(var(--spacing) * 4);
  }
  .text-center {
    text-align: center;
  }
  .text-sm {
    font-size: var(--text-sm);
    line-height: var(--tw-leading, var(--text-sm--line-height));
  }
  .font-medium {
    --tw-font-weight: var(--font-weight-medium);
    font-weight: var(--font-weight-medium);
  }
  .whitespace-nowrap {
    white-space: nowrap;
  }
  .text-neutral-50 {
    color: var(--color-neutral-50);
  }
  .opacity-50 {
    opacity: 50%;
  }
  .shadow-\[0_0_0_1px_rgb\(0_0_0\/0\.9\)\,inset_0_0_0_1px_rgb\(255_255_255\/0\.06\)\,inset_0_1px_0_rgb\(255_255_255\/0\.14\)\,0_1px_2px_rgb\(0_0_0\/0\.25\)\,0_4px_12px_-4px_rgb\(0_0_0\/0\.4\)\] {
    --tw-shadow: 0 0 0 1px var(--tw-shadow-color, rgb(0 0 0/0.9)), inset 0 0 0 1px var(--tw-shadow-color, rgb(255 255 255/0.06)), inset 0 1px 0 var(--tw-shadow-color, rgb(255 255 255/0.14)), 0 1px 2px var(--tw-shadow-color, rgb(0 0 0/0.25)), 0 4px 12px -4px var(--tw-shadow-color, rgb(0 0 0/0.4));
    box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-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-\[scale\,filter\,box-shadow\] {
    transition-property: scale,filter,box-shadow;
    transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
    transition-duration: var(--tw-duration, var(--default-transition-duration));
  }
  .transition-transform {
    transition-property: transform, translate, scale, rotate;
    transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
    transition-duration: var(--tw-duration, var(--default-transition-duration));
  }
  .duration-300 {
    --tw-duration: 300ms;
    transition-duration: 300ms;
  }
  .ease-\[cubic-bezier\(0\.34\,1\.35\,0\.64\,1\)\] {
    --tw-ease: cubic-bezier(0.34,1.35,0.64,1);
    transition-timing-function: cubic-bezier(0.34,1.35,0.64,1);
  }
  .ease-out {
    --tw-ease: var(--ease-out);
    transition-timing-function: var(--ease-out);
  }
  .outline-none {
    --tw-outline-style: none;
    outline-style: none;
  }
  .select-none {
    -webkit-user-select: none;
    user-select: none;
  }
  @media (hover: hover) {
    .hover\:brightness-110:hover {
      --tw-brightness: brightness(110%);
      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,);
    }
  }
  .focus-visible\:ring-2:focus-visible {
    --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
    box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
  }
  .focus-visible\:ring-ring:focus-visible {
    --tw-ring-color: var(--ring);
  }
  .focus-visible\:ring-offset-2:focus-visible {
    --tw-ring-offset-width: 2px;
    --tw-ring-offset-shadow: var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
  }
  .focus-visible\:ring-offset-background:focus-visible {
    --tw-ring-offset-color: var(--background);
  }
  .active\:scale-\[0\.97\]:active {
    scale: 0.97;
  }
  .active\:shadow-\[0_0_0_1px_rgb\(0_0_0\/0\.9\)\,inset_0_0_0_1px_rgb\(255_255_255\/0\.05\)\,inset_0_1px_0_rgb\(255_255_255\/0\.08\)\,0_0_1px_rgb\(0_0_0\/0\.2\)\,0_1px_3px_-2px_rgb\(0_0_0\/0\.3\)\]:active {
    --tw-shadow: 0 0 0 1px var(--tw-shadow-color, rgb(0 0 0/0.9)), inset 0 0 0 1px var(--tw-shadow-color, rgb(255 255 255/0.05)), inset 0 1px 0 var(--tw-shadow-color, rgb(255 255 255/0.08)), 0 0 1px var(--tw-shadow-color, rgb(0 0 0/0.2)), 0 1px 3px -2px var(--tw-shadow-color, rgb(0 0 0/0.3));
    box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
  }
  .active\:brightness-95:active {
    --tw-brightness: brightness(95%);
    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,);
  }
  .active\:duration-100:active {
    --tw-duration: 100ms;
    transition-duration: 100ms;
  }
  .active\:ease-out:active {
    --tw-ease: var(--ease-out);
    transition-timing-function: var(--ease-out);
  }
  .disabled\:cursor-not-allowed:disabled {
    cursor: not-allowed;
  }
  .disabled\:opacity-50:disabled {
    opacity: 50%;
  }
  @media (prefers-reduced-motion: reduce) {
    .motion-reduce\:active\:scale-100:active {
      --tw-scale-x: 100%;
      --tw-scale-y: 100%;
      --tw-scale-z: 100%;
      scale: var(--tw-scale-x) var(--tw-scale-y);
    }
  }
}
: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 {
  --card: oklch(1 0 0);
  --muted: oklch(0.97 0 0);
  --muted-foreground: oklch(0.556 0 0);
  --border: oklch(0.922 0 0);
  --primary: oklch(0.205 0 0);
  --card-foreground: oklch(0.145 0 0);
  --popover: oklch(1 0 0);
  --popover-foreground: oklch(0.145 0 0);
  --primary-foreground: oklch(0.985 0 0);
  --secondary: oklch(0.97 0 0);
  --secondary-foreground: oklch(0.205 0 0);
  --accent: oklch(0.97 0 0);
  --accent-foreground: oklch(0.205 0 0);
  --destructive: oklch(0.577 0.245 27.325);
  --input: oklch(0.922 0 0);
  --ring: oklch(0.708 0 0);
  --chart-1: oklch(0.87 0 0);
  --chart-2: oklch(0.556 0 0);
  --chart-3: oklch(0.439 0 0);
  --chart-4: oklch(0.371 0 0);
  --chart-5: oklch(0.269 0 0);
  --radius: 0.625rem;
  --sidebar: oklch(0.985 0 0);
  --sidebar-foreground: oklch(0.145 0 0);
  --sidebar-primary: oklch(0.205 0 0);
  --sidebar-primary-foreground: oklch(0.985 0 0);
  --sidebar-accent: oklch(0.97 0 0);
  --sidebar-accent-foreground: oklch(0.205 0 0);
  --sidebar-border: oklch(0.922 0 0);
  --sidebar-ring: oklch(0.708 0 0);
  --background: oklch(1 0 0);
  --foreground: oklch(0.145 0 0);
  --chrome: oklch(0.967 0 0);
  --brand: oklch(0.6 0.23 29);
  --chart-background: oklch(1 0 0);
  --chart-foreground: oklch(0.145 0.004 285);
  --chart-foreground-muted: oklch(0.55 0.014 260);
  --chart-line-primary: var(--chart-1);
  --chart-line-secondary: var(--chart-2);
  --chart-crosshair: oklch(0.4 0.1828 274.34);
  --chart-grid: oklch(0.9 0 0);
  --chart-brush-border: var(--chart-grid);
  --chart-tooltip-background: oklch(0.21 0.006 285 / 0.8);
  --chart-tooltip-foreground: oklch(0.985 0 0);
  --chart-tooltip-muted: oklch(0.65 0.01 260);
  --chart-marker-background: oklch(0.97 0.005 260);
  --chart-marker-border: oklch(0.85 0.01 260);
  --chart-marker-foreground: oklch(0.3 0.01 260);
  --chart-label: oklch(0.45 0.01 260);
  --chart-scale-01: oklch(0.98 0.003 106);
  --chart-scale-02: oklch(0.92 0.008 106);
  --chart-scale-03: oklch(0.82 0.015 106);
  --chart-scale-04: oklch(0.68 0.02 106);
  --chart-scale-05: oklch(0.55 0.025 106);
  --chart-scale-pattern-color: oklch(0.96 0.005 106);
}
.dark {
  --background: oklch(0.145 0 0);
  --foreground: oklch(0.985 0 0);
  --chrome: oklch(0.18 0 0);
  --card: oklch(0.205 0 0);
  --muted: oklch(0.269 0 0);
  --muted-foreground: oklch(0.708 0 0);
  --border: oklch(1 0 0 / 10%);
  --primary: oklch(0.922 0 0);
  --card-foreground: oklch(0.985 0 0);
  --popover: oklch(0.205 0 0);
  --popover-foreground: oklch(0.985 0 0);
  --primary-foreground: oklch(0.205 0 0);
  --secondary: oklch(0.269 0 0);
  --secondary-foreground: oklch(0.985 0 0);
  --accent: oklch(0.269 0 0);
  --accent-foreground: oklch(0.985 0 0);
  --destructive: oklch(0.704 0.191 22.216);
  --input: oklch(1 0 0 / 15%);
  --ring: oklch(0.556 0 0);
  --chart-1: oklch(0.87 0 0);
  --chart-2: oklch(0.556 0 0);
  --chart-3: oklch(0.439 0 0);
  --chart-4: oklch(0.371 0 0);
  --chart-5: oklch(0.269 0 0);
  --sidebar: oklch(0.205 0 0);
  --sidebar-foreground: oklch(0.985 0 0);
  --sidebar-primary: oklch(0.922 0 0);
  --sidebar-primary-foreground: oklch(0.205 0 0);
  --sidebar-accent: oklch(0.269 0 0);
  --sidebar-accent-foreground: oklch(0.985 0 0);
  --sidebar-border: oklch(1 0 0 / 10%);
  --sidebar-ring: oklch(0.556 0 0);
  --brand: oklch(0.66 0.23 32);
  --chart-background: oklch(0.145 0 0);
  --chart-foreground: oklch(0.45 0 0);
  --chart-foreground-muted: oklch(0.65 0.01 260);
  --chart-crosshair: oklch(0.45 0 0);
  --chart-grid: oklch(0.25 0 0);
  --chart-brush-border: var(--chart-grid);
  --chart-marker-background: oklch(0.25 0.01 260);
  --chart-marker-border: oklch(0.4 0.01 260);
  --chart-marker-foreground: oklch(0.9 0 0);
  --chart-label: oklch(0.75 0.01 260);
  --chart-scale-01: oklch(0.28 0.03 270);
  --chart-scale-02: oklch(0.38 0.04 270);
  --chart-scale-03: oklch(0.48 0.05 270);
  --chart-scale-04: oklch(0.58 0.06 270);
  --chart-scale-05: oklch(0.68 0.07 270);
  --chart-scale-pattern-color: oklch(0.145 0 0);
}
@keyframes docs-dot-swing {
  0%, 100% {
    transform: translateX(0) scale(1);
  }
  45% {
    transform: translateX(-6px) scale(0.7);
  }
}
@keyframes hell-flicker {
  0%, 100% {
    opacity: 1;
  }
  41% {
    opacity: 0.7;
  }
  43% {
    opacity: 1;
  }
  77% {
    opacity: 0.85;
  }
  78% {
    opacity: 1;
  }
}
@keyframes lights-off {
  0% {
    opacity: 0;
  }
  15% {
    opacity: 1;
  }
  30% {
    opacity: 0.2;
  }
  45% {
    opacity: 1;
  }
  60% {
    opacity: 0.5;
  }
  70%, 100% {
    opacity: 1;
  }
}
@keyframes lights-on {
  0% {
    opacity: 1;
  }
  20% {
    opacity: 0;
  }
  32% {
    opacity: 1;
  }
  50% {
    opacity: 0.1;
  }
  60% {
    opacity: 0.9;
  }
  75%, 100% {
    opacity: 0;
  }
}
@keyframes lights-fade {
  to {
    opacity: 0;
  }
}
@keyframes eyes-dim {
  from {
    opacity: 0;
  }
}
@keyframes eyes-open {
  from {
    opacity: 0;
    transform: scaleY(0.05);
  }
}
@keyframes eyes-blink {
  0%, 92%, 100% {
    transform: none;
  }
  95% {
    transform: scaleY(0.08);
  }
}
@keyframes crack-life {
  0% {
    opacity: 0;
    transform: scale(0.92);
  }
  4% {
    opacity: 1;
    transform: none;
  }
  80% {
    opacity: 1;
  }
  100% {
    opacity: 0;
  }
}
@keyframes egg-wobble {
  0%, 84%, 100% {
    transform: none;
  }
  87% {
    transform: rotate(-14deg);
  }
  90% {
    transform: rotate(10deg);
  }
  93% {
    transform: rotate(-6deg);
  }
  96% {
    transform: rotate(3deg);
  }
}
@keyframes page-glitch {
  0% {
    transform: none;
    filter: none;
    clip-path: none;
  }
  12% {
    transform: translate(-6px, 2px) skewX(-2deg);
    filter: hue-rotate(90deg) saturate(3);
  }
  24% {
    transform: translate(5px, -1px);
    filter: invert(1) hue-rotate(180deg);
    clip-path: inset(18% 0 52% 0);
  }
  36% {
    transform: none;
    filter: contrast(2.2) saturate(0);
    clip-path: none;
  }
  48% {
    transform: translate(9px, 0) skewX(4deg);
    filter: hue-rotate(-60deg);
    clip-path: inset(0 0 70% 0);
  }
  60% {
    transform: translate(-3px, 3px);
    filter: none;
    clip-path: inset(40% 0 20% 0);
  }
  72% {
    transform: translate(2px, -4px) scaleY(1.02);
    filter: saturate(4) contrast(1.4);
    clip-path: none;
  }
  86% {
    transform: translate(-8px, 0);
    filter: invert(1);
    clip-path: inset(62% 0 8% 0);
  }
}
@keyframes glitch-label {
  0% {
    transform: translate(0, 0);
    opacity: 1;
  }
  33% {
    transform: translate(-4px, 2px);
    opacity: 0.6;
  }
  66% {
    transform: translate(5px, -2px);
    opacity: 1;
  }
}
@keyframes deleted-progress {
  from {
    width: 0;
  }
  to {
    width: 100%;
  }
}
@keyframes deleted-restore {
  0%, 70% {
    opacity: 1;
  }
  100% {
    opacity: 0;
  }
}
@keyframes creeper-swell {
  0% {
    transform: scale(0.3) translateY(40px);
    opacity: 0;
    filter: none;
  }
  20% {
    transform: scale(1);
    opacity: 1;
  }
  45%, 65%, 85% {
    filter: brightness(3) saturate(0);
  }
  55%, 75% {
    filter: none;
  }
  100% {
    transform: scale(1.35);
    filter: brightness(4) saturate(0);
  }
}
@keyframes creeper-flash {
  from {
    opacity: 0.85;
  }
  to {
    opacity: 0;
  }
}
@property --tw-rotate-x {
  syntax: "*";
  inherits: false;
}
@property --tw-rotate-y {
  syntax: "*";
  inherits: false;
}
@property --tw-rotate-z {
  syntax: "*";
  inherits: false;
}
@property --tw-skew-x {
  syntax: "*";
  inherits: false;
}
@property --tw-skew-y {
  syntax: "*";
  inherits: false;
}
@property --tw-gradient-position {
  syntax: "*";
  inherits: false;
}
@property --tw-gradient-from {
  syntax: "<color>";
  inherits: false;
  initial-value: #0000;
}
@property --tw-gradient-via {
  syntax: "<color>";
  inherits: false;
  initial-value: #0000;
}
@property --tw-gradient-to {
  syntax: "<color>";
  inherits: false;
  initial-value: #0000;
}
@property --tw-gradient-stops {
  syntax: "*";
  inherits: false;
}
@property --tw-gradient-via-stops {
  syntax: "*";
  inherits: false;
}
@property --tw-gradient-from-position {
  syntax: "<length-percentage>";
  inherits: false;
  initial-value: 0%;
}
@property --tw-gradient-via-position {
  syntax: "<length-percentage>";
  inherits: false;
  initial-value: 50%;
}
@property --tw-gradient-to-position {
  syntax: "<length-percentage>";
  inherits: false;
  initial-value: 100%;
}
@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-duration {
  syntax: "*";
  inherits: false;
}
@property --tw-ease {
  syntax: "*";
  inherits: false;
}
@property --tw-blur {
  syntax: "*";
  inherits: false;
}
@property --tw-brightness {
  syntax: "*";
  inherits: false;
}
@property --tw-contrast {
  syntax: "*";
  inherits: false;
}
@property --tw-grayscale {
  syntax: "*";
  inherits: false;
}
@property --tw-hue-rotate {
  syntax: "*";
  inherits: false;
}
@property --tw-invert {
  syntax: "*";
  inherits: false;
}
@property --tw-opacity {
  syntax: "*";
  inherits: false;
}
@property --tw-saturate {
  syntax: "*";
  inherits: false;
}
@property --tw-sepia {
  syntax: "*";
  inherits: false;
}
@property --tw-drop-shadow {
  syntax: "*";
  inherits: false;
}
@property --tw-drop-shadow-color {
  syntax: "*";
  inherits: false;
}
@property --tw-drop-shadow-alpha {
  syntax: "<percentage>";
  inherits: false;
  initial-value: 100%;
}
@property --tw-drop-shadow-size {
  syntax: "*";
  inherits: false;
}
@property --tw-scale-x {
  syntax: "*";
  inherits: false;
  initial-value: 1;
}
@property --tw-scale-y {
  syntax: "*";
  inherits: false;
  initial-value: 1;
}
@property --tw-scale-z {
  syntax: "*";
  inherits: false;
  initial-value: 1;
}
@layer properties {
  @supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) {
    *, ::before, ::after, ::backdrop {
      --tw-rotate-x: initial;
      --tw-rotate-y: initial;
      --tw-rotate-z: initial;
      --tw-skew-x: initial;
      --tw-skew-y: initial;
      --tw-gradient-position: initial;
      --tw-gradient-from: #0000;
      --tw-gradient-via: #0000;
      --tw-gradient-to: #0000;
      --tw-gradient-stops: initial;
      --tw-gradient-via-stops: initial;
      --tw-gradient-from-position: 0%;
      --tw-gradient-via-position: 50%;
      --tw-gradient-to-position: 100%;
      --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-duration: initial;
      --tw-ease: initial;
      --tw-blur: initial;
      --tw-brightness: initial;
      --tw-contrast: initial;
      --tw-grayscale: initial;
      --tw-hue-rotate: initial;
      --tw-invert: initial;
      --tw-opacity: initial;
      --tw-saturate: initial;
      --tw-sepia: initial;
      --tw-drop-shadow: initial;
      --tw-drop-shadow-color: initial;
      --tw-drop-shadow-alpha: 100%;
      --tw-drop-shadow-size: initial;
      --tw-scale-x: 1;
      --tw-scale-y: 1;
      --tw-scale-z: 1;
    }
  }
}