From 2dbb1b218431f4d53ff6494dde2c9223c88f0c39 Mon Sep 17 00:00:00 2001 From: code-parth Date: Wed, 19 Nov 2025 15:21:10 +0800 Subject: [PATCH 1/4] fix: blogs mdx and add smooth-cursor analytics via npm --- .../blog/color-theory-for-web-design.mdx | 8 +- apps/www/content/blog/framer-motion-react.mdx | 2 +- .../content/blog/installing-tailwind-css.mdx | 101 +++++---- .../content/blog/next-js-getting-started.mdx | 56 ++--- apps/www/content/blog/tailwind-css-button.mdx | 89 ++++---- apps/www/content/blog/tailwind-css-themes.mdx | 70 +++--- .../content/docs/components/smooth-cursor.mdx | 6 +- apps/www/package.json | 1 + apps/www/public/llms-full.txt | 200 +----------------- apps/www/public/r/registry.json | 3 +- apps/www/public/r/smooth-cursor.json | 5 +- apps/www/public/registry.json | 3 +- apps/www/registry.json | 3 +- apps/www/registry/magicui/smooth-cursor.tsx | 200 +----------------- apps/www/registry/registry-ui.ts | 2 +- pnpm-lock.yaml | 17 ++ 16 files changed, 221 insertions(+), 545 deletions(-) diff --git a/apps/www/content/blog/color-theory-for-web-design.mdx b/apps/www/content/blog/color-theory-for-web-design.mdx index 737374133..a2c745522 100644 --- a/apps/www/content/blog/color-theory-for-web-design.mdx +++ b/apps/www/content/blog/color-theory-for-web-design.mdx @@ -158,10 +158,10 @@ The table below breaks down the most common contrast requirements you'll encount ### WCAG 2.1 Contrast Ratio Requirements -| Conformance Level | Normal Text (<18pt) | Large Text (≥18pt or 14pt bold) | UI Components & Graphics | -| :----------------- | :------------------ | :------------------------------ | :----------------------- | -| **AA (Minimum)** | **4.5:1** | **3:1** | **3:1** | -| **AAA (Enhanced)** | **7:1** | **4.5:1** | **4.5:1** | +| Conformance Level | Normal Text (<18pt) | Large Text (≥18pt or 14pt bold) | UI Components & Graphics | +| :----------------- | :--------------------: | :--------------------------------: | :----------------------- | +| **AA (Minimum)** | **4.5:1** | **3:1** | **3:1** | +| **AAA (Enhanced)** | **7:1** | **4.5:1** | **4.5:1** | As you can see, the requirements are more lenient for larger text and essential graphics, but aiming for the higher AA standard for your body copy is a solid starting point for any project. diff --git a/apps/www/content/blog/framer-motion-react.mdx b/apps/www/content/blog/framer-motion-react.mdx index 17ca79a88..52112e542 100644 --- a/apps/www/content/blog/framer-motion-react.mdx +++ b/apps/www/content/blog/framer-motion-react.mdx @@ -148,7 +148,7 @@ Sometimes, seeing the difference laid out helps clarify why one approach is bett ### Animation Props vs Variants -| Feature | Direct Props (e.g., animate={{...}}) | Variants (e.g., variants={...}) | +| Feature | Direct Props (e.g., `animate={{...}}`) | Variants (e.g., `variants={...}`) | | :---------------- | :-------------------------------------------- | :------------------------------------------ | | **Readability** | Poor for complex states. JSX gets cluttered. | Excellent. Separates logic from markup. | | **Reusability** | Low. Logic is tied directly to the component. | High. Can be imported and used anywhere. | diff --git a/apps/www/content/blog/installing-tailwind-css.mdx b/apps/www/content/blog/installing-tailwind-css.mdx index 672307e55..ec7b290e0 100644 --- a/apps/www/content/blog/installing-tailwind-css.mdx +++ b/apps/www/content/blog/installing-tailwind-css.mdx @@ -97,17 +97,19 @@ Alright, this next part is the most critical step of the entire process. Serious Open up your `tailwind.config.js` file and find the `content` array. You need to add paths to every single file that might contain a Tailwind class—HTML files, JavaScript components, you name it. -/** @type {import('tailwindcss').Config} \*/ +```js +/** @type {import('tailwindcss').Config} */ export default { -content: [ -'./public/**/_.html', -'./src/\*\*/_.{js,jsx,ts,tsx,vue}', -], -theme: { -extend: {}, -}, -plugins: [], + content: [ + './public/**/*.html', + './src/**/*.{js,jsx,ts,tsx,vue}', + ], + theme: { + extend: {}, + }, + plugins: [], } +``` This example uses "glob patterns" to find any `.html` files in the `public` folder and a variety of component files inside `src`. Make sure you adjust these paths to match your project's actual folder structure. @@ -174,18 +176,20 @@ npx tailwindcss init -p The real key is modifying your `tailwind.config.js`. You need to point the `content` array to your **JSX** and **TSX** files so Tailwind’s JIT compiler knows where to look for utility classes. +```js // tailwind.config.js -/** @type {import('tailwindcss').Config} \*/ +/** @type {import('tailwindcss').Config} */ export default { -content: [ -"./index.html", -"./src/**/\*.{js,ts,jsx,tsx}", -], -theme: { -extend: {}, -}, -plugins: [], + content: [ + "./index.html", + "./src/**/*.{js,ts,jsx,tsx}", + ], + theme: { + extend: {}, + }, + plugins: [], } +``` Lastly, create a main CSS file (something like `src/index.css`) and drop in the three core Tailwind directives. Just make sure to import this CSS file at the very top of your main entry point, which is usually `src/main.jsx`. @@ -199,19 +203,21 @@ But if you're adding Tailwind to an _existing_ Next.js project, the process is n The only significant difference is the `content` path in `tailwind.config.js`. You’ll want to configure it to scan the `pages`, `components`, and `app` directories commonly found in Next.js apps. +```js // tailwind.config.js -/** @type {import('tailwindcss').Config} \*/ +/** @type {import('tailwindcss').Config} */ module.exports = { -content: [ -'./pages/**/_.{js,ts,jsx,tsx,mdx}', -'./components/\*\*/_.{js,ts,jsx,tsx,mdx}', -'./app/\*_/_.{js,ts,jsx,tsx,mdx}', -], -theme: { -extend: {}, -}, -plugins: [], + content: [ + './pages/**/*.{js,ts,jsx,tsx,mdx}', + './components/**/*.{js,ts,jsx,tsx,mdx}', + './app/**/*.{js,ts,jsx,tsx,mdx}', + ], + theme: { + extend: {}, + }, + plugins: [], } +``` Once that's configured, just add the `@tailwind` directives to your `globals.css` file and make sure it’s imported in your `_app.js` or `layout.js` file. @@ -251,22 +257,25 @@ You don't have to throw the baby out with the bathwater. The `theme.extend` obje For instance, here’s how you might add a couple of custom brand colors and a new display font: +```js // tailwind.config.js module.exports = { -theme: { -extend: { -colors: { -'brand-primary': '#0A74DA', -'brand-secondary': '#F6C90E', -}, -fontFamily: { -'sans': ['Inter', 'sans-serif'], -'display': ['Poppins', 'sans-serif'], -}, -}, -}, -plugins: [], + theme: { + extend: { + colors: { + 'brand-primary': '#0A74DA', + 'brand-secondary': '#F6C90E', + }, + fontFamily: { + 'sans': ['Inter', 'sans-serif'], + 'display': ['Poppins', 'sans-serif'], + }, + }, + }, + plugins: [], } +``` + Just like that, you can start using classes like `bg-brand-primary` or `font-display` right in your markup. This is how you enforce consistency and stick to your design specs, which is a huge step up from a basic installation. > The ability to extend the theme is what makes Tailwind so scalable. You can start with the defaults for rapid prototyping and then gradually introduce custom tokens as your design system matures, all without breaking your existing work. @@ -283,13 +292,15 @@ First, you'll need to install the package: Then, just require it inside the `plugins` array in your config file. +```js // tailwind.config.js module.exports = { -// ... -plugins: [ -require('@tailwindcss/typography'), -], + // ... + plugins: [ + require('@tailwindcss/typography'), + ], } +``` Now, just by adding a single `prose` className to any container of raw HTML, it will be beautifully styled from top to bottom. This modular approach is great because it keeps your main configuration file clean and makes it simple to add or remove features as your project's needs change. diff --git a/apps/www/content/blog/next-js-getting-started.mdx b/apps/www/content/blog/next-js-getting-started.mdx index 47435a771..0d9ffe961 100644 --- a/apps/www/content/blog/next-js-getting-started.mdx +++ b/apps/www/content/blog/next-js-getting-started.mdx @@ -141,14 +141,16 @@ Let's get our hands dirty and build out two pages every website needs: a homepag And that's literally all it takes. You've just created the `/about` route. Now, let's drop some basic React code into `app/about/page.tsx` so it actually displays something. // app/about/page.tsx +```tsx export default function AboutPage() { -return ( - -
-

