Styledex
← Index

Ghost Cursor

superdesignCC0-1.0Prompt onlyLanding · landingLight · lightMedium · mediumBlue · blueVivid · vividSerif · serifGeneral · generalProfessional · professional

A fluid, smoky cursor trail effect using Three.js shader materials and post-processing bloom. Category: Components. Prompt author listed upstream: Jason Zhou.

NNorthwind
Product · Pricing
Specimen
Make the work feel finished

One interface, dressed in this style. Compare type, color, radius, and density.

128
Active
4.8
Score
12ms
Edge

Attribution

"Ghost Cursor" from https://github.com/superdesigndev/superdesign-prompts (CC0-1.0). © Superdesign. prompts/ghost-cursor @ 6c6090694520.

https://github.com/superdesigndev/superdesign-prompts · prompts/ghost-cursor · 6c6090694520 · © Superdesign

Prompt

You are given a task to integrate an existing React component in the codebase
~~~/README.md
# GhostCursor
A fluid, smoky cursor trail effect using Three.js shader materials and post-processing bloom. This component adds a cinematic, ghostly trail that follows the cursor with organic movement.
## Usage
```tsx
import { GhostCursor } from '@/sd-components/72b3d7f3-3ac5-4819-9e63-117cebf79200';
const MyComponent = () => {
  return (
    <div style={{ position: 'relative', width: '100%', height: '500px' }}>
      {/* Content here */}
      <GhostCursor
        color="#B19EEF"
        trailLength={20}
      />
    </div>
  );
};
```
**Note**: The parent container should have `position: relative` or similar positioning context, as the GhostCursor uses `absolute inset-0`.
## Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `color` | `string` | `'#B19EEF'` | Base color of the trail |
| `brightness` | `number` | `1` | Brightness multiplier |
| `trailLength` | `number` | `50` | Length of the motion trail (number of previous positions) |
| `inertia` | `number` | `0.5` | Movement inertia (0-1), higher means more lag/fluidity |
| `bloomStrength` | `number` | `0.1` | Intensity of the glow effect |
| `bloomRadius` | `number` | `1.0` | Radius of the bloom blur |
| `bloomThreshold` | `number` | `0.025` | Brightness threshold for bloom |
| `grainIntensity` | `number` | `0.05` | Film grain intensity (0-1) |
| `edgeIntensity` | `number` | `0` | Edge accentuation intensity |
| `mixBlendMode` | `CSSProperties['mixBlendMode']` | `'screen'` | CSS blend mode for the canvas |
| `fadeDelayMs` | `number` | `1000` (desktop) | Delay before trail fades out when idle |
| `fadeDurationMs` | `number` | `1500` (desktop) | Duration of fade out animation |
| `zIndex` | `number` | `10` | CSS z-index of the canvas |
| `className` | `string` | `undefined` | Additional CSS classes |
## Dependencies
- `three`: Required for WebGL rendering
~~~
~~~/src/App.tsx
import React from 'react';
import { GhostCursor } from './Component';
const App = () => {
  return (
    <div style={{
      position: 'relative',
      minHeight: '100vh',
      backgroundColor: '#111',
      color: '#fff',
      display: 'flex',
      flexDirection: 'column',
      justifyContent: 'center',
      alignItems: 'center',
      overflow: 'hidden'
    }}>
      <div className="z-20 text-center pointer-events-none select-none">
        <h1 className="text-4xl md:text-6xl font-bold mb-4 tracking-tighter">Ghost Cursor</h1>
        <p className="text-gray-400 text-lg">Move your cursor to see the effect</p>
      </div>
      <GhostCursor
        // Visuals
        color="#B19EEF"
        brightness={1.2}
        edgeIntensity={0}
        // Trail and motion
        trailLength={20}
        inertia={0.4}
        // Post-processing
        grainIntensity={0.05}
        bloomStrength={0.5}
        bloomRadius={0.7}
        bloomThreshold={0}
        // Fade-out behavior
        fadeDelayMs={200}
        fadeDurationMs={1000}
      />
      {/* Background decoration */}
      <div className="absolute inset-0 z-0 bg-gradient-to-tr from-purple-900/10 to-blue-900/10" />
    </div>
  );
};
export default App;
~~~
~~~/package.json
{
  "name": "ghost-cursor",
  "version": "1.0.0",
  "description": "A fluid, smoky cursor trail effect using Three.js",
  "main": "src/Component.tsx",
  "dependencies": {
    "react": "^18.0.0",
    "react-dom": "^18.0.0",
    "three": "^0.160.0",
    "@types/three": "^0.160.0"
  }
}
~~~
~~~/src/Component.tsx
import React, { useEffect, useMemo, useRef } from 'react';
import * as THREE from 'three';
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js';
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js';
import { UnrealBloomPass } from 'three/examples/jsm/postprocessing/UnrealBloomPass.js';
import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js';
type GhostCursorProps = {
  className?: string;
  style?: React.CSSProperties;
  trailLength?: number;
  inertia?: number;
  grainIntensity?: number;
  bloomStrength?: number;
  bloomRadius?: number;
  bloomThreshold?: number;
  brightness?: number;
  color?: string;
  mixBlendMode?: React.CSSProperties['mixBlendMode'];
  edgeIntensity?: number;
  maxDevicePixelRatio?: number;
  targetPixels?: number;
  fadeDelayMs?: number;
  fadeDurationMs?: number;
  zIndex?: number;
};
/**
 * GhostCursor component creates a fluid, smoky cursor trail effect using Three.js.
 * It uses custom shaders for the fluid simulation and post-processing for bloom/grain.
 */
export const GhostCursor: React.FC<GhostCursorProps> = ({
  className,
  style,
  trailLength = 50,
  inertia = 0.5,
  grainIntensity = 0.05,
  bloomStrength = 0.1,
  bloomRadius = 1.0,
  bloomThreshold = 0.025,
  brightness = 1,
  color = '#B19EEF',
  mixBlendMode = 'screen',
  edgeIntensity = 0,
  maxDevicePixelRatio = 0.5,
  targetPixels,
  fadeDelayMs,
  fadeDurationMs,
  zIndex = 10
}) => {
  const containerRef = useRef<HTMLDivElement | null>(null);
  const rendererRef = useRef<THREE.WebGLRenderer | null>(null);
  const composerRef = useRef<EffectComposer | null>(null);
  const materialRef = useRef<THREE.ShaderMaterial | null>(null);
  const bloomPassRef = useRef<UnrealBloomPass | null>(null);
  const filmPassRef = useRef<ShaderPass | null>(null);
  // Trail circular buffer
  const trailBufRef = useRef<THREE.Vector2[]>([]);
  const headRef = useRef(0);
  const rafRef = useRef<number | null>(null);
  const resizeObsRef = useRef<ResizeObserver | null>(null);
  const currentMouseRef = useRef(new THREE.Vector2(0.5, 0.5));
  const velocityRef = useRef(new THREE.Vector2(0, 0));
  const fadeOpacityRef = useRef(1.0);
  const lastMoveTimeRef = useRef(typeof performance !== 'undefined' ? performance.now() : Date.now());
  const pointerActiveRef = useRef(false);
  const runningRef = useRef(false);
  const isTouch = useMemo(
    () => typeof window !== 'undefined' && ('ontouchstart' in window || navigator.maxTouchPoints > 0),
    []
  );
  const pixelBudget = targetPixels ?? (isTouch ? 0.9e6 : 1.3e6);
  const fadeDelay = fadeDelayMs ?? (isTouch ? 500 : 1000);
  const fadeDuration = fadeDurationMs ?? (isTouch ? 1000 : 1500);
  const baseVertexShader = `
    varying vec2 vUv;
    void main() {
      vUv = uv;
      gl_Position = vec4(position, 1.0);
    }
  `;
  const fragmentShader = `
    uniform float iTime;
    uniform vec3  iResolution;
    uniform vec2  iMouse;
    uniform vec2  iPrevMouse[MAX_TRAIL_LENGTH];
    uniform float iOpacity;
    uniform float iScale;
    uniform vec3  iBaseColor;
    uniform float iBrightness;
    uniform float iEdgeIntensity;
    varying vec2  vUv;
    float hash(vec2 p){ return fract(sin(dot(p,vec2(127.1,311.7))) * 43758.5453123); }
    float noise(vec2 p){
      vec2 i = floor(p), f = fract(p);
      f = f * f * (3. - 2. * f);
      return mix(mix(hash(i + vec2(0.,0.)), hash(i + vec2(1.,0.)), f.x),
                 mix(hash(i + vec2(0.,1.)), hash(i + vec2(1.,1.)), f.x), f.y);
    }
    float fbm(vec2 p){
      float v = 0.0;
      float a = 0.5;
      mat2 m = mat2(cos(0.5), sin(0.5), -sin(0.5), cos(0.5));
      for(int i=0;i<5;i++){
        v += a * noise(p);
        p = m * p * 2.0;
        a *= 0.5;
      }
      return v;
    }
    vec3 tint1(vec3 base){ return mix(base, vec3(1.0), 0.15); }
    vec3 tint2(vec3 base){ return mix(base, vec3(0.8, 0.9, 1.0), 0.25); }
    vec4 blob(vec2 p, vec2 mousePos, float intensity, float activity) {
      vec2 q = vec2(fbm(p * iScale + iTime * 0.1), fbm(p * iScale + vec2(5.2,1.3) + iTime * 0.1));
      vec2 r = vec2(fbm(p * iScale + q * 1.5 + iTime * 0.15), fbm(p * iScale + q * 1.5 + vec2(8.3,2.8) + iTime * 0.15));
      float smoke = fbm(p * iScale + r * 0.8);
      float radius = 0.5 + 0.3 * (1.0 / iScale);
      float distFactor = 1.0 - smoothstep(0.0, radius * activity, length(p - mousePos));
      float alpha = pow(smoke, 2.5) * distFactor;
      vec3 c1 = tint1(iBaseColor);
      vec3 c2 = tint2(iBaseColor);
      vec3 color = mix(c1, c2, sin(iTime * 0.5) * 0.5 + 0.5);
      return vec4(color * alpha * intensity, alpha * intensity);
    }
    void main() {
      vec2 uv = (gl_FragCoord.xy / iResolution.xy * 2.0 - 1.0) * vec2(iResolution.x / iResolution.y, 1.0);
      vec2 mouse = (iMouse * 2.0 - 1.0) * vec2(iResolution.x / iResolution.y, 1.0);
      vec3 colorAcc = vec3(0.0);
      float alphaAcc = 0.0;
      vec4 b = blob(uv, mouse, 1.0, iOpacity);
      colorAcc += b.rgb;
      alphaAcc += b.a;
      for (int i = 0; i < MAX_TRAIL_LENGTH; i++) {
        vec2 pm = (iPrevMouse[i] * 2.0 - 1.0) * vec2(iResolution.x / iResolution.y, 1.0);
        float t = 1.0 - float(i) / float(MAX_TRAIL_LENGTH);
        t = pow(t, 2.0);
        if (t > 0.01) {
          vec4 bt = blob(uv, pm, t * 0.8, iOpacity);
          colorAcc += bt.rgb;
          alphaAcc += bt.a;
        }
      }
      colorAcc *= iBrightness;
      vec2 uv01 = gl_FragCoord.xy / iResolution.xy;
      float edgeDist = min(min(uv01.x, 1.0 - uv01.x), min(uv01.y, 1.0 - uv01.y));
      float distFromEdge = clamp(edgeDist * 2.0, 0.0, 1.0);
      float k = clamp(iEdgeIntensity, 0.0, 1.0);
      float edgeMask = mix(1.0 - k, 1.0, distFromEdge);
      float outAlpha = clamp(alphaAcc * iOpacity * edgeMask, 0.0, 1.0);
      gl_FragColor = vec4(colorAcc, outAlpha);
    }
  `;
  const FilmGrainShader = useMemo(() => {
    return {
      uniforms: {
        tDiffuse: { value: null },
        iTime: { value: 0 },
        intensity: { value: grainIntensity }
      },
      vertexShader: `
        varying vec2 vUv;
        void main(){
          vUv = uv;
          gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
        }
      `,
      fragmentShader: `
        uniform sampler2D tDiffuse;
        uniform float iTime;
        uniform float intensity;
        varying vec2 vUv;
        float hash1(float n){ return fract(sin(n)*43758.5453); }
        void main(){
          vec4 color = texture2D(tDiffuse, vUv);
          float n = hash1(vUv.x*1000.0 + vUv.y*2000.0 + iTime) * 2.0 - 1.0;
          color.rgb += n * intensity * color.rgb;
          gl_FragColor = color;
        }
      `
    };
  }, [grainIntensity]);
  const UnpremultiplyPass = useMemo(
    () =>
      new ShaderPass({
        uniforms: { tDiffuse: { value: null } },
        vertexShader: `
          varying vec2 vUv;
          void main(){
            vUv = uv;
            gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
          }
        `,
        fragmentShader: `
          uniform sampler2D tDiffuse;
          varying vec2 vUv;
          void main(){
            vec4 c = texture2D(tDiffuse, vUv);
            float a = max(c.a, 1e-5);
            vec3 straight = c.rgb / a;
            gl_FragColor = vec4(clamp(straight, 0.0, 1.0), c.a);
          }
        `
      }),
    []
  );
  function calculateScale(el: HTMLElement) {
    const r = el.getBoundingClientRect();
    const base = 600;
    const current = Math.min(Math.max(1, r.width), Math.max(1, r.height));
    return Math.max(0.5, Math.min(2.0, current / base));
  }
  useEffect(() => {
    const host = containerRef.current;
    const parent = host?.parentElement;
    if (!host || !parent) return;
    // Note: Parent should be positioned relative or absolute by the user.
    // We won't modify parent style to avoid side effects/conflicts.
    const renderer = new THREE.WebGLRenderer({
      antialias: !isTouch,
      alpha: true,
      depth: false,
      stencil: false,
      powerPreference: 'default',
      premultipliedAlpha: false,
      preserveDrawingBuffer: false
    });
    renderer.setClearColor(0x000000, 0);
    rendererRef.current = renderer;
    renderer.domElement.style.pointerEvents = 'none';
    if (mixBlendMode) {
      renderer.domElement.style.mixBlendMode = String(mixBlendMode);
    } else {
      renderer.domElement.style.removeProperty('mix-blend-mode');
    }
    renderer.domElement.style.display = 'block';
    renderer.domElement.style.width = '100%';
    renderer.domElement.style.height = '100%';
    renderer.domElement.style.background = 'transparent';
    host.appendChild(renderer.domElement);
    const scene = new THREE.Scene();
    const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);
    const geom = new THREE.PlaneGeometry(2, 2);
    const maxTrail = Math.max(1, Math.floor(trailLength));
    trailBufRef.current = Array.from({ length: maxTrail }, () => new THREE.Vector2(0.5, 0.5));
    headRef.current = 0;
    const baseColor = new THREE.Color(color);
    const material = new THREE.ShaderMaterial({
      defines: { MAX_TRAIL_LENGTH: maxTrail },
      uniforms: {
        iTime: { value: 0 },
        iResolution: { value: new THREE.Vector3(1, 1, 1) },
        iMouse: { value: new THREE.Vector2(0.5, 0.5) },
        iPrevMouse: { value: trailBufRef.current.map(v => v.clone()) },
        iOpacity: { value: 1.0 },
        iScale: { value: 1.0 },
        iBaseColor: { value: new THREE.Vector3(baseColor.r, baseColor.g, baseColor.b) },
        iBrightness: { value: brightness },
        iEdgeIntensity: { value: edgeIntensity }
      },
      vertexShader: baseVertexShader,
      fragmentShader,
      transparent: true,
      depthTest: false,
      depthWrite: false
    });
    materialRef.current = material;
    const mesh = new THREE.Mesh(geom, material);
    scene.add(mesh);
    const composer = new EffectComposer(renderer);
    composerRef.current = composer;
    const renderPass = new RenderPass(scene, camera);
    composer.addPass(renderPass);
    const bloomPass = new UnrealBloomPass(new THREE.Vector2(1, 1), bloomStrength, bloomRadius, bloomThreshold);
    bloomPassRef.current = bloomPass;
    composer.addPass(bloomPass);
    const filmPass = new ShaderPass(FilmGrainShader as any);
    filmPassRef.current = filmPass;
    composer.addPass(filmPass);
    composer.addPass(UnpremultiplyPass);
    const resize = () => {
      const rect = host.getBoundingClientRect();
      const cssW = Math.max(1, Math.floor(rect.width));
      const cssH = Math.max(1, Math.floor(rect.height));
      const currentDPR = Math.min(
        typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1,
        maxDevicePixelRatio
      );
      const need = cssW * cssH * currentDPR * currentDPR;
      const scale = need <= pixelBudget ? 1 : Math.max(0.5, Math.min(1, Math.sqrt(pixelBudget / Math.max(1, need))));
      const pixelRatio = currentDPR * scale;
      renderer.setPixelRatio(pixelRatio);
      renderer.setSize(cssW, cssH, false);
      composer.setPixelRatio?.(pixelRatio);
      composer.setSize(cssW, cssH);
      const wpx = Math.max(1, Math.floor(cssW * pixelRatio));
      const hpx = Math.max(1, Math.floor(cssH * pixelRatio));
      material.uniforms.iResolution.value.set(wpx, hpx, 1);
      material.uniforms.iScale.value = calculateScale(host);
      bloomPass.setSize(wpx, hpx);
    };
    resize();
    const ro = new ResizeObserver(resize);
    resizeObsRef.current = ro;
    ro.observe(host);
    const start = typeof performance !== 'undefined' ? performance.now() : Date.now();
    const animate = () => {
      const now = performance.now();
      const t = (now - start) / 1000;
      const mat = materialRef.current!;
      const comp = composerRef.current!;
      if (pointerActiveRef.current) {
        velocityRef.current.set(
          currentMouseRef.current.x - mat.uniforms.iMouse.value.x,
          currentMouseRef.current.y - mat.uniforms.iMouse.value.y
        );
        mat.uniforms.iMouse.value.copy(currentMouseRef.current);
        fadeOpacityRef.current = 1.0;
      } else {
        velocityRef.current.multiplyScalar(inertia);
        if (velocityRef.current.lengthSq() > 1e-6) {
          mat.uniforms.iMouse.value.add(velocityRef.current);
        }
        const dt = now - lastMoveTimeRef.current;
        if (dt > fadeDelay) {
          const k = Math.min(1, (dt - fadeDelay) / fadeDuration);
          fadeOpacityRef.current = Math.max(0, 1 - k);
        }
      }
      const N = trailBufRef.current.length;
      headRef.current = (headRef.current + 1) % N;
      trailBufRef.current[headRef.current].copy(mat.uniforms.iMouse.value);
      const arr = mat.uniforms.iPrevMouse.value as THREE.Vector2[];
      for (let i = 0; i < N; i++) {
        const srcIdx = (headRef.current - i + N) % N;
        arr[i].copy(trailBufRef.current[srcIdx]);
      }
      mat.uniforms.iOpacity.value = fadeOpacityRef.current;
      mat.uniforms.iTime.value = t;
      if (filmPassRef.current?.uniforms?.iTime) {
        filmPassRef.current.uniforms.iTime.value = t;
      }
      comp.render();
      if (!pointerActiveRef.current && fadeOpacityRef.current <= 0.001) {
        runningRef.current = false;
        rafRef.current = null;
        return;
      }
      rafRef.current = requestAnimationFrame(animate);
    };
    const ensureLoop = () => {
      if (!runningRef.current) {
        runningRef.current = true;
        rafRef.current = requestAnimationFrame(animate);
      }
    };
    const onPointerMove = (e: PointerEvent) => {
      const rect = parent.getBoundingClientRect();
      const x = THREE.MathUtils.clamp((e.clientX - rect.left) / Math.max(1, rect.width), 0, 1);
      const y = THREE.MathUtils.clamp(1 - (e.clientY - rect.top) / Math.max(1, rect.height), 0, 1);
      currentMouseRef.current.set(x, y);
      pointerActiveRef.current = true;
      lastMoveTimeRef.current = performance.now();
      ensureLoop();
    };
    const onPointerEnter = () => {
      pointerActiveRef.current = true;
      ensureLoop();
    };
    const onPointerLeave = () => {
      pointerActiveRef.current = false;
      lastMoveTimeRef.current = performance.now();
      ensureLoop();
    };
    parent.addEventListener('pointermove', onPointerMove, { passive: true });
    parent.addEventListener('pointerenter', onPointerEnter, { passive: true });
    parent.addEventListener('pointerleave', onPointerLeave, { passive: true });
    ensureLoop();
    return () => {
      if (rafRef.current) cancelAnimationFrame(rafRef.current);
      runningRef.current = false;
      rafRef.current = null;
      parent.removeEventListener('pointermove', onPointerMove);
      parent.removeEventListener('pointerenter', onPointerEnter);
      parent.removeEventListener('pointerleave', onPointerLeave);
      resizeObsRef.current?.disconnect();
      scene.clear();
      geom.dispose();
      material.dispose();
      composer.dispose();
      renderer.dispose();
      if (renderer.domElement && renderer.domElement.parentElement) {
        renderer.domElement.parentElement.removeChild(renderer.domElement);
      }
    };
  }, [
    trailLength,
    inertia,
    grainIntensity,
    bloomStrength,
    bloomRadius,
    bloomThreshold,
    pixelBudget,
    fadeDelay,
    fadeDuration,
    isTouch,
    color,
    brightness,
    mixBlendMode,
    edgeIntensity
  ]);
  useEffect(() => {
    if (materialRef.current) {
      const c = new THREE.Color(color);
      (materialRef.current.uniforms.iBaseColor.value as THREE.Vector3).set(c.r, c.g, c.b);
    }
  }, [color]);
  useEffect(() => {
    if (materialRef.current) {
      materialRef.current.uniforms.iBrightness.value = brightness;
    }
  }, [brightness]);
  useEffect(() => {
    if (materialRef.current) {
      materialRef.current.uniforms.iEdgeIntensity.value = edgeIntensity;
    }
  }, [edgeIntensity]);
  useEffect(() => {
    if (filmPassRef.current?.uniforms?.intensity) {
      filmPassRef.current.uniforms.intensity.value = grainIntensity;
    }
  }, [grainIntensity]);
  useEffect(() => {
    const el = rendererRef.current?.domElement;
    if (!el) return;
    if (mixBlendMode) {
      el.style.mixBlendMode = String(mixBlendMode);
    } else {
      el.style.removeProperty('mix-blend-mode');
    }
  }, [mixBlendMode]);
  const mergedStyle = useMemo<React.CSSProperties>((…

---
"Ghost Cursor" from https://github.com/superdesigndev/superdesign-prompts (CC0-1.0). © Superdesign. prompts/ghost-cursor @ 6c6090694520.
powered by Styledex — https://styledex.dev

Tokens

DTCG

{
  "$schema": "https://www.designtokens.org/schemas/2025.10/format.json",
  "$description": "Ghost Cursor. \"Ghost Cursor\" from https://github.com/superdesigndev/superdesign-prompts (CC0-1.0). © Superdesign. prompts/ghost-cursor @ 6c6090694520.",
  "$extensions": {
    "dev.styledex": {
      "id": "superdesign-ghost-cursor",
      "license": "CC0-1.0",
      "attribution": "\"Ghost Cursor\" from https://github.com/superdesigndev/superdesign-prompts (CC0-1.0). © Superdesign. prompts/ghost-cursor @ 6c6090694520.",
      "banner": "powered by Styledex — https://styledex.dev"
    }
  },
  "color": {
    "background": {
      "$type": "color",
      "$value": "#ffffff"
    },
    "foreground": {
      "$type": "color",
      "$value": "#111111"
    },
    "card": {
      "$type": "color",
      "$value": "#ffffff"
    },
    "card-foreground": {
      "$type": "color",
      "$value": "#111111"
    },
    "primary": {
      "$type": "color",
      "$value": "#b19eef"
    },
    "primary-foreground": {
      "$type": "color",
      "$value": "#fafaf9"
    },
    "secondary": {
      "$type": "color",
      "$value": "#111111"
    },
    "secondary-foreground": {
      "$type": "color",
      "$value": "#111111"
    },
    "accent": {
      "$type": "color",
      "$value": "#b19eef"
    },
    "accent-foreground": {
      "$type": "color",
      "$value": "#fafaf9"
    },
    "muted": {
      "$type": "color",
      "$value": "#f0f0f0"
    },
    "muted-foreground": {
      "$type": "color",
      "$value": "#646464"
    },
    "border": {
      "$type": "color",
      "$value": "#dbdbdb"
    },
    "destructive": {
      "$type": "color",
      "$value": "#dc2626"
    },
    "ring": {
      "$type": "color",
      "$value": "#b19eef"
    }
  },
  "font": {
    "sans": {
      "$type": "fontFamily",
      "$value": [
        "ui-sans-serif",
        "system-ui",
        "sans-serif"
      ]
    },
    "serif": {
      "$type": "fontFamily",
      "$value": [
        "ui-serif",
        "Georgia",
        "serif"
      ]
    },
    "mono": {
      "$type": "fontFamily",
      "$value": [
        "ui-monospace",
        "SFMono-Regular",
        "Menlo",
        "monospace"
      ]
    }
  },
  "radius": {
    "base": {
      "$type": "dimension",
      "$value": "0.5rem"
    }
  },
  "spacing": {
    "base": {
      "$type": "dimension",
      "$value": "1rem"
    }
  },
  "shadow": {
    "base": {
      "$type": "shadow",
      "$value": "0 1px 2px rgba(0,0,0,0.06), 0 8px 24px rgba(0,0,0,0.04)"
    }
  }
}

CSS

/* powered by Styledex — https://styledex.dev */
/* "Ghost Cursor" from https://github.com/superdesigndev/superdesign-prompts (CC0-1.0). © Superdesign. prompts/ghost-cursor @ 6c6090694520. */
:root {
  --background: #ffffff;
  --foreground: #111111;
  --card: #ffffff;
  --card-foreground: #111111;
  --primary: #b19eef;
  --primary-foreground: #fafaf9;
  --secondary: #111111;
  --secondary-foreground: #111111;
  --accent: #b19eef;
  --accent-foreground: #fafaf9;
  --muted: #f0f0f0;
  --muted-foreground: #646464;
  --border: #dbdbdb;
  --destructive: #dc2626;
  --ring: #b19eef;
  --radius: 0.5rem;
  --font-sans: ui-sans-serif, system-ui, sans-serif;
  --font-serif: ui-serif, Georgia, serif;
  --font-mono: ui-monospace, SFMono-Regular, Menlo, monospace;
  --spacing: 1rem;
  --shadow: 0 1px 2px rgba(0,0,0,0.06), 0 8px 24px rgba(0,0,0,0.04);
}

Tailwind

/* powered by Styledex — https://styledex.dev */
/* "Ghost Cursor" from https://github.com/superdesigndev/superdesign-prompts (CC0-1.0). © Superdesign. prompts/ghost-cursor @ 6c6090694520. */
/** Tailwind theme extend for Ghost Cursor (superdesign-ghost-cursor) */
module.exports = {
  "theme": {
    "extend": {
      "colors": {
        "background": "#ffffff",
        "foreground": "#111111",
        "card": "#ffffff",
        "primary": "#b19eef",
        "primary-foreground": "#fafaf9",
        "secondary": "#111111",
        "accent": "#b19eef",
        "muted": "#f0f0f0",
        "border": "#dbdbdb",
        "destructive": "#dc2626",
        "ring": "#b19eef"
      },
      "borderRadius": {
        "lg": "0.5rem"
      },
      "fontFamily": {
        "sans": [
          "ui-sans-serif",
          "system-ui",
          "sans-serif"
        ],
        "serif": [
          "ui-serif",
          "Georgia",
          "serif"
        ],
        "mono": [
          "ui-monospace",
          "SFMono-Regular",
          "Menlo",
          "monospace"
        ]
      },
      "boxShadow": {
        "styledex": "0 1px 2px rgba(0,0,0,0.06), 0 8px 24px rgba(0,0,0,0.04)"
      }
    }
  }
};

shadcn

/* powered by Styledex — https://styledex.dev */
/* "Ghost Cursor" from https://github.com/superdesigndev/superdesign-prompts (CC0-1.0). © Superdesign. prompts/ghost-cursor @ 6c6090694520. */
@layer base {
:root {
  --background: #ffffff;
  --foreground: #111111;
  --card: #ffffff;
  --card-foreground: #111111;
  --primary: #b19eef;
  --primary-foreground: #fafaf9;
  --secondary: #111111;
  --secondary-foreground: #111111;
  --accent: #b19eef;
  --accent-foreground: #fafaf9;
  --muted: #f0f0f0;
  --muted-foreground: #646464;
  --border: #dbdbdb;
  --destructive: #dc2626;
  --ring: #b19eef;
  --radius: 0.5rem;
  --font-sans: ui-sans-serif, system-ui, sans-serif;
  --font-serif: ui-serif, Georgia, serif;
  --font-mono: ui-monospace, SFMono-Regular, Menlo, monospace;
  --spacing: 1rem;
  --shadow: 0 1px 2px rgba(0,0,0,0.06), 0 8px 24px rgba(0,0,0,0.04);
}
}