Back to buttons
"use client";

import * as React from "react";
import {
  motion,
  useMotionTemplate,
  useMotionValue,
  useReducedMotion,
  useSpring,
  useTransform,
} from "motion/react";
import { cn } from "@/lib/utils";

export interface StickyButtonProps
  extends Omit<
    React.ButtonHTMLAttributes<HTMLButtonElement>,
    "onDrag" | "onDragStart" | "onDragEnd" | "onAnimationStart"
  > {
  /** Distance in px beyond the button's edge where the pull starts. */
  radius?: number;
  /** How far the body follows the cursor, as a fraction of the cursor offset. */
  strength?: number;
  /** How far the label drifts inside the body, from 0 (fixed) to 1 (edge of the padding). */
  parallax?: number;
  /** Maximum tilt in degrees while the body is pulled. */
  tilt?: number;
}

// Largest label drift at `parallax = 1`, kept inside the button's padding.
const MAX_LABEL_SHIFT = { x: 12, y: 5 };

const BODY_SPRING = { stiffness: 170, damping: 16, mass: 0.6 };
// The label lags a little behind the body so the two layers visibly separate.
const LABEL_SPRING = { stiffness: 120, damping: 14, mass: 0.7 };

/** 1 inside the button, easing smoothly to 0 at the edge of the field. */
function falloff(t: number) {
  if (t <= 0) return 1;
  if (t >= 1) return 0;
  return 1 - t * t * (3 - 2 * t);
}

