Published

Make your mega menu follow cursor intent

Jurre Houtkamp — author avatar

Jurre

0 min read

Move your cursor diagonally from a nav item toward its open panel, and most mega menus will switch sections before you get there. It’s called the diagonal problem and I fixed it in Framer with a single code override, built entirely by prompting the Framer Agent.

How I fixed a mega menu hover bug with the Framer Agent

Ever hovered a mega menu, moved toward the link you wanted, and watched the whole panel switch before you could reach it?

Your cursor briefly passes over a neighbouring navigation item on its way into the open menu. The menu treats that accidental crossing as a new hover and replaces the panel you meant to use.

It is a common issue in mega menus, including Framer sites such as Framer.com. I asked the Framer Agent to fix it, and within minutes it had built and applied a trajectory-based code override that keeps the current panel open while the cursor moves into it.

Here is how I fixed it, how you can use the same override, and how to prompt the Agent to build a version for your own navigation.

The problem: moving diagonally changes the menu

The bug is easy to reproduce.

  1. Open a top-level navigation item.

  2. Move diagonally toward a link in its menu panel.

  3. Briefly cross a neighbouring navigation item.

  4. Watch the menu switch to the wrong panel.

The menu only sees the cursor’s current position. It does not understand that the cursor is travelling toward the panel already on screen.

This is known as the diagonal problem. A common solution is the safe triangle: an invisible protected area between the active navigation item and its open panel. While the cursor moves through that area, other navigation items ignore hover.

Why I did not use a hover delay

A hover delay is the usual shortcut. Add a short delay before switching panels and quick accidental crossings stop causing problems.

It also makes intentional switching feel slow.

A delay cannot tell whether someone is moving into the menu panel or moving across the navigation. It only knows how long the cursor has been over an item.

I wanted the menu to stay stable during a diagonal move into the panel, while still switching immediately when someone moved sideways to another item.

The prompt I gave the Framer Agent

I started with a plain-language description of the issue:

“I have a hover mega menu in Framer. When I open a top-level navigation item and move quickly toward its submenu, my cursor sometimes crosses a neighbouring item and opens the wrong panel. Can you create a code override that prevents this, similar to a safe triangle?”

The Framer Agent created an override that tracks pointer velocity and projects the cursor’s path forward.

If that projected path will cross the bottom edge of the navigation row within the next 500 milliseconds, the override treats the cursor as moving toward the open panel. It then protects that movement for a moment.

What the override does

While the cursor is travelling into the open panel, the override:

  • Prevents sibling navigation items from receiving pointer events.

  • Keeps the active item interactive, so its panel stays open.

  • Cancels the guard when the cursor moves horizontally.

  • Restores normal hover behaviour when the cursor slows down or stops.

The important detail is that it does not use a fixed triangle or hardcoded menu dimensions. Each navigation item measures its position at runtime, so the same override works across different layouts and menu widths.

The code has four values worth tuning:

  • The minimum pointer speed required to activate the guard

  • How far ahead the cursor path is projected

  • How strongly upward movement cancels the guard

  • How long the guard remains active after the cursor stops

Let the Agent apply the override

I did not have to find every navigation item and attach the override myself.

After the Agent created it, I pointed it at the menu and said:

“Apply this override to every top-level navigation item in this mega menu. Do not apply it to links inside the submenu panels.”

The Agent found the relevant canvas elements and applied the override across the navigation.

If you use the override from this post, you can do the same. Add the code to your project, point the Agent at your menu, and ask it to apply the override to the top-level items.

You still keep full control. The override remains editable, and every applied connection stays visible on the canvas.

Keep the existing menu interactions in Framer

The override handles pointer geometry. Framer’s native interactions should still control which menu panel is open. I made two small changes to the existing interactions:

  1. The active navigation item no longer closes its panel when the cursor leaves the item itself. The cursor needs to leave that item to reach the panel below it.

  2. Each open menu state closes when the cursor leaves the full navigation area, including the menu panel.

That separation keeps the setup simple. The override protects the path from the navigation item to its panel. Framer still controls variants, hover styling, transitions, and closing behaviour.

Tune it with follow-up prompts

The first version worked, but I wanted to refine the feel of the interaction.

Instead of manually editing the code, I used short follow-up prompts:

“Make it slightly more aggressive on sideways and down movement.”

“Keep the current hover active based on the same trajectory values.”

“It does not immediately switch when I move side to side.”

