Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/apursley2012/cosmic-developer/llms.txt

Use this file to discover all available pages before exploring further.

CustomCursor gives the Cosmic Developer portfolio its signature input feel: the native OS cursor is hidden globally, and in its place a three-layer Framer Motion cursor tracks mouse movement with spring physics. The cursor reacts visibly when hovering over links, buttons, or any element marked .interactive, and its mix-blend-screen blending mode keeps it legible against both dark and light surfaces.

Usage

Add <CustomCursor /> once at the root of your application, outside of any scrolling containers. It attaches its own window event listeners and renders three fixed-position elements at the top of the stacking context.
import { C as CustomCursor } from '../components/cosmos/CustomCursor'
import { S as StarfieldBackground } from '../components/cosmos/StarfieldBackground'
import { A as AuroraBackground } from '../components/cosmos/AuroraBackground'

// In your app root — render once, above everything else:
function App() {
  return (
    <>
      <CustomCursor />
      <StarfieldBackground />
      <AuroraBackground />
      <main>{/* page content */}</main>
    </>
  )
}

How It Works

1

Mouse position tracking

A mousemove listener updates a { x, y } state on every pointer movement. The coordinates are stored as clientX / clientY (viewport-relative), which pairs naturally with position: fixed cursor elements.
2

Interactive state detection

A mouseover listener checks event.target on every element the cursor enters. The isHovered state is set to true if the target is, or is a descendant of, an <a> or <button>, or if the target has the .interactive class. All other elements set it back to false.
3

Spring-physics rendering

Three motion.div elements are animated to the cursor position. Each uses a spring transition with different stiffness, damping, and mass values so the layers follow the pointer at different rates — producing a trailing, organic feel.

The Three Cursor Layers

LayerSizeStyleSpring configz-index
Inner dotw-4 h-4bg-aurora-teal, filled circlestiffness 500, damping 28, mass 0.59999
Outer ringw-12 h-12border border-aurora-teal/50, hollow circlestiffness 250, damping 20, mass 0.89998
Magenta blurw-2 h-2bg-aurora-magenta blur-[2px], soft dotstiffness 100, damping 30, mass 1.59997
The inner dot snaps almost instantly (high stiffness, low mass). The outer ring lags behind noticeably (lower stiffness, higher mass). The magenta dot drags furthest of all, creating a comet-tail effect on fast movements.

Hover scale

When isHovered is true:
  • The inner dot scales up to 1.5×
  • The outer ring scales up to 1.2× and transitions to opacity: 0.8
When isHovered is false, the ring drops back to opacity: 0.3.

The .interactive Class

Any element — not just native <a> and <button> tags — can trigger the cursor’s expanded hover state by carrying the interactive class:
<button className="interactive">Click me</button>

<div className="interactive" role="button" onClick={handleClick}>
  Custom clickable card
</div>

<a href="/projects" className="interactive">
  View Projects
</a>
The mouseover listener checks for .interactive via target.classList.contains('interactive'), so the class must be on the exact element the cursor enters (or a parent that contains it via the closest('a') / closest('button') fallback).
The closest() fallback means child elements inside a <button> (like an icon <svg>) will still trigger the hover state even without carrying the .interactive class themselves — the listener walks up the DOM tree automatically.

mix-blend-screen

The inner dot (the w-4 h-4 teal circle, z-[9999]) uses mix-blend-screen. The outer ring and the magenta blur dot do not carry this class. In screen blending mode, the RGB values of the cursor and the background pixels are combined additively (the formula is 1 - (1-a)(1-b)). This means:
  • On dark backgrounds (near #000000), the cursor colour shows at nearly full saturation.
  • On light backgrounds (near #ffffff), the screen blend approaches white, keeping the cursor visible as a bright halo.
The result is an inner dot that never fully disappears regardless of what colour is beneath it — critical for a portfolio where the background cycles between deep navy, aurora greens, and near-white text.

Hiding the Native Cursor

CustomCursor alone does not hide the browser’s default arrow cursor. That is handled in main.css:
body {
  cursor: none;
}

a,
button,
[role="button"],
input,
textarea,
select {
  cursor: none;
}
These rules ensure the native cursor is suppressed both on the body and on interactive form elements that would otherwise revert to their OS-default pointer or text-beam cursor.
The cursor: none declaration is applied globally to body in main.css. If you embed CustomCursor inside a larger application that has its own cursor styles, you will need to scope these CSS rules to a wrapper element (e.g. #cosmic-root * { cursor: none; }) to avoid hiding the cursor on unrelated parts of the page.
To add other elements — navigation links, project cards, form inputs — to the interactive hover set without editing the component source, simply add the interactive class to them in your JSX. The cursor listener picks it up automatically.

Build docs developers (and LLMs) love