|
|
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'); |
|
|
|
|
|
|
|
|
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); |