Created
March 15, 2026 06:51
-
-
Save MattWenJun/e60205e24dbd692009abcac534bcaf4d to your computer and use it in GitHub Desktop.
Luna Calculator - Interactive Demo
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!DOCTYPE html> | |
| <html lang="zh"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Luna Calculator</title> | |
| <style> | |
| * { margin: 0; padding: 0; box-sizing: border-box; } | |
| body { | |
| min-height: 100vh; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%); | |
| font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; | |
| } | |
| .calculator { | |
| background: rgba(255,255,255,0.08); | |
| backdrop-filter: blur(20px); | |
| border-radius: 24px; | |
| padding: 28px; | |
| box-shadow: 0 20px 60px rgba(0,0,0,0.4); | |
| border: 1px solid rgba(255,255,255,0.1); | |
| opacity: 0; | |
| transform: translateY(30px); | |
| animation: slideUp 0.6s ease forwards; | |
| } | |
| @keyframes slideUp { | |
| to { opacity: 1; transform: translateY(0); } | |
| } | |
| .display { | |
| background: rgba(0,0,0,0.3); | |
| border-radius: 16px; | |
| padding: 20px 24px; | |
| margin-bottom: 20px; | |
| text-align: right; | |
| min-height: 90px; | |
| display: flex; | |
| flex-direction: column; | |
| justify-content: flex-end; | |
| } | |
| .display .expression { | |
| color: rgba(255,255,255,0.4); | |
| font-size: 16px; | |
| min-height: 24px; | |
| word-break: break-all; | |
| } | |
| .display .result { | |
| color: #fff; | |
| font-size: 42px; | |
| font-weight: 300; | |
| letter-spacing: 1px; | |
| } | |
| .buttons { | |
| display: grid; | |
| grid-template-columns: repeat(4, 1fr); | |
| gap: 12px; | |
| } | |
| .btn { | |
| width: 72px; | |
| height: 64px; | |
| border: none; | |
| border-radius: 16px; | |
| font-size: 22px; | |
| font-weight: 500; | |
| cursor: pointer; | |
| transition: all 0.15s ease; | |
| opacity: 0; | |
| transform: scale(0.5); | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .btn::after { | |
| content: ''; | |
| position: absolute; | |
| inset: 0; | |
| background: radial-gradient(circle at var(--x, 50%) var(--y, 50%), rgba(255,255,255,0.3), transparent 60%); | |
| opacity: 0; | |
| transition: opacity 0.3s; | |
| } | |
| .btn:hover::after { opacity: 1; } | |
| .btn:active { transform: scale(0.95) !important; } | |
| .btn.pop { animation: popIn 0.3s ease forwards; } | |
| @keyframes popIn { | |
| 0% { opacity: 0; transform: scale(0.5); } | |
| 70% { transform: scale(1.08); } | |
| 100% { opacity: 1; transform: scale(1); } | |
| } | |
| .btn.num { | |
| background: rgba(255,255,255,0.12); | |
| color: #fff; | |
| } | |
| .btn.num:hover { background: rgba(255,255,255,0.2); } | |
| .btn.op { | |
| background: rgba(255,165,0,0.7); | |
| color: #fff; | |
| } | |
| .btn.op:hover { background: rgba(255,165,0,0.9); } | |
| .btn.func { | |
| background: rgba(255,255,255,0.06); | |
| color: rgba(255,255,255,0.7); | |
| font-size: 18px; | |
| } | |
| .btn.func:hover { background: rgba(255,255,255,0.12); } | |
| .btn.eq { | |
| background: linear-gradient(135deg, #e94560, #c23152); | |
| color: #fff; | |
| } | |
| .btn.eq:hover { background: linear-gradient(135deg, #ff6b81, #e94560); } | |
| .btn.zero { | |
| grid-column: span 2; | |
| width: 100%; | |
| } | |
| .title { | |
| text-align: center; | |
| color: rgba(255,255,255,0.3); | |
| font-size: 12px; | |
| letter-spacing: 3px; | |
| text-transform: uppercase; | |
| margin-bottom: 16px; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="calculator"> | |
| <div class="title">🌙 Luna Calculator</div> | |
| <div class="display"> | |
| <div class="expression" id="expr"></div> | |
| <div class="result" id="result">0</div> | |
| </div> | |
| <div class="buttons"> | |
| <button class="btn func" data-v="AC">AC</button> | |
| <button class="btn func" data-v="±">±</button> | |
| <button class="btn func" data-v="%">%</button> | |
| <button class="btn op" data-v="÷">÷</button> | |
| <button class="btn num" data-v="7">7</button> | |
| <button class="btn num" data-v="8">8</button> | |
| <button class="btn num" data-v="9">9</button> | |
| <button class="btn op" data-v="×">×</button> | |
| <button class="btn num" data-v="4">4</button> | |
| <button class="btn num" data-v="5">5</button> | |
| <button class="btn num" data-v="6">6</button> | |
| <button class="btn op" data-v="-">−</button> | |
| <button class="btn num" data-v="1">1</button> | |
| <button class="btn num" data-v="2">2</button> | |
| <button class="btn num" data-v="3">3</button> | |
| <button class="btn op" data-v="+">+</button> | |
| <button class="btn num zero" data-v="0">0</button> | |
| <button class="btn num" data-v=".">.</button> | |
| <button class="btn eq" data-v="=">=</button> | |
| </div> | |
| </div> | |
| <script> | |
| // Animate buttons popping in one by one | |
| const btns = document.querySelectorAll('.btn'); | |
| btns.forEach((btn, i) => { | |
| setTimeout(() => btn.classList.add('pop'), 80 + i * 60); | |
| btn.addEventListener('mousemove', e => { | |
| const r = btn.getBoundingClientRect(); | |
| btn.style.setProperty('--x', ((e.clientX - r.left) / r.width * 100) + '%'); | |
| btn.style.setProperty('--y', ((e.clientY - r.top) / r.height * 100) + '%'); | |
| }); | |
| }); | |
| let current = '0', previous = '', operator = '', shouldReset = false; | |
| const resultEl = document.getElementById('result'); | |
| const exprEl = document.getElementById('expr'); | |
| function updateDisplay() { | |
| resultEl.textContent = current; | |
| if (previous && operator) { | |
| exprEl.textContent = previous + ' ' + operator + ' '; | |
| } else { | |
| exprEl.textContent = ''; | |
| } | |
| } | |
| function calculate(a, op, b) { | |
| a = parseFloat(a); b = parseFloat(b); | |
| switch(op) { | |
| case '+': return a + b; | |
| case '−': return a - b; | |
| case '×': return a * b; | |
| case '÷': return b === 0 ? 'Error' : a / b; | |
| } | |
| } | |
| document.querySelector('.buttons').addEventListener('click', e => { | |
| const btn = e.target.closest('.btn'); | |
| if (!btn) return; | |
| const v = btn.dataset.v; | |
| if (v >= '0' && v <= '9' || v === '.') { | |
| if (shouldReset || current === '0' && v !== '.') { | |
| current = v === '.' ? '0.' : v; | |
| shouldReset = false; | |
| } else { | |
| if (v === '.' && current.includes('.')) return; | |
| current += v; | |
| } | |
| } else if (['+','−','×','÷'].includes(v)) { | |
| if (previous && operator && !shouldReset) { | |
| const r = calculate(previous, operator, current); | |
| current = String(r); | |
| previous = current; | |
| } else { | |
| previous = current; | |
| } | |
| operator = v; | |
| shouldReset = true; | |
| } else if (v === '=') { | |
| if (!previous || !operator) return; | |
| const r = calculate(previous, operator, current); | |
| exprEl.textContent = previous + ' ' + operator + ' ' + current + ' ='; | |
| current = String(parseFloat(r.toFixed(10))); | |
| previous = ''; | |
| operator = ''; | |
| shouldReset = true; | |
| } else if (v === 'AC') { | |
| current = '0'; previous = ''; operator = ''; | |
| } else if (v === '±') { | |
| current = String(-parseFloat(current)); | |
| } else if (v === '%') { | |
| current = String(parseFloat(current) / 100); | |
| } | |
| updateDisplay(); | |
| }); | |
| </script> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment