cinematic-countdown-canvas / components /number-animations.js
ProjectGenesis's picture
Create animated numbers (1–10) in the style of Netflix’s "Top 10" design.
64aa32a verified
class CinematicNumber extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
}
connectedCallback() {
this.render();
}
static get observedAttributes() {
return ['number', 'animation', 'loop'];
}
attributeChangedCallback(name, oldValue, newValue) {
if (oldValue !== newValue) {
this.render();
}
}
render() {
const number = this.getAttribute('number') || '1';
const animation = this.getAttribute('animation') || 'zoom';
const loop = this.getAttribute('loop') === 'true';
this.shadowRoot.innerHTML = `
<style>
:host {
display: block;
font-family: 'Bebas Neue', sans-serif;
}
.number-container {
position: relative;
display: inline-block;
}
.cinematic-number {
font-size: 20rem;
font-weight: 900;
letter-spacing: -0.05em;
background: linear-gradient(145deg, #8b0000, #ff0000, #8b0000);
background-size: 200% 200%;
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
text-shadow:
0 0 30px rgba(255, 0, 0, 0.8),
0 0 60px rgba(255, 0, 0, 0.6),
0 0 90px rgba(255, 0, 0, 0.4),
2px 2px 4px rgba(0, 0, 0, 0.8),
-2px -2px 4px rgba(255, 255, 255, 0.1);
animation: shimmer 3s ease-in-out infinite;
}
@keyframes shimmer {
0%, 100% {
background-position: 0% 50%;
filter: brightness(1);
}
50% {
background-position: 100% 50%;
filter: brightness(1.2);
}
}
.particles {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
pointer-events: none;
}
.particle {
position: absolute;
width: 4px;
height: 4px;
background: #ff0000;
border-radius: 50%;
animation: particleFloat 2s ease-out forwards;
}
@keyframes particleFloat {
0% {
transform: translate(0, 0) scale(1);
opacity: 1;
}
100% {
transform: translate(var(--tx), var(--ty)) scale(0);
opacity: 0;
}
}
</style>
<div class="number-container">
<div class="cinematic-number">${number}</div>
<div class="particles"></div>
</div>
`;
this.applyAnimation(animation);
}
applyAnimation(animationType) {
const numberElement = this.shadowRoot.querySelector('.cinematic-number');
const particlesContainer = this.shadowRoot.querySelector('.particles');
// Remove existing particles
particlesContainer.innerHTML = '';
switch(animationType) {
case 'smoke':
this.createParticles(particlesContainer);
break;
}
}
createParticles(container) {
for (let i = 0; i < 20; i++) {
const particle = document.createElement('div');
particle.classList.add('particle');
const angle = Math.random() * Math.PI * 2;
const distance = 80 + Math.random() * 120;
const tx = Math.cos(angle) * distance;
const ty = Math.sin(angle) * distance;
particle.style.setProperty('--tx', `${tx}px`);
particle.style.setProperty('--ty', `${ty}px`);
particle.style.left = '50%';
particle.style.top = '50%';
particle.style.animationDelay = `${Math.random() * 0.3}s`;
container.appendChild(particle);
}
}
}
customElements.define('cinematic-number', CinematicNumber);