import { DemonicButton } from "@/components/evil-buttons/demonic-button";
export function ButtonDemo() {
return (
<DemonicButton holdDuration={1800} onSummon={() => console.log("Summoned")}>
Corrupt the World
</DemonicButton>
);
}
DemonicButton
By jay sharma
Try the interaction, copy the React source files, or download the complete example.
About DemonicButton
DemonicButton 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/demonic-button.tsx
import { DemonicButton } from "@/components/evil-buttons/demonic-button";
export function ButtonDemo() {
return (
<DemonicButton holdDuration={1800} onSummon={() => console.log("Summoned")}>
Corrupt the World
</DemonicButton>
);
}
components/evil-buttons/demonic-button.tsx
"use client";
import * as React from "react";
import { cn } from "@/lib/utils";
export interface DemonicButtonProps
extends Omit<
React.ButtonHTMLAttributes<HTMLButtonElement>,
"children" | "onClick"
> {
/** Button label. Falls back to `label`. */
children?: React.ReactNode;
/** Label used when no children are provided. */
label?: React.ReactNode;
/** Milliseconds of holding needed to fully summon the demon. */
holdDuration?: number;
/** Fired once each time the charge reaches 100%. */
onSummon?: () => void;
}
/** Extra canvas room around the button so flames can spill past its edges. */
const FIRE_BLEED_X = 40;
const FIRE_HEIGHT = 150;
/** Charge drains this many times faster than it fills once released. */
const DRAIN_RATE = 2.4;
type Particle = {
x: number;
y: number;
vx: number;
vy: number;
age: number;
life: number;
size: number;
seed: number;
};
// Blackbody-ish ramp: white-hot core, through orange and red, into smoke.
const FIRE_RAMP: [number, [number, number, number, number]][] = [
[0, [255, 244, 214, 1]],
[0.18, [255, 196, 92, 0.95]],
[0.42, [255, 118, 38, 0.8]],
[0.68, [206, 48, 24, 0.5]],
[0.86, [96, 24, 18, 0.22]],
[1, [40, 20, 20, 0]],
];
const SPRITE_STEPS = 24;
const SPRITE_SIZE = 64;
function sampleRamp(t: number) {
for (let i = 1; i < FIRE_RAMP.length; i++) {
const [t1, c1] = FIRE_RAMP[i];
if (t <= t1) {
const [t0, c0] = FIRE_RAMP[i - 1];
const k = (t - t0) / (t1 - t0);
return c0.map((v, j) => v + (c1[j] - v) * k) as [
number,
number,
number,
number,
];
}
}
return FIRE_RAMP[FIRE_RAMP.length - 1][1];
}
/** Pre-rendered soft radial blobs, one per step of the fire ramp. */
function createSprites() {
return Array.from({ length: SPRITE_STEPS }, (_, i) => {
const [r, g, b, a] = sampleRamp(i / (SPRITE_STEPS - 1));
const canvas = document.createElement("canvas");
canvas.width = canvas.height = SPRITE_SIZE;
const ctx = canvas.getContext("2d");
if (ctx) {
const half = SPRITE_SIZE / 2;
const gradient = ctx.createRadialGradient(half, half, 0, half, half, half);
gradient.addColorStop(0, `rgba(${r}, ${g}, ${b}, ${a})`);
gradient.addColorStop(0.45, `rgba(${r}, ${g}, ${b}, ${a * 0.55})`);
gradient.addColorStop(1, `rgba(${r}, ${g}, ${b}, 0)`);
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, SPRITE_SIZE, SPRITE_SIZE);
}
return canvas;
});
}
function Horn({ side }: { side: "left" | "right" }) {
const id = React.useId().replace(/:/g, "");
// Outer edge sweeps out and up to the tip, inner edge curls back to the base.
const shape =
"M13 64 C6 50 1.5 33 6.5 15 C8 9.5 10.5 5 13 2 C12.5 12 15 26 21 38 C24.5 45 28.5 53 31 64 Z";
return (
<svg
viewBox="0 0 34 66"
className={cn("h-full w-full", side === "right" && "-scale-x-100")}
aria-hidden
>
<defs>
<linearGradient id={`${id}-body`} x1="0.5" y1="1" x2="0.2" y2="0">
<stop offset="0" stopColor="#1c0907" />
<stop offset="0.45" stopColor="#4a120d" />
<stop offset="0.8" stopColor="#8a3a24" />
<stop offset="1" stopColor="#e9d6bf" />
</linearGradient>
<linearGradient id={`${id}-shade`} x1="0" y1="0" x2="1" y2="0">
<stop offset="0" stopColor="#ffffff" stopOpacity="0.18" />
<stop offset="0.35" stopColor="#ffffff" stopOpacity="0" />
<stop offset="0.75" stopColor="#000000" stopOpacity="0" />
<stop offset="1" stopColor="#000000" stopOpacity="0.45" />
</linearGradient>
<clipPath id={`${id}-clip`}>
<path d={shape} />
</clipPath>
</defs>
<path d={shape} fill={`url(#${id}-body)`} />
<g clipPath={`url(#${id}-clip)`}>
<rect width="34" height="66" fill={`url(#${id}-shade)`} />
{/* Growth ridges wrapping around the horn. */}
<g
fill="none"
stroke="#000000"
strokeOpacity="0.38"
strokeWidth="0.9"
strokeLinecap="round"
>
<path d="M3 56 Q17 50 32 58" />
<path d="M2 48 Q15 42 28 49" />
<path d="M2 40 Q13 35 25 41" />
<path d="M2.5 32 Q12 28 21.5 33" />
<path d="M3.5 25 Q11 21.5 18.5 25.5" />
<path d="M5 18 Q10.5 15.5 16 18.5" />
<path d="M7 12 Q10.5 10.5 14.5 12.5" />
</g>
<path
d="M8 58 C4 44 3 30 7.5 16"
fill="none"
stroke="#ffd9b8"
strokeOpacity="0.28"
strokeWidth="1.2"
strokeLinecap="round"
/>
</g>
</svg>
);
}
export const DemonicButton = React.forwardRef<
HTMLButtonElement,
DemonicButtonProps
>(
(
{
children,
label = "Summon",
holdDuration = 2200,
onSummon,
className,
disabled,
type = "button",
...props
},
ref,
) => {
const wrapperRef = React.useRef<HTMLDivElement>(null);
const buttonRef = React.useRef<HTMLButtonElement | null>(null);
const canvasRef = React.useRef<HTMLCanvasElement>(null);
const glowRef = React.useRef<HTMLSpanElement>(null);
const leftHornRef = React.useRef<HTMLSpanElement>(null);
const rightHornRef = React.useRef<HTMLSpanElement>(null);
const holdingRef = React.useRef(false);
const progressRef = React.useRef(0);
const summonedRef = React.useRef(false);
const particlesRef = React.useRef<Particle[]>([]);
const spawnDebtRef = React.useRef(0);
const spritesRef = React.useRef<HTMLCanvasElement[] | null>(null);
const rafRef = React.useRef<number | null>(null);
const lastTimeRef = React.useRef(0);
const sizeRef = React.useRef({ width: 0, height: 0, dpr: 1 });
const reduceMotionRef = React.useRef(false);
const onSummonRef = React.useRef(onSummon);
const [summoned, setSummoned] = React.useState(false);
React.useEffect(() => {
onSummonRef.current = onSummon;
}, [onSummon]);
const setButtonRef = (node: HTMLButtonElement | null) => {
buttonRef.current = node;
if (typeof ref === "function") ref(node);
else if (ref) ref.current = node;
};
// Keep the canvas backing store matched to its CSS size and DPR.
React.useEffect(() => {
const wrapper = wrapperRef.current;
const canvas = canvasRef.current;
if (!wrapper || !canvas) return;
const query = window.matchMedia("(prefers-reduced-motion: reduce)");
reduceMotionRef.current = query.matches;
const onMotionChange = () => {
reduceMotionRef.current = query.matches;
};
query.addEventListener("change", onMotionChange);
const resize = () => {
const dpr = Math.min(window.devicePixelRatio || 1, 2);
const width = wrapper.offsetWidth + FIRE_BLEED_X * 2;
sizeRef.current = { width, height: FIRE_HEIGHT, dpr };
canvas.width = Math.round(width * dpr);
canvas.height = Math.round(FIRE_HEIGHT * dpr);
};
resize();
const observer = new ResizeObserver(resize);
observer.observe(wrapper);
return () => {
observer.disconnect();
query.removeEventListener("change", onMotionChange);
if (rafRef.current !== null) cancelAnimationFrame(rafRef.current);
};
}, []);
const paint = (progress: number, now: number) => {
const eased = 1 - Math.pow(1 - progress, 3);
// Slight overshoot as the horns break free, then settle.
const pop =
progress > 0.85 ? Math.sin(((progress - 0.85) / 0.15) * Math.PI) * 0.06 : 0;
const tremble =
reduceMotionRef.current || !holdingRef.current || progress >= 1
? 0
: progress * progress;
const shakeX = (Math.sin(now * 0.09) + Math.sin(now * 0.137)) * 1.2 * tremble;
const shakeY = Math.cos(now * 0.113) * 0.8 * tremble;
const hornY = 26 * (1 - eased);
const hornScale = 0.55 + 0.45 * eased + pop;
const hornTilt = 18 * (1 - eased);
if (leftHornRef.current) {
leftHornRef.current.style.transform = `translateY(${hornY}px) rotate(${-hornTilt}deg) scale(${hornScale})`;
leftHornRef.current.style.opacity = String(Math.min(1, progress * 3));
}
if (rightHornRef.current) {
rightHornRef.current.style.transform = `translateY(${hornY}px) rotate(${hornTilt}deg) scale(${hornScale})`;
rightHornRef.current.style.opacity = String(Math.min(1, progress * 3));
}
if (buttonRef.current) {
buttonRef.current.style.transform = `translate(${shakeX}px, ${shakeY}px)`;
}
if (glowRef.current) {
const flicker = reduceMotionRef.current
? 0
: (Math.sin(now * 0.021) + Math.sin(now * 0.047)) * 0.05;
glowRef.current.style.opacity = String(
Math.max(0, Math.min(1, eased * 0.9 + flicker * eased)),
);
}
};
const drawFire = (intensity: number, dt: number, now: number) => {
const canvas = canvasRef.current;
const ctx = canvas?.getContext("2d");
const button = buttonRef.current;
if (!canvas || !ctx || !button) return;
spritesRef.current ??= createSprites();
const sprites = spritesRef.current;
const { width, height, dpr } = sizeRef.current;
const particles = particlesRef.current;
const baseY = height - button.offsetHeight / 2;
const left = FIRE_BLEED_X + 6;
const right = width - FIRE_BLEED_X - 6;
const center = width / 2;
if (intensity > 0.02 && !reduceMotionRef.current) {
spawnDebtRef.current += dt * (70 + 260 * intensity);
while (spawnDebtRef.current >= 1) {
spawnDebtRef.current -= 1;
// Bias spawns toward the middle so the flame has a body.
const spread = (Math.random() + Math.random()) / 2;
const life = 0.45 + Math.random() * 0.55 * (0.6 + intensity);
particles.push({
x: left + spread * (right - left),
y: baseY + Math.random() * 6,
vx: (Math.random() - 0.5) * 18,
vy: -(35 + Math.random() * 55) * (0.55 + intensity),
age: 0,
life,
size: (9 + Math.random() * 13) * (0.55 + 0.6 * intensity),
seed: Math.random() * 1000,
});
}
}
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
ctx.clearRect(0, 0, width, height);
let alive = 0;
for (let i = 0; i < particles.length; i++) {
const p = particles[i];
p.age += dt;
if (p.age >= p.life) continue;
// Buoyancy, a gentle pull to the centerline, and turbulent sway.
p.vy -= 60 * dt;
p.vx += (center - p.x) * 0.9 * dt;
p.vx += Math.sin(now * 0.004 + p.seed) * 40 * dt;
p.x += p.vx * dt;
p.y += p.vy * dt;
particles[alive++] = p;
const t = p.age / p.life;
const radius = p.size * (1 - t * 0.55);
const sprite = sprites[Math.min(SPRITE_STEPS - 1, Math.floor(t * SPRITE_STEPS))];
ctx.globalAlpha = Math.min(1, (1 - t) * 1.4);
ctx.drawImage(sprite, p.x - radius, p.y - radius, radius * 2, radius * 2);
}
particles.length = alive;
ctx.globalAlpha = 1;
};
const loop = (now: number) => {
const last = lastTimeRef.current || now;
const dt = Math.min((now - last) / 1000, 0.05);
lastTimeRef.current = now;
const holding = holdingRef.current;
const step = dt / (holdDuration / 1000);
let progress = progressRef.current + (holding ? step : -step * DRAIN_RATE);
progress = Math.max(0, Math.min(1, progress));
progressRef.current = progress;
if (progress >= 1 && !summonedRef.current) {
summonedRef.current = true;
setSummoned(true);
onSummonRef.current?.();
} else if (progress < 1 && summonedRef.current) {
summonedRef.current = false;
setSummoned(false);
}
// Flames build slowly, then roar once the demon is summoned.
const intensity = progress >= 1 ? 1 : Math.pow(progress, 1.6) * 0.85;
paint(progress, now);
drawFire(intensity, dt, now);
if (!holding && progress <= 0 && particlesRef.current.length === 0) {
rafRef.current = null;
lastTimeRef.current = 0;
return;
}
rafRef.current = requestAnimationFrame(loop);
};
const ensureLoop = () => {
if (rafRef.current === null) {
lastTimeRef.current = 0;
rafRef.current = requestAnimationFrame(loop);
}
};
const startHold = () => {
if (disabled) return;
holdingRef.current = true;
buttonRef.current?.setAttribute("data-holding", "");
ensureLoop();
};
const stopHold = () => {
if (!holdingRef.current) return;
holdingRef.current = false;
buttonRef.current?.removeAttribute("data-holding");
ensureLoop();
};
return (
<div ref={wrapperRef} className="relative inline-flex select-none">
<canvas
ref={canvasRef}
aria-hidden
className="pointer-events-none absolute bottom-1/2 z-0"
style={{
left: -FIRE_BLEED_X,
width: `calc(100% + ${FIRE_BLEED_X * 2}px)`,
height: FIRE_HEIGHT,
}}
/>
<span
ref={leftHornRef}
aria-hidden
className="pointer-events-none absolute bottom-[45%] left-[14%] z-[1] h-11 w-6 origin-bottom opacity-0"
style={{ transform: "translateY(26px) rotate(-18deg) scale(0.55)" }}
>
<Horn side="left" />
</span>
<span
ref={rightHornRef}
aria-hidden
className="pointer-events-none absolute right-[14%] bottom-[45%] z-[1] h-11 w-6 origin-bottom opacity-0"
style={{ transform: "translateY(26px) rotate(18deg) scale(0.55)" }}
>
<Horn side="right" />
</span>
<button
ref={setButtonRef}
type={type}
disabled={disabled}
data-state={summoned ? "summoned" : "idle"}
aria-live="polite"
onPointerDown={(e) => {
if (e.button !== 0 && e.pointerType === "mouse") return;
e.currentTarget.setPointerCapture?.(e.pointerId);
startHold();
}}
onPointerUp={stopHold}
onPointerCancel={stopHold}
onLostPointerCapture={stopHold}
onKeyDown={(e) => {
if ((e.key === " " || e.key === "Enter") && !e.repeat) {
e.preventDefault();
startHold();
}
}}
onKeyUp={(e) => {
if (e.key === " " || e.key === "Enter") {
e.preventDefault();
stopHold();
}
}}
onBlur={stopHold}
onContextMenu={(e) => e.preventDefault()}
className={cn(
"relative z-10 inline-flex h-9 min-w-36 cursor-pointer touch-none items-center justify-center overflow-hidden rounded-[10px] px-5 text-sm font-medium text-neutral-50 outline-none",
// Graded dark surface: 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)]",
// The loop writes `transform` every frame for the tremble, so the press animates the separate `scale` property.
"transition-[scale,filter,box-shadow] duration-300 ease-[cubic-bezier(0.34,1.35,0.64,1)] hover:brightness-110",
// Press eases in fast and settles the shadow; release springs back on the slower base curve.
// data-holding covers pointer, touch, and Space/Enter holds, where :active is unreliable.
"data-holding:scale-[0.97] data-holding:brightness-95 data-holding:duration-100 data-holding:ease-out data-holding: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:data-holding: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}
>
{/* Ember glow that rises from the bottom as the charge builds. */}
<span
ref={glowRef}
aria-hidden
className="pointer-events-none absolute inset-0 opacity-0"
style={{
background:
"radial-gradient(120% 90% at 50% 130%, rgb(255 140 50 / 0.55), rgb(200 40 20 / 0.28) 45%, transparent 75%)",
}}
/>
<span className="relative">{children ?? label}</span>
</button>
</div>
);
},
);
DemonicButton.displayName = "DemonicButton";
export default DemonicButton;
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/demonic-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 {
.pointer-events-none {
pointer-events: none;
}
.absolute {
position: absolute;
}
.relative {
position: relative;
}
.inset-0 {
inset: 0px;
}
.right-\[14\%\] {
right: 14%;
}
.bottom-1\/2 {
bottom: calc(1 / 2 * 100%);
}
.bottom-\[45\%\] {
bottom: 45%;
}
.left-\[14\%\] {
left: 14%;
}
.z-0 {
z-index: 0;
}
.z-10 {
z-index: 10;
}
.z-\[1\] {
z-index: 1;
}
.inline-flex {
display: inline-flex;
}
.h-9 {
height: calc(var(--spacing) * 9);
}
.h-11 {
height: calc(var(--spacing) * 11);
}
.h-full {
height: 100%;
}
.w-6 {
width: calc(var(--spacing) * 6);
}
.w-full {
width: 100%;
}
.min-w-36 {
min-width: calc(var(--spacing) * 36);
}
.origin-bottom {
transform-origin: bottom;
}
.-scale-x-100 {
--tw-scale-x: calc(100% * -1);
scale: var(--tw-scale-x) var(--tw-scale-y);
}
.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;
}
.touch-none {
touch-action: none;
}
.resize {
resize: both;
}
.items-center {
align-items: center;
}
.justify-center {
justify-content: center;
}
.overflow-hidden {
overflow: hidden;
}
.rounded-\[10px\] {
border-radius: 10px;
}
.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-5 {
padding-inline: calc(var(--spacing) * 5);
}
.text-sm {
font-size: var(--text-sm);
line-height: var(--tw-leading, var(--text-sm--line-height));
}
.font-medium {
--tw-font-weight: var(--font-weight-medium);
font-weight: var(--font-weight-medium);
}
.text-neutral-50 {
color: var(--color-neutral-50);
}
.opacity-0 {
opacity: 0%;
}
.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-\[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));
}
.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);
}
.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);
}
.disabled\:cursor-not-allowed:disabled {
cursor: not-allowed;
}
.disabled\:opacity-50:disabled {
opacity: 50%;
}
.data-holding\:scale-\[0\.97\][data-holding] {
scale: 0.97;
}
.data-holding\: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\)\][data-holding] {
--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);
}
.data-holding\:brightness-95[data-holding] {
--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,);
}
.data-holding\:duration-100[data-holding] {
--tw-duration: 100ms;
transition-duration: 100ms;
}
.data-holding\:ease-out[data-holding] {
--tw-ease: var(--ease-out);
transition-timing-function: var(--ease-out);
}
@media (prefers-reduced-motion: reduce) {
.motion-reduce\:data-holding\:scale-100[data-holding] {
--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-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;
}
@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;
}
@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-scale-x: 1;
--tw-scale-y: 1;
--tw-scale-z: 1;
--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;
}
}
}