Mantine Scene

@gfazioli/mantine-scene

A composable decorative background component for React applications built with Mantine. Layer gradients, glow blobs, dot grids, mesh gradients, and noise textures to create rich atmospheric backgrounds.

Installation

yarn add @gfazioli/mantine-scene

After installation import package styles at the root of your application:

import '@gfazioli/mantine-scene/styles.css';

You can import styles within a layer @layer mantine-scene by importing @gfazioli/mantine-scene/styles.layer.css file.

import '@gfazioli/mantine-scene/styles.layer.css';

Usage

Scene is a decorative background container that composes multiple visual layers: gradients, glow blobs, dot grids, mesh gradients, noise textures, star fields, shooting stars, snow, rain, confetti, auroras, parallax waves, Wi-Fi / radar pulses, sweeping beams, and even an interactive 3D globe. Place it behind your content to create rich, atmospheric backgrounds.

The root element has aria-hidden="true" since it is purely decorative.

Fullscreen

Set fullscreen to cover the entire viewport with position: fixed. The scene sits behind all content at z-index: 0 by default (configurable via the zIndex prop).

See the fullscreen demo page →

Decorative background

Scene renders behind your content

Interactive easing
import { Box, Text } from '@mantine/core';
import { Scene } from '@gfazioli/mantine-scene';

function Demo() {
  return (
    <Box pos="relative" h={300} style={{ borderRadius: 'var(--mantine-radius-md)', overflow: 'hidden', background: 'var(--mantine-color-body)' }}>
      <Scene lazy lazyThreshold={0.1}>
        <Scene.Gradient from="violet" fromOpacity={0.15} />
        <Scene.Glow color="violet" size={400} blur={120} opacity={0.3} top="20%" left="30%" />
        <Scene.DotGrid color="gray" opacity={0.3} spacing={24} />
        <Scene.Noise opacity={0.025} />
      </Scene>
      <Box pos="relative" style={{ zIndex: 1 }} p="xl">
        <Text size="xl" fw={700} c="white">Decorative background</Text>
        <Text c="dimmed" mt="sm">Scene renders behind your content</Text>
      </Box>
    </Box>
  );
}

Reduced Motion

Scene respects prefers-reduced-motion by default. Use the reducedMotion prop to control behavior:

  • 'auto' (default) — disables animations when the user has enabled reduced motion in their OS settings
  • 'always' — always disables all animations
  • 'never' — keeps animations regardless of user preference

Interactive

Set interactive on the root Scene to enable mouse tracking. Sub-components that support it will react to the cursor position automatically:

  • Scene.StarWarp — focal point follows the mouse
  • Scene.Glow — blob position follows the mouse
  • Scene.Gradient — radial/conic gradient center follows the mouse
  • Scene.Globe — when followCursor is set on the globe, the sphere rotates toward the cursor position

When the mouse leaves the scene, components fall back to their default position props.

onMousePosition callback

When interactive is enabled, pass onMousePosition to receive every smoothed cursor update. The callback receives { x, y } as percentages (0–100) relative to the scene container — useful for coordinating external UI elements with the scene's tracking.

x: y:
import { useState } from 'react';
import { Scene } from '@gfazioli/mantine-scene';
import { Box, Code, Group, Stack } from '@mantine/core';

function Demo() {
  const [pos, setPos] = useState<{ x: number; y: number } | null>(null);

  return (
    <Stack gap="md">
      <Group>
        <Code>x: {pos ? pos.x.toFixed(1) : '—'}</Code>
        <Code>y: {pos ? pos.y.toFixed(1) : '—'}</Code>
      </Group>

      <Box pos="relative" h={300} style={{ borderRadius: 'var(--mantine-radius-md)', overflow: 'hidden', background: 'var(--mantine-color-body)' }}>
        <Scene interactive onMousePosition={setPos}>
          <Scene.Gradient
            type="radial"
            colors={['rgba(0, 200, 255, 0.2) 0%', 'transparent 70%']}
          />
          <Scene.Glow color="cyan" size={300} blur={120} top="50%" left="50%" />
        </Scene>
      </Box>
    </Stack>
  );
}