About Us

-

This is the about page for our first Next.js app!

-
-); } + return ( +
+

About Us

+

This is the about page for our first Next.js app!

+
+ ); +} +``` With that code in place, Next.js handles the rest. Fire up your browser and navigate to `localhost:3000/about`, and you'll see your new component rendered on the screen. @@ -211,33 +213,35 @@ One of the coolest things about Server Components is the ability to just drop `a Let's look at a practical example. Say you want to pull in a list of products from a public REST API and display them on a page. Here’s how you could fetch and render that data right inside a page component. +```tsx // app/products/page.tsx async function getProducts() { -const res = await fetch('https://dummyjson.com/products'); -if (!res.ok) { -// This will activate the closest `error.js` Error Boundary -throw new Error('Failed to fetch data'); -} -return res.json(); + const res = await fetch('https://dummyjson.com/products'); + if (!res.ok) { + // This will activate the closest `error.js` Error Boundary + throw new Error('Failed to fetch data'); + } + return res.json(); } export default async function ProductsPage() { const data = await getProducts(); -return ( - -
-

Our Products

-
- {data.products.map((product) => ( -
-

{product.title}

-

{product.description}

+ return ( +
+

Our Products

+
+ {{"{"}}data.products.map((product) => ( +
+

{{"{"}}product.title{{"}"}}

+

{{"{"}}product.description{{"}"}}

+
+ )){{"}"}}
- ))} -
-
-); } + + ); +} +``` Did you catch it? `ProductsPage` is an `async` function. That one keyword lets you `await` the `getProducts()` call right before returning your JSX. It’s an incredibly clean and direct way to handle what used to be a much more complicated task. diff --git a/apps/www/content/blog/tailwind-css-button.mdx b/apps/www/content/blog/tailwind-css-button.mdx index 0dfbe9ce4..9d4727636 100644 --- a/apps/www/content/blog/tailwind-css-button.mdx +++ b/apps/www/content/blog/tailwind-css-button.mdx @@ -173,18 +173,21 @@ By giving it precise paths to your template files, you make sure the compiler sc A typical setup for a Next.js project, for example, might look like this: -/** @type {import('tailwindcss').Config} \*/ +```js +/** @type {import('tailwindcss').Config} */ module.exports = { -content: [ -'./src/pages/**/_.{js,ts,jsx,tsx,mdx}', -'./src/components/\*\*/_.{js,ts,jsx,tsx,mdx}', -'./src/app/\*_/_.{js,ts,jsx,tsx,mdx}', -], -theme: { -extend: {}, -}, -plugins: [], + content: [ + './src/pages/**/*.{js,ts,jsx,tsx,mdx}', + './src/components/**/*.{js,ts,jsx,tsx,mdx}', + './src/app/**/*.{js,ts,jsx,tsx,mdx}', + ], + theme: { + extend: {}, + }, + plugins: [], }; +``` + Keeping these paths clean and accurate is a simple but incredibly powerful way to guarantee a lean, optimized build every time. ### The HTML vs. CSS Performance Question @@ -215,19 +218,18 @@ We'll set up our component to accept props for `variant` (like `primary` or `sec Here’s the basic skeleton: +```jsx // A basic structure for a reusable Button component function Button({ children, variant = 'primary', size = 'md', ...props }) { -// We'll add class logic here -const baseClasses = 'font-semibold rounded-lg shadow-md focus:outline-none focus:ring-2 focus:ring-offset-2'; - +// We'll add class logic here const baseClasses = 'font-semibold rounded-lg shadow-md focus:outline-none focus:ring-2 focus:ring-offset-2'; // The final className will be built conditionally -return ( - - -); + return ( + + ); } +``` This is our starting point. We've got a set of base styles that apply to _every_ button. Now, we just need a clean way to manage the classes that change based on the `variant` and `size` props we pass in. @@ -243,33 +245,36 @@ Let’s bring these tools together to flesh out our component's logic. Here’s the finished `Button` component, fully equipped to handle variants and sizes gracefully. If you're looking for more real-world examples, exploring a high-quality **[Tailwind CSS component library](https://magicui.design/blog/tailwind-css-component-library)** can show you just how powerful this pattern is. +```tsx import { clsx } from "clsx" import { twMerge } from "tailwind-merge" function Button({ children, variant = 'primary', size = 'md', className, ...props }) { -const baseClasses = 'font-semibold rounded-lg shadow-md focus:outline-none focus:ring-2 focus:ring-offset-2 transition-colors'; - -const variantClasses = { -primary: 'bg-indigo-600 text-white hover:bg-indigo-700 focus:ring-indigo-500', -secondary: 'bg-gray-200 text-gray-800 hover:bg-gray-300 focus:ring-gray-500', -}; - -const sizeClasses = { -sm: 'py-1 px-3 text-sm', -md: 'py-2 px-4 text-base', -lg: 'py-3 px-6 text-lg', -}; - -const finalClasses = twMerge( -clsx(baseClasses, variantClasses[variant], sizeClasses[size], className) -); - -return ( - - -); } + const baseClasses = + 'font-semibold rounded-lg shadow-md focus:outline-none focus:ring-2 focus:ring-offset-2 transition-colors'; + + const variantClasses = { + primary: 'bg-indigo-600 text-white hover:bg-indigo-700 focus:ring-indigo-500', + secondary: 'bg-gray-200 text-gray-800 hover:bg-gray-300 focus:ring-gray-500', + }; + + const sizeClasses = { + sm: 'py-1 px-3 text-sm', + md: 'py-2 px-4 text-base', + lg: 'py-3 px-6 text-lg', + }; + + const finalClasses = twMerge( + clsx(baseClasses, variantClasses[variant], sizeClasses[size], className) + ); + + return ( + + ); +} +``` And there you have it. This reusable `Button` component is now the foundation for a scalable, maintainable UI system. You can call it anywhere in your app, pass a few props, and get a perfectly styled, consistent button every time. diff --git a/apps/www/content/blog/tailwind-css-themes.mdx b/apps/www/content/blog/tailwind-css-themes.mdx index d92b84780..21a766897 100644 --- a/apps/www/content/blog/tailwind-css-themes.mdx +++ b/apps/www/content/blog/tailwind-css-themes.mdx @@ -81,17 +81,19 @@ So where does the real magic happen? It's when Tailwind takes these definitions Let's say we want to add a custom primary color to our configuration: +```js // tailwind.config.js module.exports = { -theme: { -extend: { -colors: { -primary: '#3490dc', -}, -}, -}, -plugins: [], + theme: { + extend: { + colors: { + primary: '#3490dc', + }, + }, + }, + plugins: [], } +``` With just that simple addition, you've unlocked a whole set of brand-new utilities. You can now jump into your HTML and use classes like `bg-primary`, `text-primary`, and `border-primary`. This direct link between your config and your code is what makes Tailwind so incredibly powerful for theming. It ensures you’re always pulling from approved brand colors, which means no more guesswork or random one-off hex codes. @@ -125,29 +127,33 @@ A polished theme almost always needs custom typography and a sophisticated appro To bring in custom fonts, you can drop a standard `@font-face` rule into your main CSS file and then give it a name in your `tailwind.config.js`. -/_ In your main CSS file _/ +```css +/* In your main CSS file */ @font-face { -font-family: 'Inter'; -src: url('/fonts/Inter-Regular.woff2') format('woff2'); -font-weight: 400; -font-style: normal; + font-family: 'Inter'; + src: url('/fonts/Inter-Regular.woff2') format('woff2'); + font-weight: 400; + font-style: normal; } +``` From there, you just wire it up in your theme configuration to make it available as a utility class, like `font-display`. This keeps your typography consistent and lets you apply it with the same utility-first workflow you use for everything else. In the same way, you can define more specific responsive breakpoints for trickier layouts. While Tailwind's defaults are a great starting point, your design might need to target some non-standard screen sizes. +```js // tailwind.config.js module.exports = { -theme: { -extend: { -screens: { -'xs': '480px', -'3xl': '1920px', -}, -}, -}, + theme: { + extend: { + screens: { + 'xs': '480px', + '3xl': '1920px', + }, + }, + }, } +``` Just like that, you’ve created new `xs:` and `3xl:` prefixes, giving you much finer control over how your layout snaps into place across a wider range of devices. @@ -157,10 +163,12 @@ Sometimes you need a little escape hatch. Maybe you need to access your design t The `theme()` function is your bridge between your config file and your custom CSS. It lets you pull any design token from `tailwind.config.js` right into a CSS rule. It's perfect for styling complex components that need a bit more than just utility classes. +```css .custom-component { -background-color: theme('colors.primary'); -padding: theme('spacing.4'); + background-color: theme('colors.primary'); + padding: theme('spacing.4'); } +``` This ensures that even your custom CSS stays perfectly in sync with your design system. @@ -189,16 +197,18 @@ Two of the most indispensable official plugins are: Getting them set up is a breeze. First, pull the package in with npm, then simply register it in the `plugins` array of your `tailwind.config.js` file. +```js // tailwind.config.js module.exports = { -theme: { -// ... -}, -plugins: [ -require('@tailwindcss/typography'), -require('@tailwindcss/forms'), -], + theme: { + // ... + }, + plugins: [ + require('@tailwindcss/typography'), + require('@tailwindcss/forms'), + ], } +``` Just like that, you’ve unlocked powerful new features that feel like a natural part of your theme. diff --git a/apps/www/content/docs/components/smooth-cursor.mdx b/apps/www/content/docs/components/smooth-cursor.mdx index daf1d396a..3c73f33eb 100644 --- a/apps/www/content/docs/components/smooth-cursor.mdx +++ b/apps/www/content/docs/components/smooth-cursor.mdx @@ -34,6 +34,10 @@ npx shadcn@latest add @magicui/smooth-cursor +```bash +npx add smooth-cursor framer-motion +``` + Copy and paste the following code into your project. @@ -104,7 +108,7 @@ select { | Prop | Type | Default | Description | | -------------- | ----------------- | ---------------------- | ------------------------------------------------------ | -| `cursor` | `React.ReactNode` | `` | Custom cursor component to replace the default cursor | +| `cursor` | `JSX.Element`. | `` | Custom cursor component to replace the default cursor | | `springConfig` | `SpringConfig` | See below | Configuration object for the spring animation behavior | ### SpringConfig Type diff --git a/apps/www/package.json b/apps/www/package.json index 23825bda3..12ffde7cb 100644 --- a/apps/www/package.json +++ b/apps/www/package.json @@ -65,6 +65,7 @@ "react-tweet": "^3.2.2", "rough-notation": "^0.5.1", "schema-dts": "^1.1.5", + "smooth-cursor": "^0.1.2", "sonner": "^1.7.4", "svg-dotted-map": "^2.0.1", "tailwind-merge": "^3.3.1", diff --git a/apps/www/public/llms-full.txt b/apps/www/public/llms-full.txt index 528ffc5dd..f7795c542 100644 --- a/apps/www/public/llms-full.txt +++ b/apps/www/public/llms-full.txt @@ -13226,16 +13226,11 @@ Description: A customizable, physics-based smooth cursor animation component wit --- file: magicui/smooth-cursor.tsx --- "use client" -import { FC, useEffect, useRef, useState } from "react" -import { motion, useSpring } from "motion/react" - -interface Position { - x: number - y: number -} +import { JSX } from "react" +import { SmoothCursor as SmoothCursorComponent } from "smooth-cursor" export interface SmoothCursorProps { - cursor?: React.ReactNode + cursor?: JSX.Element springConfig?: { damping: number stiffness: number @@ -13244,193 +13239,8 @@ export interface SmoothCursorProps { } } -const DefaultCursorSVG: FC = () => { - return ( - - - - - - - - - - - - - - - - - - - ) -} - -export function SmoothCursor({ - cursor = , - springConfig = { - damping: 45, - stiffness: 400, - mass: 1, - restDelta: 0.001, - }, -}: SmoothCursorProps) { - const [isMoving, setIsMoving] = useState(false) - const lastMousePos = useRef({ x: 0, y: 0 }) - const velocity = useRef({ x: 0, y: 0 }) - const lastUpdateTime = useRef(Date.now()) - const previousAngle = useRef(0) - const accumulatedRotation = useRef(0) - - const cursorX = useSpring(0, springConfig) - const cursorY = useSpring(0, springConfig) - const rotation = useSpring(0, { - ...springConfig, - damping: 60, - stiffness: 300, - }) - const scale = useSpring(1, { - ...springConfig, - stiffness: 500, - damping: 35, - }) - - useEffect(() => { - const updateVelocity = (currentPos: Position) => { - const currentTime = Date.now() - const deltaTime = currentTime - lastUpdateTime.current - - if (deltaTime > 0) { - velocity.current = { - x: (currentPos.x - lastMousePos.current.x) / deltaTime, - y: (currentPos.y - lastMousePos.current.y) / deltaTime, - } - } - - lastUpdateTime.current = currentTime - lastMousePos.current = currentPos - } - - const smoothMouseMove = (e: MouseEvent) => { - const currentPos = { x: e.clientX, y: e.clientY } - updateVelocity(currentPos) - - const speed = Math.sqrt( - Math.pow(velocity.current.x, 2) + Math.pow(velocity.current.y, 2) - ) - - cursorX.set(currentPos.x) - cursorY.set(currentPos.y) - - if (speed > 0.1) { - const currentAngle = - Math.atan2(velocity.current.y, velocity.current.x) * (180 / Math.PI) + - 90 - - let angleDiff = currentAngle - previousAngle.current - if (angleDiff > 180) angleDiff -= 360 - if (angleDiff < -180) angleDiff += 360 - accumulatedRotation.current += angleDiff - rotation.set(accumulatedRotation.current) - previousAngle.current = currentAngle - - scale.set(0.95) - setIsMoving(true) - - const timeout = setTimeout(() => { - scale.set(1) - setIsMoving(false) - }, 150) - - return () => clearTimeout(timeout) - } - } - - let rafId: number - const throttledMouseMove = (e: MouseEvent) => { - if (rafId) return - - rafId = requestAnimationFrame(() => { - smoothMouseMove(e) - rafId = 0 - }) - } - - document.body.style.cursor = "none" - window.addEventListener("mousemove", throttledMouseMove) - - return () => { - window.removeEventListener("mousemove", throttledMouseMove) - document.body.style.cursor = "auto" - if (rafId) cancelAnimationFrame(rafId) - } - }, [cursorX, cursorY, rotation, scale]) - - return ( - - {cursor} - - ) +export function SmoothCursor({ cursor, springConfig }: SmoothCursorProps) { + return } diff --git a/apps/www/public/r/registry.json b/apps/www/public/r/registry.json index 9a498d52b..abd0f2654 100644 --- a/apps/www/public/r/registry.json +++ b/apps/www/public/r/registry.json @@ -193,7 +193,8 @@ "type": "registry:ui", "description": "A customizable, physics-based smooth cursor animation component with spring animations and rotation effects", "dependencies": [ - "framer-motion" + "framer-motion", + "smooth-cursor" ], "files": [ { diff --git a/apps/www/public/r/smooth-cursor.json b/apps/www/public/r/smooth-cursor.json index 3c2ea0205..40ce8260f 100644 --- a/apps/www/public/r/smooth-cursor.json +++ b/apps/www/public/r/smooth-cursor.json @@ -4,12 +4,13 @@ "type": "registry:ui", "description": "A customizable, physics-based smooth cursor animation component with spring animations and rotation effects", "dependencies": [ - "framer-motion" + "framer-motion", + "smooth-cursor" ], "files": [ { "path": "registry/magicui/smooth-cursor.tsx", - "content": "\"use client\"\n\nimport { FC, useEffect, useRef, useState } from \"react\"\nimport { motion, useSpring } from \"motion/react\"\n\ninterface Position {\n x: number\n y: number\n}\n\nexport interface SmoothCursorProps {\n cursor?: React.ReactNode\n springConfig?: {\n damping: number\n stiffness: number\n mass: number\n restDelta: number\n }\n}\n\nconst DefaultCursorSVG: FC = () => {\n return (\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n )\n}\n\nexport function SmoothCursor({\n cursor = ,\n springConfig = {\n damping: 45,\n stiffness: 400,\n mass: 1,\n restDelta: 0.001,\n },\n}: SmoothCursorProps) {\n const [isMoving, setIsMoving] = useState(false)\n const lastMousePos = useRef({ x: 0, y: 0 })\n const velocity = useRef({ x: 0, y: 0 })\n const lastUpdateTime = useRef(Date.now())\n const previousAngle = useRef(0)\n const accumulatedRotation = useRef(0)\n\n const cursorX = useSpring(0, springConfig)\n const cursorY = useSpring(0, springConfig)\n const rotation = useSpring(0, {\n ...springConfig,\n damping: 60,\n stiffness: 300,\n })\n const scale = useSpring(1, {\n ...springConfig,\n stiffness: 500,\n damping: 35,\n })\n\n useEffect(() => {\n const updateVelocity = (currentPos: Position) => {\n const currentTime = Date.now()\n const deltaTime = currentTime - lastUpdateTime.current\n\n if (deltaTime > 0) {\n velocity.current = {\n x: (currentPos.x - lastMousePos.current.x) / deltaTime,\n y: (currentPos.y - lastMousePos.current.y) / deltaTime,\n }\n }\n\n lastUpdateTime.current = currentTime\n lastMousePos.current = currentPos\n }\n\n const smoothMouseMove = (e: MouseEvent) => {\n const currentPos = { x: e.clientX, y: e.clientY }\n updateVelocity(currentPos)\n\n const speed = Math.sqrt(\n Math.pow(velocity.current.x, 2) + Math.pow(velocity.current.y, 2)\n )\n\n cursorX.set(currentPos.x)\n cursorY.set(currentPos.y)\n\n if (speed > 0.1) {\n const currentAngle =\n Math.atan2(velocity.current.y, velocity.current.x) * (180 / Math.PI) +\n 90\n\n let angleDiff = currentAngle - previousAngle.current\n if (angleDiff > 180) angleDiff -= 360\n if (angleDiff < -180) angleDiff += 360\n accumulatedRotation.current += angleDiff\n rotation.set(accumulatedRotation.current)\n previousAngle.current = currentAngle\n\n scale.set(0.95)\n setIsMoving(true)\n\n const timeout = setTimeout(() => {\n scale.set(1)\n setIsMoving(false)\n }, 150)\n\n return () => clearTimeout(timeout)\n }\n }\n\n let rafId: number\n const throttledMouseMove = (e: MouseEvent) => {\n if (rafId) return\n\n rafId = requestAnimationFrame(() => {\n smoothMouseMove(e)\n rafId = 0\n })\n }\n\n document.body.style.cursor = \"none\"\n window.addEventListener(\"mousemove\", throttledMouseMove)\n\n return () => {\n window.removeEventListener(\"mousemove\", throttledMouseMove)\n document.body.style.cursor = \"auto\"\n if (rafId) cancelAnimationFrame(rafId)\n }\n }, [cursorX, cursorY, rotation, scale])\n\n return (\n \n {cursor}\n \n )\n}\n", + "content": "\"use client\"\n\nimport { JSX } from \"react\"\nimport { SmoothCursor as SmoothCursorComponent } from \"smooth-cursor\"\n\nexport interface SmoothCursorProps {\n cursor?: JSX.Element\n springConfig?: {\n damping: number\n stiffness: number\n mass: number\n restDelta: number\n }\n}\n\nexport function SmoothCursor({ cursor, springConfig }: SmoothCursorProps) {\n return \n}\n", "type": "registry:ui" } ] diff --git a/apps/www/public/registry.json b/apps/www/public/registry.json index 9a498d52b..abd0f2654 100644 --- a/apps/www/public/registry.json +++ b/apps/www/public/registry.json @@ -193,7 +193,8 @@ "type": "registry:ui", "description": "A customizable, physics-based smooth cursor animation component with spring animations and rotation effects", "dependencies": [ - "framer-motion" + "framer-motion", + "smooth-cursor" ], "files": [ { diff --git a/apps/www/registry.json b/apps/www/registry.json index 9a498d52b..abd0f2654 100644 --- a/apps/www/registry.json +++ b/apps/www/registry.json @@ -193,7 +193,8 @@ "type": "registry:ui", "description": "A customizable, physics-based smooth cursor animation component with spring animations and rotation effects", "dependencies": [ - "framer-motion" + "framer-motion", + "smooth-cursor" ], "files": [ { diff --git a/apps/www/registry/magicui/smooth-cursor.tsx b/apps/www/registry/magicui/smooth-cursor.tsx index bf338a047..71bca4738 100644 --- a/apps/www/registry/magicui/smooth-cursor.tsx +++ b/apps/www/registry/magicui/smooth-cursor.tsx @@ -1,15 +1,10 @@ "use client" -import { FC, useEffect, useRef, useState } from "react" -import { motion, useSpring } from "motion/react" - -interface Position { - x: number - y: number -} +import { JSX } from "react" +import { SmoothCursor as SmoothCursorComponent } from "smooth-cursor" export interface SmoothCursorProps { - cursor?: React.ReactNode + cursor?: JSX.Element springConfig?: { damping: number stiffness: number @@ -18,191 +13,6 @@ export interface SmoothCursorProps { } } -const DefaultCursorSVG: FC = () => { - return ( - - - - - - - - - - - - - - - - - - - ) -} - -export function SmoothCursor({ - cursor = , - springConfig = { - damping: 45, - stiffness: 400, - mass: 1, - restDelta: 0.001, - }, -}: SmoothCursorProps) { - const [isMoving, setIsMoving] = useState(false) - const lastMousePos = useRef({ x: 0, y: 0 }) - const velocity = useRef({ x: 0, y: 0 }) - const lastUpdateTime = useRef(Date.now()) - const previousAngle = useRef(0) - const accumulatedRotation = useRef(0) - - const cursorX = useSpring(0, springConfig) - const cursorY = useSpring(0, springConfig) - const rotation = useSpring(0, { - ...springConfig, - damping: 60, - stiffness: 300, - }) - const scale = useSpring(1, { - ...springConfig, - stiffness: 500, - damping: 35, - }) - - useEffect(() => { - const updateVelocity = (currentPos: Position) => { - const currentTime = Date.now() - const deltaTime = currentTime - lastUpdateTime.current - - if (deltaTime > 0) { - velocity.current = { - x: (currentPos.x - lastMousePos.current.x) / deltaTime, - y: (currentPos.y - lastMousePos.current.y) / deltaTime, - } - } - - lastUpdateTime.current = currentTime - lastMousePos.current = currentPos - } - - const smoothMouseMove = (e: MouseEvent) => { - const currentPos = { x: e.clientX, y: e.clientY } - updateVelocity(currentPos) - - const speed = Math.sqrt( - Math.pow(velocity.current.x, 2) + Math.pow(velocity.current.y, 2) - ) - - cursorX.set(currentPos.x) - cursorY.set(currentPos.y) - - if (speed > 0.1) { - const currentAngle = - Math.atan2(velocity.current.y, velocity.current.x) * (180 / Math.PI) + - 90 - - let angleDiff = currentAngle - previousAngle.current - if (angleDiff > 180) angleDiff -= 360 - if (angleDiff < -180) angleDiff += 360 - accumulatedRotation.current += angleDiff - rotation.set(accumulatedRotation.current) - previousAngle.current = currentAngle - - scale.set(0.95) - setIsMoving(true) - - const timeout = setTimeout(() => { - scale.set(1) - setIsMoving(false) - }, 150) - - return () => clearTimeout(timeout) - } - } - - let rafId: number - const throttledMouseMove = (e: MouseEvent) => { - if (rafId) return - - rafId = requestAnimationFrame(() => { - smoothMouseMove(e) - rafId = 0 - }) - } - - document.body.style.cursor = "none" - window.addEventListener("mousemove", throttledMouseMove) - - return () => { - window.removeEventListener("mousemove", throttledMouseMove) - document.body.style.cursor = "auto" - if (rafId) cancelAnimationFrame(rafId) - } - }, [cursorX, cursorY, rotation, scale]) - - return ( - - {cursor} - - ) +export function SmoothCursor({ cursor, springConfig }: SmoothCursorProps) { + return } diff --git a/apps/www/registry/registry-ui.ts b/apps/www/registry/registry-ui.ts index c7cadab21..8ea336bce 100644 --- a/apps/www/registry/registry-ui.ts +++ b/apps/www/registry/registry-ui.ts @@ -173,7 +173,7 @@ export const ui: Registry["items"] = [ type: "registry:ui", }, ], - dependencies: ["framer-motion"], + dependencies: ["framer-motion", "smooth-cursor"], }, { name: "progressive-blur", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 707e1fdad..84869b6cc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -164,6 +164,9 @@ importers: schema-dts: specifier: ^1.1.5 version: 1.1.5 + smooth-cursor: + specifier: ^0.1.2 + version: 0.1.2(react-dom@19.1.1(react@19.1.1))(react@19.1.1) sonner: specifier: ^1.7.4 version: 1.7.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1) @@ -5269,6 +5272,12 @@ packages: resolution: {integrity: sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==} engines: {node: '>=18'} + smooth-cursor@0.1.2: + resolution: {integrity: sha512-u2bFVOrvkUQCCyJRgMsWxcyod5oM+Lg70pjGrYedi6AZAMTLgNDzHyeRSMHJK/NFRKDt657O6Sg7LAtSgqF1fw==} + peerDependencies: + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + sonner@1.7.4: resolution: {integrity: sha512-DIS8z4PfJRbIyfVFDVnK9rO3eYDtse4Omcm6bt0oEr5/jtLgysmjuBl1frJ9E/EQZrFmKx2A8m/s5s9CRXIzhw==} peerDependencies: @@ -11578,6 +11587,14 @@ snapshots: ansi-styles: 6.2.3 is-fullwidth-code-point: 5.1.0 + smooth-cursor@0.1.2(react-dom@19.1.1(react@19.1.1))(react@19.1.1): + dependencies: + framer-motion: 12.23.12(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + transitivePeerDependencies: + - '@emotion/is-prop-valid' + sonner@1.7.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1): dependencies: react: 19.1.1 From 50474738d52f35d4d077b352981a8739a1ee3021 Mon Sep 17 00:00:00 2001 From: code-parth Date: Wed, 19 Nov 2025 20:30:27 +0800 Subject: [PATCH 2/4] fix: correct code formatting in Next.js tutorial for clarity --- apps/www/content/blog/next-js-getting-started.mdx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/www/content/blog/next-js-getting-started.mdx b/apps/www/content/blog/next-js-getting-started.mdx index 0d9ffe961..b71556daa 100644 --- a/apps/www/content/blog/next-js-getting-started.mdx +++ b/apps/www/content/blog/next-js-getting-started.mdx @@ -140,8 +140,8 @@ Let's get our hands dirty and build out two pages every website needs: a homepag And that's literally all it takes. You've just created the `/about` route. Now, let's drop some basic React code into `app/about/page.tsx` so it actually displays something. -// app/about/page.tsx ```tsx +// app/about/page.tsx export default function AboutPage() { return (
@@ -164,6 +164,7 @@ To connect your homepage to the about page, you'll import `Link` from `next/link Here’s a quick example of how you could add a link to your `app/page.tsx`: +````tsx // app/page.tsx import Link from 'next/link'; @@ -179,6 +180,7 @@ export default function HomePage() {
) } +``` The `href` prop tells the `` component where to go. When a user clicks it, Next.js fetches what it needs in the background and swaps out the view without that classic white flash of a page refresh. From 124b193fb4587a53228652c995ddc141dadf89d8 Mon Sep 17 00:00:00 2001 From: code-parth Date: Wed, 19 Nov 2025 21:03:26 +0800 Subject: [PATCH 3/4] fix: improve code formatting for consistency across blog articles --- .../blog/color-theory-for-web-design.mdx | 4 +- apps/www/content/blog/framer-motion-react.mdx | 2 +- .../content/blog/installing-tailwind-css.mdx | 28 +++++--------- .../content/blog/next-js-getting-started.mdx | 20 +++++----- apps/www/content/blog/tailwind-css-button.mdx | 38 +++++++++++-------- apps/www/content/blog/tailwind-css-themes.mdx | 19 ++++------ .../content/docs/components/smooth-cursor.mdx | 8 ++-- 7 files changed, 59 insertions(+), 60 deletions(-) diff --git a/apps/www/content/blog/color-theory-for-web-design.mdx b/apps/www/content/blog/color-theory-for-web-design.mdx index a2c745522..12566e457 100644 --- a/apps/www/content/blog/color-theory-for-web-design.mdx +++ b/apps/www/content/blog/color-theory-for-web-design.mdx @@ -160,8 +160,8 @@ The table below breaks down the most common contrast requirements you'll encount | Conformance Level | Normal Text (<18pt) | Large Text (≥18pt or 14pt bold) | UI Components & Graphics | | :----------------- | :--------------------: | :--------------------------------: | :----------------------- | -| **AA (Minimum)** | **4.5:1** | **3:1** | **3:1** | -| **AAA (Enhanced)** | **7:1** | **4.5:1** | **4.5:1** | +| **AA (Minimum)** | **4.5:1** | **3:1** | **3:1** | +| **AAA (Enhanced)** | **7:1** | **4.5:1** | **4.5:1** | As you can see, the requirements are more lenient for larger text and essential graphics, but aiming for the higher AA standard for your body copy is a solid starting point for any project. diff --git a/apps/www/content/blog/framer-motion-react.mdx b/apps/www/content/blog/framer-motion-react.mdx index 52112e542..1ed4ec2f9 100644 --- a/apps/www/content/blog/framer-motion-react.mdx +++ b/apps/www/content/blog/framer-motion-react.mdx @@ -148,7 +148,7 @@ Sometimes, seeing the difference laid out helps clarify why one approach is bett ### Animation Props vs Variants -| Feature | Direct Props (e.g., `animate={{...}}`) | Variants (e.g., `variants={...}`) | +| Feature | Direct Props (e.g., `animate={{...}}`) | Variants (e.g., `variants={...}`) | | :---------------- | :-------------------------------------------- | :------------------------------------------ | | **Readability** | Poor for complex states. JSX gets cluttered. | Excellent. Separates logic from markup. | | **Reusability** | Low. Logic is tied directly to the component. | High. Can be imported and used anywhere. | diff --git a/apps/www/content/blog/installing-tailwind-css.mdx b/apps/www/content/blog/installing-tailwind-css.mdx index ec7b290e0..e36445bf2 100644 --- a/apps/www/content/blog/installing-tailwind-css.mdx +++ b/apps/www/content/blog/installing-tailwind-css.mdx @@ -100,10 +100,7 @@ Open up your `tailwind.config.js` file and find the `content` array. You need to ```js /** @type {import('tailwindcss').Config} */ export default { - content: [ - './public/**/*.html', - './src/**/*.{js,jsx,ts,tsx,vue}', - ], + content: ["./public/**/*.html", "./src/**/*.{js,jsx,ts,tsx,vue}"], theme: { extend: {}, }, @@ -180,10 +177,7 @@ The real key is modifying your `tailwind.config.js`. You need to point the `cont // tailwind.config.js /** @type {import('tailwindcss').Config} */ export default { - content: [ - "./index.html", - "./src/**/*.{js,ts,jsx,tsx}", - ], + content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"], theme: { extend: {}, }, @@ -208,9 +202,9 @@ The only significant difference is the `content` path in `tailwind.config.js`. Y /** @type {import('tailwindcss').Config} */ module.exports = { content: [ - './pages/**/*.{js,ts,jsx,tsx,mdx}', - './components/**/*.{js,ts,jsx,tsx,mdx}', - './app/**/*.{js,ts,jsx,tsx,mdx}', + "./pages/**/*.{js,ts,jsx,tsx,mdx}", + "./components/**/*.{js,ts,jsx,tsx,mdx}", + "./app/**/*.{js,ts,jsx,tsx,mdx}", ], theme: { extend: {}, @@ -263,12 +257,12 @@ module.exports = { theme: { extend: { colors: { - 'brand-primary': '#0A74DA', - 'brand-secondary': '#F6C90E', + "brand-primary": "#0A74DA", + "brand-secondary": "#F6C90E", }, fontFamily: { - 'sans': ['Inter', 'sans-serif'], - 'display': ['Poppins', 'sans-serif'], + sans: ["Inter", "sans-serif"], + display: ["Poppins", "sans-serif"], }, }, }, @@ -296,9 +290,7 @@ Then, just require it inside the `plugins` array in your config file. // tailwind.config.js module.exports = { // ... - plugins: [ - require('@tailwindcss/typography'), - ], + plugins: [require("@tailwindcss/typography")], } ``` diff --git a/apps/www/content/blog/next-js-getting-started.mdx b/apps/www/content/blog/next-js-getting-started.mdx index b71556daa..057e50037 100644 --- a/apps/www/content/blog/next-js-getting-started.mdx +++ b/apps/www/content/blog/next-js-getting-started.mdx @@ -55,9 +55,11 @@ Before you dive in, just make sure you have **Node.js version 18.17 or later** i To get the ball rolling, navigate in your terminal to wherever you want your new project to live. Then, run this single command: +```bash npx create-next-app@latest +``` -This kicks off an interactive setup process that will walk you through a few questions. It’s a great way to customize the project to your specific needs right from the get-go. +This kicks off an interactive setup process that will walk you through a few questions. It's a great way to customize the project to your specific needs right from the get-go. > Answering these prompts thoughtfully is your first big step. The choices you make here—like using TypeScript or setting up Tailwind CSS—will shape your development workflow from day one. @@ -144,11 +146,11 @@ And that's literally all it takes. You've just created the `/about` route. Now, // app/about/page.tsx export default function AboutPage() { return ( -
-

About Us

-

This is the about page for our first Next.js app!

-
- ); +
+

