awesome-galaxy-orbit-carousel
v0.1.0
Published
A stunning galaxy-themed 3D orbital carousel React component with elliptical orbit rings, gravitational pull animations, per-slide accent colours, and a sci-fi core.
Maintainers
Readme
awesome-galaxy-orbit-carousel
A stunning galaxy-themed 3D orbital carousel React component. Slides orbit the central core on tilted elliptical rings with Kepler-inspired depth scaling, gravitational pull focus animations, a pulsing sci-fi core, starfield background, and full keyboard navigation — zero external dependencies beyond React.
Features
- Multi-ring elliptical orbits — slides distributed across 1–4 rings, auto-computed from slide count or fully customisable
- Kepler-inspired depth — slides closer to the viewer scale up and brighten; distant slides fade back
- Gravitational pull animation — clicking or auto-advancing a slide triggers a visual "pull" before focus locks
- Animated galaxy core — pulsing orb at center updates colour to match the focused slide
- Orbit path rings — decorative ellipse outlines with per-ring accent tints
- Starfield + nebula background — 120 blinking stars and radial nebula clouds
- Autoplay with configurable interval — pauses on hover automatically
- Keyboard navigation —
←/→to cycle slides,Enterto fireonClick - Planet cards — each slide renders as a card with image, tag badge, title, description, and an Explore CTA on focus
- ESM + CJS dual build — works in Vite, Next.js, CRA, and any modern bundler
- Full TypeScript types included
Installation
npm install awesome-galaxy-orbit-carousel
# or
yarn add awesome-galaxy-orbit-carousel
# or
pnpm add awesome-galaxy-orbit-carouselPeer dependencies — React ≥ 17 and ReactDOM ≥ 17 must already be installed.
Importing Styles
The component relies on keyframe animations (pulseGlow, coreSpin, corePulse, ringPulse, fadeUp) and CSS custom properties (--font-display, --font-body) declared in a separate stylesheet. Import it once at your app root:
import 'awesome-galaxy-orbit-carousel/style.css';For the best visual result, load these Google Fonts in your index.html <head>:
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Syne:wght@700;800;900&family=DM+Sans:wght@300;400;500&display=swap"
rel="stylesheet"
/>The component still renders correctly without the fonts — it falls back to
sans-serif.
Quick Start
import { AwesomeGalaxyOrbitCarousel } from 'awesome-galaxy-orbit-carousel';
import 'awesome-galaxy-orbit-carousel/style.css';
const slides = [
{ title: 'Aurora', description: 'Northern lights over the tundra.', color: '#020d2e', tag: 'Nature' },
{ title: 'Nebula', description: 'Star-forming clouds in deep space.', color: '#120a2e', tag: 'Space' },
{ title: 'Coral', description: 'Vibrant reef ecosystems underwater.', color: '#0d1f0a', tag: 'Ocean' },
{ title: 'Volcano', description: 'Raw power erupting from the Earth.', color: '#1a0a0a', tag: 'Earth' },
{ title: 'Glacier', description: 'Ancient ice slowly carving valleys.', color: '#0a1e2e', tag: 'Arctic' },
];
export default function App() {
return (
<div style={{ width: '100vw', height: '100vh' }}>
<AwesomeGalaxyOrbitCarousel data={slides} />
</div>
);
}Props
| Prop | Type | Default | Description |
|---|---|---|---|
| data | OrbitSlide[] | required | Array of slide objects. See OrbitSlide below. Minimum 2 slides recommended. |
| rings | OrbitRingConfig[] | auto | Ring layout config. Auto-computed from slide count if omitted. |
| intervalSeconds | number | 4 | Seconds between auto-focus advances. |
| autoRotate | boolean | true | Whether slides auto-advance and the rings continuously rotate. |
| onFocusChange | (index: number) => void | — | Fired after each focus transition completes with the new 0-based index. |
| width | number \| string | '100%' | CSS width of the component container. |
| height | number \| string | '100%' | CSS height of the component container. |
| className | string | — | Optional CSS class applied to the root container. |
OrbitSlide object
| Field | Type | Required | Description |
|---|---|---|---|
| title | string | ✅ | Main heading on the planet card. Also used as the two-letter placeholder when no image is provided. |
| description | string | ➖ | Short body text shown on the card. |
| image | string | ➖ | URL of a cover image displayed at the top of the card. |
| tag | string | ➖ | Small badge shown top-right of the card image area. |
| color | string | ➖ | Hex background colour of the card (e.g. '#020d2e'). Maps to a vibrant accent used for glows and borders. |
| onClick | () => void | ➖ | Fired when the focused card's Explore → button is clicked, or when Enter is pressed while the slide is focused. |
OrbitRingConfig object
Only needed if you want to override the automatic ring layout.
| Field | Type | Required | Description |
|---|---|---|---|
| count | number | ✅ | Number of slides to place on this ring. |
| radiusX | number | ✅ | Semi-major axis in px (horizontal spread). |
| radiusY | number | ✅ | Semi-minor axis in px (vertical spread — smaller = more elliptical). |
| period | number | ✅ | Orbital period in seconds. Negative value reverses direction. |
| phaseOffset | number | ➖ | Starting angle offset in radians. Staggers ring start positions. |
| tilt | number | ➖ | Tilt of the orbital plane in degrees — creates the 3D inclined look. |
Examples
Example 1 — Minimal, no images
import { AwesomeGalaxyOrbitCarousel } from 'awesome-galaxy-orbit-carousel';
import 'awesome-galaxy-orbit-carousel/style.css';
var slides = [
{ title: 'Mercury', description: 'Closest planet to the Sun.', color: '#1a1a1a', tag: 'Planet' },
{ title: 'Venus', description: 'Hottest planet in the solar system.', color: '#1a1200', tag: 'Planet' },
{ title: 'Earth', description: 'Our pale blue dot.', color: '#0a1628', tag: 'Planet' },
{ title: 'Mars', description: 'The red, dusty frontier.', color: '#1a0a0a', tag: 'Planet' },
{ title: 'Jupiter', description: 'King of the gas giants.', color: '#120f0a', tag: 'Planet' },
{ title: 'Saturn', description: 'Famous for its ring system.', color: '#141008', tag: 'Planet' },
];
export default function PlanetsDemo() {
return (
<div style={{ width: '100vw', height: '100vh' }}>
<AwesomeGalaxyOrbitCarousel data={slides} />
</div>
);
}Example 2 — Full featured with images, callbacks, and custom size
import { useState } from 'react';
import { AwesomeGalaxyOrbitCarousel } from 'awesome-galaxy-orbit-carousel';
import 'awesome-galaxy-orbit-carousel/style.css';
var slides = [
{
title: 'Arctic',
description: 'Vast frozen tundra lit only by the polar glow.',
tag: 'Explore',
color: '#020d2e',
image: 'https://images.unsplash.com/photo-1517411032315-54ef2cb783bb?w=600',
onClick: function() { window.open('https://example.com/arctic', '_blank'); },
},
{
title: 'Desert',
description: 'Golden sand sculpted by the wind into endless dunes.',
tag: 'Journey',
color: '#1a0f00',
image: 'https://images.unsplash.com/photo-1509316785289-025f5b846b35?w=600',
onClick: function() { window.open('https://example.com/desert', '_blank'); },
},
{
title: 'Jungle',
description: 'A cathedral of ancient trees alive with unseen creatures.',
tag: 'Wildlife',
color: '#0d1f0a',
image: 'https://images.unsplash.com/photo-1448375240586-882707db888b?w=600',
onClick: function() { window.open('https://example.com/jungle', '_blank'); },
},
{
title: 'Abyss',
description: "The deep ocean floor — Earth's last frontier.",
tag: 'Discovery',
color: '#001820',
image: 'https://images.unsplash.com/photo-1518020382113-a7e8fc38eac9?w=600',
onClick: function() { window.open('https://example.com/abyss', '_blank'); },
},
{
title: 'Summit',
description: 'Where the air thins and the world lies below.',
tag: 'Adventure',
color: '#0f0f18',
image: 'https://images.unsplash.com/photo-1464822759023-fed622ff2c3b?w=600',
onClick: function() { window.open('https://example.com/summit', '_blank'); },
},
];
export default function FullFeaturedDemo() {
var [activeIndex, setActiveIndex] = useState(0);
return (
<div style={{ position: 'relative', width: '100vw', height: '100vh' }}>
{/* Slide counter overlay */}
<div style={{
position: 'absolute', top: 20, right: 24, zIndex: 100,
color: '#00f5ff', fontFamily: 'monospace', fontSize: '13px',
background: 'rgba(0,0,0,0.4)', padding: '6px 14px', borderRadius: '20px',
border: '1px solid rgba(0,245,255,0.2)',
}}>
{activeIndex + 1} / {slides.length} — {slides[activeIndex].title}
</div>
<AwesomeGalaxyOrbitCarousel
data={slides}
intervalSeconds={5}
autoRotate={true}
width="100%"
height="100%"
onFocusChange={function(index) { setActiveIndex(index); }}
/>
</div>
);
}Example 3 — Custom ring layout (autoRotate={false} + manual rings)
Override the automatic ring layout and disable autoplay to drive navigation with your own buttons.
import { useState } from 'react';
import { AwesomeGalaxyOrbitCarousel } from 'awesome-galaxy-orbit-carousel';
import 'awesome-galaxy-orbit-carousel/style.css';
var slides = [
{ title: 'Hydrogen', description: 'The most abundant element.', color: '#080022', tag: '#1' },
{ title: 'Helium', description: 'Noble gas, second lightest.', color: '#020d2e', tag: '#2' },
{ title: 'Carbon', description: 'Basis of all organic life.', color: '#0d1f0a', tag: '#6' },
{ title: 'Oxygen', description: 'Essential for respiration.', color: '#001820', tag: '#8' },
{ title: 'Iron', description: 'Core of the Earth itself.', color: '#1a0a0a', tag: '#26' },
{ title: 'Gold', description: 'Prized across all cultures.', color: '#1a1200', tag: '#79' },
{ title: 'Uranium', description: 'Heaviest natural element.', color: '#0a1e2e', tag: '#92' },
];
// Two custom rings: 3 slides on inner, 4 on outer
var customRings = [
{ count: 3, radiusX: 200, radiusY: 85, period: 10, phaseOffset: 0, tilt: 14 },
{ count: 4, radiusX: 340, radiusY: 130, period: 18, phaseOffset: Math.PI / 4, tilt: 22 },
];
var btnStyle = {
padding: '10px 28px',
background: 'rgba(0,245,255,0.08)',
border: '1px solid rgba(0,245,255,0.3)',
color: '#00f5ff',
borderRadius: '100px',
cursor: 'pointer',
fontFamily: 'sans-serif',
fontSize: '13px',
letterSpacing: '0.08em',
};
export default function CustomRingsDemo() {
var [focused, setFocused] = useState(0);
var total = slides.length;
function prev() {
setFocused(function(i) { return (i - 1 + total) % total; });
}
function next() {
setFocused(function(i) { return (i + 1) % total; });
}
return (
<div style={{ position: 'relative', width: '100vw', height: '100vh' }}>
<AwesomeGalaxyOrbitCarousel
key={focused}
data={slides}
rings={customRings}
autoRotate={false}
width="100%"
height="100%"
onFocusChange={setFocused}
/>
{/* Navigation buttons */}
<div style={{
position: 'absolute', bottom: 40, left: '50%',
transform: 'translateX(-50%)',
display: 'flex', gap: '16px', zIndex: 100,
}}>
<button style={btnStyle} onClick={prev}>← Prev</button>
<button style={btnStyle} onClick={next}>Next →</button>
</div>
{/* Focused slide label */}
<div style={{
position: 'absolute', top: 24, left: '50%',
transform: 'translateX(-50%)',
color: '#fff', fontFamily: 'sans-serif', fontSize: '14px',
background: 'rgba(0,0,0,0.5)', padding: '6px 18px', borderRadius: '20px',
border: '1px solid rgba(255,255,255,0.12)',
whiteSpace: 'nowrap', zIndex: 100,
}}>
Focused: {slides[focused].title}
</div>
</div>
);
}CSS Custom Properties
Override fonts in your own :root:
:root {
--font-display: 'Your Display Font', sans-serif;
--font-body: 'Your Body Font', sans-serif;
}Default values are 'Syne' (display, weights 700–900) and 'DM Sans' (body, weights 300–500).
Keyboard Controls
| Key | Action |
|---|---|
| → | Focus next slide |
| ← | Focus previous slide |
| Enter | Fire onClick of the currently focused slide |
License
MIT