Lazy mode

Set lazy on the root Scene to pause all child animations and the internal mouse-tracking requestAnimationFrame loop when the scene leaves the viewport. Animations resume automatically when the scene re-enters.

This uses IntersectionObserver under the hood and is opt-in (default false) to preserve backwards compatibility. Tune the visibility threshold via lazyThreshold (0–1).

<Scene lazy lazyThreshold={0.1}>
  <Scene.StarField count={120} />
  <Scene.Glow color="violet" size={400} />
</Scene>

When paused, the root receives a data-paused attribute and a CSS rule sets animation-play-state: paused on every child element — no React re-render or remount required.

Scroll the page — when this scene leaves the viewport, all child animations and the mouse-tracking rAF loop pause. They resume automatically when it re-enters.

import { Scene } from '@gfazioli/mantine-scene';
import { Box, Stack, Text } from '@mantine/core';

function Demo() {
  return (
    <Stack gap="md">
      <Text size="sm" c="dimmed">
        Scroll the page — when this scene leaves the viewport,
        all child animations and the mouse-tracking rAF loop pause.
        They resume automatically when it re-enters.
      </Text>

      <Box pos="relative" h={300} style={{ borderRadius: 'var(--mantine-radius-md)', overflow: 'hidden', background: 'var(--mantine-color-body)' }}>
        <Scene lazy>
          <Scene.Gradient
            type="radial"
            colors={['rgba(120, 0, 255, 0.3) 0%', 'transparent 70%']}
          />
          <Scene.StarField count={120} twinkle />
          <Scene.Glow color="violet" size={400} blur={140} top="50%" left="50%" />
        </Scene>
      </Box>
    </Stack>
  );
}

Responsive Props

Some props accept Mantine's StyleProp<T> — either a static value or a breakpoint object (e.g. { base: 30, md: 100 }). CSS-derivable values (sizes, spacings) are emitted as scoped CSS variables with @media queries via Mantine's InlineStyles, so they update with zero React re-renders on viewport changes — the same pattern used by Mantine core's SimpleGrid.

Supported responsive props:

  • Scene.Glowsize, blur (CSS-driven, no re-render)
  • Scene.DotGridspacing (CSS-driven, no re-render)
  • Scene.StarFieldcount (resolved in JS — particles re-seed at breakpoints)
  • Scene.StarWarpcount (resolved in JS)
  • Scene.Snowcount (resolved in JS)
  • Scene.Raincount (resolved in JS)
  • Scene.Confetticount (resolved in JS)
import { Scene } from '@gfazioli/mantine-scene';

function Demo() {
  return (
    <Scene>
      {/* Fewer stars on small screens for performance */}
      <Scene.StarField count={{ base: 30, sm: 60, md: 100 }} />

      {/* Smaller glow on mobile */}
      <Scene.Glow
        color="violet"
        size={{ base: 200, md: 400 }}
        blur={{ base: 60, md: 120 }}
      />

      {/* Tighter dot grid on small screens */}
      <Scene.DotGrid color="gray" spacing={{ base: 16, md: 24 }} />

      {/* Lighter rain on mobile */}
      <Scene.Rain count={{ base: 40, md: 100 }} angle={15} />
    </Scene>
  );
}

Plain number values continue to work as before — responsive objects are entirely opt-in. The legacy ResponsiveValue<T> type re-export remains for backwards compatibility but is now an alias for StyleProp<T> and is marked as deprecated.

Responsive props

base

Resize the browser to see stars, glow, and dot spacing adapt

import { Badge, Box, Group, Text } from '@mantine/core';
import { useMatches } from '@mantine/core';
import { Scene } from '@gfazioli/mantine-scene';