The Agent adjusted the thresholds and replaced an earlier angle-based check with the trajectory projection. That made the menu stable while moving into a panel, without adding friction when switching across the navigation.

Start with the default values and test the menu. If it feels too cautious or too sensitive, describe what happens and what you want instead. The Agent can adjust the logic from there.

Debug the interaction visually

The most useful prompt came when I wanted to understand why the guard was activating:

“Visualize the safe hover area so I can debug it.”

The Agent added a temporary debug layer with a projected cursor path, a live velocity readout, and visual states for blocked navigation items.

That turned tuning into observation. I could see when the guard engaged, which items it blocked, and how the cursor’s direction affected the result.

Once the interaction felt right, I asked the Agent to remove the debug layer.

Try the live demo with the debug layer enabled here: https://dark-favorites-952985.framer.app/

Use the same approach in your own menu

You can copy the override and ask the Framer Agent to apply it to your navigation, or ask the Agent to create a version for your menu from scratch.

The key is to describe the intended behaviour clearly:

“Keep the current panel open while the cursor moves into it. Let users switch immediately when they move sideways to another navigation item.”

The Agent handles the implementation and repetitive canvas work. You can test the result, refine it with follow-up prompts, and keep the full interaction editable in Framer.

// SafeHover.tsx — a trajectory-based "safe triangle" for hover menus.
// Apply withSafeHover as a Code Override to each nav item that opens a menu.
import { type ComponentType, forwardRef, startTransition, useCallback, useEffect, useRef, useState } from "react"
import { RenderTarget } from "framer"

// Minimum cursor speed (px/ms) required to start or maintain traveling intent.
const SPEED_THRESHOLD_PX_PER_MS = 0.15
// Guard engages only when the current vertical velocity would cross the row bottom within this many ms.
const PROJECTED_EXIT_MS = 500
// Cancel traveling immediately only when movement is this many degrees above horizontal.
const UPWARD_CANCEL_ANGLE_DEGREES = 10
// How long (ms) to keep traveling after speed drops below the threshold.
const DWELL_GRACE_MS = 150

type PointerSample = {
    x: number
    y: number
    t: number
}

