diff --git a/apps/www/content/docs/components/pulsating-button.mdx b/apps/www/content/docs/components/pulsating-button.mdx
index 6e216500f..7814dd482 100644
--- a/apps/www/content/docs/components/pulsating-button.mdx
+++ b/apps/www/content/docs/components/pulsating-button.mdx
@@ -41,14 +41,27 @@ Add the following animations to your global CSS file.
```css title="app/globals.css" showLineNumbers {2, 4-12}
@theme inline {
--animate-pulse: pulse var(--duration) ease-out infinite;
+ --animate-pulse-ripple: pulse-ripple var(--duration)
+ cubic-bezier(0.16, 1, 0.3, 1) infinite;
@keyframes pulse {
0%,
100% {
- box-shadow: 0 0 0 0 var(--pulse-color);
+ box-shadow: 0 0 0 0 var(--pulse-color, oklch(from var(--bg) l c h / 0.5));
}
50% {
- box-shadow: 0 0 0 8px var(--pulse-color);
+ box-shadow: 0 0 0 var(--distance)
+ var(--pulse-color, oklch(from var(--bg) l c h / 0.5));
+ }
+ }
+
+ @keyframes pulse-ripple {
+ 0% {
+ box-shadow: 0 0 0 0 oklch(from var(--pulse-color, var(--bg)) l c h / 1);
+ }
+ 100% {
+ box-shadow: 0 0 0 var(--distance)
+ oklch(from var(--pulse-color, var(--bg)) l c h / 0);
}
}
}
@@ -70,15 +83,23 @@ import { PulsatingButton } from "@/components/ui/pulsating-button"
Pulsating Button
```
+## Examples
+
+### Ripple Variant
+
+
+
## Props
-| Prop | Type | Default | Description |
-| ------------ | ----------------- | ------- | -------------------------------------------------------- |
-| `children` | `React.ReactNode` | `-` | The content of the button. |
-| `className` | `string` | `-` | Additional class names for the button. |
-| `pulseColor` | `string` | `-` | The rbg numbers only for the color of the pulsing waves. |
-| `duration` | `string` | `-` | The time span of one pulse. |
+| Prop | Type | Default | Description |
+| ------------ | --------------------- | -------------------------------- | ------------------------------------------ |
+| `children` | `React.ReactNode` | `-` | The content of the button. |
+| `className` | `string` | `-` | Additional class names for the button. |
+| `pulseColor` | `string` | `Derived from button background` | Any valid CSS color for the pulsing waves. |
+| `duration` | `string` | `1.5s` | The duration of one pulse. |
+| `distance` | `string` | `8px` | How far the pulse expands from the button. |
+| `variant` | `"pulse" \| "ripple"` | `pulse` | The pulse animation style. |
## Credits
-- Credit to [@shikhap04](https://github.com/shikhap04)
+- Credit to [@shikhap04](https://github.com/shikhap04) & [@abdmjd1](https://github.com/abdmjd1)
diff --git a/apps/www/public/llms-full.txt b/apps/www/public/llms-full.txt
index 2a89c2f05..4c49bccda 100644
--- a/apps/www/public/llms-full.txt
+++ b/apps/www/public/llms-full.txt
@@ -12883,13 +12883,17 @@ Title: Pulsating Button
Description: An animated pulsating button useful for capturing attention of users.
--- file: magicui/pulsating-button.tsx ---
-import React from "react"
+"use client"
+
+import React, { useImperativeHandle, useLayoutEffect, useRef } from "react"
import { cn } from "@/lib/utils"
interface PulsatingButtonProps extends React.ButtonHTMLAttributes {
pulseColor?: string
duration?: string
+ distance?: string
+ variant?: "pulse" | "ripple"
}
export const PulsatingButton = React.forwardRef<
@@ -12900,29 +12904,106 @@ export const PulsatingButton = React.forwardRef<
{
className,
children,
- pulseColor = "#808080",
+ pulseColor,
duration = "1.5s",
+ distance = "8px",
+ variant = "pulse",
...props
},
ref
) => {
+ const innerRef = useRef(null)
+ useImperativeHandle(ref, () => innerRef.current!)
+
+ useLayoutEffect(() => {
+ const button = innerRef.current
+ if (!button) return
+
+ if (pulseColor) {
+ button.style.removeProperty("--bg")
+ return
+ }
+
+ let animationFrameId = 0
+ let currentBg = ""
+
+ const updateBg = () => {
+ animationFrameId = 0
+
+ const nextBg = getComputedStyle(button).backgroundColor
+ if (nextBg === currentBg) return
+
+ currentBg = nextBg
+ button.style.setProperty("--bg", nextBg)
+ }
+
+ const scheduleBgUpdate = () => {
+ if (animationFrameId) return
+ animationFrameId = window.requestAnimationFrame(updateBg)
+ }
+
+ updateBg()
+
+ const themeObserver = new MutationObserver(scheduleBgUpdate)
+ themeObserver.observe(document.documentElement, {
+ attributes: true,
+ attributeFilter: ["class"],
+ })
+
+ const buttonObserver = new MutationObserver(scheduleBgUpdate)
+ buttonObserver.observe(button, {
+ attributes: true,
+ })
+
+ const syncEvents = [
+ "blur",
+ "focus",
+ "pointerenter",
+ "pointerleave",
+ ] as const
+
+ for (const eventName of syncEvents) {
+ button.addEventListener(eventName, scheduleBgUpdate)
+ }
+
+ return () => {
+ if (animationFrameId) {
+ window.cancelAnimationFrame(animationFrameId)
+ }
+
+ themeObserver.disconnect()
+ buttonObserver.disconnect()
+
+ for (const eventName of syncEvents) {
+ button.removeEventListener(eventName, scheduleBgUpdate)
+ }
+ }
+ }, [pulseColor])
+
return (
)
}
@@ -12942,6 +13023,25 @@ export default function PulsatingButtonDemo() {
}
+===== EXAMPLE: pulsating-button-demo-2 =====
+Title: Pulsating Button Demo 2
+
+--- file: example/pulsating-button-demo-2.tsx ---
+import { PulsatingButton } from "@/registry/magicui/pulsating-button"
+
+export default function PulsatingButtonDemo2() {
+ return (
+
+ Join Affiliate Program
+
+ )
+}
+
+
===== COMPONENT: rainbow-button =====
Title: Rainbow Button
diff --git a/apps/www/public/llms.txt b/apps/www/public/llms.txt
index bbed3d35b..ffbd983a5 100644
--- a/apps/www/public/llms.txt
+++ b/apps/www/public/llms.txt
@@ -193,6 +193,7 @@ This file provides LLM-friendly entry points to documentation and examples.
- [Cool Mode Demo](https://github.com/magicuidesign/magicui/blob/main/example/cool-mode-demo.tsx): Example usage
- [Cool Mode Custom](https://github.com/magicuidesign/magicui/blob/main/example/cool-mode-custom.tsx): Example usage
- [Pulsating Button Demo](https://github.com/magicuidesign/magicui/blob/main/example/pulsating-button-demo.tsx): Example usage
+- [Pulsating Button Demo 2](https://github.com/magicuidesign/magicui/blob/main/example/pulsating-button-demo-2.tsx): Example usage
- [Ripple Button Demo](https://github.com/magicuidesign/magicui/blob/main/example/ripple-button-demo.tsx): Example usage
- [File Tree Demo](https://github.com/magicuidesign/magicui/blob/main/example/file-tree-demo.tsx): Example usage
- [Blur Fade Demo](https://github.com/magicuidesign/magicui/blob/main/example/blur-fade-demo.tsx): Example usage
diff --git a/apps/www/public/r/pulsating-button-demo-2.json b/apps/www/public/r/pulsating-button-demo-2.json
new file mode 100644
index 000000000..02bb6f21a
--- /dev/null
+++ b/apps/www/public/r/pulsating-button-demo-2.json
@@ -0,0 +1,17 @@
+{
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
+ "name": "pulsating-button-demo-2",
+ "type": "registry:example",
+ "title": "Pulsating Button Demo 2",
+ "description": "Example showing an animated pulsating button with a ripple varaint.",
+ "registryDependencies": [
+ "@magicui/pulsating-button"
+ ],
+ "files": [
+ {
+ "path": "registry/example/pulsating-button-demo-2.tsx",
+ "content": "import { PulsatingButton } from \"@/registry/magicui/pulsating-button\"\n\nexport default function PulsatingButtonDemo2() {\n return (\n \n Join Affiliate Program\n \n )\n}\n",
+ "type": "registry:example"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/apps/www/public/r/pulsating-button.json b/apps/www/public/r/pulsating-button.json
index 853f24b01..dff60452f 100644
--- a/apps/www/public/r/pulsating-button.json
+++ b/apps/www/public/r/pulsating-button.json
@@ -7,22 +7,31 @@
"files": [
{
"path": "registry/magicui/pulsating-button.tsx",
- "content": "import React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\ninterface PulsatingButtonProps extends React.ButtonHTMLAttributes {\n pulseColor?: string\n duration?: string\n}\n\nexport const PulsatingButton = React.forwardRef<\n HTMLButtonElement,\n PulsatingButtonProps\n>(\n (\n {\n className,\n children,\n pulseColor = \"#808080\",\n duration = \"1.5s\",\n ...props\n },\n ref\n ) => {\n return (\n \n )\n }\n)\n\nPulsatingButton.displayName = \"PulsatingButton\"\n",
+ "content": "\"use client\"\n\nimport React, { useImperativeHandle, useLayoutEffect, useRef } from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\ninterface PulsatingButtonProps extends React.ButtonHTMLAttributes {\n pulseColor?: string\n duration?: string\n distance?: string\n variant?: \"pulse\" | \"ripple\"\n}\n\nexport const PulsatingButton = React.forwardRef<\n HTMLButtonElement,\n PulsatingButtonProps\n>(\n (\n {\n className,\n children,\n pulseColor,\n duration = \"1.5s\",\n distance = \"8px\",\n variant = \"pulse\",\n ...props\n },\n ref\n ) => {\n const innerRef = useRef(null)\n useImperativeHandle(ref, () => innerRef.current!)\n\n useLayoutEffect(() => {\n const button = innerRef.current\n if (!button) return\n\n if (pulseColor) {\n button.style.removeProperty(\"--bg\")\n return\n }\n\n let animationFrameId = 0\n let currentBg = \"\"\n\n const updateBg = () => {\n animationFrameId = 0\n\n const nextBg = getComputedStyle(button).backgroundColor\n if (nextBg === currentBg) return\n\n currentBg = nextBg\n button.style.setProperty(\"--bg\", nextBg)\n }\n\n const scheduleBgUpdate = () => {\n if (animationFrameId) return\n animationFrameId = window.requestAnimationFrame(updateBg)\n }\n\n updateBg()\n\n const themeObserver = new MutationObserver(scheduleBgUpdate)\n themeObserver.observe(document.documentElement, {\n attributes: true,\n attributeFilter: [\"class\"],\n })\n\n const buttonObserver = new MutationObserver(scheduleBgUpdate)\n buttonObserver.observe(button, {\n attributes: true,\n })\n\n const syncEvents = [\n \"blur\",\n \"focus\",\n \"pointerenter\",\n \"pointerleave\",\n ] as const\n\n for (const eventName of syncEvents) {\n button.addEventListener(eventName, scheduleBgUpdate)\n }\n\n return () => {\n if (animationFrameId) {\n window.cancelAnimationFrame(animationFrameId)\n }\n\n themeObserver.disconnect()\n buttonObserver.disconnect()\n\n for (const eventName of syncEvents) {\n button.removeEventListener(eventName, scheduleBgUpdate)\n }\n }\n }, [pulseColor])\n\n return (\n \n )\n }\n)\n\nPulsatingButton.displayName = \"PulsatingButton\"\n",
"type": "registry:ui"
}
],
"cssVars": {
"theme": {
- "animate-pulse": "pulse var(--duration) ease-out infinite"
+ "animate-pulse": "pulse var(--duration) ease-out infinite",
+ "animate-pulse-ripple": "pulse-ripple var(--duration) cubic-bezier(0.16, 1, 0.3, 1) infinite"
}
},
"css": {
"@keyframes pulse": {
"0%, 100%": {
- "boxShadow": "0 0 0 0 var(--pulse-color)"
+ "boxShadow": "0 0 0 0 var(--pulse-color, oklch(from var(--bg) l c h / 0.5))"
},
"50%": {
- "boxShadow": "0 0 0 8px var(--pulse-color)"
+ "boxShadow": "0 0 0 var(--distance) var(--pulse-color, oklch(from var(--bg) l c h / 0.5))"
+ }
+ },
+ "@keyframes pulse-ripple": {
+ "0%": {
+ "boxShadow": "0 0 0 0 oklch(from var(--pulse-color, var(--bg)) l c h / 1)"
+ },
+ "100%": {
+ "boxShadow": "0 0 0 var(--distance) oklch(from var(--pulse-color, var(--bg)) l c h / 0)"
}
}
}
diff --git a/apps/www/public/r/registry.json b/apps/www/public/r/registry.json
index 2bbad9d0e..197f12ebf 100644
--- a/apps/www/public/r/registry.json
+++ b/apps/www/public/r/registry.json
@@ -1000,16 +1000,25 @@
],
"cssVars": {
"theme": {
- "animate-pulse": "pulse var(--duration) ease-out infinite"
+ "animate-pulse": "pulse var(--duration) ease-out infinite",
+ "animate-pulse-ripple": "pulse-ripple var(--duration) cubic-bezier(0.16, 1, 0.3, 1) infinite"
}
},
"css": {
"@keyframes pulse": {
"0%, 100%": {
- "boxShadow": "0 0 0 0 var(--pulse-color)"
+ "boxShadow": "0 0 0 0 var(--pulse-color, oklch(from var(--bg) l c h / 0.5))"
},
"50%": {
- "boxShadow": "0 0 0 8px var(--pulse-color)"
+ "boxShadow": "0 0 0 var(--distance) var(--pulse-color, oklch(from var(--bg) l c h / 0.5))"
+ }
+ },
+ "@keyframes pulse-ripple": {
+ "0%": {
+ "boxShadow": "0 0 0 0 oklch(from var(--pulse-color, var(--bg)) l c h / 1)"
+ },
+ "100%": {
+ "boxShadow": "0 0 0 var(--distance) oklch(from var(--pulse-color, var(--bg)) l c h / 0)"
}
}
}
@@ -3020,6 +3029,21 @@
}
]
},
+ {
+ "name": "pulsating-button-demo-2",
+ "type": "registry:example",
+ "title": "Pulsating Button Demo 2",
+ "description": "Example showing an animated pulsating button with a ripple varaint.",
+ "registryDependencies": [
+ "@magicui/pulsating-button"
+ ],
+ "files": [
+ {
+ "path": "registry/example/pulsating-button-demo-2.tsx",
+ "type": "registry:example"
+ }
+ ]
+ },
{
"name": "ripple-button-demo",
"type": "registry:example",
diff --git a/apps/www/public/registry.json b/apps/www/public/registry.json
index 2bbad9d0e..197f12ebf 100644
--- a/apps/www/public/registry.json
+++ b/apps/www/public/registry.json
@@ -1000,16 +1000,25 @@
],
"cssVars": {
"theme": {
- "animate-pulse": "pulse var(--duration) ease-out infinite"
+ "animate-pulse": "pulse var(--duration) ease-out infinite",
+ "animate-pulse-ripple": "pulse-ripple var(--duration) cubic-bezier(0.16, 1, 0.3, 1) infinite"
}
},
"css": {
"@keyframes pulse": {
"0%, 100%": {
- "boxShadow": "0 0 0 0 var(--pulse-color)"
+ "boxShadow": "0 0 0 0 var(--pulse-color, oklch(from var(--bg) l c h / 0.5))"
},
"50%": {
- "boxShadow": "0 0 0 8px var(--pulse-color)"
+ "boxShadow": "0 0 0 var(--distance) var(--pulse-color, oklch(from var(--bg) l c h / 0.5))"
+ }
+ },
+ "@keyframes pulse-ripple": {
+ "0%": {
+ "boxShadow": "0 0 0 0 oklch(from var(--pulse-color, var(--bg)) l c h / 1)"
+ },
+ "100%": {
+ "boxShadow": "0 0 0 var(--distance) oklch(from var(--pulse-color, var(--bg)) l c h / 0)"
}
}
}
@@ -3020,6 +3029,21 @@
}
]
},
+ {
+ "name": "pulsating-button-demo-2",
+ "type": "registry:example",
+ "title": "Pulsating Button Demo 2",
+ "description": "Example showing an animated pulsating button with a ripple varaint.",
+ "registryDependencies": [
+ "@magicui/pulsating-button"
+ ],
+ "files": [
+ {
+ "path": "registry/example/pulsating-button-demo-2.tsx",
+ "type": "registry:example"
+ }
+ ]
+ },
{
"name": "ripple-button-demo",
"type": "registry:example",
diff --git a/apps/www/registry.json b/apps/www/registry.json
index 2bbad9d0e..197f12ebf 100644
--- a/apps/www/registry.json
+++ b/apps/www/registry.json
@@ -1000,16 +1000,25 @@
],
"cssVars": {
"theme": {
- "animate-pulse": "pulse var(--duration) ease-out infinite"
+ "animate-pulse": "pulse var(--duration) ease-out infinite",
+ "animate-pulse-ripple": "pulse-ripple var(--duration) cubic-bezier(0.16, 1, 0.3, 1) infinite"
}
},
"css": {
"@keyframes pulse": {
"0%, 100%": {
- "boxShadow": "0 0 0 0 var(--pulse-color)"
+ "boxShadow": "0 0 0 0 var(--pulse-color, oklch(from var(--bg) l c h / 0.5))"
},
"50%": {
- "boxShadow": "0 0 0 8px var(--pulse-color)"
+ "boxShadow": "0 0 0 var(--distance) var(--pulse-color, oklch(from var(--bg) l c h / 0.5))"
+ }
+ },
+ "@keyframes pulse-ripple": {
+ "0%": {
+ "boxShadow": "0 0 0 0 oklch(from var(--pulse-color, var(--bg)) l c h / 1)"
+ },
+ "100%": {
+ "boxShadow": "0 0 0 var(--distance) oklch(from var(--pulse-color, var(--bg)) l c h / 0)"
}
}
}
@@ -3020,6 +3029,21 @@
}
]
},
+ {
+ "name": "pulsating-button-demo-2",
+ "type": "registry:example",
+ "title": "Pulsating Button Demo 2",
+ "description": "Example showing an animated pulsating button with a ripple varaint.",
+ "registryDependencies": [
+ "@magicui/pulsating-button"
+ ],
+ "files": [
+ {
+ "path": "registry/example/pulsating-button-demo-2.tsx",
+ "type": "registry:example"
+ }
+ ]
+ },
{
"name": "ripple-button-demo",
"type": "registry:example",
diff --git a/apps/www/registry/__index__.tsx b/apps/www/registry/__index__.tsx
index 1719d025d..b3c0ec9ed 100644
--- a/apps/www/registry/__index__.tsx
+++ b/apps/www/registry/__index__.tsx
@@ -3143,6 +3143,23 @@ export const Index: Record = {
}),
meta: undefined,
},
+ "pulsating-button-demo-2": {
+ name: "pulsating-button-demo-2",
+ description: "Example showing an animated pulsating button with a ripple varaint.",
+ type: "registry:example",
+ registryDependencies: ["@magicui/pulsating-button"],
+ files: [{
+ path: "registry/example/pulsating-button-demo-2.tsx",
+ type: "registry:example",
+ target: ""
+ }],
+ component: React.lazy(async () => {
+ const mod = await import("@/registry/example/pulsating-button-demo-2.tsx")
+ const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') ?? item.name
+ return { default: mod.default ?? mod[exportName] }
+ }),
+ meta: undefined,
+ },
"ripple-button-demo": {
name: "ripple-button-demo",
description: "Example showing an animated button with ripple effect.",
diff --git a/apps/www/registry/example/pulsating-button-demo-2.tsx b/apps/www/registry/example/pulsating-button-demo-2.tsx
new file mode 100644
index 000000000..edb87b5de
--- /dev/null
+++ b/apps/www/registry/example/pulsating-button-demo-2.tsx
@@ -0,0 +1,13 @@
+import { PulsatingButton } from "@/registry/magicui/pulsating-button"
+
+export default function PulsatingButtonDemo2() {
+ return (
+
+ Join Affiliate Program
+
+ )
+}
diff --git a/apps/www/registry/magicui/pulsating-button.tsx b/apps/www/registry/magicui/pulsating-button.tsx
index 7e1254883..e2ea6142b 100644
--- a/apps/www/registry/magicui/pulsating-button.tsx
+++ b/apps/www/registry/magicui/pulsating-button.tsx
@@ -1,10 +1,14 @@
-import React from "react"
+"use client"
+
+import React, { useImperativeHandle, useLayoutEffect, useRef } from "react"
import { cn } from "@/lib/utils"
interface PulsatingButtonProps extends React.ButtonHTMLAttributes {
pulseColor?: string
duration?: string
+ distance?: string
+ variant?: "pulse" | "ripple"
}
export const PulsatingButton = React.forwardRef<
@@ -15,29 +19,106 @@ export const PulsatingButton = React.forwardRef<
{
className,
children,
- pulseColor = "#808080",
+ pulseColor,
duration = "1.5s",
+ distance = "8px",
+ variant = "pulse",
...props
},
ref
) => {
+ const innerRef = useRef(null)
+ useImperativeHandle(ref, () => innerRef.current!)
+
+ useLayoutEffect(() => {
+ const button = innerRef.current
+ if (!button) return
+
+ if (pulseColor) {
+ button.style.removeProperty("--bg")
+ return
+ }
+
+ let animationFrameId = 0
+ let currentBg = ""
+
+ const updateBg = () => {
+ animationFrameId = 0
+
+ const nextBg = getComputedStyle(button).backgroundColor
+ if (nextBg === currentBg) return
+
+ currentBg = nextBg
+ button.style.setProperty("--bg", nextBg)
+ }
+
+ const scheduleBgUpdate = () => {
+ if (animationFrameId) return
+ animationFrameId = window.requestAnimationFrame(updateBg)
+ }
+
+ updateBg()
+
+ const themeObserver = new MutationObserver(scheduleBgUpdate)
+ themeObserver.observe(document.documentElement, {
+ attributes: true,
+ attributeFilter: ["class"],
+ })
+
+ const buttonObserver = new MutationObserver(scheduleBgUpdate)
+ buttonObserver.observe(button, {
+ attributes: true,
+ })
+
+ const syncEvents = [
+ "blur",
+ "focus",
+ "pointerenter",
+ "pointerleave",
+ ] as const
+
+ for (const eventName of syncEvents) {
+ button.addEventListener(eventName, scheduleBgUpdate)
+ }
+
+ return () => {
+ if (animationFrameId) {
+ window.cancelAnimationFrame(animationFrameId)
+ }
+
+ themeObserver.disconnect()
+ buttonObserver.disconnect()
+
+ for (const eventName of syncEvents) {
+ button.removeEventListener(eventName, scheduleBgUpdate)
+ }
+ }
+ }, [pulseColor])
+
return (
)
}
diff --git a/apps/www/registry/registry-examples.ts b/apps/www/registry/registry-examples.ts
index 702f6aa65..18e913412 100644
--- a/apps/www/registry/registry-examples.ts
+++ b/apps/www/registry/registry-examples.ts
@@ -1520,6 +1520,20 @@ export const examples: Registry["items"] = [
},
],
},
+ {
+ name: "pulsating-button-demo-2",
+ type: "registry:example",
+ title: "Pulsating Button Demo 2",
+ description:
+ "Example showing an animated pulsating button with a ripple varaint.",
+ registryDependencies: ["@magicui/pulsating-button"],
+ files: [
+ {
+ path: "example/pulsating-button-demo-2.tsx",
+ type: "registry:example",
+ },
+ ],
+ },
{
name: "ripple-button-demo",
type: "registry:example",
diff --git a/apps/www/registry/registry-ui.ts b/apps/www/registry/registry-ui.ts
index c9e0de4cd..140129cdd 100644
--- a/apps/www/registry/registry-ui.ts
+++ b/apps/www/registry/registry-ui.ts
@@ -952,12 +952,30 @@ export const ui: Registry["items"] = [
cssVars: {
theme: {
"animate-pulse": "pulse var(--duration) ease-out infinite",
+ "animate-pulse-ripple":
+ "pulse-ripple var(--duration) cubic-bezier(0.16, 1, 0.3, 1) infinite",
},
},
css: {
"@keyframes pulse": {
- "0%, 100%": { boxShadow: "0 0 0 0 var(--pulse-color)" },
- "50%": { boxShadow: "0 0 0 8px var(--pulse-color)" },
+ "0%, 100%": {
+ boxShadow:
+ "0 0 0 0 var(--pulse-color, oklch(from var(--bg) l c h / 0.5))",
+ },
+ "50%": {
+ boxShadow:
+ "0 0 0 var(--distance) var(--pulse-color, oklch(from var(--bg) l c h / 0.5))",
+ },
+ },
+ "@keyframes pulse-ripple": {
+ "0%": {
+ boxShadow:
+ "0 0 0 0 oklch(from var(--pulse-color, var(--bg)) l c h / 1)",
+ },
+ "100%": {
+ boxShadow:
+ "0 0 0 var(--distance) oklch(from var(--pulse-color, var(--bg)) l c h / 0)",
+ },
},
},
},
diff --git a/apps/www/styles/globals.css b/apps/www/styles/globals.css
index e4d249b9f..cea7b8501 100644
--- a/apps/www/styles/globals.css
+++ b/apps/www/styles/globals.css
@@ -167,6 +167,7 @@
alternate;
--animate-shine: shine var(--duration) infinite linear;
--animate-pulse: pulse var(--duration) ease-out infinite;
+ --animate-pulse-ripple: pulse-ripple var(--duration) cubic-bezier(0.16, 1, 0.3, 1) infinite;
--animate-rainbow: rainbow var(--speed, 2s) infinite linear;
--animate-line-shadow: line-shadow 15s linear infinite;
--animate-aurora: aurora 8s ease-in-out infinite alternate;
@@ -366,10 +367,19 @@
@keyframes pulse {
0%,
100% {
- box-shadow: 0 0 0 0 var(--pulse-color);
+ box-shadow: 0 0 0 0 var(--pulse-color, oklch(from var(--bg) l c h / 0.5));
}
50% {
- box-shadow: 0 0 0 8px var(--pulse-color);
+ box-shadow: 0 0 0 var(--distance) var(--pulse-color, oklch(from var(--bg) l c h / 0.5));
+ }
+ }
+
+ @keyframes pulse-ripple {
+ 0% {
+ box-shadow: 0 0 0 0 oklch(from var(--pulse-color, var(--bg)) l c h / 1);
+ }
+ 100% {
+ box-shadow: 0 0 0 var(--distance) oklch(from var(--pulse-color, var(--bg)) l c h / 0);
}
}