More Cards Snippets
Glowing Stars Card — Free HTML CSS JS Constellation Snippet
Glowing Stars Card · Cards · Plain HTML, CSS & JS · Live preview
What's included
Features
About this UI Snippet
Glowing Stars Card — A Constellation That Wakes on Hover

The glowing stars card hides a night sky in plain sight: at rest it shows a faint grid of dim dots, but hovering brings a scattered constellation to life — selected stars brighten, scale up, glow, and twinkle, while the field slowly reshuffles which stars are lit so the sky always feels alive. This snippet builds it with plain HTML, CSS, and a little vanilla JavaScript.
A grid of cells, some marked as stars
JavaScript builds an 18 × 14 grid of cells, each containing a tiny 2px dot. As each cell is created, there's a 16% chance it's flagged with an .on class, designating it a star. The rest stay as the dim background dots that give the sky texture. Generating the field in code keeps the markup minimal and makes the density a single tweakable probability.
Brighten and twinkle on hover
By default even the .on stars are dim. When the card is hovered, a CSS rule turns those stars white, adds a glowing box-shadow halo, and scales them up 1.6× — so the constellation pops out of the background. Each star also carries an infinite gsTwinkle keyframe (fading between full and 35% opacity) that is paused at rest and set to running only on hover. Pausing rather than removing the animation means each star resumes its own twinkle phase instantly when you hover, with no restart flicker.
Per-star twinkle speed
Every star gets a random --tw duration between 1.4 and 3.4 seconds, used as its twinkle animation length. Because each star pulses at its own rate, the constellation shimmers asynchronously instead of blinking in unison — the key to a believable starfield. The speed lives in a CSS variable so the shared keyframe can animate every star at a different tempo.
A drifting sky
To keep the card alive even while you hold a hover, a setInterval every 900ms toggles the .on state of one random cell, occasionally promoting a background dot to a star or retiring one, and assigns a fresh twinkle speed when it lights up. Over time this makes the constellation slowly rearrange itself, so the sky is never static and two views of the card are never identical.
Readable content over the sky
The heading and description sit in a .gs-body layer above the stars, with a bottom-up gradient scrim so the text stays legible against the brightest part of the field. The glyph badge and copy are unaffected by the star animation, keeping the card usable as a real feature or product tile.
Customizing it
Change COLS/ROWS for a finer or coarser sky, adjust the 16% probability for more or fewer stars, tune the twinkle duration range, change the hover glow color, or slow the 900ms reshuffle interval. Make the stars colored instead of white for a nebula feel. Pair it with a meteor card or a sparkles text headline for a cosmic section.
Step by step
How to Use
- 1Paste HTML, CSS, and JSA card shows a faint field of dim dots at rest.
- 2Hover the cardScattered stars brighten, glow, scale up, and twinkle.
- 3Hold the hoverStars pulse asynchronously at their own speeds.
- 4Watch it driftThe constellation slowly reshuffles over time.
- 5Change the densityAdjust the star probability and grid size.
- 6Recolor the glowSwap white stars for a colored nebula look.
Real-world uses
Common Use Cases
Got questions?
Frequently Asked Questions
JavaScript builds an 18 by 14 grid of cells, each holding a tiny dot, and flags about 16% of them with an on class as it creates them. Those become stars; the rest stay as dim background dots. The density is a single probability, so you can make the sky sparser or denser with one number.
Each star carries an infinite twinkle keyframe that is paused at rest and set to running only on hover. Pausing keeps each star's animation phase intact, so when you hover they resume their own pulsing instantly with no restart flicker — which removing and re-adding the animation would cause.
Every star gets a random twinkle duration between 1.4 and 3.4 seconds stored in a --tw CSS variable. Because each star pulses at its own tempo, they fade in and out asynchronously rather than blinking in unison, which is what makes a starfield look real instead of mechanical.
A setInterval every 900ms toggles the on state of one random cell, occasionally promoting a background dot to a star or retiring one and giving it a fresh twinkle speed. Over time the constellation slowly rearranges, so the sky never looks static even during a sustained hover.
Render the grid from an array, precomputing which cells are stars and their twinkle speeds so renders are stable. Put the reshuffle interval in a mount effect with cleanup on unmount. The hover brighten and twinkle are pure CSS. In Tailwind, build the grid with grid utilities and drive the per-star duration via an inline --tw variable.
Glowing Stars Card — Export as HTML, React, Vue, Angular & Tailwind
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Glowing Stars Card</title>
<style>
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:system-ui,-apple-system,sans-serif;background:#05050d;color:#fff;display:flex;justify-content:center;align-items:center;min-height:100vh;padding:24px}
.gs-card{position:relative;width:300px;height:340px;border-radius:20px;background:linear-gradient(180deg,#0c0c1c,#06060f);border:1px solid #1d1d33;overflow:hidden;cursor:pointer}
.gs-sky{position:absolute;inset:0;display:grid;padding:14px}
.gs-star{position:relative;display:flex;align-items:center;justify-content:center}
.gs-star i{width:2px;height:2px;border-radius:50%;background:#3a3a55;transition:background .4s,box-shadow .4s,transform .4s}
.gs-card:hover .gs-star.on i{background:#fff;box-shadow:0 0 6px 1px rgba(255,255,255,.8);transform:scale(1.6)}
.gs-star.on i{animation:gsTwinkle var(--tw) ease-in-out infinite;animation-play-state:paused}
.gs-card:hover .gs-star.on i{animation-play-state:running}
@keyframes gsTwinkle{0%,100%{opacity:1}50%{opacity:.35}}
.gs-body{position:absolute;inset:0;z-index:1;display:flex;flex-direction:column;justify-content:flex-end;padding:24px;background:linear-gradient(0deg,rgba(6,6,15,.85),transparent 55%)}
.gs-glyph{width:46px;height:46px;border-radius:12px;background:rgba(129,140,248,.16);border:1px solid rgba(129,140,248,.4);display:flex;align-items:center;justify-content:center;font-size:22px;color:#c7d2fe;margin-bottom:14px}
.gs-body h3{font-size:21px;font-weight:800}
.gs-body p{font-size:13px;color:#9a9ab8;line-height:1.55;margin-top:6px}
</style>
</head>
<body>
<div class="gs-stage">
<article class="gs-card" id="gsCard">
<div class="gs-sky" id="gsSky" aria-hidden="true"></div>
<div class="gs-body">
<div class="gs-glyph">✦</div>
<h3>Reach the stars</h3>
<p>Hover the card — the constellation brightens and twinkles to life.</p>
</div>
</article>
</div>
<script>
var sky = document.getElementById('gsSky');
var COLS = 18, ROWS = 14;
sky.style.gridTemplateColumns = 'repeat(' + COLS + ',1fr)';
sky.style.gridTemplateRows = 'repeat(' + ROWS + ',1fr)';
// Build a field of cells; mark ~16% of them as "stars" that light on hover.
for (var i = 0; i < COLS * ROWS; i++) {
var cell = document.createElement('div');
cell.className = 'gs-star';
if (Math.random() < 0.16) {
cell.classList.add('on');
cell.style.setProperty('--tw', (1.4 + Math.random() * 2).toFixed(2) + 's');
cell.firstChild; // noop
}
cell.innerHTML = '<i></i>';
sky.appendChild(cell);
}
// Periodically reshuffle which stars are lit so the constellation drifts.
var stars = Array.prototype.slice.call(sky.querySelectorAll('.gs-star'));
setInterval(function () {
// turn a few on/off to make the sky feel alive even while hovering
var pick = stars[Math.floor(Math.random() * stars.length)];
pick.classList.toggle('on');
if (pick.classList.contains('on')) {
pick.style.setProperty('--tw', (1.4 + Math.random() * 2).toFixed(2) + 's');
}
}, 900);
</script>
</body>
</html><!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Glowing Stars Card</title>
<!-- Tailwind CSS v3+ — arbitrary value classes (e.g. bg-[#0f172a]) are valid -->
<script src="https://cdn.tailwindcss.com"></script>
<style>
@keyframes gsTwinkle{0%,100%{opacity:1}50%{opacity:.35}}
* {
box-sizing:border-box;margin:0;padding:0
}
body {
font-family:system-ui,-apple-system,sans-serif;background:#05050d;color:#fff;display:flex;justify-content:center;align-items:center;min-height:100vh;padding:24px
}
.gs-star {
position:relative;display:flex;align-items:center;justify-content:center
}
.gs-star i {
width:2px;height:2px;border-radius:50%;background:#3a3a55;transition:background .4s,box-shadow .4s,transform .4s
}
.gs-card:hover .gs-star.on i {
background:#fff;box-shadow:0 0 6px 1px rgba(255,255,255,.8);transform:scale(1.6)
}
.gs-star.on i {
animation:gsTwinkle var(--tw) ease-in-out infinite;animation-play-state:paused
}
.gs-card:hover .gs-star.on i {
animation-play-state:running
}
.gs-body h3 {
font-size:21px;font-weight:800
}
.gs-body p {
font-size:13px;color:#9a9ab8;line-height:1.55;margin-top:6px
}
</style>
</head>
<body>
<div class="gs-stage">
<article class="gs-card relative w-[300px] h-[340px] rounded-[20px] [background:linear-gradient(180deg,#0c0c1c,#06060f)] border border-[#1d1d33] overflow-hidden cursor-pointer" id="gsCard">
<div class="absolute inset-0 grid p-3.5" id="gsSky" aria-hidden="true"></div>
<div class="gs-body absolute inset-0 z-[1] flex flex-col justify-end p-6 [background:linear-gradient(0deg,rgba(6,6,15,.85),transparent_55%)]">
<div class="w-[46px] h-[46px] rounded-xl bg-[rgba(129,140,248,.16)] border border-[rgba(129,140,248,.4)] flex items-center justify-center text-[22px] text-[#c7d2fe] mb-3.5">✦</div>
<h3>Reach the stars</h3>
<p>Hover the card — the constellation brightens and twinkles to life.</p>
</div>
</article>
</div>
<script>
var sky = document.getElementById('gsSky');
var COLS = 18, ROWS = 14;
sky.style.gridTemplateColumns = 'repeat(' + COLS + ',1fr)';
sky.style.gridTemplateRows = 'repeat(' + ROWS + ',1fr)';
// Build a field of cells; mark ~16% of them as "stars" that light on hover.
for (var i = 0; i < COLS * ROWS; i++) {
var cell = document.createElement('div');
cell.className = 'gs-star';
if (Math.random() < 0.16) {
cell.classList.add('on');
cell.style.setProperty('--tw', (1.4 + Math.random() * 2).toFixed(2) + 's');
cell.firstChild; // noop
}
cell.innerHTML = '<i></i>';
sky.appendChild(cell);
}
// Periodically reshuffle which stars are lit so the constellation drifts.
var stars = Array.prototype.slice.call(sky.querySelectorAll('.gs-star'));
setInterval(function () {
// turn a few on/off to make the sky feel alive even while hovering
var pick = stars[Math.floor(Math.random() * stars.length)];
pick.classList.toggle('on');
if (pick.classList.contains('on')) {
pick.style.setProperty('--tw', (1.4 + Math.random() * 2).toFixed(2) + 's');
}
}, 900);
</script>
</body>
</html>import React, { useEffect } from 'react';
// CSS — optionally move to GlowingStarsCard.module.css
const css = `
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:system-ui,-apple-system,sans-serif;background:#05050d;color:#fff;display:flex;justify-content:center;align-items:center;min-height:100vh;padding:24px}
.gs-card{position:relative;width:300px;height:340px;border-radius:20px;background:linear-gradient(180deg,#0c0c1c,#06060f);border:1px solid #1d1d33;overflow:hidden;cursor:pointer}
.gs-sky{position:absolute;inset:0;display:grid;padding:14px}
.gs-star{position:relative;display:flex;align-items:center;justify-content:center}
.gs-star i{width:2px;height:2px;border-radius:50%;background:#3a3a55;transition:background .4s,box-shadow .4s,transform .4s}
.gs-card:hover .gs-star.on i{background:#fff;box-shadow:0 0 6px 1px rgba(255,255,255,.8);transform:scale(1.6)}
.gs-star.on i{animation:gsTwinkle var(--tw) ease-in-out infinite;animation-play-state:paused}
.gs-card:hover .gs-star.on i{animation-play-state:running}
@keyframes gsTwinkle{0%,100%{opacity:1}50%{opacity:.35}}
.gs-body{position:absolute;inset:0;z-index:1;display:flex;flex-direction:column;justify-content:flex-end;padding:24px;background:linear-gradient(0deg,rgba(6,6,15,.85),transparent 55%)}
.gs-glyph{width:46px;height:46px;border-radius:12px;background:rgba(129,140,248,.16);border:1px solid rgba(129,140,248,.4);display:flex;align-items:center;justify-content:center;font-size:22px;color:#c7d2fe;margin-bottom:14px}
.gs-body h3{font-size:21px;font-weight:800}
.gs-body p{font-size:13px;color:#9a9ab8;line-height:1.55;margin-top:6px}
`;
export default function GlowingStarsCard() {
// Auto-generated escape hatch: the original snippet's vanilla JS runs once
// after mount and queries the rendered DOM. For idiomatic React, lift this
// into state + handlers.
useEffect(() => {
const _listeners = [];
const _originalAddEventListener = EventTarget.prototype.addEventListener;
EventTarget.prototype.addEventListener = function(type, listener, options) {
_listeners.push({ target: this, type, listener, options });
return _originalAddEventListener.call(this, type, listener, options);
};
var sky = document.getElementById('gsSky');
var COLS = 18, ROWS = 14;
sky.style.gridTemplateColumns = 'repeat(' + COLS + ',1fr)';
sky.style.gridTemplateRows = 'repeat(' + ROWS + ',1fr)';
// Build a field of cells; mark ~16% of them as "stars" that light on hover.
for (var i = 0; i < COLS * ROWS; i++) {
var cell = document.createElement('div');
cell.className = 'gs-star';
if (Math.random() < 0.16) {
cell.classList.add('on');
cell.style.setProperty('--tw', (1.4 + Math.random() * 2).toFixed(2) + 's');
cell.firstChild; // noop
}
cell.innerHTML = '<i></i>';
sky.appendChild(cell);
}
// Periodically reshuffle which stars are lit so the constellation drifts.
var stars = Array.prototype.slice.call(sky.querySelectorAll('.gs-star'));
setInterval(function () {
// turn a few on/off to make the sky feel alive even while hovering
var pick = stars[Math.floor(Math.random() * stars.length)];
pick.classList.toggle('on');
if (pick.classList.contains('on')) {
pick.style.setProperty('--tw', (1.4 + Math.random() * 2).toFixed(2) + 's');
}
}, 900);
// Cleanup: restore addEventListener and remove all listeners
return () => {
EventTarget.prototype.addEventListener = _originalAddEventListener;
for (const { target, type, listener, options } of _listeners) {
target.removeEventListener(type, listener, options);
}
};
}, []);
return (
<>
<style>{css}</style>
<div className="gs-stage">
<article className="gs-card" id="gsCard">
<div className="gs-sky" id="gsSky" aria-hidden="true"></div>
<div className="gs-body">
<div className="gs-glyph">✦</div>
<h3>Reach the stars</h3>
<p>Hover the card — the constellation brightens and twinkles to life.</p>
</div>
</article>
</div>
</>
);
}import React, { useEffect } from 'react';
// Requires Tailwind CSS v3+ — https://tailwindcss.com/docs/installation
// Arbitrary value classes (e.g. bg-[#0f172a]) are valid Tailwind v3+
export default function GlowingStarsCard() {
// Auto-generated escape hatch: the original snippet's vanilla JS runs once
// after mount and queries the rendered DOM. For idiomatic React, lift this
// into state + handlers.
useEffect(() => {
const _listeners = [];
const _originalAddEventListener = EventTarget.prototype.addEventListener;
EventTarget.prototype.addEventListener = function(type, listener, options) {
_listeners.push({ target: this, type, listener, options });
return _originalAddEventListener.call(this, type, listener, options);
};
var sky = document.getElementById('gsSky');
var COLS = 18, ROWS = 14;
sky.style.gridTemplateColumns = 'repeat(' + COLS + ',1fr)';
sky.style.gridTemplateRows = 'repeat(' + ROWS + ',1fr)';
// Build a field of cells; mark ~16% of them as "stars" that light on hover.
for (var i = 0; i < COLS * ROWS; i++) {
var cell = document.createElement('div');
cell.className = 'gs-star';
if (Math.random() < 0.16) {
cell.classList.add('on');
cell.style.setProperty('--tw', (1.4 + Math.random() * 2).toFixed(2) + 's');
cell.firstChild; // noop
}
cell.innerHTML = '<i></i>';
sky.appendChild(cell);
}
// Periodically reshuffle which stars are lit so the constellation drifts.
var stars = Array.prototype.slice.call(sky.querySelectorAll('.gs-star'));
setInterval(function () {
// turn a few on/off to make the sky feel alive even while hovering
var pick = stars[Math.floor(Math.random() * stars.length)];
pick.classList.toggle('on');
if (pick.classList.contains('on')) {
pick.style.setProperty('--tw', (1.4 + Math.random() * 2).toFixed(2) + 's');
}
}, 900);
// Cleanup: restore addEventListener and remove all listeners
return () => {
EventTarget.prototype.addEventListener = _originalAddEventListener;
for (const { target, type, listener, options } of _listeners) {
target.removeEventListener(type, listener, options);
}
};
}, []);
return (
<>
<style>{`
@keyframes gsTwinkle{0%,100%{opacity:1}50%{opacity:.35}}
* {
box-sizing:border-box;margin:0;padding:0
}
body {
font-family:system-ui,-apple-system,sans-serif;background:#05050d;color:#fff;display:flex;justify-content:center;align-items:center;min-height:100vh;padding:24px
}
.gs-star {
position:relative;display:flex;align-items:center;justify-content:center
}
.gs-star i {
width:2px;height:2px;border-radius:50%;background:#3a3a55;transition:background .4s,box-shadow .4s,transform .4s
}
.gs-card:hover .gs-star.on i {
background:#fff;box-shadow:0 0 6px 1px rgba(255,255,255,.8);transform:scale(1.6)
}
.gs-star.on i {
animation:gsTwinkle var(--tw) ease-in-out infinite;animation-play-state:paused
}
.gs-card:hover .gs-star.on i {
animation-play-state:running
}
.gs-body h3 {
font-size:21px;font-weight:800
}
.gs-body p {
font-size:13px;color:#9a9ab8;line-height:1.55;margin-top:6px
}
`}</style>
<div className="gs-stage">
<article className="gs-card relative w-[300px] h-[340px] rounded-[20px] [background:linear-gradient(180deg,#0c0c1c,#06060f)] border border-[#1d1d33] overflow-hidden cursor-pointer" id="gsCard">
<div className="absolute inset-0 grid p-3.5" id="gsSky" aria-hidden="true"></div>
<div className="gs-body absolute inset-0 z-[1] flex flex-col justify-end p-6 [background:linear-gradient(0deg,rgba(6,6,15,.85),transparent_55%)]">
<div className="w-[46px] h-[46px] rounded-xl bg-[rgba(129,140,248,.16)] border border-[rgba(129,140,248,.4)] flex items-center justify-center text-[22px] text-[#c7d2fe] mb-3.5">✦</div>
<h3>Reach the stars</h3>
<p>Hover the card — the constellation brightens and twinkles to life.</p>
</div>
</article>
</div>
</>
);
}<template>
<div class="gs-stage">
<article class="gs-card" id="gsCard">
<div class="gs-sky" id="gsSky" aria-hidden="true"></div>
<div class="gs-body">
<div class="gs-glyph">✦</div>
<h3>Reach the stars</h3>
<p>Hover the card — the constellation brightens and twinkles to life.</p>
</div>
</article>
</div>
</template>
<script setup>
import { onMounted } from 'vue';
let sky;
var COLS = 18, ROWS = 14;
onMounted(() => {
sky = document.getElementById('gsSky');
sky.style.gridTemplateColumns = 'repeat(' + COLS + ',1fr)';
sky.style.gridTemplateRows = 'repeat(' + ROWS + ',1fr)';
// Build a field of cells; mark ~16% of them as "stars" that light on hover.
for (var i = 0; i < COLS * ROWS; i++) {
var cell = document.createElement('div');
cell.className = 'gs-star';
if (Math.random() < 0.16) {
cell.classList.add('on');
cell.style.setProperty('--tw', (1.4 + Math.random() * 2).toFixed(2) + 's');
cell.firstChild; // noop
}
cell.innerHTML = '<i></i>';
sky.appendChild(cell);
}
// Periodically reshuffle which stars are lit so the constellation drifts.
var stars = Array.prototype.slice.call(sky.querySelectorAll('.gs-star'));
setInterval(function () {
// turn a few on/off to make the sky feel alive even while hovering
var pick = stars[Math.floor(Math.random() * stars.length)];
pick.classList.toggle('on');
if (pick.classList.contains('on')) {
pick.style.setProperty('--tw', (1.4 + Math.random() * 2).toFixed(2) + 's');
}
}, 900);
});
</script>
<style scoped>
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:system-ui,-apple-system,sans-serif;background:#05050d;color:#fff;display:flex;justify-content:center;align-items:center;min-height:100vh;padding:24px}
.gs-card{position:relative;width:300px;height:340px;border-radius:20px;background:linear-gradient(180deg,#0c0c1c,#06060f);border:1px solid #1d1d33;overflow:hidden;cursor:pointer}
.gs-sky{position:absolute;inset:0;display:grid;padding:14px}
.gs-star{position:relative;display:flex;align-items:center;justify-content:center}
.gs-star i{width:2px;height:2px;border-radius:50%;background:#3a3a55;transition:background .4s,box-shadow .4s,transform .4s}
.gs-card:hover .gs-star.on i{background:#fff;box-shadow:0 0 6px 1px rgba(255,255,255,.8);transform:scale(1.6)}
.gs-star.on i{animation:gsTwinkle var(--tw) ease-in-out infinite;animation-play-state:paused}
.gs-card:hover .gs-star.on i{animation-play-state:running}
@keyframes gsTwinkle{0%,100%{opacity:1}50%{opacity:.35}}
.gs-body{position:absolute;inset:0;z-index:1;display:flex;flex-direction:column;justify-content:flex-end;padding:24px;background:linear-gradient(0deg,rgba(6,6,15,.85),transparent 55%)}
.gs-glyph{width:46px;height:46px;border-radius:12px;background:rgba(129,140,248,.16);border:1px solid rgba(129,140,248,.4);display:flex;align-items:center;justify-content:center;font-size:22px;color:#c7d2fe;margin-bottom:14px}
.gs-body h3{font-size:21px;font-weight:800}
.gs-body p{font-size:13px;color:#9a9ab8;line-height:1.55;margin-top:6px}
</style>// @ts-nocheck
// Note: vanilla JS DOM manipulation is preserved as-is inside ngAfterViewInit().
// For idiomatic Angular, replace document.getElementById() with @ViewChild() refs
// and move state into component properties with two-way binding.
import { Component, AfterViewInit, ViewEncapsulation } from '@angular/core';
import { CommonModule } from '@angular/common';
@Component({
selector: 'app-glowing-stars-card',
standalone: true,
imports: [CommonModule],
encapsulation: ViewEncapsulation.None,
template: `
<div class="gs-stage">
<article class="gs-card" id="gsCard">
<div class="gs-sky" id="gsSky" aria-hidden="true"></div>
<div class="gs-body">
<div class="gs-glyph">✦</div>
<h3>Reach the stars</h3>
<p>Hover the card — the constellation brightens and twinkles to life.</p>
</div>
</article>
</div>
`,
styles: [`
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:system-ui,-apple-system,sans-serif;background:#05050d;color:#fff;display:flex;justify-content:center;align-items:center;min-height:100vh;padding:24px}
.gs-card{position:relative;width:300px;height:340px;border-radius:20px;background:linear-gradient(180deg,#0c0c1c,#06060f);border:1px solid #1d1d33;overflow:hidden;cursor:pointer}
.gs-sky{position:absolute;inset:0;display:grid;padding:14px}
.gs-star{position:relative;display:flex;align-items:center;justify-content:center}
.gs-star i{width:2px;height:2px;border-radius:50%;background:#3a3a55;transition:background .4s,box-shadow .4s,transform .4s}
.gs-card:hover .gs-star.on i{background:#fff;box-shadow:0 0 6px 1px rgba(255,255,255,.8);transform:scale(1.6)}
.gs-star.on i{animation:gsTwinkle var(--tw) ease-in-out infinite;animation-play-state:paused}
.gs-card:hover .gs-star.on i{animation-play-state:running}
@keyframes gsTwinkle{0%,100%{opacity:1}50%{opacity:.35}}
.gs-body{position:absolute;inset:0;z-index:1;display:flex;flex-direction:column;justify-content:flex-end;padding:24px;background:linear-gradient(0deg,rgba(6,6,15,.85),transparent 55%)}
.gs-glyph{width:46px;height:46px;border-radius:12px;background:rgba(129,140,248,.16);border:1px solid rgba(129,140,248,.4);display:flex;align-items:center;justify-content:center;font-size:22px;color:#c7d2fe;margin-bottom:14px}
.gs-body h3{font-size:21px;font-weight:800}
.gs-body p{font-size:13px;color:#9a9ab8;line-height:1.55;margin-top:6px}
`]
})
export class GlowingStarsCardComponent implements AfterViewInit {
ngAfterViewInit(): void {
var sky = document.getElementById('gsSky');
var COLS = 18, ROWS = 14;
sky.style.gridTemplateColumns = 'repeat(' + COLS + ',1fr)';
sky.style.gridTemplateRows = 'repeat(' + ROWS + ',1fr)';
// Build a field of cells; mark ~16% of them as "stars" that light on hover.
for (var i = 0; i < COLS * ROWS; i++) {
var cell = document.createElement('div');
cell.className = 'gs-star';
if (Math.random() < 0.16) {
cell.classList.add('on');
cell.style.setProperty('--tw', (1.4 + Math.random() * 2).toFixed(2) + 's');
cell.firstChild; // noop
}
cell.innerHTML = '<i></i>';
sky.appendChild(cell);
}
// Periodically reshuffle which stars are lit so the constellation drifts.
var stars = Array.prototype.slice.call(sky.querySelectorAll('.gs-star'));
setInterval(function () {
// turn a few on/off to make the sky feel alive even while hovering
var pick = stars[Math.floor(Math.random() * stars.length)];
pick.classList.toggle('on');
if (pick.classList.contains('on')) {
pick.style.setProperty('--tw', (1.4 + Math.random() * 2).toFixed(2) + 's');
}
}, 900);
}
}