export function withSafeHover(Component): ComponentType {
    return forwardRef(function WithSafeHover(props: any, ref) {
        const [blocked, setBlocked] = useState(false)
        const blockedRef = useRef(false)
        const elementRef = useRef<Element | null>(null)
        const historyRef = useRef<PointerSample[]>([])
        const travelingRef = useRef(false)
        const lastFastTimestampRef = useRef(0)
        const prevInsideRef = useRef(false)
        const allowInsideUnblockedRef = useRef(false)
        const enteredDuringTravelRef = useRef(false)

        const isCanvasRenderTarget =
            typeof window !== "undefined" &&
            typeof RenderTarget !== "undefined" &&
            RenderTarget.current() === RenderTarget.canvas

        const setBlockedIfChanged = useCallback((nextBlocked: boolean) => {
            if (blockedRef.current === nextBlocked) return
            blockedRef.current = nextBlocked
            startTransition(() => {
                setBlocked(nextBlocked)
            })
        }, [])

        const setCombinedRef = useCallback(
            (node: any) => {
                if (node && typeof node.getBoundingClientRect === "function") {
                    elementRef.current = node
                } else {
                    elementRef.current = null
                }

                if (typeof ref === "function") {
                    ref(node)
                } else if (ref && typeof ref === "object" && "current" in ref) {
                    ;(ref as { current: any }).current = node
                }
            },
            [ref]
        )

        useEffect(() => {
            if (typeof window === "undefined") return
            if (isCanvasRenderTarget) return

            const upwardCancelTanThreshold = Math.tan((UPWARD_CANCEL_ANGLE_DEGREES * Math.PI) / 180)

            const onPointerMove = (event: PointerEvent) => {
                // Desktop-only: never block for touch or pen input.
                if (event.pointerType !== "mouse") return

                const element = elementRef.current
                if (!element) {
                    setBlockedIfChanged(false)
                    return
                }

                const now = event.timeStamp || performance.now()
                const sample: PointerSample = { x: event.clientX, y: event.clientY, t: now }

                // A rolling ~100ms window of samples smooths out jitter.
                const nextHistory = [...historyRef.current, sample]
                    .filter((point) => now - point.t <= 100)
                    .slice(-5)
                historyRef.current = nextHistory

                let speed = 0
                let dx = 0
                let dy = 0
                let dt = 1
                if (nextHistory.length >= 2) {
                    const first = nextHistory[0]
                    const last = nextHistory[nextHistory.length - 1]
                    dt = Math.max(last.t - first.t, 1)
                    dx = last.x - first.x
                    dy = last.y - first.y
                    speed = Math.hypot(dx, dy) / dt
                }

                const movingClearlyUpward =
                    dy < 0 && Math.abs(dy) > upwardCancelTanThreshold * Math.abs(dx)
                const isFastEnough = speed >= SPEED_THRESHOLD_PX_PER_MS
                const vy = dy / dt

                // Project the path forward: would the cursor cross the bottom
                // of the nav row (this item's bottom edge) within PROJECTED_EXIT_MS?
                const rect = element.getBoundingClientRect()
                const pointerBelowRow = sample.y > rect.bottom
                const projectedExitMs =
                    vy > 0 ? (rect.bottom - sample.y) / vy : Number.POSITIVE_INFINITY
                const projectedToExitSoon = projectedExitMs < PROJECTED_EXIT_MS
                const shouldStartTraveling =
                    isFastEnough && vy > 0 && (projectedToExitSoon || pointerBelowRow)
                const shouldMaintainTraveling =
                    isFastEnough && !movingClearlyUpward && (pointerBelowRow || projectedToExitSoon)

                const wasTraveling = travelingRef.current
                let isTraveling = wasTraveling

                if (!wasTraveling) {
                    isTraveling = shouldStartTraveling
                    if (isTraveling) lastFastTimestampRef.current = now
                } else if (movingClearlyUpward) {
                    isTraveling = false
                } else if (shouldMaintainTraveling) {
                    isTraveling = true
                    lastFastTimestampRef.current = now
                } else if (isFastEnough) {
                    // Fast but flat while still inside the row: a sideways move, cancel.
                    isTraveling = false
                } else {
                    // Coasting: keep the guard alive briefly after deceleration.
                    isTraveling = wasTraveling && now - lastFastTimestampRef.current < DWELL_GRACE_MS
                }

                const isInside =
                    sample.x >= rect.left &&
                    sample.x <= rect.right &&
                    sample.y >= rect.top &&
                    sample.y <= rect.bottom

                // Remember whether this item was the "origin": hovered and
                // unblocked when travel began. The origin is never blocked.
                if (!wasTraveling && isTraveling) {
                    allowInsideUnblockedRef.current = isInside && !blockedRef.current
                    enteredDuringTravelRef.current = false
                }

                if (isTraveling && !prevInsideRef.current && isInside) {
                    enteredDuringTravelRef.current = true
                }

                let shouldBlock = false
                if (isTraveling) {
                    if (!isInside) {
                        shouldBlock = true
                    } else if (enteredDuringTravelRef.current) {
                        shouldBlock = true
                    } else if (allowInsideUnblockedRef.current) {
                        shouldBlock = false
                    }
                }

                if (!isTraveling) {
                    enteredDuringTravelRef.current = false
                    allowInsideUnblockedRef.current = false
                }

                setBlockedIfChanged(shouldBlock)
                travelingRef.current = isTraveling
                prevInsideRef.current = isInside
            }

            window.addEventListener("pointermove", onPointerMove, { passive: true })
            return () => {
                window.removeEventListener("pointermove", onPointerMove)
            }
        }, [isCanvasRenderTarget, setBlockedIfChanged])

        // Stay inert on the Framer canvas.
        if (isCanvasRenderTarget) {
            return <Component ref={setCombinedRef} {...props} />
        }

        return (
            <Component
                ref={setCombinedRef}
                {...props}
                style={{
                    ...props?.style,
                    ...(blocked ? { pointerEvents: "none" } : null),
                }}
            />
        )
    })
}
// SafeHover.tsx — a trajectory-based "safe triangle" for hover menus.
// Apply withSafeHover as a Code Override to each nav item that opens a menu.
import { type ComponentType, forwardRef, startTransition, useCallback, useEffect, useRef, useState } from "react"
import { RenderTarget } from "framer"

// Minimum cursor speed (px/ms) required to start or maintain traveling intent.
const SPEED_THRESHOLD_PX_PER_MS = 0.15
// Guard engages only when the current vertical velocity would cross the row bottom within this many ms.
const PROJECTED_EXIT_MS = 500
// Cancel traveling immediately only when movement is this many degrees above horizontal.
const UPWARD_CANCEL_ANGLE_DEGREES = 10
// How long (ms) to keep traveling after speed drops below the threshold.
const DWELL_GRACE_MS = 150