function Demo() {
  const breakpoint = useMatches({
    base: 'base',
    xs: 'xs',
    sm: 'sm',
    md: 'md',
    lg: 'lg',
    xl: 'xl',
  });

  return (
    <Box pos="relative" h={300} style={{ borderRadius: 'var(--mantine-radius-md)', overflow: 'hidden', background: 'var(--mantine-color-body)' }}>
      <Scene>
        <Scene.StarField
          count={{ base: 20, sm: 50, md: 100 }}
          twinkle
          opacity={0.7}
        />
        <Scene.Glow
          color="violet"
          size={{ base: 150, sm: 250, md: 400 }}
          blur={{ base: 40, sm: 80, md: 120 }}
          opacity={0.4}
          top="30%"
          left="40%"
        />
        <Scene.DotGrid
          color="gray"
          spacing={{ base: 14, sm: 20, md: 28 }}
          opacity={0.2}
          fade="radial"
        />
        <Scene.Noise opacity={0.02} />
      </Scene>
      <Box pos="relative" style={{ zIndex: 1 }} p="xl">
        <Group>
          <Text size="lg" fw={700} c="white">Responsive props</Text>
          <Badge variant="filled" color="violet" size="lg">{breakpoint}</Badge>
        </Group>
        <Text c="dimmed" mt="xs" size="sm">
          Resize the browser to see stars, glow, and dot spacing adapt
        </Text>
      </Box>
    </Box>
  );
}

Gradient

Scene.Gradient supports three types: radial (default), linear, and conic. Use from and to with Mantine theme colors, or pass raw CSS color stops via colors.

Set animate to enable rotate (hue-rotate) or pulse (opacity oscillation) animations.

From
To
From opacity
To opacity
Angle
Opacity
Duration
Pulse min opacity
Interactive easing
import { Scene } from '@gfazioli/mantine-scene';
import { Box } from '@mantine/core';

function Demo() {
  return (
    <Box pos="relative" h={300} style={{ borderRadius: 'var(--mantine-radius-md)', overflow: 'hidden', background: 'var(--mantine-color-body)' }}>
      <Scene lazy lazyThreshold={0.1}>
        <Scene.Gradient from="violet" to="pink" />
      </Scene>
    </Box>
  );
}

Glow

Scene.Glow renders animated floating blobs. Each blob can be positioned, sized, and animated independently. Disable animation with animate={false}.

Choose between animation types: float (default), pulse (scale oscillation), and breathe (opacity oscillation). Use delay to stagger multiple blobs.

Color
Size
Blur
Opacity
Top
Left
Duration
Delay
Drift x
Drift y
Interactive easing
import { Scene } from '@gfazioli/mantine-scene';
import { Box } from '@mantine/core';

function Demo() {
  return (
    <Box pos="relative" h={350} style={{ borderRadius: 'var(--mantine-radius-md)', overflow: 'hidden', background: 'var(--mantine-color-body)' }}>
      <Scene lazy lazyThreshold={0.1}>
        <Scene.Glow top={50} />
      </Scene>
    </Box>
  );
}

DotGrid

Scene.DotGrid creates a repeating dot pattern. Enable stagger for a honeycomb-like layout. Use fade to mask the edges (radial, top, bottom, or edges).

Color
Dot size
Spacing
Duration
Opacity
import { Scene } from '@gfazioli/mantine-scene';
import { Box } from '@mantine/core';

function Demo() {
  return (
    <Box pos="relative" h={300} style={{ borderRadius: 'var(--mantine-radius-md)', overflow: 'hidden', background: 'var(--mantine-color-body)' }}>
      <Scene lazy lazyThreshold={0.1}>
        <Scene.DotGrid />
      </Scene>
    </Box>
  );
}

Mesh

Scene.Mesh overlays multiple radial gradients to simulate a mesh gradient effect. Define color stops with position and spread.

Set animate to enable a hue-rotate shift. Use blend to change the CSS mix-blend-mode.

Opacity
Duration
import { Scene } from '@gfazioli/mantine-scene';
import { Box } from '@mantine/core';

function Demo() {
  return (
    <Box pos="relative" h={300} style={{ borderRadius: 'var(--mantine-radius-md)', overflow: 'hidden', background: 'var(--mantine-color-body)' }}>
      <Scene lazy lazyThreshold={0.1}>
        <Scene.Mesh
          stops={[
            { color: 'violet', position: '20% 20%', spread: 50 },
            { color: 'pink', position: '80% 30%', spread: 45 },
            { color: 'blue', position: '50% 80%', spread: 55 },
          ]}
        />
      </Scene>
    </Box>
  );
}

Noise

Scene.Noise adds a film grain texture via an inline SVG filter. Control the noise with seed, type, and octaves. Use tint with a Mantine theme color to overlay a color tint.

Opacity
Grain
Seed
Octaves
Tint
Tint opacity
import { Scene } from '@gfazioli/mantine-scene';
import { Box } from '@mantine/core';

function Demo() {
  return (
    <Box pos="relative" h={300} style={{ borderRadius: 'var(--mantine-radius-md)', overflow: 'hidden', background: 'var(--mantine-color-body)' }}>
      <Scene lazy lazyThreshold={0.1}>
        <Scene.Noise opacity={0.15} tint="" />
      </Scene>
    </Box>
  );
}

StarField

Scene.StarField renders a CSS-only star field using box-shadow on a single element per layer. Stars are positioned deterministically via a PRNG seeded with the seed prop.

Enable twinkle for a staggered opacity animation across three layers.

Open standalone view →

Count
Color
Min size
Max size
Duration
Density
Seed
Opacity
import { Scene } from '@gfazioli/mantine-scene';
import { Box } from '@mantine/core';

function Demo() {
  return (
    <Box pos="relative" h={300} style={{ borderRadius: 'var(--mantine-radius-md)', overflow: 'hidden', background: 'var(--mantine-color-body)' }}>
      <Scene lazy lazyThreshold={0.1}>
        <Scene.StarField color="blue" />
      </Scene>
    </Box>
  );
}

StarWarp

Scene.StarWarp creates a classic hyperspace / warp speed effect where stars radiate from a configurable focal point. Stars scale up as they move outward, simulating depth and perspective.

Use direction to choose between flying away (out) or approaching (in). Move the focal point with focalX / focalY to shift the vanishing point. Enable trail for elongated streaks.

Open standalone view →

Count
Speed
Focal x
Focal y
Color
Min size
Max size
Opacity
Seed
Interactive easing
import { Scene } from '@gfazioli/mantine-scene';
import { Box } from '@mantine/core';

function Demo() {
  return (
    <Box pos="relative" h={400} style={{ borderRadius: 'var(--mantine-radius-md)', overflow: 'hidden', background: 'var(--mantine-color-body)' }}>
      <Scene lazy lazyThreshold={0.1}>
        <Scene.StarWarp color="blue" />
      </Scene>
    </Box>
  );
}

ShootingStar

Scene.ShootingStar creates streaks of light that traverse the scene at a configurable angle. Each trail has a gradient from transparent to the chosen color.

Control count (number of lanes), trailLength, speed, and minInterval/maxInterval between appearances.

Count
Color
Trail length
Angle
Speed
Min interval
Max interval
Seed
Opacity
import { Scene } from '@gfazioli/mantine-scene';
import { Box } from '@mantine/core';

function Demo() {
  return (
    <Box pos="relative" h={300} style={{ borderRadius: 'var(--mantine-radius-md)', overflow: 'hidden', background: 'var(--mantine-color-body)' }}>
      <Scene lazy lazyThreshold={0.1}>
        <Scene.ShootingStar count={50} color="blue" angle={150} minInterval={1} maxInterval={3} />
      </Scene>
    </Box>
  );
}

Snow

Scene.Snow renders falling snowflakes with horizontal drift. Each flake has a randomized size, speed, and position (deterministic via seed).

Use wind (-1 to 1) to bias the drift direction and speed as a multiplier on fall duration.

Count
Color
Min size
Max size
Speed
Drift
Wind
Opacity
import { Scene } from '@gfazioli/mantine-scene';
import { Box } from '@mantine/core';

function Demo() {
  return (
    <Box pos="relative" h={300} style={{ borderRadius: 'var(--mantine-radius-md)', overflow: 'hidden', background: 'var(--mantine-color-body)' }}>
      <Scene lazy lazyThreshold={0.1}>
        <Scene.Snow color="blue" />
      </Scene>
    </Box>
  );
}

Rain

Scene.Rain renders falling raindrops as angled streaks. Customize count, color, streak angle (degrees), thickness, and speed. Min/max streak length are randomized per drop.

Enable splash to render expanding splash rings at the bottom edge. Tune the effect with splashCount, splashSize (base diameter), splashOpacity, and splashThickness (ring width). splashColor overrides the default (which mirrors color).

For a thunderstorm effect, combine Scene.Rain with a bright Scene.Glow to simulate a lightning flash.

Count
Color
Angle
Speed
Min length
Max length
Thickness
Opacity
Splash count
Splash size
Splash opacity
Splash thickness
import { Scene } from '@gfazioli/mantine-scene';
import { Box } from '@mantine/core';

function Demo() {
  return (
    <Box pos="relative" h={300} style={{ borderRadius: 'var(--mantine-radius-md)', overflow: 'hidden', background: 'var(--mantine-color-body)' }}>
      <Scene lazy lazyThreshold={0.1}>
        <Scene.Rain count={80} />
      </Scene>
    </Box>
  );
}

Confetti

Scene.Confetti renders multi-colored falling confetti pieces with rotation and horizontal flutter. Each piece picks one shape from shapes ('rectangle', 'circle', or 'triangle') and one color from the colors palette (Mantine theme colors).

Tune count, duration, flutter (sway range in px), and the size range with minSize/maxSize.

Open standalone view →

Origin: top vs bottom

Set origin="bottom" to flip the animation into a "confetti cannon" — pieces shoot upward in a parabolic arc, peak at rise pixels above the bottom edge, then fall back down. Pair it with burst for a celebratory one-shot blast triggered from a button.

<Scene>
  <Scene.Confetti
    origin="bottom"
    rise={400}
    burst
    count={120}
    shapes={['rectangle', 'triangle']}
  />
</Scene>

Continuous mode

By default Scene.Confetti loops continuously, emitting pieces in an infinite stream. Use the configurator below to tune count, sizes, flutter, and timing.

Count
Duration
Speed
Min size
Max size
Flutter
Opacity
Origin
Rise
import { Scene } from '@gfazioli/mantine-scene';
import { Box } from '@mantine/core';

function Demo() {
  return (
    <Box pos="relative" h={300} style={{ borderRadius: 'var(--mantine-radius-md)', overflow: 'hidden', background: 'var(--mantine-color-body)' }}>
      <Scene lazy lazyThreshold={0.1}>
        <Scene.Confetti />
      </Scene>
    </Box>
  );
}

Burst mode with onComplete

Set burst to run a single one-shot animation instead of looping. Combine it with a React key to retrigger the burst on demand — the onComplete callback fires once the last piece has settled.

<Scene>
  <Scene.Confetti
    key={trigger}
    count={120}
    burst
    duration={3.5}
    onComplete={() => console.log('settled')}
  />
</Scene>

Triggered: 0

Completed: 0

import { useState } from 'react';
import { Scene } from '@gfazioli/mantine-scene';
import { Box, Button, Group, Stack, Text } from '@mantine/core';

function Demo() {
  const [bursts, setBursts] = useState(0);
  const [completed, setCompleted] = useState(0);

  return (
    <Stack>
      <Group justify="space-between">
        <Group gap="xs">
          <Text size="sm">Triggered: {bursts}</Text>
          <Text size="sm">Completed: {completed}</Text>
        </Group>
        <Button onClick={() => setBursts((n) => n + 1)}>Trigger burst</Button>
      </Group>

      <Box pos="relative" h={280} style={{ borderRadius: 'var(--mantine-radius-md)', overflow: 'hidden', background: 'var(--mantine-color-body)' }}>
        <Scene lazy lazyThreshold={0.1}>
          {bursts > 0 && (
            <Scene.Confetti
              key={bursts}
              count={120}
              burst
              origin="bottom"
              rise={400}
              duration={3.5}
              shapes={['rectangle', 'triangle', 'circle']}
              onComplete={() => setCompleted((n) => n + 1)}
            />
          )}
        </Scene>
      </Box>
    </Stack>
  );
}

Aurora

Scene.Aurora creates shimmering aurora borealis bands. Each band is a blurred, colored rectangle with a wave animation.

Customize colors (Mantine theme colors), bands count, position (top/center/bottom), blur, and opacity.

Bands
Duration
Opacity
Blur
import { Scene } from '@gfazioli/mantine-scene';
import { Box } from '@mantine/core';

function Demo() {
  return (
    <Box pos="relative" h={300} style={{ borderRadius: 'var(--mantine-radius-md)', overflow: 'hidden', background: 'var(--mantine-color-body)' }}>
      <Scene lazy lazyThreshold={0.1}>
        <Scene.Aurora />
      </Scene>
    </Box>
  );
}

Waves

Scene.Waves renders a stack of parallax SVG wave layers that pan horizontally for a soothing, ocean-like background. Each layer's path is generated deterministically from the seed and tiles seamlessly under a translateX(-50%) CSS animation — no requestAnimationFrame, no canvas, no React re-renders during the animation.

Pass a single Mantine theme color to derive shades across layers automatically (back layers use lighter shades, front layers darker), or an explicit array to map a specific color per layer. Use position ('top' / 'bottom') to anchor the waves to either edge.

Use direction ('left' / 'right') to flip the horizontal pan, and parallax to control the speed differential between foreground and background layers:

  • parallax={0} flattens parallax — all layers move at the same speed
  • parallax={1} (default) is the standard depth effect
  • parallax={2} exaggerates the differential
  • Negative values invert the parallax (background layers faster than foreground), creating a dreamy reverse-depth feel
<Scene>
  <Scene.Waves
    count={4}
    colors="violet"
    amplitude={50}
    wavelength={520}
    height={300}
    direction="right"
    parallax={1.5}
    position="bottom"
  />
</Scene>
Count
Colors
Amplitude
Wavelength
Height
Speed
Direction
Parallax
Position
Blur
Opacity
import { Scene } from '@gfazioli/mantine-scene';
import { Box } from '@mantine/core';

function Demo() {
  return (
    <Box pos="relative" h={300} style={{ borderRadius: 'var(--mantine-radius-md)', overflow: 'hidden', background: 'var(--mantine-color-body)' }}>
      <Scene lazy lazyThreshold={0.1}>
        <Scene.Waves height={200} />
      </Scene>
    </Box>
  );
}

Radar

Scene.Radar emits concentric pulses from a configurable origin — the visual you reach for when the section's topic is signal, connectivity, broadcast or radar sweep. Set shape='arc' (default) for the Wi-Fi icon look, shape='circle' for a full ring. The opening of the arc is controlled by arcDirection (up / down / left / right). Each wave fades from peakOpacity to 0 as it travels from origin to maxRadius; multiple waves are emitted on a staggered cadence (count × interval).

Color
Count
Interval
Duration
Stroke width
Blur
Peak opacity
import { Scene } from '@gfazioli/mantine-scene';
import { Box } from '@mantine/core';

function Demo() {
  return (
    <Box pos="relative" h={400} style={{ borderRadius: 'var(--mantine-radius-md)', overflow: 'hidden', background: 'var(--mantine-color-dark-9)' }}>
      <Scene lazy lazyThreshold={0.1}>
        <Scene.Radar color="orange" peakOpacity={0.6} />
      </Scene>
    </Box>
  );
}

Beams

Scene.Beams paints sweeping light columns (or rows, with direction='horizontal') — the "search-light" pattern from Magic UI landings. Colours cycle through the colors prop, each beam blurred for soft edges. Width, blur amount and sweep duration are independent so you can tune the look from subtle ambient to dramatic cinematic.

Colors
Count
Width
Blur
Radius
Opacity
Min duration
Max duration
Seed
import { Scene } from '@gfazioli/mantine-scene';
import { Box } from '@mantine/core';

function Demo() {
  return (
    <Box pos="relative" h={400} style={{ borderRadius: 'var(--mantine-radius-md)', overflow: 'hidden', background: 'var(--mantine-color-dark-9)' }}>
      <Scene lazy lazyThreshold={0.1}>
        <Scene.Beams />
      </Scene>
    </Box>
  );
}

Globe

Scene.Globe renders an interactive 3D globe powered by cobe (~10kB WebGL). Drag to rotate (when interactive), or let autoRotate spin it. Pass markers ({ location: [lat, lng], size? }[]) to plot points on the surface. Theme colours (baseColor, glowColor, markerColor) accept Mantine palette references and are converted to the RGB tuples cobe expects.

cobe is an optional peer dependency — install it explicitly only when you use Scene.Globe:

yarn add cobe
# or
npm install cobe

Without cobe, the component renders nothing and logs a dev-only warning instead of crashing.

About colours and alpha: cobe's WebGL shader ignores the alpha channel of baseColor / glowColor / markerColor. To fade the atmospheric glow use glowIntensity (0..1); for the continents and markers, pass a darker palette shade ('gray.3' vs 'gray.7') or a darker CSS color instead of an rgba(..., 0.5).

