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 @@
+
+
+
+
+
+
+ Калькулятор
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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;
+ }
+}