const StickyButton = React.forwardRef<HTMLButtonElement, StickyButtonProps>(
  (
    {
      children,
      className,
      radius = 120,
      strength = 0.45,
      parallax = 0.35,
      tilt = 8,
      disabled,
      type = "button",
      ...props
    },
    ref,
  ) => {
    const wrapperRef = React.useRef<HTMLSpanElement>(null);
    const reduceMotion = useReducedMotion();

    const pullX = useMotionValue(0);
    const pullY = useMotionValue(0);
    const labelPullX = useMotionValue(0);
    const labelPullY = useMotionValue(0);
    const glowX = useMotionValue(0);
    const glowY = useMotionValue(0);
    const engaged = useMotionValue(0);

    const x = useSpring(pullX, BODY_SPRING);
    const y = useSpring(pullY, BODY_SPRING);
    const labelX = useSpring(labelPullX, LABEL_SPRING);
    const labelY = useSpring(labelPullY, LABEL_SPRING);
    const glowOpacity = useSpring(engaged, { stiffness: 200, damping: 30 });

    const reach = Math.max(radius, 1) * strength;
    const rotateY = useTransform(x, [-reach, reach], [-tilt, tilt]);
    const rotateX = useTransform(y, [-reach, reach], [tilt, -tilt]);
    const glow = useMotionTemplate`radial-gradient(120px circle at ${glowX}px ${glowY}px, color-mix(in oklab, var(--foreground) 12%, transparent), transparent 70%)`;

    React.useEffect(() => {
      if (reduceMotion || disabled) return;

      const release = () => {
        pullX.set(0);
        pullY.set(0);
        labelPullX.set(0);
        labelPullY.set(0);
        engaged.set(0);
      };

      const onPointerMove = (event: PointerEvent) => {
        if (event.pointerType === "touch") return;
        const wrapper = wrapperRef.current;
        if (!wrapper) return;

        // The wrapper never moves, so it is a stable resting frame to measure from.
        const rect = wrapper.getBoundingClientRect();
        const dx = event.clientX - (rect.left + rect.width / 2);
        const dy = event.clientY - (rect.top + rect.height / 2);
        const outsideX = Math.max(Math.abs(dx) - rect.width / 2, 0);
        const outsideY = Math.max(Math.abs(dy) - rect.height / 2, 0);
        const pull = falloff(Math.hypot(outsideX, outsideY) / radius);

        if (pull === 0) {
          release();
          return;
        }

        pullX.set(dx * strength * pull);
        pullY.set(dy * strength * pull);
        const nx = Math.max(-1, Math.min(1, dx / (rect.width / 2)));
        const ny = Math.max(-1, Math.min(1, dy / (rect.height / 2)));
        labelPullX.set(nx * MAX_LABEL_SHIFT.x * parallax * pull);
        labelPullY.set(ny * MAX_LABEL_SHIFT.y * parallax * pull);
        glowX.set(event.clientX - rect.left);
        glowY.set(event.clientY - rect.top);
        engaged.set(pull);
      };

      window.addEventListener("pointermove", onPointerMove, { passive: true });
      document.documentElement.addEventListener("pointerleave", release);
      window.addEventListener("blur", release);
      return () => {
        window.removeEventListener("pointermove", onPointerMove);
        document.documentElement.removeEventListener("pointerleave", release);
        window.removeEventListener("blur", release);
        release();
      };
    }, [
      radius,
      strength,
      parallax,
      reduceMotion,
      disabled,
      pullX,
      pullY,
      labelPullX,
      labelPullY,
      glowX,
      glowY,
      engaged,
    ]);

    return (
      <span
        ref={wrapperRef}
        className="relative inline-flex [perspective:600px]"
      >
        <motion.button
          ref={ref}
          type={type}
          disabled={disabled}
          style={{ x, y, rotateX, rotateY }}
          whileTap={disabled ? undefined : { scale: 0.95 }}
          transition={{ type: "spring", stiffness: 400, damping: 22 }}
          className={cn(
            "relative inline-flex h-9 cursor-pointer items-center justify-center rounded-md border border-dashed border-border bg-background px-4 text-sm font-medium text-foreground shadow-[0_1px_2px_rgb(0_0_0/0.05),0_2px_6px_-3px_rgb(0_0_0/0.07),inset_0_-1px_0_rgb(0_0_0/0.03)] dark:shadow-[0_1px_2px_rgb(0_0_0/0.4),inset_0_1px_0_rgb(255_255_255/0.05)] outline-none select-none transform-3d",
            "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}
        >
          {/* Soft spotlight that tracks the cursor across the face. */}
          <motion.span
            aria-hidden
            className="pointer-events-none absolute inset-0 rounded-[inherit]"
            style={{ backgroundImage: glow, opacity: glowOpacity }}
          />
          <motion.span
            className="relative inline-flex items-center gap-1.5 whitespace-nowrap"
            style={{ x: labelX, y: labelY }}
          >
            {children}
          </motion.span>
        </motion.button>
      </span>
    );
  },
);

StickyButton.displayName = "StickyButton";

export { StickyButton };
export default StickyButton;

Button Demo

By jay sharma

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

About Button Demo

Button Demo 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/sticky-button.tsx

import StickyButton from "@/components/evil-buttons/sticky";

export function ButtonDemo() {
  return <StickyButton>Launch</StickyButton>;
}

components/evil-buttons/sticky.tsx

"use client";

import * as React from "react";
import {
  motion,
  useMotionTemplate,
  useMotionValue,
  useReducedMotion,
  useSpring,
  useTransform,
} from "motion/react";
import { cn } from "@/lib/utils";

export interface StickyButtonProps
  extends Omit<
    React.ButtonHTMLAttributes<HTMLButtonElement>,
    "onDrag" | "onDragStart" | "onDragEnd" | "onAnimationStart"
  > {
  /** Distance in px beyond the button's edge where the pull starts. */
  radius?: number;
  /** How far the body follows the cursor, as a fraction of the cursor offset. */
  strength?: number;
  /** How far the label drifts inside the body, from 0 (fixed) to 1 (edge of the padding). */
  parallax?: number;
  /** Maximum tilt in degrees while the body is pulled. */
  tilt?: number;
}

// Largest label drift at `parallax = 1`, kept inside the button's padding.
const MAX_LABEL_SHIFT = { x: 12, y: 5 };

const BODY_SPRING = { stiffness: 170, damping: 16, mass: 0.6 };
// The label lags a little behind the body so the two layers visibly separate.
const LABEL_SPRING = { stiffness: 120, damping: 14, mass: 0.7 };

/** 1 inside the button, easing smoothly to 0 at the edge of the field. */
function falloff(t: number) {
  if (t <= 0) return 1;
  if (t >= 1) return 0;
  return 1 - t * t * (3 - 2 * t);
}

const StickyButton = React.forwardRef<HTMLButtonElement, StickyButtonProps>(
  (
    {
      children,
      className,
      radius = 120,
      strength = 0.45,
      parallax = 0.35,
      tilt = 8,
      disabled,
      type = "button",
      ...props
    },
    ref,
  ) => {
    const wrapperRef = React.useRef<HTMLSpanElement>(null);
    const reduceMotion = useReducedMotion();

    const pullX = useMotionValue(0);
    const pullY = useMotionValue(0);
    const labelPullX = useMotionValue(0);
    const labelPullY = useMotionValue(0);
    const glowX = useMotionValue(0);
    const glowY = useMotionValue(0);
    const engaged = useMotionValue(0);

    const x = useSpring(pullX, BODY_SPRING);
    const y = useSpring(pullY, BODY_SPRING);
    const labelX = useSpring(labelPullX, LABEL_SPRING);
    const labelY = useSpring(labelPullY, LABEL_SPRING);
    const glowOpacity = useSpring(engaged, { stiffness: 200, damping: 30 });

    const reach = Math.max(radius, 1) * strength;
    const rotateY = useTransform(x, [-reach, reach], [-tilt, tilt]);
    const rotateX = useTransform(y, [-reach, reach], [tilt, -tilt]);
    const glow = useMotionTemplate`radial-gradient(120px circle at ${glowX}px ${glowY}px, color-mix(in oklab, var(--foreground) 12%, transparent), transparent 70%)`;

    React.useEffect(() => {
      if (reduceMotion || disabled) return;

      const release = () => {
        pullX.set(0);
        pullY.set(0);
        labelPullX.set(0);
        labelPullY.set(0);
        engaged.set(0);
      };

      const onPointerMove = (event: PointerEvent) => {
        if (event.pointerType === "touch") return;
        const wrapper = wrapperRef.current;
        if (!wrapper) return;

        // The wrapper never moves, so it is a stable resting frame to measure from.
        const rect = wrapper.getBoundingClientRect();
        const dx = event.clientX - (rect.left + rect.width / 2);
        const dy = event.clientY - (rect.top + rect.height / 2);
        const outsideX = Math.max(Math.abs(dx) - rect.width / 2, 0);
        const outsideY = Math.max(Math.abs(dy) - rect.height / 2, 0);
        const pull = falloff(Math.hypot(outsideX, outsideY) / radius);

        if (pull === 0) {
          release();
          return;
        }

        pullX.set(dx * strength * pull);
        pullY.set(dy * strength * pull);
        const nx = Math.max(-1, Math.min(1, dx / (rect.width / 2)));
        const ny = Math.max(-1, Math.min(1, dy / (rect.height / 2)));
        labelPullX.set(nx * MAX_LABEL_SHIFT.x * parallax * pull);
        labelPullY.set(ny * MAX_LABEL_SHIFT.y * parallax * pull);
        glowX.set(event.clientX - rect.left);
        glowY.set(event.clientY - rect.top);
        engaged.set(pull);
      };

      window.addEventListener("pointermove", onPointerMove, { passive: true });
      document.documentElement.addEventListener("pointerleave", release);
      window.addEventListener("blur", release);
      return () => {
        window.removeEventListener("pointermove", onPointerMove);
        document.documentElement.removeEventListener("pointerleave", release);
        window.removeEventListener("blur", release);
        release();
      };
    }, [
      radius,
      strength,
      parallax,
      reduceMotion,
      disabled,
      pullX,
      pullY,
      labelPullX,
      labelPullY,
      glowX,
      glowY,
      engaged,
    ]);

    return (
      <span
        ref={wrapperRef}
        className="relative inline-flex [perspective:600px]"
      >
        <motion.button
          ref={ref}
          type={type}
          disabled={disabled}
          style={{ x, y, rotateX, rotateY }}
          whileTap={disabled ? undefined : { scale: 0.95 }}
          transition={{ type: "spring", stiffness: 400, damping: 22 }}
          className={cn(
            "relative inline-flex h-9 cursor-pointer items-center justify-center rounded-md border border-dashed border-border bg-background px-4 text-sm font-medium text-foreground shadow-[0_1px_2px_rgb(0_0_0/0.05),0_2px_6px_-3px_rgb(0_0_0/0.07),inset_0_-1px_0_rgb(0_0_0/0.03)] dark:shadow-[0_1px_2px_rgb(0_0_0/0.4),inset_0_1px_0_rgb(255_255_255/0.05)] outline-none select-none transform-3d",
            "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}
        >
          {/* Soft spotlight that tracks the cursor across the face. */}
          <motion.span
            aria-hidden
            className="pointer-events-none absolute inset-0 rounded-[inherit]"
            style={{ backgroundImage: glow, opacity: glowOpacity }}
          />
          <motion.span
            className="relative inline-flex items-center gap-1.5 whitespace-nowrap"
            style={{ x: labelX, y: labelY }}
          >
            {children}
          </motion.span>
        </motion.button>
      </span>
    );
  },
);

StickyButton.displayName = "StickyButton";

export { StickyButton };
export default StickyButton;

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/sticky-button";

CSS

/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */
@layer properties;
@layer theme, base, components, utilities;
@layer theme {
  :root, :host {
    --spacing: 0.25rem;
    --text-sm: 0.875rem;
    --text-sm--line-height: calc(1.25 / 0.875);
    --font-weight-medium: 500;
    --default-transition-duration: 150ms;
    --default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
    --default-font-family: var(--font-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 {
  .pointer-events-none {
    pointer-events: none;
  }
  .absolute {
    position: absolute;
  }
  .relative {
    position: relative;
  }
  .inset-0 {
    inset: 0px;
  }
  .inline-flex {
    display: inline-flex;
  }
  .h-9 {
    height: calc(var(--spacing) * 9);
  }
  .cursor-pointer {
    cursor: pointer;
  }
  .items-center {
    align-items: center;
  }
  .justify-center {
    justify-content: center;
  }
  .gap-1\.5 {
    gap: calc(var(--spacing) * 1.5);
  }
  .rounded-\[inherit\] {
    border-radius: inherit;
  }
  .rounded-md {
    border-radius: calc(var(--radius) * 0.8);
  }
  .border {
    border-style: var(--tw-border-style);
    border-width: 1px;
  }
  .border-dashed {
    --tw-border-style: dashed;
    border-style: dashed;
  }
  .border-border {
    border-color: var(--border);
  }
  .bg-background {
    background-color: var(--background);
  }
  .px-4 {
    padding-inline: calc(var(--spacing) * 4);
  }
  .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-foreground {
    color: var(--foreground);
  }
  .shadow-\[0_1px_2px_rgb\(0_0_0\/0\.05\)\,0_2px_6px_-3px_rgb\(0_0_0\/0\.07\)\,inset_0_-1px_0_rgb\(0_0_0\/0\.03\)\] {
    --tw-shadow: 0 1px 2px var(--tw-shadow-color, rgb(0 0 0/0.05)), 0 2px 6px -3px var(--tw-shadow-color, rgb(0 0 0/0.07)), inset 0 -1px 0 var(--tw-shadow-color, rgb(0 0 0/0.03));
    box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
  }
  .blur {
    --tw-blur: blur(8px);
    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));
  }
  .outline-none {
    --tw-outline-style: none;
    outline-style: none;
  }
  .select-none {
    -webkit-user-select: none;
    user-select: none;
  }
  .\[perspective\:600px\] {
    perspective: 600px;
  }
  .transform-3d {
    transform-style: preserve-3d;
  }
  .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);
  }
  .disabled\:cursor-not-allowed:disabled {
    cursor: not-allowed;
  }
  .disabled\:opacity-50:disabled {
    opacity: 50%;
  }
  .dark\:shadow-\[0_1px_2px_rgb\(0_0_0\/0\.4\)\,inset_0_1px_0_rgb\(255_255_255\/0\.05\)\]:where(.dark, .dark *) {
    --tw-shadow: 0 1px 2px var(--tw-shadow-color, rgb(0 0 0/0.4)), inset 0 1px 0 var(--tw-shadow-color, rgb(255 255 255/0.05));
    box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
  }
}
: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-border-style {
  syntax: "*";
  inherits: false;
  initial-value: solid;
}
@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-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;
}
@layer properties {
  @supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) {
    *, ::before, ::after, ::backdrop {
      --tw-border-style: solid;
      --tw-font-weight: initial;
      --tw-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-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;
    }
  }
}