bsc

Comprehensive codebase and cou...
Log | Files | Refs | README | LICENSE

SponsorButton.vue (4822B)


      1 <script setup lang="ts">
      2 import { ref, onMounted } from 'vue'
      3 
      4 const particles = ref<SVGElement[]>([])
      5 const isBeating = ref(false)
      6 const isTouchDevice = ref(false)
      7 
      8 onMounted(() => {
      9   isTouchDevice.value = window.matchMedia('(hover: none) and (pointer: coarse)').matches
     10 })
     11 
     12 // Varied colors like anime.js — different pinks, reds, corals
     13 const particleColors = [
     14   '#ff4772', '#ff6b9d', '#ff3355', '#ff8fab',
     15   '#e91e63', '#f06292', '#ff1744', '#ff80ab'
     16 ]
     17 
     18 const triggerBurst = () => {
     19   if (isTouchDevice.value || !particles.value?.length) return
     20 
     21   // Cancel any running animations first so re-hover always works
     22   particles.value.forEach((particle, i) => {
     23     particle.getAnimations().forEach(a => a.cancel())
     24 
     25     const angle = (i / particles.value.length) * Math.PI * 2 + (Math.random() - 0.5) * 0.6
     26     const distance = 28 + Math.random() * 32
     27     const tx = Math.cos(angle) * distance
     28     const ty = Math.sin(angle) * distance
     29     const size = 0.6 + Math.random() * 0.8 // vary particle size
     30 
     31     // Random color from palette
     32     const color = particleColors[Math.floor(Math.random() * particleColors.length)];
     33     (particle as SVGElement & { style: CSSStyleDeclaration }).style.color = color
     34 
     35     particle.animate([
     36       {
     37         transform: `translate(0px, 0px) scale(${size})`,
     38         opacity: 1,
     39         visibility: 'visible'
     40       },
     41       {
     42         transform: `translate(${tx * 0.4}px, ${ty * 0.4}px) scale(${size * 1.1})`,
     43         opacity: 1,
     44         offset: 0.2
     45       },
     46       {
     47         transform: `translate(${tx}px, ${ty}px) scale(0)`,
     48         opacity: 0,
     49         visibility: 'hidden'
     50       }
     51     ], {
     52       duration: 500 + Math.random() * 250,
     53       easing: 'cubic-bezier(0.25, 1, 0.5, 1)',
     54       fill: 'forwards'
     55     })
     56   })
     57 
     58   // Heart beat
     59   isBeating.value = true
     60   setTimeout(() => { isBeating.value = false }, 400)
     61 }
     62 </script>
     63 
     64 <template>
     65   <div class="sponsor-wrapper">
     66     <a
     67       href="https://github.com/sponsors/notamitgamer"
     68       class="sponsor-button"
     69       target="_blank"
     70       rel="noopener noreferrer"
     71       title="Sponsor Amit Dutta on GitHub"
     72       @mouseenter="triggerBurst"
     73       @click="triggerBurst"
     74       :class="{ 'mobile-pill': isTouchDevice }"
     75     >
     76       <svg
     77         xmlns="http://www.w3.org/2000/svg"
     78         viewBox="4 7 16 12"
     79         class="icon heart-icon"
     80         :class="{ beating: isBeating }"
     81       >
     82         <path
     83           fill="currentColor"
     84           d="M12 18.445a.778.778 0 0 1-.34-.078C11.39 18.235 5 15.077 5 9.889a3.889 3.889 0 0 1 6.638-2.75L12 7.5l.362-.361A3.889 3.889 0 0 1 19 9.889c0 5.17-6.387 8.344-6.66 8.478a.778.778 0 0 1-.34.078z"
     85         />
     86       </svg>
     87       <span>Sponsor</span>
     88     </a>
     89 
     90     <!-- Burst particles — desktop only -->
     91     <svg
     92       v-if="!isTouchDevice"
     93       v-for="i in 12"
     94       :key="i"
     95       ref="particles"
     96       xmlns="http://www.w3.org/2000/svg"
     97       viewBox="4 7 16 12"
     98       class="particle"
     99     >
    100       <path
    101         fill="currentColor"
    102         d="M12 18.445a.778.778 0 0 1-.34-.078C11.39 18.235 5 15.077 5 9.889a3.889 3.889 0 0 1 6.638-2.75L12 7.5l.362-.361A3.889 3.889 0 0 1 19 9.889c0 5.17-6.387 8.344-6.66 8.478a.778.778 0 0 1-.34.078z"
    103       />
    104     </svg>
    105   </div>
    106 </template>
    107 
    108 <style scoped>
    109 .sponsor-wrapper {
    110   position: relative;
    111   display: inline-flex;
    112   align-items: center;
    113   margin-left: 8px;
    114 }
    115 
    116 .sponsor-button {
    117   display: inline-flex;
    118   align-items: center;
    119   gap: 5px;
    120   padding: 5px 12px 5px 10px;
    121   background-color: transparent;
    122   color: var(--vp-c-text-1, #3c3c43);
    123   font-size: 13px;
    124   font-weight: 500;
    125   border-radius: 20px;
    126   transition:
    127     background-color 0.2s ease,
    128     color 0.2s ease;
    129   text-decoration: none;
    130   position: relative;
    131   z-index: 2;
    132   white-space: nowrap;
    133   cursor: pointer;
    134 }
    135 
    136 .sponsor-button:hover {
    137   background-color: rgba(255, 71, 114, 0.1);
    138   color: #ff4772;
    139 }
    140 
    141 /* Heart icon */
    142 .heart-icon {
    143   width: 15px;
    144   height: 15px;
    145   color: #ff4772;
    146   flex-shrink: 0;
    147   transition: transform 0.25s cubic-bezier(0.175, 0.885, 0.32, 1.275);
    148 }
    149 
    150 .sponsor-button:hover .heart-icon {
    151   transform: scale(1.2);
    152 }
    153 
    154 /* Beat animation triggered on click */
    155 .heart-icon.beating {
    156   animation: heartbeat 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
    157 }
    158 
    159 @keyframes heartbeat {
    160   0%   { transform: scale(1); }
    161   40%  { transform: scale(1.4); }
    162   70%  { transform: scale(0.9); }
    163   100% { transform: scale(1.2); }
    164 }
    165 
    166 /* Mobile: static pink pill, no hover/animation */
    167 .mobile-pill {
    168   background-color: rgba(255, 71, 114, 0.12);
    169   color: #ff4772;
    170 }
    171 
    172 .mobile-pill .heart-icon {
    173   color: #ff4772;
    174 }
    175 
    176 .particle {
    177   position: absolute;
    178   top: 50%;
    179   left: 18px; /* roughly where the heart icon center sits */
    180   width: 10px;
    181   height: 10px;
    182   margin-top: -5px;
    183   margin-left: -5px;
    184   color: #ff4772;
    185   visibility: hidden;
    186   opacity: 0;
    187   pointer-events: none;
    188   z-index: 1;
    189 }
    190 </style>
© notamitgamer • Site Built: 2026-07-21 13:58:23 UTC • git-mirror commit: 1037f62 [view raw info]
Originally created with stagit • modified by notamitgamer
Forked from github.com/notamitgamer/git-mirror