@manufosela/animation-fireflies

@manufosela/animation-fireflies

Floating firefly particles animation web component built with Lit 3 and Canvas API

FIREFLIES ANIMATION

Fireflies: 50

MAGICAL NIGHT

Move your mouse to attract the fireflies

Click Events (click on a firefly)

Theme Variations

Forest Night

Ocean Bioluminescence

Sunset Embers

Aurora Spirits

Dense Swarm

Gentle Giants

Demo code (CodePen-ready HTML, CSS, JS)
HTML (html)
<h1>FIREFLIES ANIMATION</h1>

    <div class="demo-container">
      <div class="stats">
        <span>Fireflies: <span class="value" id="fireflyCount">50</span></span>
      </div>

      <animation-fireflies
        id="mainFireflies"
        class="main-fireflies"
        interactive
      >
        <div class="overlay-content">
          <h2>MAGICAL NIGHT</h2>
          <p>Move your mouse to attract the fireflies</p>
        </div>
      </animation-fireflies>

      <div class="controls">
        <div class="control-group">
          <label>Count: <span class="value" id="countValue">50</span></label>
          <input type="range" id="countSlider" min="10" max="200" value="50" />
        </div>

        <div class="control-group">
          <label>Glow Size: <span class="value" id="glowValue">20</span>px</label>
          <input type="range" id="glowSlider" min="5" max="50" value="20" />
        </div>

        <div class="control-group">
          <label>Speed: <span class="value" id="speedValue">1.0</span>x</label>
          <input type="range" id="speedSlider" min="0.1" max="3" step="0.1" value="1" />
        </div>

        <div class="control-group">
          <label>Blink Speed: <span class="value" id="blinkValue">1.0</span>x</label>
          <input type="range" id="blinkSlider" min="0.1" max="3" step="0.1" value="1" />
        </div>

        <div class="control-group">
          <label>Mouse Attraction: <span class="value" id="attractionValue">0.5</span></label>
          <input type="range" id="attractionSlider" min="-1" max="2" step="0.1" value="0.5" />
        </div>

        <button id="pauseBtn">Pause</button>
        <button id="resetBtn">Reset</button>
        <button id="addBtn">Add 10</button>
        <button id="removeBtn">Remove 10</button>
      </div>

      <div class="event-log">
        <h4>Click Events (click on a firefly)</h4>
        <div id="logContent"></div>
      </div>

      <h2 style="text-align: center; margin: 3rem 0 1rem; font-weight: 300; color: #ffe066;">Theme Variations</h2>

      <div class="themes-grid">
        <div class="theme-card">
          <h3>Forest Night</h3>
          <animation-fireflies
            class="forest-theme"
            count="40"
            .colors=${['#88ff88', '#66dd66', '#aaff88']}
          ></animation-fireflies>
        </div>

        <div class="theme-card">
          <h3>Ocean Bioluminescence</h3>
          <animation-fireflies
            class="ocean-theme"
            count="60"
            .colors=${['#00ffff', '#88ffff', '#00aaff', '#66ddff']}
            glow-size="25"
          ></animation-fireflies>
        </div>

        <div class="theme-card">
          <h3>Sunset Embers</h3>
          <animation-fireflies
            class="sunset-theme"
            count="35"
            .colors=${['#ff6600', '#ff8800', '#ffaa00', '#ff4400']}
            speed="0.5"
          ></animation-fireflies>
        </div>

        <div class="theme-card">
          <h3>Aurora Spirits</h3>
          <animation-fireflies
            class="aurora-theme"
            count="45"
            .colors=${['#00ff88', '#00ffcc', '#88ffff', '#ff88ff', '#aa88ff']}
            glow-size="30"
            blink-speed="0.5"
          ></animation-fireflies>
        </div>

        <div class="theme-card">
          <h3>Dense Swarm</h3>
          <animation-fireflies
            count="150"
            glow-size="10"
            min-size="1"
            max-size="3"
            speed="1.5"
          ></animation-fireflies>
        </div>

        <div class="theme-card">
          <h3>Gentle Giants</h3>
          <animation-fireflies
            count="15"
            glow-size="40"
            min-size="4"
            max-size="8"
            speed="0.3"
            blink-speed="0.3"
          ></animation-fireflies>
        </div>
      </div>
    </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;
}

<script type="module" src="../src/animation-fireflies.js"></script>
    
      * {
        box-sizing: border-box;
        margin: 0;
        padding: 0;
      }

      h1 {
        text-align: center;
        padding: 2rem;
        font-weight: 300;
        letter-spacing: 4px;
        color: #ffe066;
        text-shadow: 0 0 20px rgba(255, 224, 102, 0.5);
      }

      .demo-container {
        max-width: 1400px;
        margin: 0 auto;
        padding: 0 1rem 2rem;
      }

      .main-fireflies {
        --fireflies-height: 500px;
        --fireflies-background: linear-gradient(180deg,
          #0a1628 0%,
          #0d1f3c 30%,
          #1a2a4a 60%,
          #0f1922 100%
        );
        border-radius: 16px;
        overflow: hidden;
        margin-bottom: 2rem;
        position: relative;
      }

      .overlay-content {
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: center;
        height: 100%;
        text-align: center;
        pointer-events: none;
      }

      .overlay-content h2 {
        font-size: 3rem;
        font-weight: 200;
        letter-spacing: 8px;
        margin-bottom: 1rem;
        color: rgba(255, 255, 255, 0.9);
        text-shadow: 0 0 30px rgba(255, 224, 102, 0.3);
      }

      .overlay-content p {
        font-size: 1.2rem;
        color: rgba(255, 255, 255, 0.6);
      }

      .controls {
        display: flex;
        flex-wrap: wrap;
        gap: 1.5rem;
        justify-content: center;
        padding: 1.5rem;
        background: rgba(255, 224, 102, 0.05);
        border: 1px solid rgba(255, 224, 102, 0.2);
        border-radius: 12px;
        margin-bottom: 2rem;
      }

      .control-group {
        display: flex;
        flex-direction: column;
        gap: 0.5rem;
        min-width: 140px;
      }

      .control-group label {
        font-size: 0.875rem;
        color: #ffe066;
        opacity: 0.8;
      }

      input[type='range'] {
        width: 140px;
        accent-color: #ffe066;
      }

      button {
        padding: 0.75rem 1.5rem;
        border: 1px solid #ffe066;
        border-radius: 8px;
        background: transparent;
        color: #ffe066;
        font-size: 1rem;
        cursor: pointer;
        transition: all 0.3s ease;
      }

      button:hover {
        background: rgba(255, 224, 102, 0.1);
        box-shadow: 0 0 15px rgba(255, 224, 102, 0.3);
      }

      button.active {
        background: #ffe066;
        color: #0a1628;
      }

      .value {
        font-family: monospace;
        color: #ffe066;
      }

      .themes-grid {
        display: grid;
        grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
        gap: 1.5rem;
        margin-top: 2rem;
      }

      .theme-card {
        background: rgba(0, 0, 0, 0.3);
        border: 1px solid rgba(255, 224, 102, 0.1);
        border-radius: 12px;
        overflow: hidden;
      }

      .theme-card h3 {
        padding: 1rem;
        font-weight: 400;
        font-size: 1rem;
        color: #ffe066;
        border-bottom: 1px solid rgba(255, 224, 102, 0.1);
      }

      .theme-card animation-fireflies {
        --fireflies-height: 200px;
      }

      /* Theme variations */
      .forest-theme {
        --fireflies-background: linear-gradient(180deg, #0a2618 0%, #1a4a2e 50%, #0d2f1c 100%);
      }

      .ocean-theme {
        --fireflies-background: linear-gradient(180deg, #0a1628 0%, #0d3a5c 50%, #062840 100%);
      }

      .sunset-theme {
        --fireflies-background: linear-gradient(180deg, #2d1810 0%, #4a2a1a 50%, #1a0d08 100%);
      }

      .aurora-theme {
        --fireflies-background: linear-gradient(180deg, #0a1628 0%, #1a2848 30%, #0d3a40 60%, #0a1628 100%);
      }

      /* Event log */
      .event-log {
        margin-top: 1rem;
        padding: 1rem;
        background: rgba(0, 0, 0, 0.3);
        border-radius: 8px;
        font-family: monospace;
        font-size: 0.85rem;
        max-height: 100px;
        overflow-y: auto;
      }

      .event-log h4 {
        margin-bottom: 0.5rem;
        color: #ffe066;
      }

      .event-item {
        padding: 0.25rem 0;
        border-bottom: 1px solid rgba(255, 255, 255, 0.1);
        color: rgba(255, 255, 255, 0.7);
      }

      /* Stats display */
      .stats {
        display: flex;
        gap: 2rem;
        justify-content: center;
        margin-bottom: 1rem;
        font-family: monospace;
        color: rgba(255, 255, 255, 0.7);
      }
JS (js)
const mainFireflies = document.getElementById('mainFireflies');
      const countSlider = document.getElementById('countSlider');
      const glowSlider = document.getElementById('glowSlider');
      const speedSlider = document.getElementById('speedSlider');
      const blinkSlider = document.getElementById('blinkSlider');
      const attractionSlider = document.getElementById('attractionSlider');
      const pauseBtn = document.getElementById('pauseBtn');
      const resetBtn = document.getElementById('resetBtn');
      const addBtn = document.getElementById('addBtn');
      const removeBtn = document.getElementById('removeBtn');
      const logContent = document.getElementById('logContent');
      const fireflyCountDisplay = document.getElementById('fireflyCount');

      // Update count display
      const updateCountDisplay = () => {
        fireflyCountDisplay.textContent = mainFireflies.getFireflyCount();
      };

      // Control handlers
      countSlider.addEventListener('input', (e) => {
        const value = parseInt(e.target.value);
        mainFireflies.count = value;
        mainFireflies.reset();
        document.getElementById('countValue').textContent = value;
        updateCountDisplay();
      });

      glowSlider.addEventListener('input', (e) => {
        const value = parseInt(e.target.value);
        mainFireflies.glowSize = value;
        document.getElementById('glowValue').textContent = value;
      });

      speedSlider.addEventListener('input', (e) => {
        const value = parseFloat(e.target.value);
        mainFireflies.speed = value;
        document.getElementById('speedValue').textContent = value.toFixed(1);
      });

      blinkSlider.addEventListener('input', (e) => {
        const value = parseFloat(e.target.value);
        mainFireflies.blinkSpeed = value;
        document.getElementById('blinkValue').textContent = value.toFixed(1);
      });

      attractionSlider.addEventListener('input', (e) => {
        const value = parseFloat(e.target.value);
        mainFireflies.mouseAttraction = value;
        document.getElementById('attractionValue').textContent = value.toFixed(1);
      });

      pauseBtn.addEventListener('click', () => {
        mainFireflies.toggle();
        pauseBtn.textContent = mainFireflies.paused ? 'Resume' : 'Pause';
        pauseBtn.classList.toggle('active', mainFireflies.paused);
      });

      resetBtn.addEventListener('click', () => {
        mainFireflies.reset();
        updateCountDisplay();
      });

      addBtn.addEventListener('click', () => {
        mainFireflies.addFireflies(10);
        updateCountDisplay();
      });

      removeBtn.addEventListener('click', () => {
        mainFireflies.removeFireflies(10);
        updateCountDisplay();
      });

      // Listen for firefly click events
      mainFireflies.addEventListener('firefly-click', (e) => {
        const ff = e.detail.firefly;
        const logItem = document.createElement('div');
        logItem.className = 'event-item';
        logItem.textContent = `Clicked firefly at (${Math.round(ff.x)}, ${Math.round(ff.y)}) - Color: ${ff.color}`;
        logContent.prepend(logItem);

        // Keep only last 5 entries
        while (logContent.children.length > 5) {
          logContent.removeChild(logContent.lastChild);
        }
      });

      // Initialize count display
      setTimeout(updateCountDisplay, 100);