Back to buttons
"use client";

import { useEffect, useRef, useState } from "react";
import {
  LikeBurst,
  type LikeBurstHandle,
} from "@/components/interior/like-burst";

export function LikeBurstDemo() {
  const ref = useRef<LikeBurstHandle>(null);
  const [liked, setLiked] = useState(false);

  useEffect(() => {
    if (!liked) return;
    const back = setTimeout(() => ref.current?.toggle(), 1500);
    return () => clearTimeout(back);
  }, [liked]);

  return (
    <div className="flex justify-center">
      <LikeBurst ref={ref} initialCount={128} onToggle={setLiked} />
    </div>
  );
}

Like Burst Demo

By Özer

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

About Like Burst Demo

Like Burst Demo is an animated React and Tailwind CSS button by Özer. 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

lib/demos/like-burst-demo.tsx

"use client";

import { useEffect, useRef, useState } from "react";
import {
  LikeBurst,
  type LikeBurstHandle,
} from "@/components/interior/like-burst";

export function LikeBurstDemo() {
  const ref = useRef<LikeBurstHandle>(null);
  const [liked, setLiked] = useState(false);

  useEffect(() => {
    if (!liked) return;
    const back = setTimeout(() => ref.current?.toggle(), 1500);
    return () => clearTimeout(back);
  }, [liked]);

  return (
    <div className="flex justify-center">
      <LikeBurst ref={ref} initialCount={128} onToggle={setLiked} />
    </div>
  );
}

components/interior/like-burst.tsx

"use client";

import {
  useCallback,
  useEffect,
  useImperativeHandle,
  useRef,
  useState,
} from "react";
import { AnimatePresence, motion, useReducedMotion } from "motion/react";

const EASE = [0.23, 1, 0.32, 1] as const;
const CELL = { type: "spring", stiffness: 520, damping: 34, mass: 0.45 } as const;
const CROSSFADE = { type: "spring", stiffness: 260, damping: 34, mass: 0.8 } as const;
const INSTANT = { duration: 0 } as const;

const HEART =
  "M12 20.3 4.3 12.6a4.8 4.8 0 0 1 6.8-6.8l.9.9.9-.9a4.8 4.8 0 0 1 6.8 6.8Z";

const SPARKS = Array.from({ length: 8 }, (_, i) => {
  const h = (((i + 1) * 2654435761) % 997) / 997;
  const angle = (i / 8) * Math.PI * 2 - Math.PI / 2 + (h - 0.5) * 0.4;
  const distance = 13 + h * 9;
  return {
    x: Math.round(Math.cos(angle) * distance * 10) / 10,
    y: Math.round(Math.sin(angle) * distance * 10) / 10,
    size: h > 0.5 ? 4 : 3,
    delay: Math.round(h * 50) / 1000,
  };
});

const DEFAULT_FORMAT = (value: number) =>
  new Intl.NumberFormat("en-US").format(value);

export type LikeCommit = (
  liked: boolean,
  signal: AbortSignal,
) => Promise<unknown>;

export type LikeBurstHandle = {
  toggle: () => void;
};

export type UseOptimisticLikeOptions = {
  initialLiked?: boolean;
  initialCount?: number;
  onCommit?: LikeCommit;
  onError?: (error: unknown) => void;
  settle?: number;
};

export type OptimisticLike = {
  liked: boolean;
  count: number;
  base: number;
  pending: boolean;
  burst: number;
  settled: { liked: boolean; count: number };
  toggle: () => void;
};

