Spaces:
Running
Running
| class CustomLockscreen extends HTMLElement { | |
| connectedCallback() { | |
| this.attachShadow({ mode: 'open' }); | |
| this.shadowRoot.innerHTML = ` | |
| <style> | |
| :host { | |
| display: block; | |
| position: fixed; | |
| inset: 0; | |
| z-index: 70; | |
| background: var(--background); | |
| display: none; | |
| flex-direction: column; | |
| align-items: center; | |
| justify-content: center; | |
| transition: opacity 0.3s ease; | |
| } | |
| .lockscreen { | |
| background: linear-gradient(135deg, rgba(79, 70, 229, 0.1), rgba(236, 72, 153, 0.1)); | |
| backdrop-filter: blur(20px); | |
| border: 1px solid rgba(255, 255, 255, 0.1); | |
| border-radius: 2rem; | |
| padding: 3rem 2rem; | |
| text-align: center; | |
| max-width: 90%; | |
| width: 400px; | |
| } | |
| .unlock-btn { | |
| background: linear-gradient(135deg, var(--primary), var(--secondary)); | |
| color: white; | |
| border: none; | |
| border-radius: 1rem; | |
| padding: 1rem 2rem; | |
| font-weight: 600; | |
| transition: all 0.3s ease; | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .unlock-btn::before { | |
| content: ''; | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| right: 0; | |
| bottom: 0; | |
| background: linear-gradient(90deg, transparent, rgba(255,255,255,0.3), transparent); | |
| transform: translateX(-100%); | |
| transition: transform 0.5s ease; | |
| } | |
| .unlock-btn:hover::before { | |
| transform: translateX(100%); | |
| } | |
| .pattern-grid { | |
| display: grid; | |
| grid-template-columns: repeat(3, 1fr); | |
| gap: 1rem; | |
| margin: 2rem 0; | |
| } | |
| .pattern-dot { | |
| width: 20px; | |
| height: 20px; | |
| background: var(--subtle); | |
| border-radius: 50%; | |
| transition: all 0.3s ease; | |
| cursor: pointer; | |
| } | |
| .pattern-dot.selected { | |
| background: var(--accent); | |
| transform: scale(1.2); | |
| } | |
| .pattern-line { | |
| position: absolute; | |
| background: var(--accent); | |
| height: 4px; | |
| border-radius: 2px; | |
| } | |
| .time-display { | |
| font-size: 4rem; | |
| font-weight: 300; | |
| letter-spacing: -2px; | |
| } | |
| .date-display { | |
| font-size: 1.2rem; | |
| color: var(--subtle); | |
| margin-bottom: 2rem; | |
| } | |
| .hint-text { | |
| color: var(--subtle); | |
| font-size: 0.9rem; | |
| margin-top: 1rem; | |
| } | |
| .lock-icon { | |
| width: 60px; | |
| height: 60px; | |
| background: linear-gradient(135deg, var(--primary), var(--secondary)); | |
| border-radius: 50%; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| margin-bottom: 2rem; | |
| } | |
| </style> | |
| <div class="lockscreen p-8"> | |
| <div class="lock-icon mb-6"> | |
| <i data-feather="lock" class="w-8 h-8 text-white"></i> | |
| </div> | |
| <div class="time-display mb-2">00:00</div> | |
| <div class="date-display mb-8">Loading...</div> | |
| <div class="pattern-grid relative"> | |
| ${Array.from({ length: 9 }, (_, i) => ` | |
| <div class="pattern-dot" data-dot="${i}"></div> | |
| `).join('')} | |
| </div> | |
| <p class="hint-text mt-6">Draw your pattern to unlock</p> | |
| </div> | |
| `; | |
| this.initTime(); | |
| this.initPatternLock(); | |
| } | |
| initTime() { | |
| const timeElement = this.shadowRoot.querySelector('.time-display'); | |
| const dateElement = this.shadowRoot.querySelector('.date-display'); | |
| const updateTime = () => { | |
| const now = new Date(); | |
| const timeString = now.toLocaleTimeString('en-US', { | |
| hour12: false, | |
| hour: '2-digit', | |
| minute: '2-digit' | |
| }); | |
| const dateString = now.toLocaleDateString('en-US', { | |
| weekday: 'long', | |
| year: 'numeric', | |
| month: 'long', | |
| day: 'numeric' | |
| }); | |
| timeElement.textContent = timeString; | |
| dateElement.textContent = dateString; | |
| }; | |
| updateTime(); | |
| setInterval(updateTime, 1000); | |
| } | |
| initPatternLock() { | |
| const dots = this.shadowRoot.querySelectorAll('.pattern-dot'); | |
| let selectedDots = []; | |
| const correctPattern = [0, 3, 6, 7, 8]; | |
| dots.forEach((dot, index) => { | |
| dot.addEventListener('click', () => { | |
| if (!selectedDots.includes(index)) { | |
| selectedDots.push(index); | |
| dot.classList.add('selected'); | |
| // Draw lines between selected dots | |
| if (selectedDots.length > 1) { | |
| const prevIndex = selectedDots[selectedDots.length - 2]; | |
| const prevDot = dots[prevIndex]; | |
| const line = document.createElement('div'); | |
| line.className = 'pattern-line'; | |
| // Calculate line position and dimensions | |
| const rect1 = prevDot.getBoundingClientRect(); | |
| const rect2 = dot.getBoundingClientRect(); | |
| const x1 = rect1.left + rect1.width / 2; | |
| const y1 = rect1.top + rect1.height / 2; | |
| const x2 = rect2.left + rect2.width / 2; | |
| const y2 = rect2.top + rect2.height / 2; | |
| const length = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2); | |
| const angle = Math.atan2(y2 - y1, x2 - x1); | |
| line.style.width = `${length}px`; | |
| line.style.transform = `rotate(${angle}rad)'; | |
| line.style.left = `${x1}px`; | |
| line.style.top = `${y1}px'; | |
| this.shadowRoot.querySelector('.pattern-grid').appendChild(line); | |
| } | |
| // Check if pattern is complete | |
| if (selectedDots.length === correctPattern.length) { | |
| if (JSON.stringify(selectedDots) === JSON.stringify(correctPattern)) { | |
| this.toggleLock(); | |
| } else { | |
| // Wrong pattern - reset | |
| setTimeout(() => { | |
| this.resetPattern(); | |
| }, 500); | |
| } | |
| } | |
| }); | |
| }); | |
| // Reset pattern on mouse up (for mobile) | |
| document.addEventListener('mouseup', () => this.resetPattern()); | |
| document.addEventListener('touchend', () => this.resetPattern()); | |
| } | |
| resetPattern() { | |
| const dots = this.shadowRoot.querySelectorAll('.pattern-dot'); | |
| const lines = this.shadowRoot.querySelectorAll('.pattern-line'); | |
| lines.forEach(line => line.remove()); | |
| dots.forEach(dot => dot.classList.remove('selected')); | |
| selectedDots = []; | |
| } | |
| toggleLock() { | |
| if (this.style.display === 'none') { | |
| this.style.display = 'flex'; | |
| document.body.style.overflow = 'hidden'; | |
| } else { | |
| this.style.display = 'none'; | |
| document.body.style.overflow = ''; | |
| // Remove all pattern lines | |
| this.shadowRoot.querySelectorAll('.pattern-line').forEach(line => line.remove()); | |
| selectedDots = []; | |
| } | |
| } | |
| } | |
| customElements.define('custom-lockscreen', CustomLockscreen); |