About continent detail: the planet is rendered as dots (cobe's Fibonacci-sphere sampling). Increase mapSamples (up to ~32000) for a denser, more "filled" look — above that, cobe's shader starts producing sampling artifacts.

Open standalone view →

Size
Base color
Map samples
Map brightness
Diffuse
Glow color
Glow intensity
Auto rotate speed
Inertia
Theta
Scale
Opacity
import { Scene } from '@gfazioli/mantine-scene';
import { Box } from '@mantine/core';

function Demo() {
  return (
    <Box pos="relative" h={500} style={{ borderRadius: 'var(--mantine-radius-md)', overflow: 'hidden', background: 'var(--mantine-color-dark-9)' }}>
      <Scene lazy lazyThreshold={0.1}>
        <Scene.Globe size={350} baseColor="gray" mapSamples={8000} glowColor="blue" />
      </Scene>
    </Box>
  );
}

Markers

Pass an array of { location: [lat, lng], size?, color? } to pin points on the surface. Mantine palette colours can be overridden per-marker for category encoding.

import { Scene } from '@gfazioli/mantine-scene';
import { Box } from '@mantine/core';

const cities = [
  { location: [41.9028, 12.4964] as [number, number], size: 0.08 },   // Rome
  { location: [40.7128, -74.006] as [number, number], size: 0.08 },   // New York
  { location: [35.6762, 139.6503] as [number, number], size: 0.08 },  // Tokyo
  { location: [37.7595, -122.4367] as [number, number], size: 0.08 }, // San Francisco
  { location: [-33.8688, 151.2093] as [number, number], size: 0.08 }, // Sydney
  { location: [51.5074, -0.1278] as [number, number], size: 0.08 },   // London
];

function Demo() {
  return (
    <Box pos="relative" h={500} style={{ borderRadius: 'var(--mantine-radius-md)', overflow: 'hidden', background: 'var(--mantine-color-dark-9)' }}>
      <Scene lazy lazyThreshold={0.1}>
        <Scene.Globe
          size={400}
          markers={cities}
          markerColor="orange.5"
          baseColor="gray.7"
          glowColor="blue.5"
        />
      </Scene>
    </Box>
  );
}

Arcs (flight paths)

arcs={[{ from: [lat, lng], to: [lat, lng], color? }]} draws curved 3D paths between two locations — the canonical "network topology" / "flight map" pattern. arcHeight, arcWidth and arcColor tune the look globally; each arc can override color individually.

import { Scene } from '@gfazioli/mantine-scene';
import { Box } from '@mantine/core';

// Flight network — every city is both a marker and the endpoint of arcs.
const ROM: [number, number] = [41.9028, 12.4964];
const NYC: [number, number] = [40.7128, -74.006];
const TYO: [number, number] = [35.6762, 139.6503];
const SFO: [number, number] = [37.7595, -122.4367];
const SYD: [number, number] = [-33.8688, 151.2093];
const LHR: [number, number] = [51.5074, -0.1278];

function Demo() {
  return (
    <Box pos="relative" h={500} style={{ borderRadius: 'var(--mantine-radius-md)', overflow: 'hidden', background: 'var(--mantine-color-dark-9)' }}>
      <Scene lazy lazyThreshold={0.1}>
        <Scene.Globe
          size={400}
          markers={[ROM, NYC, TYO, SFO, SYD, LHR].map((location) => ({ location, size: 0.06 }))}
          arcs={[
            { from: ROM, to: NYC },
            { from: NYC, to: SFO },
            { from: SFO, to: TYO },
            { from: TYO, to: SYD },
            { from: LHR, to: ROM },
            { from: LHR, to: NYC },
          ]}
          arcColor="cyan.4"
          arcHeight={0.35}
          arcWidth={0.6}
          markerColor="orange.5"
        />
      </Scene>
    </Box>
  );
}

Combined

All sub-components can be composed freely. Layer order follows DOM order.

All effects combined

StarField + Gradient + DotGrid + Mesh + Glow + ShootingStar + Noise

import { Box, Text } from '@mantine/core';
import { Scene } from '@gfazioli/mantine-scene';

function Demo() {
  return (
    <Box pos="relative" h={400} style={{ borderRadius: 'var(--mantine-radius-md)', overflow: 'hidden', background: 'var(--mantine-color-body)' }}>
      <Scene lazy lazyThreshold={0.1}>
        <Scene.StarField count={80} twinkle duration={4} opacity={0.6} />
        <Scene.Gradient from="violet" fromOpacity={0.12} />
        <Scene.DotGrid color="gray" opacity={0.15} spacing={28} fade="edges" />
        <Scene.Mesh
          stops={[
            { color: 'blue', position: '15% 20%', spread: 45 },
            { color: 'teal', position: '85% 40%', spread: 40 },
            { color: 'violet', position: '50% 80%', spread: 50 },
          ]}
          opacity={0.3}
        />
        <Scene.Glow color="blue" size={500} opacity={0.2} top="20%" left="30%" duration={10} />
        <Scene.Glow color="teal" size={400} opacity={0.15} top="60%" left="70%" duration={14} />
        <Scene.ShootingStar count={2} opacity={0.4} />
        <Scene.Noise opacity={0.03} />
      </Scene>
      <Box pos="relative" style={{ zIndex: 1 }} p="xl">
        <Text size="xl" fw={700} c="white">All effects combined</Text>
        <Text c="dimmed" mt="sm">StarField + Gradient + DotGrid + Mesh + Glow + ShootingStar + Noise</Text>
      </Box>
    </Box>
  );
}