type PointerSample = {
    x: number
    y: number
    t: number
}

export function withSafeHover(Component): ComponentType {
    return forwardRef(function WithSafeHover(props: any, ref) {
        const [blocked, setBlocked] = useState(false)
        const blockedRef = useRef(false)
        const elementRef = useRef<Element | null>(null)
        const historyRef = useRef<PointerSample[]>([])
        const travelingRef = useRef(false)
        const lastFastTimestampRef = useRef(0)
        const prevInsideRef = useRef(false)
        const allowInsideUnblockedRef = useRef(false)
        const enteredDuringTravelRef = useRef(false)

        const isCanvasRenderTarget =
            typeof window !== "undefined" &&
            typeof RenderTarget !== "undefined" &&
            RenderTarget.current() === RenderTarget.canvas

        const setBlockedIfChanged = useCallback((nextBlocked: boolean) => {
            if (blockedRef.current === nextBlocked) return
            blockedRef.current = nextBlocked
            startTransition(() => {
                setBlocked(nextBlocked)
            })
        }, [])

        const setCombinedRef = useCallback(
            (node: any) => {
                if (node && typeof node.getBoundingClientRect === "function") {
                    elementRef.current = node
                } else {
                    elementRef.current = null
                }

                if (typeof ref === "function") {
                    ref(node)
                } else if (ref && typeof ref === "object" && "current" in ref) {
                    ;(ref as { current: any }).current = node
                }
            },
            [ref]
        )

        useEffect(() => {
            if (typeof window === "undefined") return
            if (isCanvasRenderTarget) return

            const upwardCancelTanThreshold = Math.tan((UPWARD_CANCEL_ANGLE_DEGREES * Math.PI) / 180)

            const onPointerMove = (event: PointerEvent) => {
                // Desktop-only: never block for touch or pen input.
                if (event.pointerType !== "mouse") return

                const element = elementRef.current
                if (!element) {
                    setBlockedIfChanged(false)
                    return
                }

                const now = event.timeStamp || performance.now()
                const sample: PointerSample = { x: event.clientX, y: event.clientY, t: now }

                // A rolling ~100ms window of samples smooths out jitter.
                const nextHistory = [...historyRef.current, sample]
                    .filter((point) => now - point.t <= 100)
                    .slice(-5)
                historyRef.current = nextHistory

                let speed = 0
                let dx = 0
                let dy = 0
                let dt = 1
                if (nextHistory.length >= 2) {
                    const first = nextHistory[0]
                    const last = nextHistory[nextHistory.length - 1]
                    dt = Math.max(last.t - first.t, 1)
                    dx = last.x - first.x
                    dy = last.y - first.y
                    speed = Math.hypot(dx, dy) / dt
                }

                const movingClearlyUpward =
                    dy < 0 && Math.abs(dy) > upwardCancelTanThreshold * Math.abs(dx)
                const isFastEnough = speed >= SPEED_THRESHOLD_PX_PER_MS
                const vy = dy / dt

                // Project the path forward: would the cursor cross the bottom
                // of the nav row (this item's bottom edge) within PROJECTED_EXIT_MS?
                const rect = element.getBoundingClientRect()
                const pointerBelowRow = sample.y > rect.bottom
                const projectedExitMs =
                    vy > 0 ? (rect.bottom - sample.y) / vy : Number.POSITIVE_INFINITY
                const projectedToExitSoon = projectedExitMs < PROJECTED_EXIT_MS
                const shouldStartTraveling =
                    isFastEnough && vy > 0 && (projectedToExitSoon || pointerBelowRow)
                const shouldMaintainTraveling =
                    isFastEnough && !movingClearlyUpward && (pointerBelowRow || projectedToExitSoon)

                const wasTraveling = travelingRef.current
                let isTraveling = wasTraveling

                if (!wasTraveling) {
                    isTraveling = shouldStartTraveling
                    if (isTraveling) lastFastTimestampRef.current = now
                } else if (movingClearlyUpward) {
                    isTraveling = false
                } else if (shouldMaintainTraveling) {
                    isTraveling = true
                    lastFastTimestampRef.current = now
                } else if (isFastEnough) {
                    // Fast but flat while still inside the row: a sideways move, cancel.
                    isTraveling = false
                } else {
                    // Coasting: keep the guard alive briefly after deceleration.
                    isTraveling = wasTraveling && now - lastFastTimestampRef.current < DWELL_GRACE_MS
                }

                const isInside =
                    sample.x >= rect.left &&
                    sample.x <= rect.right &&
                    sample.y >= rect.top &&
                    sample.y <= rect.bottom

                // Remember whether this item was the "origin": hovered and
                // unblocked when travel began. The origin is never blocked.
                if (!wasTraveling && isTraveling) {
                    allowInsideUnblockedRef.current = isInside && !blockedRef.current
                    enteredDuringTravelRef.current = false
                }

                if (isTraveling && !prevInsideRef.current && isInside) {
                    enteredDuringTravelRef.current = true
                }

                let shouldBlock = false
                if (isTraveling) {
                    if (!isInside) {
                        shouldBlock = true
                    } else if (enteredDuringTravelRef.current) {
                        shouldBlock = true
                    } else if (allowInsideUnblockedRef.current) {
                        shouldBlock = false
                    }
                }

                if (!isTraveling) {
                    enteredDuringTravelRef.current = false
                    allowInsideUnblockedRef.current = false
                }

                setBlockedIfChanged(shouldBlock)
                travelingRef.current = isTraveling
                prevInsideRef.current = isInside
            }

            window.addEventListener("pointermove", onPointerMove, { passive: true })
            return () => {
                window.removeEventListener("pointermove", onPointerMove)
            }
        }, [isCanvasRenderTarget, setBlockedIfChanged])

        // Stay inert on the Framer canvas.
        if (isCanvasRenderTarget) {
            return <Component ref={setCombinedRef} {...props} />
        }

        return (
            <Component
                ref={setCombinedRef}
                {...props}
                style={{
                    ...props?.style,
                    ...(blocked ? { pointerEvents: "none" } : null),
                }}
            />
        )
    })
}
// SafeHover.tsx — a trajectory-based "safe triangle" for hover menus.
// Apply withSafeHover as a Code Override to each nav item that opens a menu.
import { type ComponentType, forwardRef, startTransition, useCallback, useEffect, useRef, useState } from "react"
import { RenderTarget } from "framer"