About Us

+

This is the about page for our first Next.js app!

+
+ ) } ``` @@ -162,11 +164,11 @@ Now that you have a couple of pages, you need a way for people to get between th To connect your homepage to the about page, you'll import `Link` from `next/link` and use it almost exactly like a regular `` tag. If you want to dive deeper into building a complete navigation bar, there are some great tutorials on building a [navbar in React.js](https://magicui.design/blog/navbar-in-react-js) that you can easily adapt. -Here’s a quick example of how you could add a link to your `app/page.tsx`: +Here's a quick example of how you could add a link to your `app/page.tsx`: -````tsx +```tsx // app/page.tsx -import Link from 'next/link'; +import Link from "next/link" export default function HomePage() { return ( diff --git a/apps/www/content/blog/tailwind-css-button.mdx b/apps/www/content/blog/tailwind-css-button.mdx index 9d4727636..9e8203761 100644 --- a/apps/www/content/blog/tailwind-css-button.mdx +++ b/apps/www/content/blog/tailwind-css-button.mdx @@ -177,15 +177,15 @@ A typical setup for a Next.js project, for example, might look like this: /** @type {import('tailwindcss').Config} */ module.exports = { content: [ - './src/pages/**/*.{js,ts,jsx,tsx,mdx}', - './src/components/**/*.{js,ts,jsx,tsx,mdx}', - './src/app/**/*.{js,ts,jsx,tsx,mdx}', + "./src/pages/**/*.{js,ts,jsx,tsx,mdx}", + "./src/components/**/*.{js,ts,jsx,tsx,mdx}", + "./src/app/**/*.{js,ts,jsx,tsx,mdx}", ], theme: { extend: {}, }, plugins: [], -}; +} ``` Keeping these paths clean and accurate is a simple but incredibly powerful way to guarantee a lean, optimized build every time. @@ -249,30 +249,38 @@ Here’s the finished `Button` component, fully equipped to handle variants and import { clsx } from "clsx" import { twMerge } from "tailwind-merge" -function Button({ children, variant = 'primary', size = 'md', className, ...props }) { +function Button({ + children, + variant = "primary", + size = "md", + className, + ...props +}) { const baseClasses = - 'font-semibold rounded-lg shadow-md focus:outline-none focus:ring-2 focus:ring-offset-2 transition-colors'; + "font-semibold rounded-lg shadow-md focus:outline-none focus:ring-2 focus:ring-offset-2 transition-colors" const variantClasses = { - primary: 'bg-indigo-600 text-white hover:bg-indigo-700 focus:ring-indigo-500', - secondary: 'bg-gray-200 text-gray-800 hover:bg-gray-300 focus:ring-gray-500', - }; + primary: + "bg-indigo-600 text-white hover:bg-indigo-700 focus:ring-indigo-500", + secondary: + "bg-gray-200 text-gray-800 hover:bg-gray-300 focus:ring-gray-500", + } const sizeClasses = { - sm: 'py-1 px-3 text-sm', - md: 'py-2 px-4 text-base', - lg: 'py-3 px-6 text-lg', - }; + sm: "py-1 px-3 text-sm", + md: "py-2 px-4 text-base", + lg: "py-3 px-6 text-lg", + } const finalClasses = twMerge( clsx(baseClasses, variantClasses[variant], sizeClasses[size], className) - ); + ) return ( - ); + ) } ``` diff --git a/apps/www/content/blog/tailwind-css-themes.mdx b/apps/www/content/blog/tailwind-css-themes.mdx index 21a766897..e693b84bf 100644 --- a/apps/www/content/blog/tailwind-css-themes.mdx +++ b/apps/www/content/blog/tailwind-css-themes.mdx @@ -87,7 +87,7 @@ module.exports = { theme: { extend: { colors: { - primary: '#3490dc', + primary: "#3490dc", }, }, }, @@ -130,8 +130,8 @@ To bring in custom fonts, you can drop a standard `@font-face` rule into your ma ```css /* In your main CSS file */ @font-face { - font-family: 'Inter'; - src: url('/fonts/Inter-Regular.woff2') format('woff2'); + font-family: "Inter"; + src: url("/fonts/Inter-Regular.woff2") format("woff2"); font-weight: 400; font-style: normal; } @@ -147,8 +147,8 @@ module.exports = { theme: { extend: { screens: { - 'xs': '480px', - '3xl': '1920px', + xs: "480px", + "3xl": "1920px", }, }, }, @@ -165,8 +165,8 @@ The `theme()` function is your bridge between your config file and your custom C ```css .custom-component { - background-color: theme('colors.primary'); - padding: theme('spacing.4'); + background-color: theme("colors.primary"); + padding: theme("spacing.4"); } ``` @@ -203,10 +203,7 @@ module.exports = { theme: { // ... }, - plugins: [ - require('@tailwindcss/typography'), - require('@tailwindcss/forms'), - ], + plugins: [require("@tailwindcss/typography"), require("@tailwindcss/forms")], } ``` diff --git a/apps/www/content/docs/components/smooth-cursor.mdx b/apps/www/content/docs/components/smooth-cursor.mdx index 3c73f33eb..ab0c38787 100644 --- a/apps/www/content/docs/components/smooth-cursor.mdx +++ b/apps/www/content/docs/components/smooth-cursor.mdx @@ -106,10 +106,10 @@ select { ## Props -| Prop | Type | Default | Description | -| -------------- | ----------------- | ---------------------- | ------------------------------------------------------ | -| `cursor` | `JSX.Element`. | `` | Custom cursor component to replace the default cursor | -| `springConfig` | `SpringConfig` | See below | Configuration object for the spring animation behavior | +| Prop | Type | Default | Description | +| -------------- | -------------- | ---------------------- | ------------------------------------------------------ | +| `cursor` | `JSX.Element`. | `` | Custom cursor component to replace the default cursor | +| `springConfig` | `SpringConfig` | See below | Configuration object for the spring animation behavior | ### SpringConfig Type From 7d7719fe292221a7aa9ef335a529a6b85504c056 Mon Sep 17 00:00:00 2001 From: code-parth Date: Wed, 19 Nov 2025 21:23:10 +0800 Subject: [PATCH 4/4] fix: add 'cdn.outrank.so' to allowed image domains in Next.js config --- apps/www/next.config.mjs | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/www/next.config.mjs b/apps/www/next.config.mjs index a9ce5f717..4079212ea 100644 --- a/apps/www/next.config.mjs +++ b/apps/www/next.config.mjs @@ -16,6 +16,7 @@ const nextConfig = { "images.unsplash.com", "img.youtube.com", "pbs.twimg.com", + "cdn.outrank.so", ], }, async redirects() {