Efek glow google

Kode efek glow google gemini 4 argon

Efek glow google

Demo

Code

html

HTML
<!DOCTYPE html>
<html lang="id" data-theme="light">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Google Gemini Energy Underglow</title>
  <link rel="stylesheet" href="style.css" />
</head>
<body>

  <!-- Controls Bar: Toggle Mode & 3D Tilt -->
  <div class="controls-bar">
    <button class="control-btn active" id="btnTheme">
      <span>🌓</span> Mode: <strong id="lblTheme">Light</strong>
    </button>
    <button class="control-btn active" id="btnTilt">
      <span>📐</span> 3D Tilt Effect: <strong>ON</strong>
    </button>
  </div>

  <!-- ========================================================
       1. ACTION BUTTONS DENGAN ENERGY GLOW
       ======================================================== -->
  <div class="section-title">1. Komponen Action Button</div>
  <div class="btn-showcase">
    
    <!-- Primary Action Button -->
    <div class="glow-wrapper btn-primary-wrapper">
      <div class="glow-underlay"></div>
      <button class="element-surface btn-primary-surface">
        <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
          <path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/>
        </svg>
        <span>✨ Generate with Gemini</span>
      </button>
    </div>

    <!-- Secondary Action Button -->
    <div class="glow-wrapper btn-secondary-wrapper">
      <div class="glow-underlay"></div>
      <button class="element-surface btn-secondary-surface">
        <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#1a73e8" stroke-width="2">
          <path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/>
        </svg>
        <span>Deploy Model</span>
      </button>
    </div>

    <!-- Icon Action Button -->
    <div class="glow-wrapper btn-icon-wrapper">
      <div class="glow-underlay"></div>
      <button class="element-surface btn-icon-surface" title="Favorite">
        <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
          <path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/>
        </svg>
      </button>
    </div>

  </div>

  <!-- ========================================================
       2. SEARCH BAR / OMNIBOX DENGAN ENERGY GLOW
       ======================================================== -->
  <div class="section-title">2. Komponen Search Bar (Omnibox)</div>
  <div class="search-showcase">
    <div class="glow-wrapper search-wrapper">
      <div class="glow-underlay"></div>
      <div class="element-surface search-surface">
        <svg class="search-icon" viewBox="0 0 24 24">
          <circle cx="11" cy="11" r="8"></circle>
          <line x1="21" y1="21" x2="16.65" y2="16.65"></line>
        </svg>
        <input type="text" placeholder="Tanyakan apa saja kepada Gemini AI..." />
        <button class="voice-btn" title="Voice Search">
          <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
            <path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"></path>
            <path d="M19 10v2a7 7 0 0 1-14 0v-2"></path>
            <line x1="12" y1="19" x2="12" y2="23"></line>
            <line x1="8" y1="23" x2="16" y2="23"></line>
          </svg>
        </button>
      </div>
    </div>
  </div>

  <!-- ========================================================
       3. FILTER CHIPS
       ======================================================== -->
  <div class="section-title">3. Komponen Filter Chips</div>
  <div class="chips-showcase">
    <div class="glow-wrapper chip-wrapper">
      <div class="glow-underlay"></div>
      <div class="element-surface chip-surface"><span>⚡ Fast Response</span></div>
    </div>
    <div class="glow-wrapper chip-wrapper">
      <div class="glow-underlay"></div>
      <div class="element-surface chip-surface"><span>🧠 Deep Reasoning</span></div>
    </div>
    <div class="glow-wrapper chip-wrapper">
      <div class="glow-underlay"></div>
      <div class="element-surface chip-surface"><span>🎨 Vision AI</span></div>
    </div>
  </div>

  <!-- ========================================================
       4. MODEL CARD BARU: GEMINI AI STUDIO DEVELOPER CARD
       ======================================================== -->
  <div class="section-title">4. Komponen Card (Model AI Studio)</div>
  <div class="card-grid">

    <div class="glow-wrapper ai-card-wrapper" id="glowAnimationWrapper">
      <!-- 1. Glow Layer di Bawah (z-index: 1) -->
      <div class="glow-underlay"></div>

      <!-- 2. Bodi Kartu Solid di Atas (z-index: 2, menutup tengah 100%) -->
      <div class="element-surface ai-card-surface">
        
        <!-- Top Bar: Header & Bookmark -->
        <div class="ai-card-top">
          <div class="ai-model-identity">
            <div class="ai-model-icon">
              <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                <path d="M12 2v4m0 12v4M4.93 4.93l2.83 2.83m8.48 8.48l2.83 2.83M2 12h4m12 0h4M4.93 19.07l2.83-2.83m8.48-8.48l2.83-2.83"/>
              </svg>
            </div>
            <div>
              <div class="ai-model-name">Gemini 2.0 Flash</div>
              <div class="ai-model-meta">
                <span class="ai-status-pill">Active</span>
                <span class="ai-model-tag">Multimodal Native</span>
              </div>
            </div>
          </div>

          <!-- Tombol Bookmark dengan Efek Glow Sendiri -->
          <div class="glow-wrapper btn-icon-wrapper" style="--glow-blur: 6px; --glow-spread: -2px;">
            <div class="glow-underlay"></div>
            <button class="element-surface btn-icon-surface" style="width: 36px; height: 36px;" title="Bookmark Model">
              <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                <path d="M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z"></path>
              </svg>
            </button>
          </div>
        </div>

        <!-- Interactive AI Prompt & Response Snippet -->
        <div class="ai-preview-box">
          <div class="ai-prompt-line">
            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#1a73e8" stroke-width="2">
              <polyline points="9 18 15 12 9 6"></polyline>
            </svg>
            <span class="prompt-text">"Explain Quantum Computing in 1 sentence"</span>
          </div>
          <div class="ai-response-pill">
            <span>⚡ Computation using superposition &amp; qubits</span>
          </div>
        </div>

        <!-- 3 Kolom Metrik Statistik -->
        <div class="ai-stats-row">
          <div class="ai-stat-card">
            <div class="ai-stat-val">1M</div>
            <div class="ai-stat-lbl">Context</div>
          </div>
          <div class="ai-stat-card">
            <div class="ai-stat-val">140 t/s</div>
            <div class="ai-stat-lbl">Speed</div>
          </div>
          <div class="ai-stat-card">
            <div class="ai-stat-val">98.4%</div>
            <div class="ai-stat-lbl">Benchmark</div>
          </div>
        </div>

        <!-- Bottom Action Bar -->
        <div class="ai-card-bottom">
          <div class="ai-price-tag">
            <span class="ai-price-title">API Access</span>
            <span class="ai-price-val">Free Tier Active</span>
          </div>

          <!-- Tombol Aksi di Dalam Card dengan Efek Glow Sendiri -->
          <div class="glow-wrapper btn-primary-wrapper" style="border-radius: 100px; --glow-blur: 8px; --glow-spread: -2px;">
            <div class="glow-underlay"></div>
            <button class="element-surface btn-primary-surface" style="padding: 9px 18px; font-size: 0.85rem; border-radius: inherit;">
              <span>Open Studio</span>
              <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                <line x1="7" y1="17" x2="17" y2="7"></line>
                <polyline points="7 7 17 7 17 17"></polyline>
              </svg>
            </button>
          </div>
        </div>

      </div>
    </div>

  </div>

  <script src="script.js"></script>