// Minimum cursor speed (px/ms) required to start or maintain traveling intent.
const SPEED_THRESHOLD_PX_PER_MS = 0.15
// Guard engages only when the current vertical velocity would cross the row bottom within this many ms.
const PROJECTED_EXIT_MS = 500
// Cancel traveling immediately only when movement is this many degrees above horizontal.
const UPWARD_CANCEL_ANGLE_DEGREES = 10
// How long (ms) to keep traveling after speed drops below the threshold.
const DWELL_GRACE_MS = 150

type PointerSample = {
    x: number
    y: number
    t: number
}

export function withSafeHover(Component): ComponentType {
    return forwardRef(function WithSafeHover(props: any, ref) {
        const [blocked, setBlocked] = useState(false)
        const blockedRef = useRef(false)
        const elementRef = useRef<Element | null>(null)
        const historyRef = useRef<PointerSample[]>([])
        const travelingRef = useRef(false)
        const lastFastTimestampRef = useRef(0)
        const prevInsideRef = useRef(false)
        const allowInsideUnblockedRef = useRef(false)
        const enteredDuringTravelRef = useRef(false)

        const isCanvasRenderTarget =
            typeof window !== "undefined" &&
            typeof RenderTarget !== "undefined" &&
            RenderTarget.current() === RenderTarget.canvas

        const setBlockedIfChanged = useCallback((nextBlocked: boolean) => {
            if (blockedRef.current === nextBlocked) return
            blockedRef.current = nextBlocked
            startTransition(() => {
                setBlocked(nextBlocked)
            })
        }, [])

        const setCombinedRef = useCallback(
            (node: any) => {
                if (node && typeof node.getBoundingClientRect === "function") {
                    elementRef.current = node
                } else {
                    elementRef.current = null
                }

                if (typeof ref === "function") {
                    ref(node)
                } else if (ref && typeof ref === "object" && "current" in ref) {
                    ;(ref as { current: any }).current = node
                }
            },
            [ref]
        )

        useEffect(() => {
            if (typeof window === "undefined") return
            if (isCanvasRenderTarget) return

            const upwardCancelTanThreshold = Math.tan((UPWARD_CANCEL_ANGLE_DEGREES * Math.PI) / 180)

            const onPointerMove = (event: PointerEvent) => {
                // Desktop-only: never block for touch or pen input.
                if (event.pointerType !== "mouse") return

                const element = elementRef.current
                if (!element) {
                    setBlockedIfChanged(false)
                    return
                }

                const now = event.timeStamp || performance.now()
                const sample: PointerSample = { x: event.clientX, y: event.clientY, t: now }

                // A rolling ~100ms window of samples smooths out jitter.
                const nextHistory = [...historyRef.current, sample]
                    .filter((point) => now - point.t <= 100)
                    .slice(-5)
                historyRef.current = nextHistory

                let speed = 0
                let dx = 0
                let dy = 0
                let dt = 1
                if (nextHistory.length >= 2) {
                    const first = nextHistory[0]
                    const last = nextHistory[nextHistory.length - 1]
                    dt = Math.max(last.t - first.t, 1)
                    dx = last.x - first.x
                    dy = last.y - first.y
                    speed = Math.hypot(dx, dy) / dt
                }

                const movingClearlyUpward =
                    dy < 0 && Math.abs(dy) > upwardCancelTanThreshold * Math.abs(dx)
                const isFastEnough = speed >= SPEED_THRESHOLD_PX_PER_MS
                const vy = dy / dt

                // Project the path forward: would the cursor cross the bottom
                // of the nav row (this item's bottom edge) within PROJECTED_EXIT_MS?
                const rect = element.getBoundingClientRect()
                const pointerBelowRow = sample.y > rect.bottom
                const projectedExitMs =
                    vy > 0 ? (rect.bottom - sample.y) / vy : Number.POSITIVE_INFINITY
                const projectedToExitSoon = projectedExitMs < PROJECTED_EXIT_MS
                const shouldStartTraveling =
                    isFastEnough && vy > 0 && (projectedToExitSoon || pointerBelowRow)
                const shouldMaintainTraveling =
                    isFastEnough && !movingClearlyUpward && (pointerBelowRow || projectedToExitSoon)

                const wasTraveling = travelingRef.current
                let isTraveling = wasTraveling

                if (!wasTraveling) {
                    isTraveling = shouldStartTraveling
                    if (isTraveling) lastFastTimestampRef.current = now
                } else if (movingClearlyUpward) {
                    isTraveling = false
                } else if (shouldMaintainTraveling) {
                    isTraveling = true
                    lastFastTimestampRef.current = now
                } else if (isFastEnough) {
                    // Fast but flat while still inside the row: a sideways move, cancel.
                    isTraveling = false
                } else {
                    // Coasting: keep the guard alive briefly after deceleration.
                    isTraveling = wasTraveling && now - lastFastTimestampRef.current < DWELL_GRACE_MS
                }

                const isInside =
                    sample.x >= rect.left &&
                    sample.x <= rect.right &&
                    sample.y >= rect.top &&
                    sample.y <= rect.bottom

                // Remember whether this item was the "origin": hovered and
                // unblocked when travel began. The origin is never blocked.
                if (!wasTraveling && isTraveling) {
                    allowInsideUnblockedRef.current = isInside && !blockedRef.current
                    enteredDuringTravelRef.current = false
                }

                if (isTraveling && !prevInsideRef.current && isInside) {
                    enteredDuringTravelRef.current = true
                }

                let shouldBlock = false
                if (isTraveling) {
                    if (!isInside) {
                        shouldBlock = true
                    } else if (enteredDuringTravelRef.current) {
                        shouldBlock = true
                    } else if (allowInsideUnblockedRef.current) {
                        shouldBlock = false
                    }
                }

                if (!isTraveling) {
                    enteredDuringTravelRef.current = false
                    allowInsideUnblockedRef.current = false
                }

                setBlockedIfChanged(shouldBlock)
                travelingRef.current = isTraveling
                prevInsideRef.current = isInside
            }

            window.addEventListener("pointermove", onPointerMove, { passive: true })
            return () => {
                window.removeEventListener("pointermove", onPointerMove)
            }
        }, [isCanvasRenderTarget, setBlockedIfChanged])

        // Stay inert on the Framer canvas.
        if (isCanvasRenderTarget) {
            return <Component ref={setCombinedRef} {...props} />
        }

        return (
            <Component
                ref={setCombinedRef}
                {...props}
                style={{
                    ...props?.style,
                    ...(blocked ? { pointerEvents: "none" } : null),
                }}
            />
        )
    })
}

Your next idea starts here

GPT 5.6 Terra

Create personal portfolio

Build startup site

Launch landing page

Start company blog

Framer UI showing Pages, Layers and Assets panels titles
Framer UI showing Pages, Layers and Assets panels titles