Загрузка данных
```html
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Современный Калькулятор</title>
<!-- Подключение шрифтов Inter и JetBrains Mono для четких цифр -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@500;700&display=swap" rel="stylesheet">
<style>
:root {
--bg-gradient: radial-gradient(circle at 10% 20%, #1e2638 0%, #0d111a 90%);
--calc-bg: rgba(26, 32, 48, 0.9);
--calc-border: rgba(255, 255, 255, 0.08);
--text-main: #f1f5f9;
--text-secondary: #94a3b8;
--btn-num-bg: rgba(255, 255, 255, 0.06);
--btn-num-hover: rgba(255, 255, 255, 0.12);
--btn-num-text: #f8fafc;
--btn-fn-bg: rgba(255, 255, 255, 0.09);
--btn-fn-hover: rgba(255, 255, 255, 0.16);
--btn-fn-text: #cbd5e1;
--btn-accent-bg: linear-gradient(135deg, #6366f1 0%, #4f46e5 100%);
--btn-accent-hover: linear-gradient(135deg, #818cf8 0%, #6366f1 100%);
--btn-accent-text: #ffffff;
--btn-op-bg: rgba(99, 102, 241, 0.16);
--btn-op-hover: rgba(99, 102, 241, 0.28);
--btn-op-text: #818cf8;
--shadow-calc: 0 25px 60px -15px rgba(0, 0, 0, 0.6), 0 0 1px 1px rgba(255, 255, 255, 0.05);
}
[data-theme="light"] {
--bg-gradient: radial-gradient(circle at 10% 20%, #f8fafc 0%, #cbd5e1 90%);
--calc-bg: rgba(255, 255, 255, 0.95);
--calc-border: rgba(0, 0, 0, 0.08);
--text-main: #0f172a;
--text-secondary: #64748b;
--btn-num-bg: #f1f5f9;
--btn-num-hover: #e2e8f0;
--btn-num-text: #1e293b;
--btn-fn-bg: #e2e8f0;
--btn-fn-hover: #cbd5e1;
--btn-fn-text: #334155;
--btn-accent-bg: linear-gradient(135deg, #4f46e5 0%, #4338ca 100%);
--btn-accent-hover: linear-gradient(135deg, #6366f1 0%, #4f46e5 100%);
--btn-accent-text: #ffffff;
--btn-op-bg: #ede9fe;
--btn-op-hover: #ddd6fe;
--btn-op-text: #4f46e5;
--shadow-calc: 0 20px 50px -10px rgba(79, 70, 229, 0.15), 0 4px 12px rgba(0, 0, 0, 0.05);
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
user-select: none;
-webkit-tap-highlight-color: transparent;
}
body {
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
background: var(--bg-gradient);
font-family: 'Inter', -apple-system, sans-serif;
padding: 20px;
color: var(--text-main);
transition: background 0.3s ease;
}
.calculator-wrapper {
position: relative;
width: 100%;
max-width: 375px;
}
.calculator {
background: var(--calc-bg);
backdrop-filter: blur(24px);
-webkit-backdrop-filter: blur(24px);
border-radius: 28px;
border: 1px solid var(--calc-border);
box-shadow: var(--shadow-calc);
padding: 22px;
position: relative;
overflow: hidden;
display: flex;
flex-direction: column;
gap: 18px;
}
.top-toolbar {
display: flex;
justify-content: space-between;
align-items: center;
}
.brand-title {
font-size: 0.95rem;
font-weight: 600;
letter-spacing: 0.5px;
color: var(--text-secondary);
display: flex;
align-items: center;
gap: 6px;
}
.brand-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: #10b981;
box-shadow: 0 0 8px #10b981;
}
.toolbar-actions {
display: flex;
gap: 8px;
}
.icon-btn {
background: var(--btn-num-bg);
border: 1px solid var(--calc-border);
color: var(--text-main);
width: 36px;
height: 36px;
border-radius: 10px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s ease;
}
.icon-btn:hover {
background: var(--btn-num-hover);
transform: translateY(-1px);
}
.icon-btn:active {
transform: scale(0.95);
}
.screen {
background: rgba(0, 0, 0, 0.15);
border-radius: 20px;
padding: 16px 20px;
display: flex;
flex-direction: column;
align-items: flex-end;
justify-content: flex-end;
min-height: 110px;
border: 1px solid var(--calc-border);
cursor: pointer;
position: relative;
transition: border-color 0.2s ease;
}
.screen:hover {
border-color: rgba(99, 102, 241, 0.4);
}
.history-expression {
font-family: 'JetBrains Mono', monospace;
font-size: 0.95rem;
color: var(--text-secondary);
min-height: 24px;
word-break: break-all;
text-align: right;
transition: color 0.2s ease;
}
.current-value {
font-family: 'JetBrains Mono', monospace;
font-size: 2.5rem;
font-weight: 700;
color: var(--text-main);
width: 100%;
text-align: right;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-top: 4px;
}
.copy-badge {
position: absolute;
top: 10px;
left: 14px;
font-size: 0.72rem;
padding: 3px 8px;
border-radius: 6px;
background: rgba(99, 102, 241, 0.25);
color: #818cf8;
opacity: 0;
transform: translateY(-4px);
transition: all 0.2s ease;
pointer-events: none;
}
.screen:hover .copy-badge {
opacity: 1;
transform: translateY(0);
}
.keypad {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 10px;
}
.btn {
height: 60px;
border-radius: 16px;
border: none;
outline: none;
cursor: pointer;
font-family: 'Inter', sans-serif;
font-size: 1.25rem;
font-weight: 600;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.15s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
overflow: hidden;
}
.btn:active {
transform: scale(0.93);
}
.btn-number {
background: var(--btn-num-bg);
color: var(--btn-num-text);
font-family: 'JetBrains Mono', monospace;
}
.btn-number:hover {
background: var(--btn-num-hover);
}
.btn-fn {
background: var(--btn-fn-bg);
color: var(--btn-fn-text);
font-size: 1.1rem;
}
.btn-fn:hover {
background: var(--btn-fn-hover);
}
.btn-op {
background: var(--btn-op-bg);
color: var(--btn-op-text);
font-size: 1.35rem;
}
.btn-op:hover {
background: var(--btn-op-hover);
}
.btn-op.active {
background: #6366f1;
color: #ffffff;
box-shadow: 0 0 14px rgba(99, 102, 241, 0.5);
}
.btn-equals {
background: var(--btn-accent-bg);
color: var(--btn-accent-text);
box-shadow: 0 4px 14px rgba(99, 102, 241, 0.4);
}
.btn-equals:hover {
background: var(--btn-accent-hover);
box-shadow: 0 6px 20px rgba(99, 102, 241, 0.5);
}
.history-drawer {
position: absolute;
inset: 0;
background: var(--calc-bg);
backdrop-filter: blur(28px);
-webkit-backdrop-filter: blur(28px);
border-radius: 28px;
z-index: 10;
padding: 24px;
display: flex;
flex-direction: column;
gap: 16px;
opacity: 0;
pointer-events: none;
transform: translateY(15px);
transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
}
.history-drawer.open {
opacity: 1;
pointer-events: all;
transform: translateY(0);
}
.history-header {
display: flex;
justify-content: space-between;
align-items: center;
padding-bottom: 12px;
border-bottom: 1px solid var(--calc-border);
}
.history-list {
flex: 1;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 10px;
padding-right: 4px;
}
.history-list::-webkit-scrollbar {
width: 4px;
}
.history-list::-webkit-scrollbar-thumb {
background: var(--calc-border);
border-radius: 4px;
}
.history-item {
background: var(--btn-num-bg);
border: 1px solid var(--calc-border);
padding: 12px 14px;
border-radius: 14px;
cursor: pointer;
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 4px;
transition: all 0.15s ease;
}
.history-item:hover {
background: var(--btn-num-hover);
border-color: rgba(99, 102, 241, 0.3);
transform: translateX(-2px);
}
.history-item-exp {
font-size: 0.85rem;
color: var(--text-secondary);
font-family: 'JetBrains Mono', monospace;
}
.history-item-val {
font-size: 1.15rem;
font-weight: 700;
color: var(--text-main);
font-family: 'JetBrains Mono', monospace;
}
.empty-state {
text-align: center;
color: var(--text-secondary);
margin-top: 60px;
font-size: 0.9rem;
}
.toast {
position: absolute;
bottom: -48px;
left: 50%;
transform: translateX(-50%) translateY(10px);
background: #10b981;
color: #ffffff;
padding: 8px 18px;
border-radius: 20px;
font-size: 0.85rem;
font-weight: 500;
opacity: 0;
pointer-events: none;
transition: all 0.25s ease;
box-shadow: 0 4px 14px rgba(16, 185, 129, 0.3);
z-index: 100;
white-space: nowrap;
}
.toast.show {
opacity: 1;
transform: translateX(-50%) translateY(0);
}
</style>
</head>
<body>
<div class="calculator-wrapper">
<div class="calculator" id="calc">
<!-- Верхняя панель управления -->
<div class="top-toolbar">
<div class="brand-title">
<div class="brand-dot"></div>
<span>CALC PRO</span>
</div>
<div class="toolbar-actions">
<button class="icon-btn" id="history-toggle-btn" title="История вычислений" aria-label="История">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"></circle>
<polyline points="12 6 12 12 16 14"></polyline>
</svg>
</button>
<button class="icon-btn" id="theme-btn" title="Сменить тему" aria-label="Сменить тему">
<svg id="theme-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="5"></circle>
<line x1="12" y1="1" x2="12" y2="3"></line>
<line x1="12" y1="21" x2="12" y2="23"></line>
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
<line x1="1" y1="12" x2="3" y2="12"></line>
<line x1="21" y1="12" x2="23" y2="12"></line>
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
</svg>
</button>
</div>
</div>
<!-- Экран калькулятора -->
<div class="screen" id="calc-screen" title="Нажмите, чтобы скопировать результат">
<span class="copy-badge">Копировать</span>
<div class="history-expression" id="expr-display"></div>
<div class="current-value" id="current-display">0</div>
</div>
<div class="keypad">
<button class="btn btn-fn" data-action="clear" title="Сбросить (Escape)">AC</button>
<button class="btn btn-fn" data-action="backspace" title="Удалить (Backspace)">⌫</button>
<button class="btn btn-fn" data-action="percent" title="Процент">%</button>
<button class="btn btn-op" data-operator="/" title="Деление (/)">÷</button>
<button class="btn btn-number" data-number="7">7</button>
<button class="btn btn-number" data-number="8">8</button>
<button class="btn btn-number" data-number="9">9</button>
<button class="btn btn-op" data-operator="*" title="Умножение (*)">×</button>
<button class="btn btn-number" data-number="4">4</button>
<button class="btn btn-number" data-number="5">5</button>
<button class="btn btn-number" data-number="6">6</button>
<button class="btn btn-op" data-operator="-" title="Вычитание (-)">−</button>
<button class="btn btn-number" data-number="1">1</button>
<button class="btn btn-number" data-number="2">2</button>
<button class="btn btn-number" data-number="3">3</button>
<button class="btn btn-op" data-operator="+" title="Сложение (+)">+</button>
<button class="btn btn-fn" data-action="negate" title="Знак (+/-)">±</button>
<button class="btn btn-number" data-number="0">0</button>
<button class="btn btn-number" data-number="." data-action="decimal">.</button>
<button class="btn btn-equals" data-action="calculate" title="Равно (Enter)">=</button>
</div>
<div class="history-drawer" id="history-drawer">
<div class="history-header">
<span style="font-weight: 600; font-size: 1rem;">История</span>
<div style="display:flex; gap: 8px;">
<button class="icon-btn" id="clear-history-btn" title="Очистить историю">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="3 6 5 6 21 6"></polyline>
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
</svg>
</button>
<button class="icon-btn" id="close-history-btn" title="Закрыть">✕</button>
</div>
</div>
<div class="history-list" id="history-list">
<div class="empty-state">История пуста</div>
</div>
</div>
</div>
<!-- Всплывающий тост копирования -->
<div class="toast" id="toast">Скопировано в буфер!</div>
</div>
<script>
const currentDisplay = document.getElementById('current-display');
const exprDisplay = document.getElementById('expr-display');
const historyDrawer = document.getElementById('history-drawer');
const historyList = document.getElementById('history-list');
const toast = document.getElementById('toast');
const themeBtn = document.getElementById('theme-btn');
const historyToggleBtn = document.getElementById('history-toggle-btn');
const closeHistoryBtn = document.getElementById('close-history-btn');
const clearHistoryBtn = document.getElementById('clear-history-btn');
const screenEl = document.getElementById('calc-screen');
let currentValue = '0';
let previousValue = null;
let operation = null;
let resetScreen = false;
let history = [];
try {
const savedHistory = localStorage.getItem('calc_pro_history');
if (savedHistory) {
history = JSON.parse(savedHistory);
}
} catch (e) {
console.warn('Не удалось прочитать историю из хранилища');
}
function formatNumber(numStr) {
if (!numStr || numStr === 'Error' || numStr === 'Infinity' || numStr === '-Infinity') return numStr;
const parts = numStr.split('.');
const integerPart = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ' ');
return parts.length > 1 ? `${integerPart}.${parts[1]}` : integerPart;
}
function updateDisplay() {
currentDisplay.textContent = formatNumber(currentValue);
const len = currentValue.length;
if (len > 13) {
currentDisplay.style.fontSize = '1.5rem';
} else if (len > 8) {
currentDisplay.style.fontSize = '1.9rem';
} else {
currentDisplay.style.fontSize = '2.5rem';
}
if (operation && previousValue !== null) {
const opSymbol = getOperatorSymbol(operation);
exprDisplay.textContent = `${formatNumber(previousValue)} ${opSymbol}`;
} else {
exprDisplay.textContent = '';
}
document.querySelectorAll('.btn-op').forEach(btn => {
if (btn.dataset.operator === operation && resetScreen) {
btn.classList.add('active');
} else {
btn.classList.remove('active');
}
});
}
function getOperatorSymbol(op) {
switch (op) {
case '+': return '+';
case '-': return '−';
case '*': return '×';
case '/': return '÷';
default: return op;
}
}
function appendNumber(number) {
if (currentValue === 'Error' || currentValue === 'Infinity') {
currentValue = '0';
}
if (resetScreen) {
currentValue = number === '.' ? '0.' : number;
resetScreen = false;
} else {
if (number === '.' && currentValue.includes('.')) return;
if (currentValue === '0' && number !== '.') {
currentValue = number;
} else {
if (currentValue.length >= 16) return;
currentValue += number;
}
}
updateDisplay();
}
function chooseOperation(op) {
if (currentValue === 'Error') return;
if (operation !== null && !resetScreen) {
calculateResult();
}
previousValue = currentValue;
operation = op;
resetScreen = true;
updateDisplay();
}
function calculateResult() {
if (operation === null || previousValue === null || resetScreen) return;
const prev = parseFloat(previousValue);
const curr = parseFloat(currentValue);
let result = 0;
if (isNaN(prev) || isNaN(curr)) return;
switch (operation) {
case '+':
result = prev + curr;
break;
case '-':
result = prev - curr;
break;
case '*':
result = prev * curr;
break;
case '/':
if (curr === 0) {
currentValue = 'Error';
operation = null;
previousValue = null;
resetScreen = true;
updateDisplay();
return;
}
result = prev / curr;
break;
default:
return;
}
result = Math.round((result + Number.EPSILON) * 1e12) / 1e12;
const formattedResult = result.toString();
addHistoryItem(`${formatNumber(previousValue)} ${getOperatorSymbol(operation)} ${formatNumber(currentValue)}`, formattedResult);
currentValue = formattedResult;
operation = null;
previousValue = null;
resetScreen = true;
updateDisplay();
}
function clearAll() {
currentValue = '0';
previousValue = null;
operation = null;
resetScreen = false;
updateDisplay();
}
function deleteLastChar() {
if (resetScreen) return;
if (currentValue === 'Error' || currentValue.length <= 1) {
currentValue = '0';
} else {
currentValue = currentValue.slice(0, -1);
}
updateDisplay();
}
function applyPercentage() {
if (currentValue === 'Error') return;
const val = parseFloat(currentValue);
if (isNaN(val)) return;
if (previousValue !== null && (operation === '+' || operation === '-')) {
const prev = parseFloat(previousValue);
currentValue = (prev * (val / 100)).toString();
} else {
currentValue = (val / 100).toString();
}
updateDisplay();
}
function toggleSign() {
if (currentValue === '0' || currentValue === 'Error') return;
if (currentValue.startsWith('-')) {
currentValue = currentValue.slice(1);
} else {
currentValue = '-' + currentValue;
}
updateDisplay();
}
function addHistoryItem(expression, result) {
history.unshift({ expression, result });
if (history.length > 20) history.pop();
try {
localStorage.setItem('calc_pro_history', JSON.stringify(history));
} catch (e) {}
renderHistory();
}
function renderHistory() {
if (history.length === 0) {
historyList.innerHTML = '<div class="empty-state">История пуста</div>';
return;
}
historyList.innerHTML = '';
history.forEach(item => {
const row = document.createElement('div');
row.className = 'history-item';
row.innerHTML = `
<span class="history-item-exp">${item.expression} =</span>
<span class="history-item-val">${formatNumber(item.result)}</span>
`;
row.addEventListener('click', () => {
currentValue = item.result;
resetScreen = true;
updateDisplay();
historyDrawer.classList.remove('open');
});
historyList.appendChild(row);
});
}
clearHistoryBtn.addEventListener('click', () => {
history = [];
try {
localStorage.removeItem('calc_pro_history');
} catch (e) {}
renderHistory();
});
historyToggleBtn.addEventListener('click', () => {
renderHistory();
historyDrawer.classList.add('open');
});
closeHistoryBtn.addEventListener('click', () => {
historyDrawer.classList.remove('open');
});
function showToast(text) {
toast.textContent = text;
toast.classList.add('show');
setTimeout(() => {
toast.classList.remove('show');
}, 1800);
}
screenEl.addEventListener('click', () => {
if (currentValue === 'Error') return;
const textarea = document.createElement('textarea');
textarea.value = currentValue;
textarea.style.position = 'fixed';
textarea.style.opacity = '0';
document.body.appendChild(textarea);
textarea.select();
try {
document.execCommand('copy');
showToast('Скопировано: ' + currentValue);
} catch (err) {
showToast('Число: ' + currentValue);
} finally {
document.body.removeChild(textarea);
}
});
document.querySelector('.keypad').addEventListener('click', (e) => {
const target = e.target.closest('button');
if (!target) return;
const num = target.dataset.number;
const op = target.dataset.operator;
const action = target.dataset.action;
if (num !== undefined) {
appendNumber(num);
} else if (op !== undefined) {
chooseOperation(op);
} else if (action !== undefined) {
switch (action) {
case 'clear': clearAll(); break;
case 'backspace': deleteLastChar(); break;
case 'percent': applyPercentage(); break;
case 'negate': toggleSign(); break;
case 'calculate': calculateResult(); break;
}
}
});
window.addEventListener('keydown', (e) => {
if (historyDrawer.classList.contains('open')) {
if (e.key === 'Escape') historyDrawer.classList.remove('open');
return;
}
if (e.key >= '0' && e.key <= '9') {
appendNumber(e.key);
} else if (e.key === '.' || e.key === ',') {
appendNumber('.');
} else if (e.key === '+' || e.key === '-' || e.key === '*' || e.key === '/') {
chooseOperation(e.key);
} else if (e.key === 'Enter' || e.key === '=') {
e.preventDefault();
calculateResult();
} else if (e.key === 'Backspace') {
deleteLastChar();
} else if (e.key === 'Escape') {
clearAll();
} else if (e.key === '%') {
applyPercentage();
}
});
function initTheme() {
const savedTheme = localStorage.getItem('calc_pro_theme') || 'dark';
document.body.setAttribute('data-theme', savedTheme);
updateThemeIcon(savedTheme);
}
function updateThemeIcon(theme) {
const icon = document.getElementById('theme-icon');
if (theme === 'light') {
icon.innerHTML = '<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>';
} else {
icon.innerHTML = `
<circle cx="12" cy="12" r="5"></circle>
<line x1="12" y1="1" x2="12" y2="3"></line>
<line x1="12" y1="21" x2="12" y2="23"></line>
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
<line x1="1" y1="12" x2="3" y2="12"></line>
<line x1="21" y1="12" x2="23" y2="12"></line>
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
`;
}
}
themeBtn.addEventListener('click', () => {
const currentTheme = document.body.getAttribute('data-theme') === 'light' ? 'light' : 'dark';
const newTheme = currentTheme === 'light' ? 'dark' : 'light';
document.body.setAttribute('data-theme', newTheme);
try {
localStorage.setItem('calc_pro_theme', newTheme);
} catch (e) {}
updateThemeIcon(newTheme);
});
initTheme();
updateDisplay();
</script>
</body>
</html>
```