Buckets:
AdhyanshVerma/data-gen-storage / exported_code /Q55_Build a Pattern Lock Screen component using HTML_ /solution_39.html
| **index.html** | |
| ```html | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> | |
| <title>Pattern Lock</title> | |
| <style> | |
| * { box-sizing: border-box; margin: 0; padding: 0; } | |
| html, body { height: 100%; } | |
| body { | |
| min-height: 100vh; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| background: radial-gradient(circle at 30% 20%, #2a3a6a, #0f1530 70%); | |
| font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; | |
| color: #fff; | |
| padding: 20px; | |
| overflow: hidden; | |
| } | |
| .wrap { | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| gap: 22px; | |
| } | |
| .title { | |
| font-size: 20px; | |
| font-weight: 600; | |
| letter-spacing: 2px; | |
| text-transform: uppercase; | |
| color: rgba(255,255,255,0.85); | |
| } | |
| .lock-container { | |
| position: relative; | |
| width: min(90vw, 360px); | |
| aspect-ratio: 1 / 1; | |
| background: rgba(255,255,255,0.04); | |
| border: 1px solid rgba(255,255,255,0.08); | |
| border-radius: 24px; | |
| box-shadow: 0 20px 60px rgba(0,0,0,0.4), inset 0 1px 0 rgba(255,255,255,0.06); | |
| touch-action: none; | |
| user-select: none; | |
| -webkit-user-select: none; | |
| } | |
| .svg-layer { | |
| position: absolute; | |
| inset: 0; | |
| width: 100%; | |
| height: 100%; | |
| z-index: 1; | |
| pointer-events: none; | |
| overflow: visible; | |
| } | |
| .grid { | |
| position: absolute; | |
| inset: 0; | |
| display: grid; | |
| grid-template-columns: repeat(3, 1fr); | |
| grid-template-rows: repeat(3, 1fr); | |
| z-index: 2; | |
| padding: 12%; | |
| } | |
| .cell { | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| } | |
| .node { | |
| width: 32%; | |
| height: 32%; | |
| border-radius: 50%; | |
| background: rgba(255,255,255,0.12); | |
| border: 2px solid rgba(255,255,255,0.28); | |
| transition: background 0.15s ease, border-color 0.15s ease, transform 0.15s ease, box-shadow 0.15s ease; | |
| } | |
| .node.active { | |
| background: #4a90ff; | |
| border-color: #7ab0ff; | |
| transform: scale(1.18); | |
| box-shadow: 0 0 18px rgba(74,144,255,0.7); | |
| } | |
| .node.error { | |
| background: #ff4a4a; | |
| border-color: #ff8a8a; | |
| box-shadow: 0 0 18px rgba(255,74,74,0.7); | |
| } | |
| .line { | |
| stroke: #4a90ff; | |
| stroke-width: 2.5; | |
| stroke-linecap: round; | |
| stroke-linejoin: round; | |
| fill: none; | |
| filter: drop-shadow(0 0 4px rgba(74,144,255,0.6)); | |
| transition: stroke 0.15s ease; | |
| } | |
| .line.error { | |
| stroke: #ff4a4a; | |
| filter: drop-shadow(0 0 4px rgba(255,74,74,0.6)); | |
| } | |
| .result { | |
| height: 26px; | |
| font-size: 18px; | |
| font-weight: 600; | |
| letter-spacing: 0.5px; | |
| text-align: center; | |
| } | |
| .result.success { color: #4ade80; } | |
| .result.error { color: #f87171; } | |
| .reset-btn { | |
| padding: 10px 28px; | |
| border: 1px solid rgba(255,255,255,0.2); | |
| border-radius: 10px; | |
| background: rgba(255,255,255,0.08); | |
| color: #fff; | |
| font-size: 14px; | |
| font-weight: 500; | |
| letter-spacing: 1px; | |
| cursor: pointer; | |
| transition: background 0.15s ease, border-color 0.15s ease; | |
| } | |
| .reset-btn:hover { background: rgba(255,255,255,0.16); border-color: rgba(255,255,255,0.35); } | |
| .reset-btn:active { transform: translateY(1px); } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="wrap"> | |
| <div class="title">Pattern Lock</div> | |
| <div class="lock-container" id="container"> | |
| <svg class="svg-layer" id="svg" viewBox="0 0 100 100" preserveAspectRatio="none"> | |
| <path class="line" id="path"></path> | |
| <path class="line" id="trail"></path> | |
| </svg> | |
| <div class="grid" id="grid"></div> | |
| </div> | |
| <div class="result" id="result"></div> | |
| <button class="reset-btn" id="reset">Reset</button> | |
| </div> | |
| <script> | |
| (function () { | |
| const CORRECT_PATTERN = [1, 2, 3, 6, 9]; | |
| const HIT_RADIUS = 7; // viewBox units | |
| const container = document.getElementById('container'); | |
| const grid = document.getElementById('grid'); | |
| const pathEl = document.getElementById('path'); | |
| const trailEl = document.getElementById('trail'); | |
| const resultEl = document.getElementById('result'); | |
| const resetBtn = document.getElementById('reset'); | |
| let sequence = []; | |
| let recording = false; | |
| let nodeCenters = []; // {x,y} in viewBox units (0..100) | |
| let lastPoint = null; | |
| let errorTimeout = null; | |
| // Build nodes | |
| for (let i = 1; i <= 9; i++) { | |
| const cell = document.createElement('div'); | |
| cell.className = 'cell'; | |
| const node = document.createElement('div'); | |
| node.className = 'node'; | |
| node.dataset.index = i; | |
| cell.appendChild(node); | |
| grid.appendChild(cell); | |
| } | |
| function computeCenters() { | |
| const rect = container.getBoundingClientRect(); | |
| nodeCenters = []; | |
| const nodes = grid.querySelectorAll('.node'); | |
| nodes.forEach((n) => { | |
| const r = n.getBoundingClientRect(); | |
| const cx = ((r.left + r.width / 2) - rect.left) / rect.width * 100; | |
| const cy = ((r.top + r.height / 2) - rect.top) / rect.height * 100; | |
| nodeCenters.push({ x: cx, y: cy }); | |
| }); | |
| } | |
| function getPointerCoords(e) { | |
| const rect = container.getBoundingClientRect(); | |
| let clientX, clientY; | |
| if (e.touches && e.touches.length) { | |
| clientX = e.touches[0].clientX; | |
| clientY = e.touches[0].clientY; | |
| } else if (e.changedTouches && e.changedTouches.length) { | |
| clientX = e.changedTouches[0].clientX; | |
| clientY = e.changedTouches[0].clientY; | |
| } else { | |
| clientX = e.clientX; | |
| clientY = e.clientY; | |
| } | |
| return { | |
| x: (clientX - rect.left) / rect.width * 100, | |
| y: (clientY - rect.top) / rect.height * 100 | |
| }; | |
| } | |
| function distToSegment(px, py, x1, y1, x2, y2) { | |
| const dx = x2 - x1, dy = y2 - y1; | |
| const len2 = dx * dx + dy * dy; | |
| let t = len2 === 0 ? 0 : ((px - x1) * dx + (py - y1) * dy) / len2; | |
| t = Math.max(0, Math.min(1, t)); | |
| const cx = x1 + t * dx, cy = y1 + t * dy; | |
| return Math.hypot(px - cx, py - cy); | |
| } | |
| function updateLines() { | |
| if (sequence.length === 0) { | |
| pathEl.setAttribute('d', ''); | |
| return; | |
| } | |
| let d = ''; | |
| sequence.forEach((idx, i) => { | |
| const c = nodeCenters[idx - 1]; | |
| d += (i === 0 ? 'M' : 'L') + c.x.toFixed(3) + ',' + c.y.toFixed(3) + ' '; | |
| }); | |
| pathEl.setAttribute('d', d.trim()); | |
| } | |
| function updateTrail(x, y) { | |
| if (!recording || sequence.length === 0 || x == null) { | |
| trailEl.setAttribute('d', ''); | |
| return; | |
| } | |
| const last = nodeCenters[sequence[sequence.length - 1] - 1]; | |
| trailEl.setAttribute('d', `M${last.x.toFixed(3)},${last.y.toFixed(3)} L${x.toFixed(3)},${y.toFixed(3)}`); | |
| } | |
| function activateNode(idx) { | |
| const node = grid.querySelector(`.node[data-index="${idx}"]`); | |
| if (node) node.classList.add('active'); | |
| } | |
| function clearPattern() { | |
| if (errorTimeout) { clearTimeout(errorTimeout); errorTimeout = null; } | |
| sequence = []; | |
| grid.querySelectorAll('.node').forEach((n) => { | |
| n.classList.remove('active'); | |
| n.classList.remove('error'); | |
| }); | |
| pathEl.classList.remove('error'); | |
| trailEl.classList.remove('error'); | |
| pathEl.setAttribute('d', ''); | |
| trailEl.setAttribute('d', ''); | |
| resultEl.textContent = ''; | |
| resultEl.className = 'result'; | |
| } | |
| function arraysEqual(a, b) { | |
| if (a.length !== b.length) return false; | |
| return a.every((v, i) => v === b[i]); | |
| } | |
| function finish() { | |
| if (!recording) return; | |
| recording = false; | |
| lastPoint = null; | |
| updateTrail(null, null); | |
| if (sequence.length === 0) return; | |
| if (arraysEqual(sequence, CORRECT_PATTERN)) { | |
| resultEl.textContent = 'Access Granted'; | |
| resultEl.className = 'result success'; | |
| } else { | |
| resultEl.textContent = 'Access Denied'; | |
| resultEl.className = 'result error'; | |
| grid.querySelectorAll('.node.active').forEach((n) => n.classList.add('error')); | |
| pathEl.classList.add('error'); | |
| trailEl.classList.add('error'); | |
| errorTimeout = setTimeout(clearPattern, 1000); | |
| } | |
| } | |
| function start(e) { | |
| if (e.cancelable) e.preventDefault(); | |
| clearPattern(); | |
| computeCenters(); | |
| const p = getPointerCoords(e); | |
| // Only begin recording if press starts on a node | |
| let hitIdx = null; | |
| for (let i = 0; i < 9; i++) { | |
| const c = nodeCenters[i]; | |
| if (Math.hypot(c.x - p.x, c.y - p.y) <= HIT_RADIUS) { hitIdx = i + 1; break; } | |
| } | |
| if (hitIdx === null) return; | |
| recording = true; | |
| sequence = [hitIdx]; | |
| activateNode(hitIdx); | |
| lastPoint = p; | |
| updateLines(); | |
| updateTrail(p.x, p.y); | |
| } | |
| function move(e) { | |
| if (!recording) return; | |
| if (e.cancelable) e.preventDefault(); | |
| const p = getPointerCoords(e); | |
| const prev = lastPoint || p; | |
| // Find unvisited nodes whose center lies near the segment prev->p | |
| const candidates = []; | |
| for (let i = 1; i <= 9; i++) { | |
| if (sequence.includes(i)) continue; | |
| const c = nodeCenters[i - 1]; | |
| const d = distToSegment(c.x, c.y, prev.x, prev.y, p.x, p.y); | |
| if (d <= HIT_RADIUS) { | |
| const distFromPrev = Math.hypot(c.x - prev.x, c.y - prev.y); | |
| candidates.push({ idx: i, distFromPrev }); | |
| } | |
| } | |
| candidates.sort((a, b) => a.distFromPrev - b.distFromPrev); | |
| let changed = false; | |
| for (const cand of candidates) { | |
| if (!sequence.includes(cand.idx)) { | |
| sequence.push(cand.idx); | |
| activateNode(cand.idx); | |
| changed = true; | |
| } | |
| } | |
| if (changed) updateLines(); | |
| updateTrail(p.x, p.y); | |
| lastPoint = p; | |
| } | |
| // Mouse | |
| container.addEventListener('mousedown', start); | |
| window.addEventListener('mousemove', move); | |
| window.addEventListener('mouseup', finish); | |
| // Touch | |
| container.addEventListener('touchstart', start, { passive: false }); | |
| window.addEventListener('touchmove', move, { passive: false }); | |
| window.addEventListener('touchend', finish); | |
| window.addEventListener('touchcancel', finish); | |
| // Reset | |
| resetBtn.addEventListener('click', clearPattern); | |
| // Recompute on resize | |
| window.addEventListener('resize', () => { | |
| computeCenters(); | |
| updateLines(); | |
| }); | |
| // Initial | |
| computeCenters(); | |
| })(); | |
| </script> | |
| </body> | |
| </html> | |
| ``` |
Xet Storage Details
- Size:
- 10.1 kB
- Xet hash:
- 60adc31044213d48db9fb88660dfd5d97f9cc7102d4be3d6a6112696d7223eb5
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.