</body>
</html>

css

CSS
/* ========================================================
   THEME VARIABLES: LIGHT & DARK MODE
   ======================================================== */
:root {
  --border-radius: 24px;
  --sys-motion--curve-standard: cubic-bezier(0.4, 0, 0.2, 1);
  --hover-fade-duration: 400ms;
}

/* LIGHT MODE */
[data-theme="light"] {
  --bg-color: #f8fafd;
  --bg-radial-1: rgba(49, 134, 255, 0.08);
  --bg-radial-2: rgba(249, 107, 214, 0.06);
  --card-bg: #ffffff;
  --card-text: #1f1f1f;
  --card-subtext: #444746;
  --card-headline: #1f1f1f;
  --card-category: #0b57d0;
  --card-border-idle: #e0e2ec;
  --card-shadow: 0 2px 10px rgba(0, 0, 0, 0.04);
  --card-shadow-hover: 0 4px 16px rgba(0, 0, 0, 0.08);

  --btn-primary-bg: #1a73e8;
  --btn-primary-text: #ffffff;
  --btn-primary-border: #1a73e8;

  --btn-surface-bg: #ffffff;
  --btn-surface-text: #1f1f1f;
  --btn-surface-border: #e0e2ec;

  --input-bg: #ffffff;
  --input-text: #1f1f1f;
  --input-placeholder: #747775;
  --input-border: #e0e2ec;

  --chip-bg: #ffffff;
  --chip-text: #1f1f1f;
  --chip-border: #e0e2ec;

  --icon-bg: #f0f4f9;
  --icon-border: #e0e2ec;

  --badge-bg: rgba(11, 87, 208, 0.08);
  --badge-border: rgba(11, 87, 208, 0.18);
  --badge-text: #0b57d0;

  --glow-outer-opacity: 0.85;
  --title-gradient: linear-gradient(135deg, #1f1f1f 40%, #0b57d0 100%);
}

/* DARK MODE */
[data-theme="dark"] {
  --bg-color: #0d0f12;
  --bg-radial-1: rgba(49, 134, 255, 0.08);
  --bg-radial-2: rgba(249, 107, 214, 0.05);
  --card-bg: #16181d;
  --card-text: #e8eaed;
  --card-subtext: #9aa0a6;
  --card-headline: #ffffff;
  --card-category: #8ab4f8;
  --card-border-idle: rgba(255, 255, 255, 0.1);
  --card-shadow: none;
  --card-shadow-hover: 0 4px 16px rgba(0, 0, 0, 0.4);

  --btn-primary-bg: #3186ff;
  --btn-primary-text: #ffffff;
  --btn-primary-border: #3186ff;

  --btn-surface-bg: #1e2025;
  --btn-surface-text: #e8eaed;
  --btn-surface-border: rgba(255, 255, 255, 0.12);

  --input-bg: #16181d;
  --input-text: #e8eaed;
  --input-placeholder: #8e918f;
  --input-border: rgba(255, 255, 255, 0.12);

  --chip-bg: #1e2025;
  --chip-text: #e8eaed;
  --chip-border: rgba(255, 255, 255, 0.12);

  --icon-bg: rgba(255, 255, 255, 0.05);
  --icon-border: rgba(255, 255, 255, 0.12);

  --badge-bg: rgba(255, 255, 255, 0.06);
  --badge-border: rgba(255, 255, 255, 0.1);
  --badge-text: #8ab4f8;

  --glow-outer-opacity: 0.7;
  --title-gradient: linear-gradient(135deg, #ffffff 40%, #a8c7fa 100%);
}

* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Google Sans", sans-serif;
  transition: background-color 0.3s ease, border-color 0.3s ease, color 0.3s ease;
}

body {
  background-color: var(--bg-color);
  color: var(--card-text);
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 3rem 1.5rem;
  overflow-x: hidden;
  background-image: 
    radial-gradient(circle at 50% 10%, var(--bg-radial-1) 0%, transparent 50%),
    radial-gradient(circle at 80% 90%, var(--bg-radial-2) 0%, transparent 40%);
}

/* ========================================================
   UNIVERSAL UNDERGLOW ENGINE
   - Layer 1 (z-index: 1): .glow-underlay (Di Bawah Elemen)
   - Layer 2 (z-index: 2): .element-surface (Di Atas Glow, Solid Menutup)
   ======================================================== */
.glow-wrapper {
  position: relative;
  display: inline-flex;
  cursor: pointer;
  user-select: none;

  --gradient-angle: 0deg;
  --glow-opacity: 0;
  --glow-blur: 12px;
  --glow-spread: -3px;

  /* Google Gemini Conic Gradient - Simetris Sempurna (30% Kiri, 30% Kanan) */
  --glif-gradient: conic-gradient(
    from var(--gradient-angle, 0deg),
    rgba(49, 134, 255, 0) 20%,
    rgba(49, 134, 255, 0.3) 28%,
    rgba(147, 120, 255, 0.6) 35%,
    rgba(249, 107, 214, 0.85) 40%,
    rgba(252, 65, 61, 1) 46%,
    rgba(255, 107, 43, 1) 50%,
    rgba(254, 199, 0, 1) 54%,
    rgba(136, 222, 66, 1) 60%,
    rgba(14, 188, 95, 1) 65%,
    rgba(0, 169, 187, 0.85) 70%,
    rgba(49, 134, 255, 0.3) 75%,
    rgba(49, 134, 255, 0) 80%
  );
}

/* 1. GLOW UNDERLAY (Selalu di Bawah / di Belakang Elemen) */
.glow-wrapper .glow-underlay {
  position: absolute;
  inset: var(--glow-spread);
  border-radius: inherit;
  background: var(--glif-gradient);
  filter: blur(var(--glow-blur));
  opacity: calc(var(--glow-opacity, 0) * var(--glow-outer-opacity, 0.85));
  pointer-events: none;
  z-index: 1;
  transition: opacity var(--hover-fade-duration) var(--sys-motion--curve-standard);
  will-change: opacity;
}

/* 2. ELEMENT SURFACE (Selalu di Depan, Solid Opaque Menutup Tengah) */
.glow-wrapper .element-surface {
  position: relative;
  z-index: 2;
  border-radius: inherit;
  width: 100%;
  height: 100%;
  transition: transform 0.25s ease, box-shadow 0.3s ease, border-color 0.3s ease;
}

/* ========================================================
   KOMPONEN: ACTION BUTTONS
   ======================================================== */
.btn-showcase {
  display: flex; gap: 1.25rem; align-items: center; justify-content: center;
  flex-wrap: wrap; margin-bottom: 3.5rem;
}
.btn-primary-wrapper { border-radius: 999px; --glow-blur: 8px; --glow-spread: -2px; }
.btn-primary-wrapper .btn-primary-surface {
  background: var(--btn-primary-bg); border: 1px solid var(--btn-primary-border);
  color: var(--btn-primary-text); padding: 12px 24px; font-size: 0.95rem; font-weight: 600;
  letter-spacing: 0.01em; display: inline-flex; align-items: center; gap: 8px;
  box-shadow: 0 0 10px rgba(26, 115, 232, 0.15);
}
.btn-secondary-wrapper { border-radius: 999px; --glow-blur: 8px; --glow-spread: -2px; }
.btn-secondary-wrapper .btn-secondary-surface {
  background: var(--btn-surface-bg); border: 1px solid var(--btn-surface-border);
  color: var(--btn-surface-text); padding: 12px 22px; font-size: 0.95rem; font-weight: 600;
  display: inline-flex; align-items: center; gap: 8px; box-shadow: 0 0 8px rgba(0, 0, 0, 0.04);
}
.btn-icon-wrapper { border-radius: 50%; --glow-blur: 7px; --glow-spread: -2px; }
.btn-icon-wrapper .btn-icon-surface {
  width: 48px; height: 48px; background: var(--btn-surface-bg); border: 1px solid var(--btn-surface-border);
  color: var(--btn-surface-text); display: inline-flex; align-items: center; justify-content: center;
  box-shadow: 0 0 8px rgba(0, 0, 0, 0.04);
}

/* ========================================================
   KOMPONEN: SEARCH BAR (OMNIBOX)
   ======================================================== */
.search-showcase { width: 100%; max-width: 620px; margin-bottom: 3.5rem; }
.search-wrapper { width: 100%; border-radius: 100px; --glow-blur: 10px; --glow-spread: -3px; }
.search-wrapper .search-surface {
  background: var(--input-bg); border: 1px solid var(--input-border);
  display: flex; align-items: center; gap: 12px; padding: 11px 20px;
  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.05);
}
.search-wrapper:hover .search-surface,
.search-wrapper:focus-within .search-surface { border-color: #1a73e8; }
.search-surface .search-icon { width: 20px; height: 20px; fill: none; stroke: #1a73e8; stroke-width: 2; }
.search-surface input { flex: 1; border: none; background: transparent; color: var(--input-text); font-size: 1rem; outline: none; }
.search-surface .voice-btn { background: transparent; border: none; color: #1a73e8; cursor: pointer; display: flex; align-items: center; }

/* ========================================================
   KOMPONEN: FILTER CHIPS
   ======================================================== */
.chips-showcase { display: flex; gap: 0.75rem; justify-content: center; flex-wrap: wrap; margin-bottom: 3.5rem; }
.chip-wrapper { border-radius: 100px; --glow-blur: 6px; --glow-spread: -1.5px; }
.chip-wrapper .chip-surface {
  background: var(--chip-bg); border: 1px solid var(--chip-border); color: var(--chip-text);
  padding: 7px 16px; font-size: 0.85rem; font-weight: 500; box-shadow: 0 2px 6px rgba(0, 0, 0, 0.04);
  display: inline-flex; align-items: center; gap: 6px;
}

/* ========================================================
   KOMPONEN: GEMINI AI STUDIO MODEL CARD
   ======================================================== */
.card-grid { display: flex; justify-content: center; margin-bottom: 4rem; perspective: 1200px; }
.ai-card-wrapper { border-radius: 24px; width: 380px; --glow-blur: 12px; --glow-spread: -3.5px; }
.ai-card-wrapper .ai-card-surface {
  background: var(--card-bg); border: 1px solid var(--card-border-idle);
  padding: 1.8rem; display: flex; flex-direction: column; gap: 1.25rem;
  box-shadow: var(--card-shadow); border-radius: inherit;
}
.ai-card-wrapper:hover .ai-card-surface { box-shadow: var(--card-shadow-hover); border-color: #c4c7c5; }

/* Header Card */
.ai-card-top { display: flex; align-items: flex-start; justify-content: space-between; }
.ai-model-identity { display: flex; align-items: center; gap: 12px; }
.ai-model-icon {
  width: 44px; height: 44px; border-radius: 12px;
  background: linear-gradient(135deg, rgba(49, 134, 255, 0.12), rgba(249, 107, 214, 0.12));
  border: 1px solid rgba(49, 134, 255, 0.2);
  display: flex; align-items: center; justify-content: center; color: #1a73e8; flex-shrink: 0;
}
.ai-model-name { font-size: 1.15rem; font-weight: 700; color: var(--card-headline); }
.ai-model-meta { display: flex; align-items: center; gap: 6px; margin-top: 2px; }
.ai-status-pill {
  font-size: 0.72rem; padding: 2px 8px; border-radius: 100px;
  background: rgba(14, 188, 95, 0.1); color: #0ebc5f; font-weight: 600;
  display: inline-flex; align-items: center; gap: 4px;
}
.ai-status-pill::before { content: ""; width: 5px; height: 5px; border-radius: 50%; background: #0ebc5f; }
.ai-model-tag { font-size: 0.72rem; color: var(--card-subtext); }

/* Preview Snippet */
.ai-preview-box {
  background: var(--icon-bg); border: 1px solid var(--card-border-idle);
  border-radius: 14px; padding: 1rem 1.1rem; display: flex; flex-direction: column; gap: 8px;
}
.ai-prompt-line { display: flex; align-items: center; gap: 8px; font-size: 0.82rem; color: var(--card-subtext); }
.ai-prompt-line span.prompt-text { color: var(--card-headline); font-weight: 500; }
.ai-response-pill {
  display: inline-flex; align-items: center; gap: 6px; background: var(--card-bg);
  border: 1px solid var(--card-border-idle); border-radius: 8px; padding: 6px 10px;
  font-size: 0.78rem; font-family: ui-monospace, SFMono-Regular, monospace; color: #0b57d0; width: fit-content;
}

/* Stats Row */
.ai-stats-row { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
.ai-stat-card {
  background: var(--icon-bg); border: 1px solid var(--card-border-idle);
  border-radius: 12px; padding: 8px 10px; text-align: center;
}
.ai-stat-val { font-size: 0.95rem; font-weight: 700; color: var(--card-headline); }
.ai-stat-lbl { font-size: 0.68rem; color: var(--card-subtext); text-transform: uppercase; margin-top: 2px; }

/* Bottom Action */
.ai-card-bottom {
  display: flex; align-items: center; justify-content: space-between;
  padding-top: 1rem; border-top: 1px solid var(--card-border-idle); margin-top: 0.25rem;
}
.ai-price-title { font-size: 0.72rem; color: var(--card-subtext); text-transform: uppercase; }
.ai-price-val { font-size: 0.9rem; font-weight: 600; color: #0ebc5f; }

.section-title {
  font-size: 0.85rem; text-transform: uppercase; letter-spacing: 0.1em;
  color: var(--card-category); font-weight: 700; margin-bottom: 1.2rem; text-align: center;
}
.controls-bar { display: flex; gap: 1rem; margin-bottom: 3rem; justify-content: center; }
.control-btn {
  background: var(--btn-surface-bg); border: 1px solid var(--btn-surface-border);
  color: var(--btn-surface-text); padding: 9px 18px; border-radius: 999px;
  font-size: 0.85rem; font-weight: 500; cursor: pointer; display: flex; align-items: center; gap: 8px;
}
.control-btn.active { border-color: #1a73e8; color: #1a73e8; }

JS

JavaScript
// State Konfigurasi
let enableTilt = true;
let currentTheme = 'light';

// Toggle Tema (Light <-> Dark)
const btnTheme = document.getElementById('btnTheme');
const lblTheme = document.getElementById('lblTheme');
btnTheme.addEventListener('click', () => {
  currentTheme = currentTheme === 'light' ? 'dark' : 'light';
  document.documentElement.setAttribute('data-theme', currentTheme);
  lblTheme.textContent = currentTheme === 'light' ? 'Light' : 'Dark';
});

// Toggle Efek Kemiringan 3D pada Kartu
const btnTilt = document.getElementById('btnTilt');
btnTilt.addEventListener('click', () => {
  enableTilt = !enableTilt;
  btnTilt.classList.toggle('active', enableTilt);
  btnTilt.innerHTML = `<span>📐</span> 3D Tilt Effect: <strong>${enableTilt ? 'ON' : 'OFF'}</strong>`;
  if (!enableTilt) {
    document.querySelectorAll('.ai-card-wrapper .element-surface').forEach(c => c.style.transform = '');
  }
});

// Interpolasi sudut terpendek (Lerp) untuk gerakan rotasi fluida tanpa jeda
function lerpAngle(current, target, factor) {
  let diff = (target - current) % 360;
  if (diff < -180) diff += 360;
  if (diff > 180) diff -= 360;
  return current + diff * factor;
}

/**
 * UNIVERSAL ENERGY GLOW TRACKER
 * Menghubungkan pelacakan arah mouse secara independen ke setiap elemen .glow-wrapper
 */
function attachEnergyGlow(wrapper) {
  // Hanya memilih layer langsung dari wrapper ini (:scope)
  const glowUnderlay = wrapper.querySelector(':scope > .glow-underlay');
  const elementSurface = wrapper.querySelector(':scope > .element-surface');
  if (!glowUnderlay) return;

  let currentAngle = 0;
  let targetAngle = 0;
  let isHovered = false;
  let rafId = null;
  let fadeTimeout = null;
  const isCard = wrapper.classList.contains('ai-card-wrapper');
  const isNested = !!wrapper.closest('.ai-card-surface');

  // Offset 180 derajat agar puncak warna paling terang menghadap tepat ke arah mouse
  const peakOffset = 180;

  function renderFrame() {
    if (!isHovered) {
      wrapper.style.setProperty('--glow-opacity', '0');
      if (isCard && enableTilt && elementSurface) {
        elementSurface.style.transform = 'perspective(1200px) rotateX(0deg) rotateY(0deg)';
      }
    }

    // Damping factor 0.15 untuk pergerakan halus (60 - 120 FPS)
    currentAngle = lerpAngle(currentAngle, targetAngle, 0.15);
    wrapper.style.setProperty('--gradient-angle', `${currentAngle}deg`);

    if (isHovered) {
      rafId = requestAnimationFrame(renderFrame);
    }
  }

  wrapper.addEventListener('mouseenter', (e) => {
    if (isNested) e.stopPropagation();
    if (fadeTimeout) clearTimeout(fadeTimeout);
    isHovered = true;
    wrapper.style.setProperty('--glow-opacity', '1');

    const rect = wrapper.getBoundingClientRect();
    const mouseX = e.clientX - rect.left;
    const mouseY = e.clientY - rect.top;
    const centerX = rect.width / 2;
    const centerY = rect.height / 2;

    const rad = Math.atan2(mouseY - centerY, mouseX - centerX);
    const deg = (rad * (180 / Math.PI)) + 90;
    targetAngle = deg - peakOffset;
    currentAngle = targetAngle; // Snapping instan saat kursor pertama masuk

    wrapper.style.setProperty('--gradient-angle', `${currentAngle}deg`);

    if (rafId) cancelAnimationFrame(rafId);
    rafId = requestAnimationFrame(renderFrame);
  });

  wrapper.addEventListener('mousemove', (e) => {
    if (isNested) e.stopPropagation();
    const rect = wrapper.getBoundingClientRect();
    const mouseX = e.clientX - rect.left;
    const mouseY = e.clientY - rect.top;
    const centerX = rect.width / 2;
    const centerY = rect.height / 2;

    // Hitung sudut kursor mouse terhadap titik pusat elemen
    const rad = Math.atan2(mouseY - centerY, mouseX - centerX);
    const deg = (rad * (180 / Math.PI)) + 90;
    targetAngle = deg - peakOffset;

    // Kemiringan 3D kartu yang presisi dan seimbang
    if (isCard && enableTilt && elementSurface) {
      const tiltX = -((mouseY - centerY) / centerY) * 7;
      const tiltY = ((mouseX - centerX) / centerX) * 7;
      elementSurface.style.transform = `perspective(1200px) rotateX(${tiltX.toFixed(2)}deg) rotateY(${tiltY.toFixed(2)}deg)`;
    }
  });

  wrapper.addEventListener('mouseleave', (e) => {
    if (isNested) e.stopPropagation();
    isHovered = false;

    // Nilai opacity langsung diatur ke 0 -> memudar halus via CSS transition
    wrapper.style.setProperty('--glow-opacity', '0');

    if (isCard && enableTilt && elementSurface) {
      elementSurface.style.transform = 'perspective(1200px) rotateX(0deg) rotateY(0deg)';
    }

    fadeTimeout = setTimeout(() => {
      if (!isHovered && rafId) {
        cancelAnimationFrame(rafId);
        rafId = null;
      }
    }, 450);
  });

  // Dukungan interaksi focus pada Search Input
  if (wrapper.querySelector('input')) {
    const input = wrapper.querySelector('input');
    input.addEventListener('focus', () => {
      isHovered = true;
      wrapper.style.setProperty('--glow-opacity', '1');
      if (!rafId) rafId = requestAnimationFrame(renderFrame);
    });
    input.addEventListener('blur', () => {
      isHovered = false;
      wrapper.style.setProperty('--glow-opacity', '0');
    });
  }
}

// Inisialisasi otomatis ke seluruh komponen yang memiliki kelas .glow-wrapper
document.querySelectorAll('.glow-wrapper').forEach(attachEnergyGlow);
Deddy Hidayat
Deddy Hidayat
Educator

Cats lover