CSS
/* ─── 09 Soft UI Calculator — warm cream desk calculator ──────────── */
@import url('https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&family=Nunito:wght@400;600;700;800&display=swap');
.nm-clc {
--nm-clc-bg: #f0ebe0;
--nm-clc-sd: #d2cdc0;
--nm-clc-sl: #fffefc;
--nm-clc-ib: #e6e1d6;
--nm-clc-op: #c8783a;
--nm-clc-op2: #a05828;
--nm-clc-fn: #5a6e7a;
--nm-clc-txt: #6a6058;
--nm-clc-txt2:#1a1510;
--nm-clc-neu: 9px 9px 20px var(--nm-clc-sd), -9px -9px 20px var(--nm-clc-sl);
--nm-clc-neu-in: inset 7px 7px 16px var(--nm-clc-sd), inset -7px -7px 16px var(--nm-clc-sl);
--nm-clc-neu-sm: 6px 6px 13px var(--nm-clc-sd), -6px -6px 13px var(--nm-clc-sl);
--nm-clc-neu-sm-in: inset 5px 5px 11px var(--nm-clc-sd), inset -5px -5px 11px var(--nm-clc-sl);
position: relative;
width: 100%;
min-height: 820px;
background: var(--nm-clc-bg);
font-family: 'Nunito', system-ui, sans-serif;
color: var(--nm-clc-txt);
display: flex;
align-items: center;
justify-content: center;
padding: 32px 16px;
overflow: hidden;
box-sizing: border-box;
}
.nm-clc *,
.nm-clc *::before,
.nm-clc *::after { box-sizing: border-box; }
.nm-clc .card {
position: relative;
width: 100%;
max-width: 400px;
background: var(--nm-clc-bg);
border-radius: 40px;
padding: 36px 32px 32px;
box-shadow: 20px 20px 50px var(--nm-clc-sd), -20px -20px 50px var(--nm-clc-sl);
display: flex;
flex-direction: column;
gap: 24px;
}
.nm-clc .card::before {
content: '';
position: absolute;
inset: 0;
border-radius: 40px;
background: radial-gradient(ellipse at 20% 20%, rgba(255, 255, 255, 0.15) 0%, transparent 60%);
pointer-events: none;
}
/* Display */
.nm-clc .display-wrap {
background: var(--nm-clc-bg);
border-radius: 24px;
padding: 22px 22px 18px;
box-shadow: var(--nm-clc-neu-in);
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 8px;
min-height: 110px;
justify-content: flex-end;
position: relative;
overflow: hidden;
}
.nm-clc .display-wrap::before {
content: '';
position: absolute;
inset: 0;
background: repeating-linear-gradient(
0deg, transparent 0px, transparent 2px,
rgba(0, 0, 0, 0.012) 2px, rgba(0, 0, 0, 0.012) 3px
);
border-radius: 24px;
pointer-events: none;
}
.nm-clc .display-expr {
font-family: 'Space Mono', ui-monospace, monospace;
font-size: 13px;
color: var(--nm-clc-txt);
opacity: 0.65;
min-height: 18px;
letter-spacing: 0.5px;
text-align: right;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
width: 100%;
}
.nm-clc .display-result {
font-family: 'Space Mono', ui-monospace, monospace;
font-size: 42px;
font-weight: 700;
color: var(--nm-clc-txt2);
letter-spacing: -1.5px;
line-height: 1;
transition: transform 0.15s ease, color 0.15s ease;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
text-align: right;
width: 100%;
}
.nm-clc .display-result.flash {
color: var(--nm-clc-op);
transform: scale(1.02);
}
@keyframes nm-clc-slide {
from { transform: translateY(6px); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}
.nm-clc .display-result.fresh { animation: nm-clc-slide 0.18s ease-out; }
/* Memory row */
.nm-clc .mem-row {
display: flex;
gap: 10px;
justify-content: center;
}
.nm-clc .mem-key {
flex: 1;
padding: 10px 0;
background: var(--nm-clc-bg);
border: none;
cursor: pointer;
border-radius: 14px;
font-family: 'Space Mono', ui-monospace, monospace;
font-size: 11px;
font-weight: 700;
color: var(--nm-clc-fn);
box-shadow: var(--nm-clc-neu-sm);
transition: all 0.12s;
letter-spacing: 0.5px;
}
.nm-clc .mem-key:hover { box-shadow: 7px 7px 16px var(--nm-clc-sd), -7px -7px 16px var(--nm-clc-sl); }
.nm-clc .mem-key:active {
box-shadow: var(--nm-clc-neu-sm-in);
color: var(--nm-clc-op);
}
/* Keypad */
.nm-clc .keypad {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 12px;
}
.nm-clc .key {
aspect-ratio: 1;
background: var(--nm-clc-bg);
border: none;
cursor: pointer;
border-radius: 50%;
display: inline-flex;
align-items: center;
justify-content: center;
font-family: 'Nunito', system-ui, sans-serif;
font-size: 18px;
font-weight: 700;
color: var(--nm-clc-txt2);
box-shadow: var(--nm-clc-neu-sm);
transition: box-shadow 0.12s ease, transform 0.1s ease, color 0.15s;
user-select: none;
position: relative;
outline: none;
}
.nm-clc .key::before {
content: '';
position: absolute;
top: 12%;
left: 15%;
width: 30%;
height: 25%;
border-radius: 50%;
background: rgba(255, 255, 255, 0.55);
filter: blur(3px);
pointer-events: none;
}
.nm-clc .key:hover {
box-shadow: 8px 8px 18px var(--nm-clc-sd), -8px -8px 18px var(--nm-clc-sl);
transform: translateY(-1px);
}
.nm-clc .key:active,
.nm-clc .key.pressed {
box-shadow: var(--nm-clc-neu-sm-in);
transform: scale(0.94);
color: var(--nm-clc-op);
}
.nm-clc .key.fn {
background: var(--nm-clc-ib);
font-size: 14px;
font-weight: 800;
color: var(--nm-clc-fn);
box-shadow: var(--nm-clc-neu-sm);
}
.nm-clc .key.fn:active,
.nm-clc .key.fn.pressed {
box-shadow: var(--nm-clc-neu-sm-in);
color: var(--nm-clc-op);
}
.nm-clc .key.op {
background: var(--nm-clc-bg);
color: var(--nm-clc-op);
font-size: 22px;
}
.nm-clc .key.op::after {
content: '';
position: absolute;
inset: 6px;
border-radius: 50%;
border: 1.5px solid rgba(200, 120, 58, 0.15);
pointer-events: none;
}
.nm-clc .key.op:active,
.nm-clc .key.op.pressed {
color: var(--nm-clc-op2);
box-shadow: var(--nm-clc-neu-sm-in);
}
.nm-clc .key.eq {
grid-column: span 2;
border-radius: 32px;
aspect-ratio: unset;
padding: 0 24px;
height: 64px;
background: linear-gradient(145deg, var(--nm-clc-op), var(--nm-clc-op2));
color: #fff8f0;
font-size: 22px;
box-shadow: 8px 8px 18px rgba(180, 90, 30, 0.35), -5px -5px 14px var(--nm-clc-sl);
}
.nm-clc .key.eq::before { display: none; }
.nm-clc .key.eq:active,
.nm-clc .key.eq.pressed {
box-shadow: inset 5px 5px 14px rgba(0, 0, 0, 0.22);
transform: scale(0.97);
color: rgba(255, 248, 240, 0.85);
}
.nm-clc .key.zero {
grid-column: span 2;
border-radius: 32px;
aspect-ratio: unset;
padding: 0 24px;
height: 64px;
justify-content: flex-start;
}
@keyframes nm-clc-ripple {
0% { transform: scale(0.95); opacity: 0.6; }
100% { transform: scale(1.6); opacity: 0; }
}
.nm-clc .key.rippling::after {
content: '';
position: absolute;
inset: 0;
border-radius: inherit;
border: 2px solid var(--nm-clc-op);
animation: nm-clc-ripple 0.35s ease-out forwards;
pointer-events: none;
}
.nm-clc .key.op.rippling::after {
animation: nm-clc-ripple 0.35s ease-out forwards;
border: 2px solid var(--nm-clc-op);
}
@media (prefers-reduced-motion: reduce) {
.nm-clc .display-result,
.nm-clc .key { transition: none; animation: none; }
} JS
(() => {
const root = document.querySelector('.nm-clc');
if (!root) return;
let display = '0';
let operand = null;
let operator = null;
let waitingForOperand = false;
let memory = 0;
const resultEl = root.querySelector('[data-nm-clc-result]');
const exprEl = root.querySelector('[data-nm-clc-expr]');
function updateDisplay(val, newExpr) {
const len = String(val).length;
resultEl.style.fontSize = len > 10 ? '26px' : len > 7 ? '34px' : '42px';
resultEl.classList.remove('fresh');
void resultEl.offsetWidth;
resultEl.classList.add('fresh');
resultEl.textContent = val;
if (newExpr !== undefined) exprEl.textContent = newExpr;
}
function compute() {
const cur = parseFloat(display);
let result;
switch (operator) {
case '+': result = operand + cur; break;
case '-': result = operand - cur; break;
case '*': result = operand * cur; break;
case '/': result = cur !== 0 ? operand / cur : 'Error'; break;
}
if (result === 'Error') {
display = 'Error';
} else {
display = parseFloat(result.toFixed(10)).toString();
if (display.length > 12) display = parseFloat(result.toExponential(4)).toString();
}
operand = parseFloat(display);
waitingForOperand = true;
updateDisplay(display);
}
function ripple(action) {
const btn = root.querySelector('[data-nm-clc-key="' + action + '"]');
if (!btn) return;
btn.classList.add('pressed', 'rippling');
setTimeout(() => btn.classList.remove('pressed', 'rippling'), 200);
}
function action(act) {
ripple(act);
if (/^\d$/.test(act)) {
if (waitingForOperand) {
display = act;
waitingForOperand = false;
} else {
display = display === '0' ? act : display + act;
}
if (display.length > 12) return;
updateDisplay(display, display);
return;
}
switch (act) {
case '.':
if (!display.includes('.')) { display += '.'; updateDisplay(display); }
break;
case 'AC':
display = '0'; operand = null; operator = null; waitingForOperand = false;
resultEl.classList.add('flash');
setTimeout(() => resultEl.classList.remove('flash'), 250);
updateDisplay('0', '');
break;
case '+/-':
display = String(-parseFloat(display));
updateDisplay(display);
break;
case '%':
display = String(parseFloat(display) / 100);
updateDisplay(display);
break;
case '/': case '*': case '-': case '+':
if (operator && !waitingForOperand) compute();
operand = parseFloat(display);
operator = act;
waitingForOperand = true;
updateDisplay(display, display + ' ' + (act === '*' ? '×' : act === '/' ? '÷' : act === '-' ? '−' : act));
break;
case '=':
if (operator && !waitingForOperand) {
compute();
operator = null;
resultEl.classList.add('flash');
setTimeout(() => resultEl.classList.remove('flash'), 200);
exprEl.textContent = '';
}
break;
}
}
// Click handlers
root.querySelectorAll('[data-nm-clc-key]').forEach(btn => {
btn.addEventListener('click', () => action(btn.dataset.nmClcKey));
});
// Memory handlers
root.querySelectorAll('[data-nm-clc-mem]').forEach(btn => {
btn.addEventListener('click', () => {
const op = btn.dataset.nmClcMem;
switch (op) {
case 'MC': memory = 0; break;
case 'MR': display = String(memory); waitingForOperand = false; updateDisplay(display); break;
case 'M+': memory += parseFloat(display); break;
case 'M-': memory -= parseFloat(display); break;
case 'MS': memory = parseFloat(display); break;
}
});
});
// Keyboard support
document.addEventListener('keydown', e => {
// Only if the focused element is inside our root, or no input is focused
const ae = document.activeElement;
const inField = ae && (ae.tagName === 'INPUT' || ae.tagName === 'TEXTAREA' || ae.tagName === 'SELECT');
if (inField && !root.contains(ae)) return;
const map = {
'0':'0','1':'1','2':'2','3':'3','4':'4','5':'5','6':'6','7':'7','8':'8','9':'9',
'.':'.', 'Enter':'=', '=':'=',
'+':'+', '-':'-', '*':'*', '/':'/',
'Backspace':'AC', 'Escape':'AC', '%':'%'
};
if (map[e.key]) { e.preventDefault(); action(map[e.key]); }
});
})();