// Built using Hyperiux Vault: https://vault.hyperiux.com
"use client";
import React, { useEffect, useRef, useState, type ComponentPropsWithoutRef, type ComponentType, type CSSProperties, type MouseEventHandler, type PointerEvent, type ReactNode } from "react";
import { ArrowRight } from "lucide-react";
const DEFAULT_TEXT = "Hover me";
const DEFAULT_HREF = "#";
const DEFAULT_HOVER_COLOR = "#ff6b00";
const DEFAULT_SCRAMBLE_DURATION = 1000;
const DEFAULT_STEP_MS = 50;
const DEFAULT_REVEAL_STAGGER = 1.4;
const COMPACT_LAYOUT_BREAKPOINT = 1025;
const GLYPHS = "abcdefghijklmnopqrstuvwxyz0123456789";
interface GetScrambledTextOptions {
finalText: string;
iteration: number;
maxIterations: number;
revealStagger: number;
}
function getScrambledText({
finalText,
iteration,
maxIterations,
revealStagger,
}: GetScrambledTextOptions): string {
let output = "";
for (let index = 0; index < finalText.length; index += 1) {
const char = finalText[index];
if (char === " ") {
output += char;
continue;
}
const revealThreshold =
(((index + 1) / finalText.length) * maxIterations) / revealStagger;
if (iteration >= revealThreshold) {
output += char;
continue;
}
output += GLYPHS[Math.floor(Math.random() * GLYPHS.length)];
}
return output;
}
export interface ScrambleLinkButtonOwnProps {
text?: string;
href?: string;
className?: string;
textClassName?: string;
linkProps?: Partial<ComponentPropsWithoutRef<"a">>;
children?: ReactNode;
textColor?: string;
hoverColor?: string;
showLine?: boolean;
lineClassName?: string;
showArrow?: boolean;
icon?: ComponentType<{ className?: string }>;
iconClassName?: string;
scrambleDuration?: number;
stepMs?: number;
revealStagger?: number;
onClick?: MouseEventHandler<HTMLAnchorElement>;
}
export type ScrambleLinkButtonProps = ScrambleLinkButtonOwnProps & Omit<ComponentPropsWithoutRef<"a">, 'href' | 'onClick' | keyof ScrambleLinkButtonOwnProps>;
export default function ScrambleLinkButton({
text = DEFAULT_TEXT,
href = DEFAULT_HREF,
className = "",
textClassName = "",
linkProps = {},
children,
textColor = "",
hoverColor = DEFAULT_HOVER_COLOR,
showLine = false,
lineClassName = "",
showArrow = false,
icon: Icon = ArrowRight,
iconClassName = "",
scrambleDuration = DEFAULT_SCRAMBLE_DURATION,
stepMs = DEFAULT_STEP_MS,
revealStagger = DEFAULT_REVEAL_STAGGER,
onClick,
...props
}: ScrambleLinkButtonProps) {
const scrambleRef = useRef<HTMLSpanElement | null>(null);
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const [isCompactLayout, setIsCompactLayout] = useState(false);
const [isPressed, setIsPressed] = useState(false);
const finalText = typeof children === "string" ? children : text;
const activeTextStyle =
isCompactLayout && isPressed ? { color: hoverColor } : undefined;
const activeLineStyle =
isCompactLayout && isPressed ? { color: hoverColor } : undefined;
const innerClassName = `relative inline-block ${
showLine
? `w-fit after:absolute after:left-0 after:bottom-[-4%] after:h-[1.5px] after:w-full after:origin-right after:scale-x-0 after:bg-current after:transition-transform after:duration-[450ms] after:ease-[cubic-bezier(0.625,0.05,0,1)] after:content-[''] motion-reduce:after:transition-none motion-safe:group-hover:after:origin-left motion-safe:group-hover:after:scale-x-100 motion-safe:group-data-[pressed=true]:after:origin-left motion-safe:group-data-[pressed=true]:after:scale-x-100 ${lineClassName}`
: ""
}`;
useEffect(() => {
if (scrambleRef.current) {
scrambleRef.current.textContent = finalText;
}
return () => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
};
}, [finalText]);
useEffect(() => {
const mediaQuery = window.matchMedia(
`(max-width: ${COMPACT_LAYOUT_BREAKPOINT - 1}px)`
);
const syncCompactLayout = (event: MediaQueryList | MediaQueryListEvent) => {
const matches = "matches" in event ? event.matches : ((event as any).currentTarget as MediaQueryList).matches;
setIsCompactLayout(matches);
if (!matches) {
setIsPressed(false);
}
};
syncCompactLayout(mediaQuery);
mediaQuery.addEventListener("change", syncCompactLayout);
return () => {
mediaQuery.removeEventListener("change", syncCompactLayout);
};
}, []);
const runScramble = () => {
const element = scrambleRef.current;
if (!element || !finalText.length) {
return;
}
if (window.matchMedia?.("(prefers-reduced-motion: reduce)")?.matches) {
element.textContent = finalText;
return;
}
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
let iteration = 0;
const maxIterations = Math.max(1, Math.floor(scrambleDuration / stepMs));
const updateScramble = () => {
element.textContent = getScrambledText({
finalText,
iteration,
maxIterations,
revealStagger,
});
if (iteration >= maxIterations) {
element.textContent = finalText;
return;
}
iteration += 1;
timeoutRef.current = setTimeout(updateScramble, stepMs);
};
updateScramble();
};
const onMouseEnter = () => {
runScramble();
};
const handlePointerDown = (event: PointerEvent<HTMLAnchorElement>) => {
props.onPointerDown?.(event);
if (!isCompactLayout || event.pointerType === "mouse") {
return;
}
setIsPressed(true);
runScramble();
};
const handlePointerUp = (event: PointerEvent<HTMLAnchorElement>) => {
props.onPointerUp?.(event);
};
const handlePointerCancel = (event: PointerEvent<HTMLAnchorElement>) => {
props.onPointerCancel?.(event);
};
return (
<a
href={href}
{...linkProps}
{...props}
data-pressed={isPressed ? "true" : "false"}
onClick={onClick}
onMouseEnter={onMouseEnter}
onPointerDown={handlePointerDown}
onPointerUp={handlePointerUp}
onPointerCancel={handlePointerCancel}
className={`group inline-flex w-fit items-center gap-2 cursor-pointer text-[1.1vw] ${textColor ? "text-(--scramble-text-color)" : "text-inherit"} no-underline transition-colors duration-350 ease-in-out motion-reduce:transition-none hover:text-(--scramble-hover-color) group-data-[pressed=true]:text-(--scramble-hover-color) max-lg:text-[3vw] max-md:text-[4.2vw] ${className}`}
style={{ "--scramble-hover-color": hoverColor, ...(textColor ? { "--scramble-text-color": textColor } : {}) } as CSSProperties & Record<string, string>}
>
<span className={innerClassName} style={activeLineStyle}>
<span
className={`pointer-events-none inline-block select-none whitespace-pre invisible [font-variant-ligatures:none] ${textClassName}`}
style={activeTextStyle}
>
{finalText}
</span>
<span
ref={scrambleRef}
className={`absolute inset-0 inline-block whitespace-pre text-left text-current [font-variant-ligatures:none] ${textClassName}`}
style={activeTextStyle}
aria-label={finalText}
>
{finalText}
</span>
</span>
{showArrow && Icon && (
<span className={`inline-flex items-center justify-center ${iconClassName}`}>
<Icon className="transition-transform duration-300 ease-in-out motion-reduce:rotate-0 motion-reduce:transition-none motion-safe:group-hover:-rotate-45 motion-safe:group-data-[pressed=true]:-rotate-45" />
</span>
)}
</a>
);
}
Scramble Link Button
Try the interaction, copy the React source files, or download the complete example.
About Scramble Link Button
Scramble Link Button is an animated React and Tailwind CSS button by Hyperiux Vault. 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
registry/effects/buttons/scramble-link-button/index.tsx
// Built using Hyperiux Vault: https://vault.hyperiux.com
"use client";
import React, { useEffect, useRef, useState, type ComponentPropsWithoutRef, type ComponentType, type CSSProperties, type MouseEventHandler, type PointerEvent, type ReactNode } from "react";
import { ArrowRight } from "lucide-react";
const DEFAULT_TEXT = "Hover me";
const DEFAULT_HREF = "#";
const DEFAULT_HOVER_COLOR = "#ff6b00";
const DEFAULT_SCRAMBLE_DURATION = 1000;
const DEFAULT_STEP_MS = 50;
const DEFAULT_REVEAL_STAGGER = 1.4;
const COMPACT_LAYOUT_BREAKPOINT = 1025;
const GLYPHS = "abcdefghijklmnopqrstuvwxyz0123456789";
interface GetScrambledTextOptions {
finalText: string;
iteration: number;
maxIterations: number;
revealStagger: number;
}
function getScrambledText({
finalText,
iteration,
maxIterations,
revealStagger,
}: GetScrambledTextOptions): string {
let output = "";
for (let index = 0; index < finalText.length; index += 1) {
const char = finalText[index];
if (char === " ") {
output += char;
continue;
}
const revealThreshold =
(((index + 1) / finalText.length) * maxIterations) / revealStagger;
if (iteration >= revealThreshold) {
output += char;
continue;
}
output += GLYPHS[Math.floor(Math.random() * GLYPHS.length)];
}
return output;
}
export interface ScrambleLinkButtonOwnProps {
text?: string;
href?: string;
className?: string;
textClassName?: string;
linkProps?: Partial<ComponentPropsWithoutRef<"a">>;
children?: ReactNode;
textColor?: string;
hoverColor?: string;
showLine?: boolean;
lineClassName?: string;
showArrow?: boolean;
icon?: ComponentType<{ className?: string }>;
iconClassName?: string;
scrambleDuration?: number;
stepMs?: number;
revealStagger?: number;
onClick?: MouseEventHandler<HTMLAnchorElement>;
}
export type ScrambleLinkButtonProps = ScrambleLinkButtonOwnProps & Omit<ComponentPropsWithoutRef<"a">, 'href' | 'onClick' | keyof ScrambleLinkButtonOwnProps>;
export default function ScrambleLinkButton({
text = DEFAULT_TEXT,
href = DEFAULT_HREF,
className = "",
textClassName = "",
linkProps = {},
children,
textColor = "",
hoverColor = DEFAULT_HOVER_COLOR,
showLine = false,
lineClassName = "",
showArrow = false,
icon: Icon = ArrowRight,
iconClassName = "",
scrambleDuration = DEFAULT_SCRAMBLE_DURATION,
stepMs = DEFAULT_STEP_MS,
revealStagger = DEFAULT_REVEAL_STAGGER,
onClick,
...props
}: ScrambleLinkButtonProps) {
const scrambleRef = useRef<HTMLSpanElement | null>(null);
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const [isCompactLayout, setIsCompactLayout] = useState(false);
const [isPressed, setIsPressed] = useState(false);
const finalText = typeof children === "string" ? children : text;
const activeTextStyle =
isCompactLayout && isPressed ? { color: hoverColor } : undefined;
const activeLineStyle =
isCompactLayout && isPressed ? { color: hoverColor } : undefined;
const innerClassName = `relative inline-block ${
showLine
? `w-fit after:absolute after:left-0 after:bottom-[-4%] after:h-[1.5px] after:w-full after:origin-right after:scale-x-0 after:bg-current after:transition-transform after:duration-[450ms] after:ease-[cubic-bezier(0.625,0.05,0,1)] after:content-[''] motion-reduce:after:transition-none motion-safe:group-hover:after:origin-left motion-safe:group-hover:after:scale-x-100 motion-safe:group-data-[pressed=true]:after:origin-left motion-safe:group-data-[pressed=true]:after:scale-x-100 ${lineClassName}`
: ""
}`;
useEffect(() => {
if (scrambleRef.current) {
scrambleRef.current.textContent = finalText;
}
return () => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
};
}, [finalText]);
useEffect(() => {
const mediaQuery = window.matchMedia(
`(max-width: ${COMPACT_LAYOUT_BREAKPOINT - 1}px)`
);
const syncCompactLayout = (event: MediaQueryList | MediaQueryListEvent) => {
const matches = "matches" in event ? event.matches : ((event as any).currentTarget as MediaQueryList).matches;
setIsCompactLayout(matches);
if (!matches) {
setIsPressed(false);
}
};
syncCompactLayout(mediaQuery);
mediaQuery.addEventListener("change", syncCompactLayout);
return () => {
mediaQuery.removeEventListener("change", syncCompactLayout);
};
}, []);
const runScramble = () => {
const element = scrambleRef.current;
if (!element || !finalText.length) {
return;
}
if (window.matchMedia?.("(prefers-reduced-motion: reduce)")?.matches) {
element.textContent = finalText;
return;
}
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
let iteration = 0;
const maxIterations = Math.max(1, Math.floor(scrambleDuration / stepMs));
const updateScramble = () => {
element.textContent = getScrambledText({
finalText,
iteration,
maxIterations,
revealStagger,
});
if (iteration >= maxIterations) {
element.textContent = finalText;
return;
}
iteration += 1;
timeoutRef.current = setTimeout(updateScramble, stepMs);
};
updateScramble();
};
const onMouseEnter = () => {
runScramble();
};
const handlePointerDown = (event: PointerEvent<HTMLAnchorElement>) => {
props.onPointerDown?.(event);
if (!isCompactLayout || event.pointerType === "mouse") {
return;
}
setIsPressed(true);
runScramble();
};
const handlePointerUp = (event: PointerEvent<HTMLAnchorElement>) => {
props.onPointerUp?.(event);
};
const handlePointerCancel = (event: PointerEvent<HTMLAnchorElement>) => {
props.onPointerCancel?.(event);
};
return (
<a
href={href}
{...linkProps}
{...props}
data-pressed={isPressed ? "true" : "false"}
onClick={onClick}
onMouseEnter={onMouseEnter}
onPointerDown={handlePointerDown}
onPointerUp={handlePointerUp}
onPointerCancel={handlePointerCancel}
className={`group inline-flex w-fit items-center gap-2 cursor-pointer text-[1.1vw] ${textColor ? "text-(--scramble-text-color)" : "text-inherit"} no-underline transition-colors duration-350 ease-in-out motion-reduce:transition-none hover:text-(--scramble-hover-color) group-data-[pressed=true]:text-(--scramble-hover-color) max-lg:text-[3vw] max-md:text-[4.2vw] ${className}`}
style={{ "--scramble-hover-color": hoverColor, ...(textColor ? { "--scramble-text-color": textColor } : {}) } as CSSProperties & Record<string, string>}
>
<span className={innerClassName} style={activeLineStyle}>
<span
className={`pointer-events-none inline-block select-none whitespace-pre invisible [font-variant-ligatures:none] ${textClassName}`}
style={activeTextStyle}
>
{finalText}
</span>
<span
ref={scrambleRef}
className={`absolute inset-0 inline-block whitespace-pre text-left text-current [font-variant-ligatures:none] ${textClassName}`}
style={activeTextStyle}
aria-label={finalText}
>
{finalText}
</span>
</span>
{showArrow && Icon && (
<span className={`inline-flex items-center justify-center ${iconClassName}`}>
<Icon className="transition-transform duration-300 ease-in-out motion-reduce:rotate-0 motion-reduce:transition-none motion-safe:group-hover:-rotate-45 motion-safe:group-data-[pressed=true]:-rotate-45" />
</span>
)}
</a>
);
}
demo.tsx
export { default as default } from "@/registry/effects/buttons/scramble-link-button/index";CSS
/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */
@layer properties;
@layer theme, base, components, utilities;
@layer theme {
:root, :host {
--font-sans: -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-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono",
"Courier New", monospace;
--spacing: 0.25rem;
--ease-in-out: cubic-bezier(0.4, 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-sans);
--default-mono-font-family: var(--font-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;
}
.absolute {
position: absolute;
}
.relative {
position: relative;
}
.inset-0 {
inset: 0px;
}
.inline-block {
display: inline-block;
}
.inline-flex {
display: inline-flex;
}
.w-fit {
width: fit-content;
}
.cursor-pointer {
cursor: pointer;
}
.items-center {
align-items: center;
}
.justify-center {
justify-content: center;
}
.gap-2 {
gap: calc(var(--spacing) * 2);
}
.text-left {
text-align: left;
}
.text-\[1\.1vw\] {
font-size: 1.1vw;
}
.whitespace-pre {
white-space: pre;
}
.text-\(--scramble-text-color\) {
color: var(--scramble-text-color);
}
.text-current {
color: currentcolor;
}
.text-inherit {
color: inherit;
}
.no-underline {
text-decoration-line: none;
}
.transition-colors {
transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to;
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
transition-duration: var(--tw-duration, var(--default-transition-duration));
}
.transition-transform {
transition-property: transform, translate, scale, rotate;
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
transition-duration: var(--tw-duration, var(--default-transition-duration));
}
.duration-300 {
--tw-duration: 300ms;
transition-duration: 300ms;
}
.duration-350 {
--tw-duration: 350ms;
transition-duration: 350ms;
}
.ease-in-out {
--tw-ease: var(--ease-in-out);
transition-timing-function: var(--ease-in-out);
}
.select-none {
-webkit-user-select: none;
user-select: none;
}
.\[font-variant-ligatures\:none\] {
font-variant-ligatures: none;
}
.group-data-\[pressed\=true\]\:text-\(--scramble-hover-color\):is(:where(.group)[data-pressed="true"] *) {
color: var(--scramble-hover-color);
}
.after\:absolute::after {
content: var(--tw-content);
position: absolute;
}
.after\:bottom-\[-4\%\]::after {
content: var(--tw-content);
bottom: -4%;
}
.after\:left-0::after {
content: var(--tw-content);
left: 0px;
}
.after\:h-\[1\.5px\]::after {
content: var(--tw-content);
height: 1.5px;
}
.after\:w-full::after {
content: var(--tw-content);
width: 100%;
}
.after\:origin-right::after {
content: var(--tw-content);
transform-origin: 100%;
}
.after\:scale-x-0::after {
content: var(--tw-content);
--tw-scale-x: 0%;
scale: var(--tw-scale-x) var(--tw-scale-y);
}
.after\:bg-current::after {
content: var(--tw-content);
background-color: currentcolor;
}
.after\:transition-transform::after {
content: var(--tw-content);
transition-property: transform, translate, scale, rotate;
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
transition-duration: var(--tw-duration, var(--default-transition-duration));
}
.after\:duration-\[450ms\]::after {
content: var(--tw-content);
--tw-duration: 450ms;
transition-duration: 450ms;
}
.after\:ease-\[cubic-bezier\(0\.625\,0\.05\,0\,1\)\]::after {
content: var(--tw-content);
--tw-ease: cubic-bezier(0.625,0.05,0,1);
transition-timing-function: cubic-bezier(0.625,0.05,0,1);
}
.after\:content-\[\'\'\]::after {
--tw-content: '';
content: var(--tw-content);
}
@media (hover: hover) {
.hover\:text-\(--scramble-hover-color\):hover {
color: var(--scramble-hover-color);
}
}
@media (prefers-reduced-motion: no-preference) {
@media (hover: hover) {
.motion-safe\:group-hover\:-rotate-45:is(:where(.group):hover *) {
rotate: calc(45deg * -1);
}
}
.motion-safe\:group-data-\[pressed\=true\]\:-rotate-45:is(:where(.group)[data-pressed="true"] *) {
rotate: calc(45deg * -1);
}
@media (hover: hover) {
.motion-safe\:group-hover\:after\:origin-left:is(:where(.group):hover *)::after {
content: var(--tw-content);
transform-origin: 0;
}
.motion-safe\:group-hover\:after\:scale-x-100:is(:where(.group):hover *)::after {
content: var(--tw-content);
--tw-scale-x: 100%;
scale: var(--tw-scale-x) var(--tw-scale-y);
}
}
.motion-safe\:group-data-\[pressed\=true\]\:after\:origin-left:is(:where(.group)[data-pressed="true"] *)::after {
content: var(--tw-content);
transform-origin: 0;
}
.motion-safe\:group-data-\[pressed\=true\]\:after\:scale-x-100:is(:where(.group)[data-pressed="true"] *)::after {
content: var(--tw-content);
--tw-scale-x: 100%;
scale: var(--tw-scale-x) var(--tw-scale-y);
}
}
@media (prefers-reduced-motion: reduce) {
.motion-reduce\:rotate-0 {
rotate: 0deg;
}
.motion-reduce\:transition-none {
transition-property: none;
}
.motion-reduce\:after\:transition-none::after {
content: var(--tw-content);
transition-property: none;
}
}
@media (width < 64rem) {
.max-lg\:text-\[3vw\] {
font-size: 3vw;
}
}
@media (width < 48rem) {
.max-md\:text-\[4\.2vw\] {
font-size: 4.2vw;
}
}
}
: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;
}
@property --tw-duration {
syntax: "*";
inherits: false;
}
@property --tw-ease {
syntax: "*";
inherits: false;
}
@property --tw-content {
syntax: "*";
initial-value: "";
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-duration: initial;
--tw-ease: initial;
--tw-content: "";
--tw-scale-x: 1;
--tw-scale-y: 1;
--tw-scale-z: 1;
}
}
}