Spaces:
Running
Running
| // Skill Bubble Web Component | |
| class SkillBubble extends HTMLElement { | |
| constructor() { | |
| super(); | |
| this.attachShadow({ mode: 'open' }); | |
| } | |
| connectedCallback() { | |
| const skill = this.getAttribute('skill') || 'JavaScript'; | |
| const level = this.getAttribute('level') || '80'; | |
| const icon = this.getAttribute('icon') || 'code'; | |
| this.shadowRoot.innerHTML = ` | |
| <style> | |
| :host { | |
| display: inline-block; | |
| margin: 8px; | |
| } | |
| .bubble { | |
| width: 120px; | |
| height: 120px; | |
| border-radius: 50%; | |
| background: linear-gradient(135deg, #6366f1, #4f46e5); | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| justify-content: center; | |
| color: white; | |
| text-align: center; | |
| transition: transform 0.3s ease, box-shadow 0.3s ease; | |
| cursor: pointer; | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| { | |
| transform: .bubble:hover scale(1.1); | |
| box-shadow: 0 10px 25px rgba(99, 102, 241, 0.4); | |
| } | |
| .icon { | |
| width: 32px; | |
| height: 32px; | |
| margin-bottom: 8px; | |
| stroke: white; | |
| } | |
| .skill-name { | |
| font-size: 12px; | |
| font-weight: 600; | |
| margin-bottom: 4px; | |
| } | |
| .skill-level { | |
| font-size: 10px; | |
| opacity: 0.8; | |
| } | |
| .progress { | |
| position: absolute; | |
| bottom: 0; | |
| left: 0; | |
| height: 4px; | |
| background: rgba(255, 255, 255, 0.3); | |
| width: 100%; | |
| border-radius: 0 0 50% 50%; | |
| } | |
| .progress-fill { | |
| height: 100%; | |
| background: white; | |
| border-radius: 0 0 50% 50%; | |
| transition: width 1s ease; | |
| } | |
| </style> | |
| <div class="bubble"> | |
| <svg class="icon" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4"></path> | |
| </svg> | |
| <div class="skill-name">${skill}</div> | |
| <div class="skill-level">${level}%</div> | |
| <div class="progress"> | |
| <div class="progress-fill" style="width: ${level}%"></div> | |
| </div> | |
| </div> | |
| `; | |
| // Animate progress bar after a delay | |
| setTimeout(() => { | |
| const progressFill = this.shadowRoot.querySelector('.progress-fill'); | |
| if (progressFill) { | |
| progressFill.style.width = `${level}%`; | |
| } | |
| }, 500); | |
| } | |
| } | |
| // Register the custom element | |
| customElements.define('skill-bubble', SkillBubble); | |
| // Usage example: | |
| // <skill-bubble skill="React" level="90" icon="code"></skill-bubble> | |
| // <skill-bubble skill="Node.js" level="85" icon="server"></skill-bubble> | |
| // <skill-bubble skill="Python" level="80" icon="cpu"></skill-bubble> |