@manufosela/constellation-sky
A Lit 3 web component for animated star constellations with connecting lines using Canvas API
Demo code (CodePen-ready HTML, CSS, JS)
HTML (html)
<!-- Hero Section -->
<section class="hero">
<constellation-sky
id="hero-sky"
star-count="120"
line-distance="150"
speed="0.2"
interactive
></constellation-sky>
<div class="hero-content">
<h1>Constellation Sky</h1>
<p>Interactive animated star constellations for the web</p>
<p style="color: #666; font-size: 0.9rem;">Move your mouse to interact with the stars</p>
</div>
<div class="scroll-hint">Scroll down for more demos</div>
</section>
<!-- Demo Sections -->
<div class="demo-sections">
<!-- Theme Variations -->
<section class="demo-section">
<h2>Theme Variations</h2>
<p>Different background themes using CSS custom properties</p>
<div class="demo-grid">
<div class="demo-card">
<h3>Ocean Theme</h3>
<constellation-sky
class="theme-ocean"
star-count="60"
star-color="#87ceeb"
line-color="#4da6ff"
line-distance="100"
></constellation-sky>
</div>
<div class="demo-card">
<h3>Warm Theme</h3>
<constellation-sky
class="theme-warm"
star-count="70"
star-color="#ffcc66"
line-color="#ff9933"
line-distance="90"
></constellation-sky>
</div>
<div class="demo-card">
<h3>Forest Theme</h3>
<constellation-sky
class="theme-forest"
star-count="50"
star-color="#90ee90"
line-color="#32cd32"
line-distance="80"
></constellation-sky>
</div>
<div class="demo-card">
<h3>Sunset Theme</h3>
<constellation-sky
class="theme-sunset"
star-count="80"
star-color="#ff69b4"
line-color="#ff1493"
line-distance="110"
></constellation-sky>
</div>
</div>
</section>
<!-- Configuration Demo -->
<section class="demo-section">
<h2>Live Configuration</h2>
<p>Adjust all parameters in real-time</p>
<div class="demo-card" style="max-width: 800px; margin: 0 auto;">
<constellation-sky
id="configurable"
star-count="80"
line-distance="120"
speed="0.3"
interactive
></constellation-sky>
<div class="controls-panel">
<div class="control-group">
<label>Stars: <span id="star-value">80</span></label>
<input type="range" id="star-range" min="20" max="200" value="80">
</div>
<div class="control-group">
<label>Line Distance: <span id="line-value">120</span></label>
<input type="range" id="line-range" min="50" max="250" value="120">
</div>
<div class="control-group">
<label>Speed: <span id="speed-value">0.3</span></label>
<input type="range" id="speed-range" min="0" max="1" step="0.1" value="0.3">
</div>
<div class="control-group">
<label>Star Color</label>
<input type="color" id="star-color" value="#ffffff">
</div>
<div class="control-group">
<label>Line Color</label>
<input type="color" id="line-color" value="#ffffff">
</div>
<button id="toggle-lines">Toggle Lines</button>
<button id="toggle-pause">Pause</button>
<button id="reset-btn">Reset</button>
</div>
</div>
</section>
<!-- Dense vs Sparse -->
<section class="demo-section">
<h2>Density Comparison</h2>
<p>Different star counts and line distances</p>
<div class="demo-grid">
<div class="demo-card">
<h3>Sparse (30 stars)</h3>
<constellation-sky
star-count="30"
line-distance="200"
speed="0.2"
></constellation-sky>
</div>
<div class="demo-card">
<h3>Dense (150 stars)</h3>
<constellation-sky
star-count="150"
line-distance="80"
speed="0.4"
></constellation-sky>
</div>
</div>
</section>
<!-- Stars Only -->
<section class="demo-section">
<h2>Stars Only Mode</h2>
<p>No connecting lines - pure starfield</p>
<div class="demo-card" style="max-width: 800px; margin: 0 auto;">
<constellation-sky
star-count="200"
speed="0.1"
show-lines="false"
interactive="false"
style="--constellation-height: 400px;"
></constellation-sky>
</div>
</section>
</div> CSS (css)
:root {
--bg: #0c0f14;
--bg-elevated: #141923;
--bg-panel: #171d28;
--border: #262f3f;
--text: #f4f6fb;
--text-muted: #a7b0c2;
--text-dim: #7d879b;
--accent: #ff8a3d;
--accent-strong: #ff6a00;
--accent-soft: rgba(255, 138, 61, 0.16);
--shadow: 0 20px 50px rgba(5, 8, 14, 0.45);
--radius-lg: 22px;
--radius-md: 14px;
--radius-sm: 10px;
--max-width: 1160px;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
h1, h2 {
text-align: center;
}
.hero {
position: relative;
height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.hero constellation-sky {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.hero-content {
position: relative;
z-index: 2;
text-align: center;
padding: 20px;
}
.hero h1 {
font-size: 3rem;
margin-bottom: 1rem;
text-shadow: 0 0 20px rgba(255, 255, 255, 0.3);
}
.hero p {
font-size: 1.2rem;
color: var(--text-muted);
margin-bottom: 2rem;
}
.demo-sections {
padding: 40px 20px;
max-width: 1400px;
margin: 0 auto;
}
.demo-section {
margin-bottom: 60px;
}
.demo-section h2 {
margin-bottom: 10px;
color: #4ecdc4;
}
.demo-section p {
text-align: center;
color: #888;
margin-bottom: 20px;
}
.demo-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
gap: 30px;
}
.demo-card {
background: rgba(255, 255, 255, 0.03);
border-radius: 16px;
overflow: hidden;
border: 1px solid rgba(255, 255, 255, 0.1);
}
.demo-card h3 {
padding: 15px 20px;
background: rgba(255, 255, 255, 0.05);
margin: 0;
font-size: 1rem;
}
.demo-card constellation-sky {
display: block;
height: 300px;
}
.controls-panel {
padding: 20px;
background: rgba(0, 0, 0, 0.3);
display: flex;
flex-wrap: wrap;
gap: 15px;
align-items: center;
justify-content: center;
}
.control-group {
display: flex;
flex-direction: column;
gap: 5px;
}
.control-group label {
font-size: 0.8rem;
color: #888;
}
.control-group input[type="range"] {
width: 120px;
}
.control-group input[type="color"] {
width: 60px;
height: 30px;
border: none;
border-radius: 4px;
cursor: pointer;
}
button {
padding: 10px 20px;
background: rgba(78, 205, 196, 0.2);
border: 1px solid #4ecdc4;
color: #4ecdc4;
border-radius: 6px;
cursor: pointer;
font-size: 0.9rem;
transition: all 0.2s;
}
button:hover {
background: rgba(78, 205, 196, 0.3);
}
button.active {
background: #4ecdc4;
color: #000;
}
.scroll-hint {
position: absolute;
bottom: 30px;
left: 50%;
transform: translateX(-50%);
color: var(--text-muted);
font-size: 0.9rem;
animation: bounce 2s infinite;
}
@keyframes bounce {
0%, 20%, 50%, 80%, 100% {
transform: translateX(-50%) translateY(0);
}
40% {
transform: translateX(-50%) translateY(-10px);
}
60% {
transform: translateX(-50%) translateY(-5px);
}
}
/* Custom theme examples */
.theme-warm {
--constellation-background: linear-gradient(to bottom, #1a0a00 0%, #3d1a00 50%, #2d1000 100%);
}
.theme-ocean {
--constellation-background: linear-gradient(to bottom, #000428 0%, #004e92 100%);
}
.theme-forest {
--constellation-background: linear-gradient(to bottom, #0a1a0a 0%, #1a3a1a 50%, #0d2d0d 100%);
}
.theme-sunset {
--constellation-background: linear-gradient(to bottom, #1a0a1a 0%, #4a1a4a 30%, #7a3a3a 70%, #3a1a1a 100%);
} JS (js)
import "https://esm.sh/@manufosela/constellation-sky";
// Configurable demo controls
const configurable = document.getElementById('configurable');
// Star count
document.getElementById('star-range').addEventListener('input', (e) => {
configurable.starCount = parseInt(e.target.value);
document.getElementById('star-value').textContent = e.target.value;
});
// Line distance
document.getElementById('line-range').addEventListener('input', (e) => {
configurable.lineDistance = parseInt(e.target.value);
document.getElementById('line-value').textContent = e.target.value;
});
// Speed
document.getElementById('speed-range').addEventListener('input', (e) => {
configurable.speed = parseFloat(e.target.value);
document.getElementById('speed-value').textContent = e.target.value;
});
// Star color
document.getElementById('star-color').addEventListener('input', (e) => {
configurable.starColor = e.target.value;
});
// Line color
document.getElementById('line-color').addEventListener('input', (e) => {
configurable.lineColor = e.target.value;
});
// Toggle lines
document.getElementById('toggle-lines').addEventListener('click', () => {
configurable.showLines = !configurable.showLines;
});
// Toggle pause
const pauseBtn = document.getElementById('toggle-pause');
pauseBtn.addEventListener('click', () => {
if (configurable.paused) {
configurable.resume();
pauseBtn.textContent = 'Pause';
pauseBtn.classList.remove('active');
} else {
configurable.pause();
pauseBtn.textContent = 'Resume';
pauseBtn.classList.add('active');
}
});
// Reset
document.getElementById('reset-btn').addEventListener('click', () => {
configurable.reset();
});