export function useOptimisticLike({
  initialLiked = false,
  initialCount = 0,
  onCommit,
  onError,
  settle = 400,
}: UseOptimisticLikeOptions = {}): OptimisticLike {
  const [liked, setLiked] = useState(initialLiked);
  const [count, setCount] = useState(initialCount);
  const [pending, setPending] = useState(false);
  const [burst, setBurst] = useState(0);
  const [settled, setSettled] = useState({
    liked: initialLiked,
    count: initialCount,
  });

  const likedNow = useRef(initialLiked);
  const countNow = useRef(initialCount);
  const truth = useRef({ liked: initialLiked, count: initialCount });
  const timer = useRef<ReturnType<typeof setTimeout> | null>(null);
  const inFlight = useRef<AbortController | null>(null);
  const seq = useRef(0);

  const commit = useRef(onCommit);
  commit.current = onCommit;
  const failed = useRef(onError);
  failed.current = onError;

  const flush = useCallback(() => {
    timer.current = null;
    inFlight.current?.abort();
    inFlight.current = null;
    seq.current += 1;

    const intent = likedNow.current;

    if (intent === truth.current.liked) {
      countNow.current = truth.current.count;
      setLiked(truth.current.liked);
      setCount(truth.current.count);
      setPending(false);
      return;
    }

    const target = { liked: intent, count: countNow.current };
    const run = commit.current;

    if (!run) {
      truth.current = target;
      setSettled(target);
      setPending(false);
      return;
    }

    const controller = new AbortController();
    const id = seq.current;
    inFlight.current = controller;
    setPending(true);

    run(intent, controller.signal).then(
      () => {
        if (id !== seq.current) return;
        inFlight.current = null;
        truth.current = target;
        setSettled(target);
        setPending(false);
      },
      (error: unknown) => {
        if (id !== seq.current) return;
        inFlight.current = null;
        likedNow.current = truth.current.liked;
        countNow.current = truth.current.count;
        setLiked(truth.current.liked);
        setCount(truth.current.count);
        setPending(false);
        failed.current?.(error);
      },
    );
  }, []);

  const toggle = useCallback(() => {
    const next = !likedNow.current;
    likedNow.current = next;
    countNow.current += next ? 1 : -1;

    setLiked(next);
    setCount(countNow.current);
    setPending(true);
    if (next) setBurst((b) => b + 1);

    if (timer.current) clearTimeout(timer.current);
    timer.current = setTimeout(flush, settle);
  }, [flush, settle]);

  useEffect(
    () => () => {
      if (timer.current) clearTimeout(timer.current);
      timer.current = null;
      seq.current += 1;
      inFlight.current?.abort();
      inFlight.current = null;
    },
    [],
  );

  return {
    liked,
    count,
    base: liked ? count - 1 : count,
    pending,
    burst,
    settled,
    toggle,
  };
}

export type LikeBurstProps = {
  initialLiked?: boolean;
  initialCount?: number;
  onCommit?: LikeCommit;
  onError?: (error: unknown) => void;
  onToggle?: (liked: boolean) => void;
  settle?: number;
  label?: string;
  activeLabel?: string;
  format?: (value: number) => string;
  disabled?: boolean;
  className?: string;
};

