"use client";
import * as React from "react";
import { motion, useReducedMotion, type Transition } from "motion/react";
import { cn } from "@/lib/utils";
export type MoviePassVariant = "tilt" | "snap";
export interface MoviePassButtonProps
extends Omit<
React.ButtonHTMLAttributes<HTMLButtonElement>,
| "children"
| "onAnimationStart"
| "onDrag"
| "onDragEnd"
| "onDragStart"
> {
/** Label printed on the main part of the ticket. */
children?: React.ReactNode;
/** Content of the tear-off stub. */
stub?: React.ReactNode;
/**
* How the ticket tears on click.
* - `tilt`: both halves hinge outward from the bottom of the perforation.
* - `snap`: the stub splits off in a straight line, leaving a small gap.
* @default "tilt"
*/
variant?: MoviePassVariant;
/**
* Milliseconds before the ticket mends itself. Pass `null` to stay torn
* until the next click.
* @default 1800
*/
resetDelay?: number | null;
/** Fired each time the ticket tears. */
onTear?: () => void;
}
/**
* Notch radius for the corner cutouts. Each half is notched on all four
* corners, so the two halves together form half-circle punches at the
* perforation. Masks keep the shape correct on any page background.
*/
const NOTCH = 6;
const cornerNotchMask = [
`radial-gradient(circle ${NOTCH}px at 0 0, transparent ${NOTCH}px, black ${NOTCH + 0.5}px)`,
`radial-gradient(circle ${NOTCH}px at 100% 0, transparent ${NOTCH}px, black ${NOTCH + 0.5}px)`,
`radial-gradient(circle ${NOTCH}px at 0 100%, transparent ${NOTCH}px, black ${NOTCH + 0.5}px)`,
`radial-gradient(circle ${NOTCH}px at 100% 100%, transparent ${NOTCH}px, black ${NOTCH + 0.5}px)`,
].join(", ");
const notchStyle: React.CSSProperties = {
WebkitMaskImage: cornerNotchMask,
maskImage: cornerNotchMask,
WebkitMaskRepeat: "no-repeat",
maskRepeat: "no-repeat",
// Intersect the four masks so only the corner circles are cut out.
WebkitMaskComposite: "source-in",
maskComposite: "intersect",
};
const TEAR = {
snap: {
main: { x: -3, rotate: 0 },
stub: { x: 3, rotate: 0 },
},
// Rotating around the shared bottom corners keeps the halves touching at
// the bottom while a V opens along the perforation.
tilt: {
main: { x: 0, rotate: -6 },
stub: { x: 0, rotate: 10 },
},
} as const;
const REST = { x: 0, rotate: 0 };
const TRANSITIONS: Record<MoviePassVariant, Transition> = {
snap: { type: "spring", stiffness: 700, damping: 22, mass: 0.6 },
tilt: { type: "spring", stiffness: 320, damping: 13, mass: 0.8 },
};
const MEND: Transition = { type: "spring", stiffness: 420, damping: 30 };
function DefaultStub() {
return (
<span className="flex flex-col items-center leading-none">
<span className="text-[8px] tracking-[0.2em] opacity-60">NO.</span>
<span className="mt-0.5 font-mono text-xs font-semibold">01</span>
</span>
);
}
export const MoviePassButton = React.forwardRef<
HTMLButtonElement,
MoviePassButtonProps
>(
(
{
children,
stub = <DefaultStub />,
variant = "tilt",
resetDelay = 1800,
onTear,
onClick,
className,
style,
type = "button",
...props
},
ref,
) => {
const reduceMotion = useReducedMotion();
const [torn, setTorn] = React.useState(false);
const timeoutRef = React.useRef<number | null>(null);
const clearTimer = React.useCallback(() => {
if (timeoutRef.current !== null) {
window.clearTimeout(timeoutRef.current);
timeoutRef.current = null;
}
}, []);
React.useEffect(() => clearTimer, [clearTimer]);
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
onClick?.(event);
if (event.defaultPrevented) return;
clearTimer();
if (torn) {
setTorn(false);
return;
}
setTorn(true);
onTear?.();
if (resetDelay !== null) {
timeoutRef.current = window.setTimeout(() => {
timeoutRef.current = null;
setTorn(false);
}, resetDelay);
}
};
const pose = torn ? TEAR[variant] : { main: REST, stub: REST };
const transition: Transition = reduceMotion
? { duration: 0 }
: torn
? TRANSITIONS[variant]
: MEND;
return (
<button
ref={ref}
type={type}
onClick={handleClick}
data-state={torn ? "torn" : "intact"}
data-variant={variant}
aria-pressed={resetDelay === null ? torn : undefined}
style={style}
className={cn(
"group relative inline-flex h-11 cursor-pointer items-stretch bg-transparent p-0 text-sm font-medium text-neutral-50 outline-none select-none",
// The halves are masked for the notches, which clips box-shadow, so the hairline ring and soft drop are drop-shadows that follow the ticket's outline.
"[filter:drop-shadow(0_0_0.5px_rgb(0_0_0/0.9))_drop-shadow(0_0_0.5px_rgb(0_0_0/0.9))_drop-shadow(0_1px_1px_rgb(0_0_0/0.25))_drop-shadow(0_4px_5px_rgb(0_0_0/0.25))]",
"transition-[scale,filter] duration-300 ease-[cubic-bezier(0.34,1.35,0.64,1)]",
// Press eases in fast and settles the drop; release springs back on the slower base curve.
"active:scale-[0.97] active:duration-100 active:ease-out active:[filter:drop-shadow(0_0_0.5px_rgb(0_0_0/0.9))_drop-shadow(0_0_0.5px_rgb(0_0_0/0.9))_drop-shadow(0_0_1px_rgb(0_0_0/0.2))_drop-shadow(0_1px_1.5px_rgb(0_0_0/0.2))] motion-reduce:active:scale-100",
"focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
"disabled:pointer-events-none disabled:opacity-50",
className,
)}
{...props}
>
<motion.span
initial={false}
animate={pose.main}
transition={transition}
style={{ ...notchStyle, transformOrigin: "100% 100%" }}
className="flex items-center px-6 bg-linear-to-b from-[#353535] to-[#272727] shadow-[inset_0_1px_0_rgb(255_255_255/0.14)] transition-[filter] duration-300 group-hover:brightness-110 group-active:brightness-95"
>
{children}
</motion.span>
<motion.span
aria-hidden
initial={false}
animate={pose.stub}
transition={transition}
style={{ ...notchStyle, transformOrigin: "0% 100%" }}
className="relative flex min-w-12 items-center justify-center px-3 font-mono text-xs text-neutral-50/70 bg-linear-to-b from-[#353535] to-[#272727] shadow-[inset_0_1px_0_rgb(255_255_255/0.14)] transition-[filter] duration-300 group-hover:brightness-110 group-active:brightness-95"
>
{/* Perforation along the tear line. */}
<span className="absolute inset-y-2 left-0 w-px bg-[linear-gradient(to_bottom,currentColor_50%,transparent_50%)] bg-size-[1px_5px] opacity-40" />
{stub}
</motion.span>
</button>
);
},
);
MoviePassButton.displayName = "MoviePassButton";
export default MoviePassButton;
MoviePassButton
By jay sharma
Try the interaction, copy the React source files, or download the complete example.
About MoviePassButton
MoviePassButton 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/movie-pass.tsx
import MoviePassButton from "@/components/evil-buttons/movie-pass";
export function ButtonDemo() {
return (
<MoviePassButton variant="snap" onTear={() => console.log("Ripped!")}>
Admit One
</MoviePassButton>
);
}
components/evil-buttons/movie-pass.tsx
"use client";
import * as React from "react";
import { motion, useReducedMotion, type Transition } from "motion/react";
import { cn } from "@/lib/utils";
export type MoviePassVariant = "tilt" | "snap";
export interface MoviePassButtonProps
extends Omit<
React.ButtonHTMLAttributes<HTMLButtonElement>,
| "children"
| "onAnimationStart"
| "onDrag"
| "onDragEnd"
| "onDragStart"
> {
/** Label printed on the main part of the ticket. */
children?: React.ReactNode;
/** Content of the tear-off stub. */
stub?: React.ReactNode;
/**
* How the ticket tears on click.
* - `tilt`: both halves hinge outward from the bottom of the perforation.
* - `snap`: the stub splits off in a straight line, leaving a small gap.
* @default "tilt"
*/
variant?: MoviePassVariant;
/**
* Milliseconds before the ticket mends itself. Pass `null` to stay torn
* until the next click.
* @default 1800
*/
resetDelay?: number | null;
/** Fired each time the ticket tears. */
onTear?: () => void;
}
/**
* Notch radius for the corner cutouts. Each half is notched on all four
* corners, so the two halves together form half-circle punches at the
* perforation. Masks keep the shape correct on any page background.
*/
const NOTCH = 6;
const cornerNotchMask = [
`radial-gradient(circle ${NOTCH}px at 0 0, transparent ${NOTCH}px, black ${NOTCH + 0.5}px)`,
`radial-gradient(circle ${NOTCH}px at 100% 0, transparent ${NOTCH}px, black ${NOTCH + 0.5}px)`,
`radial-gradient(circle ${NOTCH}px at 0 100%, transparent ${NOTCH}px, black ${NOTCH + 0.5}px)`,
`radial-gradient(circle ${NOTCH}px at 100% 100%, transparent ${NOTCH}px, black ${NOTCH + 0.5}px)`,
].join(", ");
const notchStyle: React.CSSProperties = {
WebkitMaskImage: cornerNotchMask,
maskImage: cornerNotchMask,
WebkitMaskRepeat: "no-repeat",
maskRepeat: "no-repeat",
// Intersect the four masks so only the corner circles are cut out.
WebkitMaskComposite: "source-in",
maskComposite: "intersect",
};
const TEAR = {
snap: {
main: { x: -3, rotate: 0 },
stub: { x: 3, rotate: 0 },
},
// Rotating around the shared bottom corners keeps the halves touching at
// the bottom while a V opens along the perforation.
tilt: {
main: { x: 0, rotate: -6 },
stub: { x: 0, rotate: 10 },
},
} as const;
const REST = { x: 0, rotate: 0 };
const TRANSITIONS: Record<MoviePassVariant, Transition> = {
snap: { type: "spring", stiffness: 700, damping: 22, mass: 0.6 },
tilt: { type: "spring", stiffness: 320, damping: 13, mass: 0.8 },
};
const MEND: Transition = { type: "spring", stiffness: 420, damping: 30 };
function DefaultStub() {
return (
<span className="flex flex-col items-center leading-none">
<span className="text-[8px] tracking-[0.2em] opacity-60">NO.</span>
<span className="mt-0.5 font-mono text-xs font-semibold">01</span>
</span>
);
}
export const MoviePassButton = React.forwardRef<
HTMLButtonElement,
MoviePassButtonProps
>(
(
{
children,
stub = <DefaultStub />,
variant = "tilt",
resetDelay = 1800,
onTear,
onClick,
className,
style,
type = "button",
...props
},
ref,
) => {
const reduceMotion = useReducedMotion();
const [torn, setTorn] = React.useState(false);
const timeoutRef = React.useRef<number | null>(null);
const clearTimer = React.useCallback(() => {
if (timeoutRef.current !== null) {
window.clearTimeout(timeoutRef.current);
timeoutRef.current = null;
}
}, []);
React.useEffect(() => clearTimer, [clearTimer]);
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
onClick?.(event);
if (event.defaultPrevented) return;
clearTimer();
if (torn) {
setTorn(false);
return;
}
setTorn(true);
onTear?.();
if (resetDelay !== null) {
timeoutRef.current = window.setTimeout(() => {
timeoutRef.current = null;
setTorn(false);
}, resetDelay);
}
};
const pose = torn ? TEAR[variant] : { main: REST, stub: REST };
const transition: Transition = reduceMotion
? { duration: 0 }
: torn
? TRANSITIONS[variant]
: MEND;
return (
<button
ref={ref}
type={type}
onClick={handleClick}
data-state={torn ? "torn" : "intact"}
data-variant={variant}
aria-pressed={resetDelay === null ? torn : undefined}
style={style}
className={cn(
"group relative inline-flex h-11 cursor-pointer items-stretch bg-transparent p-0 text-sm font-medium text-neutral-50 outline-none select-none",
// The halves are masked for the notches, which clips box-shadow, so the hairline ring and soft drop are drop-shadows that follow the ticket's outline.
"[filter:drop-shadow(0_0_0.5px_rgb(0_0_0/0.9))_drop-shadow(0_0_0.5px_rgb(0_0_0/0.9))_drop-shadow(0_1px_1px_rgb(0_0_0/0.25))_drop-shadow(0_4px_5px_rgb(0_0_0/0.25))]",
"transition-[scale,filter] duration-300 ease-[cubic-bezier(0.34,1.35,0.64,1)]",
// Press eases in fast and settles the drop; release springs back on the slower base curve.
"active:scale-[0.97] active:duration-100 active:ease-out active:[filter:drop-shadow(0_0_0.5px_rgb(0_0_0/0.9))_drop-shadow(0_0_0.5px_rgb(0_0_0/0.9))_drop-shadow(0_0_1px_rgb(0_0_0/0.2))_drop-shadow(0_1px_1.5px_rgb(0_0_0/0.2))] motion-reduce:active:scale-100",
"focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
"disabled:pointer-events-none disabled:opacity-50",
className,
)}
{...props}
>
<motion.span
initial={false}
animate={pose.main}
transition={transition}
style={{ ...notchStyle, transformOrigin: "100% 100%" }}
className="flex items-center px-6 bg-linear-to-b from-[#353535] to-[#272727] shadow-[inset_0_1px_0_rgb(255_255_255/0.14)] transition-[filter] duration-300 group-hover:brightness-110 group-active:brightness-95"
>
{children}
</motion.span>
<motion.span
aria-hidden
initial={false}
animate={pose.stub}
transition={transition}
style={{ ...notchStyle, transformOrigin: "0% 100%" }}
className="relative flex min-w-12 items-center justify-center px-3 font-mono text-xs text-neutral-50/70 bg-linear-to-b from-[#353535] to-[#272727] shadow-[inset_0_1px_0_rgb(255_255_255/0.14)] transition-[filter] duration-300 group-hover:brightness-110 group-active:brightness-95"
>
{/* Perforation along the tear line. */}
<span className="absolute inset-y-2 left-0 w-px bg-[linear-gradient(to_bottom,currentColor_50%,transparent_50%)] bg-size-[1px_5px] opacity-40" />
{stub}
</motion.span>
</button>
);
},
);
MoviePassButton.displayName = "MoviePassButton";
export default MoviePassButton;
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/movie-pass";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-xs: 0.75rem;
--text-xs--line-height: calc(1 / 0.75);
--text-sm: 0.875rem;
--text-sm--line-height: calc(1.25 / 0.875);
--font-weight-medium: 500;
--font-weight-semibold: 600;
--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 {
.absolute {
position: absolute;
}
.relative {
position: relative;
}
.inset-y-2 {
inset-block: calc(var(--spacing) * 2);
}
.left-0 {
left: 0px;
}
.mt-0\.5 {
margin-top: calc(var(--spacing) * 0.5);
}
.flex {
display: flex;
}
.inline-flex {
display: inline-flex;
}
.h-11 {
height: calc(var(--spacing) * 11);
}
.w-px {
width: 1px;
}
.min-w-12 {
min-width: calc(var(--spacing) * 12);
}
.cursor-pointer {
cursor: pointer;
}
.flex-col {
flex-direction: column;
}
.items-center {
align-items: center;
}
.items-stretch {
align-items: stretch;
}
.justify-center {
justify-content: center;
}
.bg-transparent {
background-color: transparent;
}
.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));
}
.bg-\[linear-gradient\(to_bottom\,currentColor_50\%\,transparent_50\%\)\] {
background-image: linear-gradient(to bottom,currentColor 50%,transparent 50%);
}
.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));
}
.bg-size-\[1px_5px\] {
background-size: 1px 5px;
}
.p-0 {
padding: 0px;
}
.px-3 {
padding-inline: calc(var(--spacing) * 3);
}
.px-6 {
padding-inline: calc(var(--spacing) * 6);
}
.font-mono {
font-family: var(--font-geist-mono);
}
.text-sm {
font-size: var(--text-sm);
line-height: var(--tw-leading, var(--text-sm--line-height));
}
.text-xs {
font-size: var(--text-xs);
line-height: var(--tw-leading, var(--text-xs--line-height));
}
.text-\[8px\] {
font-size: 8px;
}
.leading-none {
--tw-leading: 1;
line-height: 1;
}
.font-medium {
--tw-font-weight: var(--font-weight-medium);
font-weight: var(--font-weight-medium);
}
.font-semibold {
--tw-font-weight: var(--font-weight-semibold);
font-weight: var(--font-weight-semibold);
}
.tracking-\[0\.2em\] {
--tw-tracking: 0.2em;
letter-spacing: 0.2em;
}
.text-neutral-50 {
color: var(--color-neutral-50);
}
.text-neutral-50\/70 {
color: color-mix(in srgb, oklch(98.5% 0 none) 70%, transparent);
@supports (color: color-mix(in lab, red, red)) {
color: color-mix(in oklab, var(--color-neutral-50) 70%, transparent);
}
}
.opacity-40 {
opacity: 40%;
}
.opacity-60 {
opacity: 60%;
}
.shadow-\[inset_0_1px_0_rgb\(255_255_255\/0\.14\)\] {
--tw-shadow: inset 0 1px 0 var(--tw-shadow-color, rgb(255 255 255/0.14));
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
}
.ring {
--tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(1px + 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);
}
.\[filter\:drop-shadow\(0_0_0\.5px_rgb\(0_0_0\/0\.9\)\)_drop-shadow\(0_0_0\.5px_rgb\(0_0_0\/0\.9\)\)_drop-shadow\(0_1px_1px_rgb\(0_0_0\/0\.25\)\)_drop-shadow\(0_4px_5px_rgb\(0_0_0\/0\.25\)\)\] {
filter: drop-shadow(0 0 0.5px rgb(0 0 0/0.9)) drop-shadow(0 0 0.5px rgb(0 0 0/0.9)) drop-shadow(0 1px 1px rgb(0 0 0/0.25)) drop-shadow(0 4px 5px rgb(0 0 0/0.25));
}
.transition {
transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to, opacity, box-shadow, transform, translate, scale, rotate, filter, -webkit-backdrop-filter, backdrop-filter, display, content-visibility, overlay, pointer-events;
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
transition-duration: var(--tw-duration, var(--default-transition-duration));
}
.transition-\[filter\] {
transition-property: filter;
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
transition-duration: var(--tw-duration, var(--default-transition-duration));
}
.transition-\[scale\,filter\] {
transition-property: scale,filter;
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) {
.group-hover\:brightness-110:is(:where(.group):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,);
}
}
.group-active\:brightness-95:is(:where(.group):active *) {
--tw-brightness: brightness(95%);
filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);
}
.focus-visible\:ring-2:focus-visible {
--tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
}
.focus-visible\:ring-ring:focus-visible {
--tw-ring-color: var(--ring);
}
.focus-visible\:ring-offset-2:focus-visible {
--tw-ring-offset-width: 2px;
--tw-ring-offset-shadow: var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
}
.focus-visible\:ring-offset-background:focus-visible {
--tw-ring-offset-color: var(--background);
}
.active\:scale-\[0\.97\]:active {
scale: 0.97;
}
.active\:\[filter\:drop-shadow\(0_0_0\.5px_rgb\(0_0_0\/0\.9\)\)_drop-shadow\(0_0_0\.5px_rgb\(0_0_0\/0\.9\)\)_drop-shadow\(0_0_1px_rgb\(0_0_0\/0\.2\)\)_drop-shadow\(0_1px_1\.5px_rgb\(0_0_0\/0\.2\)\)\]:active {
filter: drop-shadow(0 0 0.5px rgb(0 0 0/0.9)) drop-shadow(0 0 0.5px rgb(0 0 0/0.9)) drop-shadow(0 0 1px rgb(0 0 0/0.2)) drop-shadow(0 1px 1.5px rgb(0 0 0/0.2));
}
.active\:duration-100:active {
--tw-duration: 100ms;
transition-duration: 100ms;
}
.active\:ease-out:active {
--tw-ease: var(--ease-out);
transition-timing-function: var(--ease-out);
}
.disabled\:pointer-events-none:disabled {
pointer-events: none;
}
.disabled\:opacity-50:disabled {
opacity: 50%;
}
@media (prefers-reduced-motion: reduce) {
.motion-reduce\:active\:scale-100:active {
--tw-scale-x: 100%;
--tw-scale-y: 100%;
--tw-scale-z: 100%;
scale: var(--tw-scale-x) var(--tw-scale-y);
}
}
}
:root {
--background: #fff;
--foreground: #111;
--primary: #171717;
--primary-foreground: #fff;
--secondary: #eee;
--secondary-foreground: #171717;
--muted: #eee;
--muted-foreground: #666;
--accent: #eee;
--accent-foreground: #171717;
--border: #ddd;
--input: #ddd;
--ring: #777;
--destructive: #dc2626;
--destructive-foreground: #fff;
--popover: #fff;
--popover-foreground: #111;
--success: #15803d;
--success-foreground: #fff;
--radius: .625rem;
--hairline: 1px;
}
.dark {
--background: #090909;
--foreground: #fafafa;
--primary: #fafafa;
--primary-foreground: #171717;
--secondary: #262626;
--secondary-foreground: #fafafa;
--muted: #262626;
--muted-foreground: #a3a3a3;
--accent: #262626;
--accent-foreground: #fafafa;
--border: #333;
--input: #333;
--popover: #171717;
--popover-foreground: #fafafa;
}
:root {
--card: oklch(1 0 0);
--muted: oklch(0.97 0 0);
--muted-foreground: oklch(0.556 0 0);
--border: oklch(0.922 0 0);
--primary: oklch(0.205 0 0);
--card-foreground: oklch(0.145 0 0);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.145 0 0);
--primary-foreground: oklch(0.985 0 0);
--secondary: oklch(0.97 0 0);
--secondary-foreground: oklch(0.205 0 0);
--accent: oklch(0.97 0 0);
--accent-foreground: oklch(0.205 0 0);
--destructive: oklch(0.577 0.245 27.325);
--input: oklch(0.922 0 0);
--ring: oklch(0.708 0 0);
--chart-1: oklch(0.87 0 0);
--chart-2: oklch(0.556 0 0);
--chart-3: oklch(0.439 0 0);
--chart-4: oklch(0.371 0 0);
--chart-5: oklch(0.269 0 0);
--radius: 0.625rem;
--sidebar: oklch(0.985 0 0);
--sidebar-foreground: oklch(0.145 0 0);
--sidebar-primary: oklch(0.205 0 0);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.97 0 0);
--sidebar-accent-foreground: oklch(0.205 0 0);
--sidebar-border: oklch(0.922 0 0);
--sidebar-ring: oklch(0.708 0 0);
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
--chrome: oklch(0.967 0 0);
--brand: oklch(0.6 0.23 29);
--chart-background: oklch(1 0 0);
--chart-foreground: oklch(0.145 0.004 285);
--chart-foreground-muted: oklch(0.55 0.014 260);
--chart-line-primary: var(--chart-1);
--chart-line-secondary: var(--chart-2);
--chart-crosshair: oklch(0.4 0.1828 274.34);
--chart-grid: oklch(0.9 0 0);
--chart-brush-border: var(--chart-grid);
--chart-tooltip-background: oklch(0.21 0.006 285 / 0.8);
--chart-tooltip-foreground: oklch(0.985 0 0);
--chart-tooltip-muted: oklch(0.65 0.01 260);
--chart-marker-background: oklch(0.97 0.005 260);
--chart-marker-border: oklch(0.85 0.01 260);
--chart-marker-foreground: oklch(0.3 0.01 260);
--chart-label: oklch(0.45 0.01 260);
--chart-scale-01: oklch(0.98 0.003 106);
--chart-scale-02: oklch(0.92 0.008 106);
--chart-scale-03: oklch(0.82 0.015 106);
--chart-scale-04: oklch(0.68 0.02 106);
--chart-scale-05: oklch(0.55 0.025 106);
--chart-scale-pattern-color: oklch(0.96 0.005 106);
}
.dark {
--background: oklch(0.145 0 0);
--foreground: oklch(0.985 0 0);
--chrome: oklch(0.18 0 0);
--card: oklch(0.205 0 0);
--muted: oklch(0.269 0 0);
--muted-foreground: oklch(0.708 0 0);
--border: oklch(1 0 0 / 10%);
--primary: oklch(0.922 0 0);
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.205 0 0);
--popover-foreground: oklch(0.985 0 0);
--primary-foreground: oklch(0.205 0 0);
--secondary: oklch(0.269 0 0);
--secondary-foreground: oklch(0.985 0 0);
--accent: oklch(0.269 0 0);
--accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.704 0.191 22.216);
--input: oklch(1 0 0 / 15%);
--ring: oklch(0.556 0 0);
--chart-1: oklch(0.87 0 0);
--chart-2: oklch(0.556 0 0);
--chart-3: oklch(0.439 0 0);
--chart-4: oklch(0.371 0 0);
--chart-5: oklch(0.269 0 0);
--sidebar: oklch(0.205 0 0);
--sidebar-foreground: oklch(0.985 0 0);
--sidebar-primary: oklch(0.922 0 0);
--sidebar-primary-foreground: oklch(0.205 0 0);
--sidebar-accent: oklch(0.269 0 0);
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.556 0 0);
--brand: oklch(0.66 0.23 32);
--chart-background: oklch(0.145 0 0);
--chart-foreground: oklch(0.45 0 0);
--chart-foreground-muted: oklch(0.65 0.01 260);
--chart-crosshair: oklch(0.45 0 0);
--chart-grid: oklch(0.25 0 0);
--chart-brush-border: var(--chart-grid);
--chart-marker-background: oklch(0.25 0.01 260);
--chart-marker-border: oklch(0.4 0.01 260);
--chart-marker-foreground: oklch(0.9 0 0);
--chart-label: oklch(0.75 0.01 260);
--chart-scale-01: oklch(0.28 0.03 270);
--chart-scale-02: oklch(0.38 0.04 270);
--chart-scale-03: oklch(0.48 0.05 270);
--chart-scale-04: oklch(0.58 0.06 270);
--chart-scale-05: oklch(0.68 0.07 270);
--chart-scale-pattern-color: oklch(0.145 0 0);
}
@keyframes docs-dot-swing {
0%, 100% {
transform: translateX(0) scale(1);
}
45% {
transform: translateX(-6px) scale(0.7);
}
}
@keyframes hell-flicker {
0%, 100% {
opacity: 1;
}
41% {
opacity: 0.7;
}
43% {
opacity: 1;
}
77% {
opacity: 0.85;
}
78% {
opacity: 1;
}
}
@keyframes lights-off {
0% {
opacity: 0;
}
15% {
opacity: 1;
}
30% {
opacity: 0.2;
}
45% {
opacity: 1;
}
60% {
opacity: 0.5;
}
70%, 100% {
opacity: 1;
}
}
@keyframes lights-on {
0% {
opacity: 1;
}
20% {
opacity: 0;
}
32% {
opacity: 1;
}
50% {
opacity: 0.1;
}
60% {
opacity: 0.9;
}
75%, 100% {
opacity: 0;
}
}
@keyframes lights-fade {
to {
opacity: 0;
}
}
@keyframes eyes-dim {
from {
opacity: 0;
}
}
@keyframes eyes-open {
from {
opacity: 0;
transform: scaleY(0.05);
}
}
@keyframes eyes-blink {
0%, 92%, 100% {
transform: none;
}
95% {
transform: scaleY(0.08);
}
}
@keyframes crack-life {
0% {
opacity: 0;
transform: scale(0.92);
}
4% {
opacity: 1;
transform: none;
}
80% {
opacity: 1;
}
100% {
opacity: 0;
}
}
@keyframes egg-wobble {
0%, 84%, 100% {
transform: none;
}
87% {
transform: rotate(-14deg);
}
90% {
transform: rotate(10deg);
}
93% {
transform: rotate(-6deg);
}
96% {
transform: rotate(3deg);
}
}
@keyframes page-glitch {
0% {
transform: none;
filter: none;
clip-path: none;
}
12% {
transform: translate(-6px, 2px) skewX(-2deg);
filter: hue-rotate(90deg) saturate(3);
}
24% {
transform: translate(5px, -1px);
filter: invert(1) hue-rotate(180deg);
clip-path: inset(18% 0 52% 0);
}
36% {
transform: none;
filter: contrast(2.2) saturate(0);
clip-path: none;
}
48% {
transform: translate(9px, 0) skewX(4deg);
filter: hue-rotate(-60deg);
clip-path: inset(0 0 70% 0);
}
60% {
transform: translate(-3px, 3px);
filter: none;
clip-path: inset(40% 0 20% 0);
}
72% {
transform: translate(2px, -4px) scaleY(1.02);
filter: saturate(4) contrast(1.4);
clip-path: none;
}
86% {
transform: translate(-8px, 0);
filter: invert(1);
clip-path: inset(62% 0 8% 0);
}
}
@keyframes glitch-label {
0% {
transform: translate(0, 0);
opacity: 1;
}
33% {
transform: translate(-4px, 2px);
opacity: 0.6;
}
66% {
transform: translate(5px, -2px);
opacity: 1;
}
}
@keyframes deleted-progress {
from {
width: 0;
}
to {
width: 100%;
}
}
@keyframes deleted-restore {
0%, 70% {
opacity: 1;
}
100% {
opacity: 0;
}
}
@keyframes creeper-swell {
0% {
transform: scale(0.3) translateY(40px);
opacity: 0;
filter: none;
}
20% {
transform: scale(1);
opacity: 1;
}
45%, 65%, 85% {
filter: brightness(3) saturate(0);
}
55%, 75% {
filter: none;
}
100% {
transform: scale(1.35);
filter: brightness(4) saturate(0);
}
}
@keyframes creeper-flash {
from {
opacity: 0.85;
}
to {
opacity: 0;
}
}
@property --tw-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-leading {
syntax: "*";
inherits: false;
}
@property --tw-font-weight {
syntax: "*";
inherits: false;
}
@property --tw-tracking {
syntax: "*";
inherits: false;
}
@property --tw-shadow {
syntax: "*";
inherits: false;
initial-value: 0 0 #0000;
}
@property --tw-shadow-color {
syntax: "*";
inherits: false;
}
@property --tw-shadow-alpha {
syntax: "<percentage>";
inherits: false;
initial-value: 100%;
}
@property --tw-inset-shadow {
syntax: "*";
inherits: false;
initial-value: 0 0 #0000;
}
@property --tw-inset-shadow-color {
syntax: "*";
inherits: false;
}
@property --tw-inset-shadow-alpha {
syntax: "<percentage>";
inherits: false;
initial-value: 100%;
}
@property --tw-ring-color {
syntax: "*";
inherits: false;
}
@property --tw-ring-shadow {
syntax: "*";
inherits: false;
initial-value: 0 0 #0000;
}
@property --tw-inset-ring-color {
syntax: "*";
inherits: false;
}
@property --tw-inset-ring-shadow {
syntax: "*";
inherits: false;
initial-value: 0 0 #0000;
}
@property --tw-ring-inset {
syntax: "*";
inherits: false;
}
@property --tw-ring-offset-width {
syntax: "<length>";
inherits: false;
initial-value: 0px;
}
@property --tw-ring-offset-color {
syntax: "*";
inherits: false;
initial-value: #fff;
}
@property --tw-ring-offset-shadow {
syntax: "*";
inherits: false;
initial-value: 0 0 #0000;
}
@property --tw-duration {
syntax: "*";
inherits: false;
}
@property --tw-ease {
syntax: "*";
inherits: false;
}
@property --tw-blur {
syntax: "*";
inherits: false;
}
@property --tw-brightness {
syntax: "*";
inherits: false;
}
@property --tw-contrast {
syntax: "*";
inherits: false;
}
@property --tw-grayscale {
syntax: "*";
inherits: false;
}
@property --tw-hue-rotate {
syntax: "*";
inherits: false;
}
@property --tw-invert {
syntax: "*";
inherits: false;
}
@property --tw-opacity {
syntax: "*";
inherits: false;
}
@property --tw-saturate {
syntax: "*";
inherits: false;
}
@property --tw-sepia {
syntax: "*";
inherits: false;
}
@property --tw-drop-shadow {
syntax: "*";
inherits: false;
}
@property --tw-drop-shadow-color {
syntax: "*";
inherits: false;
}
@property --tw-drop-shadow-alpha {
syntax: "<percentage>";
inherits: false;
initial-value: 100%;
}
@property --tw-drop-shadow-size {
syntax: "*";
inherits: false;
}
@property --tw-scale-x {
syntax: "*";
inherits: false;
initial-value: 1;
}
@property --tw-scale-y {
syntax: "*";
inherits: false;
initial-value: 1;
}
@property --tw-scale-z {
syntax: "*";
inherits: false;
initial-value: 1;
}
@layer properties {
@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) {
*, ::before, ::after, ::backdrop {
--tw-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-leading: initial;
--tw-font-weight: initial;
--tw-tracking: initial;
--tw-shadow: 0 0 #0000;
--tw-shadow-color: initial;
--tw-shadow-alpha: 100%;
--tw-inset-shadow: 0 0 #0000;
--tw-inset-shadow-color: initial;
--tw-inset-shadow-alpha: 100%;
--tw-ring-color: initial;
--tw-ring-shadow: 0 0 #0000;
--tw-inset-ring-color: initial;
--tw-inset-ring-shadow: 0 0 #0000;
--tw-ring-inset: initial;
--tw-ring-offset-width: 0px;
--tw-ring-offset-color: #fff;
--tw-ring-offset-shadow: 0 0 #0000;
--tw-duration: initial;
--tw-ease: initial;
--tw-blur: initial;
--tw-brightness: initial;
--tw-contrast: initial;
--tw-grayscale: initial;
--tw-hue-rotate: initial;
--tw-invert: initial;
--tw-opacity: initial;
--tw-saturate: initial;
--tw-sepia: initial;
--tw-drop-shadow: initial;
--tw-drop-shadow-color: initial;
--tw-drop-shadow-alpha: 100%;
--tw-drop-shadow-size: initial;
--tw-scale-x: 1;
--tw-scale-y: 1;
--tw-scale-z: 1;
}
}
}