diff --git a/2227/index.html b/2227/index.html new file mode 100644 index 00000000..df206442 --- /dev/null +++ b/2227/index.html @@ -0,0 +1,44 @@ + + + + + + + Калькулятор + + + +
+
+ +
0
+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + diff --git a/2227/script.js b/2227/script.js new file mode 100644 index 00000000..b2af6f47 --- /dev/null +++ b/2227/script.js @@ -0,0 +1,213 @@ +const MAX_DIGITS = 15; + +const state = { + currentValue: "0", + previousValue: null, + operator: null, + shouldResetDisplay: false, + isError: false, + lastOperand: null, + lastOperator: null, +}; + +const expressionEl = document.getElementById("expression"); +const resultEl = document.getElementById("result"); + +function formatNumber(str) { + if (str === "Error") return str; + if (str === "Infinity" || str === "-Infinity") return "Error"; + if (str === "NaN") return "Error"; + const num = parseFloat(str); + if (!isFinite(num)) return "Error"; + if (Number.isInteger(num) && Math.abs(num) < 1e15) { + return String(num); + } + const fixed = num.toPrecision(12); + return String(parseFloat(fixed)); +} + +function updateDisplay() { + if (state.isError) { + resultEl.textContent = "Error"; + resultEl.style.color = "#e94560"; + } else { + resultEl.textContent = state.currentValue; + resultEl.style.color = ""; + } +} + +function clearError() { + if (state.isError) { + state.isError = false; + state.currentValue = "0"; + state.previousValue = null; + state.operator = null; + state.lastOperand = null; + state.lastOperator = null; + state.shouldResetDisplay = false; + expressionEl.textContent = ""; + } +} + +function appendDigit(digit) { + clearError(); + if (state.shouldResetDisplay) { + state.currentValue = digit; + state.shouldResetDisplay = false; + } else { + if (digit === "0" && state.currentValue === "0") return; + state.currentValue = state.currentValue === "0" ? digit : state.currentValue + digit; + } + state.currentValue = state.currentValue.slice(0, MAX_DIGITS); + updateDisplay(); +} + +function handleDecimal() { + clearError(); + if (state.shouldResetDisplay) { + state.currentValue = "0."; + state.shouldResetDisplay = false; + updateDisplay(); + return; + } + if (!state.currentValue.includes(".")) { + state.currentValue += "."; + } + updateDisplay(); +} + +function handleBackspace() { + clearError(); + if (state.shouldResetDisplay) return; + if (state.currentValue.length <= 1 || (state.currentValue.length === 2 && state.currentValue.startsWith("-"))) { + state.currentValue = "0"; + } else { + state.currentValue = state.currentValue.slice(0, -1); + } + updateDisplay(); +} + +function handleOperator(op) { + clearError(); + const current = parseFloat(state.currentValue); + if (state.operator && !state.shouldResetDisplay) { + compute(); + } + state.previousValue = current; + state.operator = op; + state.shouldResetDisplay = true; + expressionEl.textContent = `${formatNumber(String(current))} ${op}`; +} + +function compute() { + const prev = state.previousValue; + const current = parseFloat(state.currentValue); + if (prev === null) return; + + let result; + switch (state.operator) { + case "+": result = prev + current; break; + case "-": result = prev - current; break; + case "*": result = prev * current; break; + case "/": + if (current === 0) { + state.isError = true; + state.currentValue = "Error"; + state.operator = null; + state.previousValue = null; + updateDisplay(); + return; + } + result = prev / current; + break; + default: return; + } + + state.lastOperand = current; + state.lastOperator = state.operator; + state.currentValue = formatNumber(String(result)); + state.operator = null; + state.previousValue = null; + state.shouldResetDisplay = true; + expressionEl.textContent = ""; + updateDisplay(); +} + +function handleEquals() { + clearError(); + if (state.operator) { + compute(); + } else if (state.lastOperator !== null && state.lastOperand !== null) { + state.previousValue = parseFloat(state.currentValue); + state.operator = state.lastOperator; + compute(); + } +} + +function handleClear() { + state.currentValue = "0"; + state.previousValue = null; + state.operator = null; + state.lastOperand = null; + state.lastOperator = null; + state.shouldResetDisplay = false; + state.isError = false; + expressionEl.textContent = ""; + resultEl.style.color = ""; + updateDisplay(); +} + +function handleSign() { + if (state.isError) return; + if (state.shouldResetDisplay) return; + state.currentValue = String(-parseFloat(state.currentValue)); + updateDisplay(); +} + +function handlePercent() { + if (state.isError) return; + const num = parseFloat(state.currentValue); + if (state.operator && state.previousValue !== null) { + const adjusted = state.previousValue * (num / 100); + state.currentValue = formatNumber(String(adjusted)); + } else { + state.currentValue = formatNumber(String(num / 100)); + } + updateDisplay(); +} + +document.getElementById("buttons").addEventListener("click", (e) => { + const btn = e.target.closest("button"); + if (!btn) return; + + const action = btn.dataset.action; + switch (action) { + case "digit": appendDigit(btn.dataset.value); break; + case "decimal": handleDecimal(); break; + case "backspace": handleBackspace(); break; + case "operator": handleOperator(btn.dataset.value); break; + case "equals": handleEquals(); break; + case "clear": handleClear(); break; + case "sign": handleSign(); break; + } +}); + +document.addEventListener("keydown", (e) => { + if (e.ctrlKey || e.metaKey || e.altKey) return; + if (e.key >= "0" && e.key <= "9") { + appendDigit(e.key); + return; + } + switch (e.key) { + case ".": handleDecimal(); break; + case "Backspace": handleBackspace(); break; + case "Delete": handleClear(); break; + case "Escape": handleClear(); break; + case "Enter": + case "=": handleEquals(); break; + case "+": handleOperator("+"); break; + case "-": handleOperator("-"); break; + case "*": handleOperator("*"); break; + case "/": handleOperator("/"); break; + } +}); diff --git a/2227/style.css b/2227/style.css new file mode 100644 index 00000000..2810d917 --- /dev/null +++ b/2227/style.css @@ -0,0 +1,236 @@ +*, *::before, *::after { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; + display: flex; + justify-content: center; + align-items: center; + min-height: 100vh; + background: #1a1a2e; + padding: 16px; + -webkit-tap-highlight-color: transparent; +} + +/* ---- Calculator container ---- */ +#app { + width: clamp(260px, 90vw, 360px); + background: #16213e; + border-radius: 20px; + padding: clamp(14px, 4vw, 24px); + box-shadow: + 0 8px 32px rgba(0, 0, 0, 0.35), + 0 2px 8px rgba(0, 0, 0, 0.2); +} + +/* ---- Display ---- */ +#display { + background: #0f3460; + border-radius: 14px; + padding: clamp(12px, 3vw, 20px); + margin-bottom: clamp(12px, 3vw, 18px); + min-height: 90px; + text-align: right; + color: #e0e0e0; + overflow: hidden; + position: relative; +} + +#expression { + font-size: clamp(12px, 3.5vw, 15px); + color: #8fa8c8; + min-height: 22px; + word-break: break-all; + line-height: 1.4; + transition: color 0.2s; +} + +#result { + font-size: clamp(26px, 8vw, 36px); + font-weight: 300; + min-height: 44px; + word-break: break-all; + line-height: 1.2; + letter-spacing: -0.02em; + transition: color 0.15s; + display: flex; + align-items: flex-end; + justify-content: flex-end; +} + +/* ---- Button grid ---- */ +#buttons { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: clamp(6px, 1.8vw, 10px); +} + +/* ---- Base button ---- */ +button { + font-size: clamp(17px, 5vw, 22px); + padding: clamp(12px, 3.5vw, 18px) 0; + border: none; + border-radius: 12px; + cursor: pointer; + background: #1a1a40; + color: #e0e0e0; + font-weight: 400; + transition: + background 0.15s ease, + transform 0.1s ease, + box-shadow 0.15s ease; + outline: none; + user-select: none; + -webkit-user-select: none; + position: relative; + overflow: hidden; +} + +/* Ripple-like highlight on tap */ +button::after { + content: ""; + position: absolute; + inset: 0; + background: radial-gradient(circle at center, rgba(255,255,255,0.12) 0%, transparent 70%); + opacity: 0; + transition: opacity 0.2s; + pointer-events: none; +} + +button:active::after { + opacity: 1; +} + +/* Hover */ +button:hover { + background: #262655; +} + +/* Active press */ +button:active { + transform: scale(0.94); + background: #2e2e65; +} + +/* Keyboard focus ring */ +button:focus-visible { + box-shadow: 0 0 0 3px rgba(83, 52, 131, 0.6); + z-index: 1; +} + +/* ---- Function keys (C, +/-, backspace) ---- */ +[data-action="clear"], +[data-action="sign"], +[data-action="backspace"] { + background: #0f3460; + color: #8fbceb; + font-weight: 500; +} + +[data-action="clear"]:hover, +[data-action="sign"]:hover, +[data-action="backspace"]:hover { + background: #154178; +} + +[data-action="clear"]:active, +[data-action="sign"]:active, +[data-action="backspace"]:active { + background: #1b4f8f; + transform: scale(0.94); +} + +/* ---- Operator keys ---- */ +[data-action="operator"] { + background: #533483; + color: #d9c4f0; + font-weight: 500; +} + +[data-action="operator"]:hover { + background: #6544a3; +} + +[data-action="operator"]:active { + background: #7654b3; + transform: scale(0.94); +} + +/* ---- Equals key ---- */ +[data-action="equals"] { + background: linear-gradient(135deg, #e94560, #d63251); + color: #fff; + font-weight: 600; + box-shadow: 0 3px 12px rgba(233, 69, 96, 0.3); +} + +[data-action="equals"]:hover { + background: linear-gradient(135deg, #f05575, #e04060); + box-shadow: 0 4px 16px rgba(233, 69, 96, 0.4); +} + +[data-action="equals"]:active { + background: linear-gradient(135deg, #ff6585, #f05070); + transform: scale(0.94); + box-shadow: 0 2px 8px rgba(233, 69, 96, 0.3); +} + +/* ---- Zero button span ---- */ +#btn-zero { + grid-column: span 2; +} + +/* ---- Responsive: narrow screens ---- */ +@media (max-width: 320px) { + #app { + border-radius: 14px; + } + + button { + border-radius: 10px; + padding: 10px 0; + } + + #display { + border-radius: 10px; + min-height: 76px; + } +} + +/* ---- Responsive: wider desktop ---- */ +@media (min-width: 600px) { + #app { + width: 360px; + } + + button { + padding: 20px 0; + font-size: 22px; + } + + #result { + font-size: 38px; + } + + #expression { + font-size: 16px; + } +} + +/* ---- Reduced motion preference ---- */ +@media (prefers-reduced-motion: reduce) { + button { + transition: none; + } + + button::after { + transition: none; + } + + #expression, #result { + transition: none; + } +}