export function LikeBurst({
  initialLiked = false,
  initialCount = 0,
  onCommit,
  onError,
  onToggle,
  settle = 400,
  label = "Like",
  activeLabel = "Liked",
  format = DEFAULT_FORMAT,
  disabled = false,
  className = "",
  ref,
}: LikeBurstProps & { ref?: React.Ref<LikeBurstHandle> }) {
  const reduced = useReducedMotion();
  const { liked, count, base, pending, burst, settled, toggle } =
    useOptimisticLike({ initialLiked, initialCount, onCommit, onError, settle });

  useImperativeHandle(ref, () => ({ toggle }), [toggle]);

  const low = format(base);
  const high = format(base + 1);
  const widest = high.length >= low.length ? high : low;
  const shown = format(count);

  return (
    <span className={`inline-flex items-center ${className}`}>
      <button
        type="button"
        disabled={disabled}
        aria-pressed={liked}
        aria-busy={pending}
        aria-label={label}
        onClick={() => {
          toggle();
          onToggle?.(!liked);
        }}
        style={{ touchAction: "manipulation" }}
        className="inline-flex h-9 select-none items-center gap-2 rounded-[9px] border border-stone-200 bg-white px-3 text-[13px] font-medium text-stone-700 outline-none focus-visible:ring-2 focus-visible:ring-stone-400 disabled:opacity-50 dark:border-white/[0.16] dark:bg-[#1D1D1A] dark:text-stone-200 dark:focus-visible:ring-stone-500"
      >
        <span aria-hidden className="relative block size-[18px]">
          <motion.svg
            viewBox="0 0 24 24"
            fill="none"
            stroke="currentColor"
            strokeWidth={1.7}
            strokeLinejoin="round"
            className="absolute inset-0 size-[18px] text-stone-500 dark:text-stone-400"
            initial={false}
            animate={{ opacity: liked ? 0 : 1 }}
            transition={reduced ? INSTANT : CROSSFADE}
          >
            <path d={HEART} />
          </motion.svg>

          <motion.svg
            viewBox="0 0 24 24"
            fill="currentColor"
            className="absolute inset-0 size-[18px] text-stone-800 dark:text-stone-100"
            initial={false}
            animate={{ opacity: liked ? 1 : 0, scale: liked ? 1 : 0.55 }}
            transition={reduced ? INSTANT : CELL}
          >
            <path d={HEART} />
          </motion.svg>

          {!reduced && burst > 0 ? (
            <span
              key={burst}
              className="pointer-events-none absolute left-1/2 top-1/2 block size-0"
            >
              {SPARKS.map((spark, i) => (
                <motion.span
                  key={i}
                  className="absolute block rounded-[1.5px] bg-stone-800 dark:bg-stone-100"
                  style={{
                    width: spark.size,
                    height: spark.size,
                    marginLeft: -spark.size / 2,
                    marginTop: -spark.size / 2,
                  }}
                  initial={{ x: 0, y: 0, scale: 0.6, opacity: 0.85 }}
                  animate={{ x: spark.x, y: spark.y, scale: 1, opacity: 0 }}
                  transition={{ duration: 0.44, delay: spark.delay, ease: EASE }}
                />
              ))}
            </span>
          ) : null}
        </span>

        <span aria-hidden className="grid">
          <motion.span
            className="col-start-1 row-start-1"
            initial={false}
            animate={{ opacity: liked ? 0 : 1 }}
            transition={reduced ? INSTANT : CROSSFADE}
          >
            {label}
          </motion.span>
          <motion.span
            className="col-start-1 row-start-1"
            initial={false}
            animate={{ opacity: liked ? 1 : 0 }}
            transition={reduced ? INSTANT : CROSSFADE}
          >
            {activeLabel}
          </motion.span>
        </span>

        <span
          aria-hidden
          className="grid overflow-hidden text-[12px] tabular-nums text-stone-500 dark:text-stone-400"
        >
          <span className="invisible col-start-1 row-start-1">{widest}</span>
          <AnimatePresence initial={false}>
            <motion.span
              key={shown}
              className="col-start-1 row-start-1 justify-self-end"
              initial={{ opacity: 0, y: reduced ? 0 : -7 }}
              animate={{ opacity: 1, y: 0 }}
              exit={{ opacity: 0, y: reduced ? 0 : 7 }}
              transition={reduced ? INSTANT : CROSSFADE}
            >
              {shown}
            </motion.span>
          </AnimatePresence>
        </span>

      </button>

      <span role="status" aria-live="polite" className="sr-only">
        {`${format(settled.count)} likes, ${settled.liked ? "liked" : "not liked"}`}
      </span>
    </span>
  );
}

demo.tsx

export { LikeBurstDemo as default } from "@/lib/demos/like-burst-demo";

CSS

/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */
@layer properties;
@layer theme, base, components, utilities;
@layer theme {
  :root, :host {
    --color-stone-100: oklch(97% 0.001 106.424);
    --color-stone-200: oklch(92.3% 0.003 48.717);
    --color-stone-400: oklch(70.9% 0.01 56.259);
    --color-stone-500: oklch(55.3% 0.013 58.071);
    --color-stone-700: oklch(37.4% 0.01 67.558);
    --color-stone-800: oklch(26.8% 0.007 34.298);
    --color-white: #fff;
    --spacing: 0.25rem;
    --font-weight-medium: 500;
    --default-transition-duration: 150ms;
    --default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
    --default-font-family: var(--font-geist-sans);
    --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;
  }
  .invisible {
    visibility: hidden;
  }
  .sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border-width: 0;
  }
  .absolute {
    position: absolute;
  }
  .relative {
    position: relative;
  }
  .inset-0 {
    inset: 0px;
  }
  .top-1\/2 {
    top: calc(1 / 2 * 100%);
  }
  .left-1\/2 {
    left: calc(1 / 2 * 100%);
  }
  .col-start-1 {
    grid-column-start: 1;
  }
  .row-start-1 {
    grid-row-start: 1;
  }
  .block {
    display: block;
  }
  .flex {
    display: flex;
  }
  .grid {
    display: grid;
  }
  .inline-flex {
    display: inline-flex;
  }
  .size-0 {
    width: 0px;
    height: 0px;
  }
  .size-\[18px\] {
    width: 18px;
    height: 18px;
  }
  .h-9 {
    height: calc(var(--spacing) * 9);
  }
  .items-center {
    align-items: center;
  }
  .justify-center {
    justify-content: center;
  }
  .gap-2 {
    gap: calc(var(--spacing) * 2);
  }
  .justify-self-end {
    justify-self: flex-end;
  }
  .overflow-hidden {
    overflow: hidden;
  }
  .rounded-\[1\.5px\] {
    border-radius: 1.5px;
  }
  .rounded-\[9px\] {
    border-radius: 9px;
  }
  .border {
    border-style: var(--tw-border-style);
    border-width: 1px;
  }
  .border-stone-200 {
    border-color: var(--color-stone-200);
  }
  .bg-stone-800 {
    background-color: var(--color-stone-800);
  }
  .bg-white {
    background-color: var(--color-white);
  }
  .px-3 {
    padding-inline: calc(var(--spacing) * 3);
  }
  .text-\[12px\] {
    font-size: 12px;
  }
  .text-\[13px\] {
    font-size: 13px;
  }
  .font-medium {
    --tw-font-weight: var(--font-weight-medium);
    font-weight: var(--font-weight-medium);
  }
  .text-stone-500 {
    color: var(--color-stone-500);
  }
  .text-stone-700 {
    color: var(--color-stone-700);
  }
  .text-stone-800 {
    color: var(--color-stone-800);
  }
  .tabular-nums {
    --tw-numeric-spacing: tabular-nums;
    font-variant-numeric: var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,);
  }
  .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;
  }
  .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-stone-400:focus-visible {
    --tw-ring-color: var(--color-stone-400);
  }
  .disabled\:opacity-50:disabled {
    opacity: 50%;
  }
  .dark\:border-white\/\[0\.16\]:where(.dark, .dark *) {
    border-color: color-mix(in srgb, #fff 16%, transparent);
    @supports (color: color-mix(in lab, red, red)) {
      border-color: color-mix(in oklab, var(--color-white) 16%, transparent);
    }
  }
  .dark\:bg-\[\#1D1D1A\]:where(.dark, .dark *) {
    background-color: #1D1D1A;
  }
  .dark\:bg-stone-100:where(.dark, .dark *) {
    background-color: var(--color-stone-100);
  }
  .dark\:text-stone-100:where(.dark, .dark *) {
    color: var(--color-stone-100);
  }
  .dark\:text-stone-200:where(.dark, .dark *) {
    color: var(--color-stone-200);
  }
  .dark\:text-stone-400:where(.dark, .dark *) {
    color: var(--color-stone-400);
  }
  .dark\:focus-visible\:ring-stone-500:where(.dark, .dark *):focus-visible {
    --tw-ring-color: var(--color-stone-500);
  }
}
: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 {
  --bezel: #ffffff;
  --panel: #ffffff;
  --well: #ffffff;
  --sub: #fafafa;
  --hairline: rgba(0, 0, 0, 0.14);
  --hairline-strong: rgba(0, 0, 0, 0.26);
  --ink: #131313;
  --ink-2: #454545;
  --ink-3: #6e6e6e;
  --accent: #4568ff;
  --accent-soft: #edf1ff;
  --moss: #3d7a4e;
  --flag: #c0442f;
  --shadow-float: 0 28px 56px -24px rgba(0, 0, 0, 0.22),
    0 0 0 1px rgba(0, 0, 0, 0.06);
  --shadow-lift: 0 1px 2px rgba(0, 0, 0, 0.04),
    0 0 0 1px rgba(0, 0, 0, 0.11);
  --shadow-well: inset 0 1px 2px rgba(0, 0, 0, 0.05),
    inset 0 0 0 1px rgba(0, 0, 0, 0.1);
  --shadow-cap: 0 1.5px 0 rgba(0, 0, 0, 0.09),
    inset 0 0 0 1px rgba(0, 0, 0, 0.11);
  --shadow-row: inset 0 0 0 1px rgba(0, 0, 0, 0.07),
    0 1px 2px rgba(0, 0, 0, 0.05);
}
.dark {
  --bezel: #141312;
  --panel: #1d1d1a;
  --well: #252522;
  --sub: #1a1a17;
  --hairline: rgba(255, 255, 255, 0.16);
  --hairline-strong: rgba(255, 255, 255, 0.28);
  --ink: #f8f8f4;
  --ink-2: #c2c2ba;
  --ink-3: #93938b;
  --accent: #93b0ff;
  --accent-soft: rgba(147, 176, 255, 0.14);
  --moss: #5bd79c;
  --flag: #f5897f;
  --shadow-float: 0 28px 56px -22px rgba(0, 0, 0, 0.7),
    0 0 0 1px rgba(255, 255, 255, 0.05);
  --shadow-lift: 0 1px 2px rgba(0, 0, 0, 0.4),
    inset 0 1px 0 rgba(255, 255, 255, 0.05),
    0 0 0 1px rgba(255, 255, 255, 0.05);
  --shadow-well: inset 0 1px 3px rgba(0, 0, 0, 0.4),
    inset 0 0 0 1px rgba(255, 255, 255, 0.03);
  --shadow-cap: 0 1.5px 0 rgba(0, 0, 0, 0.5),
    inset 0 0 0 1px rgba(255, 255, 255, 0.1);
  --shadow-row: inset 0 0 0 1px rgba(255, 255, 255, 0.06),
    0 1px 2px rgba(0, 0, 0, 0.25);
}
@keyframes reaction-drift {
  to {
    transform: translateX(-50%);
  }
}
@property --tw-border-style {
  syntax: "*";
  inherits: false;
  initial-value: solid;
}
@property --tw-font-weight {
  syntax: "*";
  inherits: false;
}
@property --tw-ordinal {
  syntax: "*";
  inherits: false;
}
@property --tw-slashed-zero {
  syntax: "*";
  inherits: false;
}
@property --tw-numeric-figure {
  syntax: "*";
  inherits: false;
}
@property --tw-numeric-spacing {
  syntax: "*";
  inherits: false;
}
@property --tw-numeric-fraction {
  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;
}
@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-ordinal: initial;
      --tw-slashed-zero: initial;
      --tw-numeric-figure: initial;
      --tw-numeric-spacing: initial;
      --tw-numeric-fraction: 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;
    }
  }
}