Загрузка данных
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<title>Школьный калькулятор и конвертер</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Golos+Text:wght@400;500;600&display=swap">
<style>
:root{--bg:#f6f8fc;--grid:#dce5f4;--card:#fff;--fg:#17213a;--mut:#5d6b8a;--key:#eef2fa;--fn:#dde7fb;--acc:#2748c7;--op:#e3e9ff;--bd:#d5deef;--red:#d03b47;--redfg:#fff}
@media (prefers-color-scheme:dark){:root:not([data-theme="light"]){--bg:#0e1424;--grid:#19223b;--card:#151d33;--fg:#e8edfb;--mut:#8f9bbd;--key:#1d2742;--fn:#22305a;--acc:#8aa0ff;--op:#28345e;--bd:#2a3556;--red:#ff7a85;--redfg:#2a0b0f}}
:root[data-theme="dark"]{--bg:#0e1424;--grid:#19223b;--card:#151d33;--fg:#e8edfb;--mut:#8f9bbd;--key:#1d2742;--fn:#22305a;--acc:#8aa0ff;--op:#28345e;--bd:#2a3556;--red:#ff7a85;--redfg:#2a0b0f}
:root{box-sizing:border-box;padding-top:env(safe-area-inset-top,0px);padding-bottom:env(safe-area-inset-bottom,0px)}
html{scroll-padding-top:env(safe-area-inset-top,0px)}
*,*::before,*::after{box-sizing:inherit}
body{margin:0;background-color:var(--bg);background-image:linear-gradient(var(--grid) 1px,transparent 1px),linear-gradient(90deg,var(--grid) 1px,transparent 1px);background-size:22px 22px;color:var(--fg);font:16px/1.4 "Golos Text",system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;min-height:100vh}
main{max-width:460px;margin:0 auto;padding:12px 12px 28px}
button{font:inherit;color:inherit}
.tabs{display:flex;gap:4px;background:var(--card);padding:4px;border-radius:14px;margin-bottom:12px;border:1px solid var(--bd)}
.tab{flex:1;border:0;background:none;color:var(--mut);padding:10px;border-radius:10px;font-weight:600;cursor:pointer}
.tab.on{background:var(--acc);color:var(--card)}
.card{background:var(--card);border:1px solid var(--bd);border-radius:16px;padding:14px;margin-bottom:10px}
.disp{text-align:right;min-height:104px;display:flex;flex-direction:column;justify-content:flex-end}
.sm{color:var(--mut);font-size:15px;min-height:22px;overflow-wrap:anywhere}
.sm.err{color:var(--red);font-weight:600}
.big{font-size:34px;font-weight:600;line-height:1.15;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}
.g5,.g4{display:grid;gap:8px;margin-bottom:8px}
.g5{grid-template-columns:repeat(5,1fr)}.g4{grid-template-columns:repeat(4,1fr)}
.k{border:0;border-radius:12px;background:var(--key);height:54px;font-size:20px;cursor:pointer;padding:0;-webkit-tap-highlight-color:transparent}
.k:active{filter:brightness(.92);transform:scale(.97)}
.k:focus-visible,.chip:focus-visible,.tab:focus-visible{outline:2px solid var(--acc);outline-offset:2px}
.f{background:var(--fn);font-size:15px;height:44px}
.o{background:var(--op);color:var(--acc);font-weight:600}
.c{color:var(--red);font-weight:600}
.e{background:var(--red);color:var(--redfg);grid-column:span 3;font-weight:600;font-size:24px}
.chips{display:flex;gap:6px;overflow-x:auto;padding-bottom:8px;margin-bottom:6px}
.chip{flex:none;border:1px solid var(--bd);background:var(--card);border-radius:20px;padding:7px 13px;font-size:14px;cursor:pointer}
.chip.on{background:var(--acc);color:var(--card);border-color:var(--acc)}
.fld{display:flex;gap:8px;margin-bottom:8px}
input,select{font:inherit;color:var(--fg);background:var(--key);border:1px solid var(--bd);border-radius:12px;padding:12px;min-width:0}
input{flex:1}select{width:44%}
.sw{display:block;margin:0 auto 8px;border:1px solid var(--bd);background:var(--op);color:var(--acc);border-radius:20px;padding:6px 16px;font-size:14px;font-weight:600;cursor:pointer}
.row{display:flex;justify-content:space-between;gap:12px;padding:9px 0;border-top:1px solid var(--bd)}
.row:first-child{border-top:0}
.row b{font-weight:500;text-align:right;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}
.hint{color:var(--mut);font-size:13px;margin:0 0 8px}
</style>
</head>
<body>
<main>
<nav class="tabs">
<button class="tab on" data-t="calc">Калькулятор</button>
<button class="tab" data-t="conv">Конвертер</button>
</nav>
<section id="calc">
<div class="card disp"><div class="sm" id="sm"></div><div class="big" id="big">0</div></div>
<div class="g5" id="sci"></div>
<div class="g4" id="main"></div>
</section>
<section id="conv" hidden>
<div class="chips" id="chips"></div>
<div class="card">
<div class="fld"><input id="inp" aria-label="Значение"><select id="sf" aria-label="Из"></select></div>
<button class="sw" id="sw">⇅ Поменять местами</button>
<div class="fld"><input id="out" readonly aria-label="Результат"><select id="st" aria-label="В"></select></div>
</div>
<div class="card"><p class="hint" id="hint"></p><div id="list"></div></div>
</section>
</main>
<script>
// ---------- вкладки ----------
const $=id=>document.getElementById(id);
document.querySelectorAll('.tab').forEach(b=>b.onclick=()=>{
document.querySelectorAll('.tab').forEach(x=>x.classList.toggle('on',x===b));
$('calc').hidden=b.dataset.t!=='calc';$('conv').hidden=b.dataset.t!=='conv';
});
// ---------- калькулятор ----------
let expr='',fresh=false,lastExpr='',ans=0,deg=true,msg='';
const fmt=r=>String(parseFloat(r.toPrecision(12)));
function calcExpr(src){
const s=src.replace(/×/g,'*').replace(/÷/g,'/').replace(/−/g,'-').replace(/π/g,'pi').replace(/√/g,'sqrt').replace(/,/g,'.').toLowerCase();
const T=s.match(/\d*\.?\d+|\d+\.|[a-z]+|[-+*/^!%()]|\S/g)||[];
let i=0;const pk=()=>T[i],nx=()=>T[i++];
const k=deg?Math.PI/180:1,R=v=>Math.abs(v)<1e-12?0:v;
const F=Object.assign(Object.create(null),{
sin:x=>R(Math.sin(x*k)),cos:x=>R(Math.cos(x*k)),
tan:x=>{const c=R(Math.cos(x*k));return c?R(Math.sin(x*k))/c:NaN},
ctg:x=>{const n=R(Math.sin(x*k));return n?R(Math.cos(x*k))/n:NaN},
asin:x=>Math.asin(x)/k,acos:x=>Math.acos(x)/k,atan:x=>Math.atan(x)/k,
ln:Math.log,log:Math.log10,sqrt:Math.sqrt,cbrt:Math.cbrt,abs:Math.abs,exp:Math.exp
});
const fact=n=>{if(n<0||n>170||n%1)throw 0;let r=1;for(let j=2;j<=n;j++)r*=j;return r};
const isP=t=>t!==undefined&&/^[\d.a-z(]/.test(t);
function ex(){let v=tm();while(pk()==='+'||pk()==='-'){const o=nx();const r=tm();v=o==='+'?v+r:v-r}return v}
function tm(){let v=un();for(;;){const t=pk();if(t==='*'||t==='/'){nx();const r=un();v=t==='*'?v*r:v/r}else if(isP(t))v*=pw();else return v}}
function un(){if(pk()==='-'){nx();return -un()}if(pk()==='+'){nx();return un()}return pw()}
function pw(){const b=po();if(pk()==='^'){nx();return Math.pow(b,un())}return b}
function po(){let v=pr();for(;;){const t=pk();if(t==='!'){nx();v=fact(v)}else if(t==='%'){nx();v/=100}else return v}}
function pr(){
const t=nx();if(t===undefined)throw 0;
if(/^(\d*\.?\d+|\d+\.)$/.test(t))return parseFloat(t);
if(t==='('){const v=ex();if(pk()===')')nx();return v}
if(t==='pi')return Math.PI;if(t==='e')return Math.E;if(t==='ans')return ans;
if(F[t]){let a;if(pk()==='('){nx();a=ex();if(pk()===')')nx()}else a=un();return F[t](a)}
throw 0;
}
if(!T.length)throw 0;
const v=ex();if(i<T.length||!isFinite(v))throw 0;
return v;
}
function render(){
$('big').textContent=expr||'0';
const sm=$('sm');sm.classList.toggle('err',!!msg);
if(msg){sm.textContent=msg;return}
if(fresh){sm.textContent=lastExpr+' =';return}
try{const r=fmt(calcExpr(expr));sm.textContent=r===expr?'':'= '+r}catch{sm.textContent=''}
}
function put(t){
msg='';
if(t==='clr'){expr='';fresh=false;lastExpr=''}
else if(t==='bk'){expr=fresh?'':expr.replace(/(asin\(|acos\(|atan\(|sin\(|cos\(|tan\(|ctg\(|ln\(|log\(|√\(|Ans|.)$/,'');fresh=false}
else if(t==='eq'){
if(!expr)return;
try{ans=calcExpr(expr);lastExpr=expr;expr=fmt(ans);fresh=true}catch{msg='Ошибка в выражении'}
}else if(t==='mode'){deg=!deg;modeBtn.textContent=deg?'DEG':'RAD'}
else{if(fresh&&!/^[+−×÷^!%]/.test(t))expr='';fresh=false;expr+=t}
render();
}
const mk=(box,list)=>list.forEach(([l,v,c])=>{
const b=document.createElement('button');b.className='k '+(c||'');b.textContent=l;b.onclick=()=>put(v);$(box).appendChild(b);return b});
mk('sci',[['DEG','mode','f'],['sin','sin(','f'],['cos','cos(','f'],['tan','tan(','f'],['ctg','ctg(','f'],
['sin⁻¹','asin(','f'],['cos⁻¹','acos(','f'],['tan⁻¹','atan(','f'],['ln','ln(','f'],['log','log(','f'],
['√','√(','f'],['x²','^2','f'],['xʸ','^','f'],['n!','!','f'],['%','%','f'],
['π','π','f'],['e','e','f'],['(','(','f'],[')',')','f'],['⌫','bk','f']]);
const modeBtn=$('sci').firstChild;
mk('main',[['7','7'],['8','8'],['9','9'],['÷','÷','o'],['4','4'],['5','5'],['6','6'],['×','×','o'],
['1','1'],['2','2'],['3','3'],['−','−','o'],['0','0'],['.','.'],['Ans','Ans'],['+','+','o'],['AC','clr','c'],['=','eq','e']]);
document.addEventListener('keydown',e=>{
if($('calc').hidden||e.target.matches('input,select')||e.ctrlKey||e.metaKey)return;
const m={'*':'×','/':'÷','-':'−','Enter':'eq','=':'eq','Backspace':'bk','Escape':'clr','Delete':'clr'};
const t=m[e.key]||(/^[\d.+^()!%]$/.test(e.key)?e.key:e.key===','?'.':null);
if(t){e.preventDefault();put(t)}
});
render();
// ---------- конвертер ----------
const U={
'Длина':{d:['км','м'],u:{'мм':1e-3,'см':1e-2,'дм':.1,'м':1,'км':1e3,'дюйм':.0254,'фут':.3048,'ярд':.9144,'миля':1609.344}},
'Масса':{d:['кг','г'],u:{'мг':1e-6,'г':1e-3,'кг':1,'ц':100,'т':1e3,'фунт':.45359237,'унция':.028349523125}},
'Площадь':{d:['га','м²'],u:{'мм²':1e-6,'см²':1e-4,'м²':1,'сотка':100,'га':1e4,'км²':1e6,'акр':4046.8564224}},
'Объём':{d:['л','мл'],u:{'мл':1e-6,'см³':1e-6,'л':1e-3,'м³':1,'галлон (США)':.003785411784}},
'Скорость':{d:['км/ч','м/с'],u:{'м/с':1,'км/ч':1/3.6,'миль/ч':.44704,'узел':.514444444}},
'Время':{d:['ч','мин'],u:{'мс':1e-3,'с':1,'мин':60,'ч':3600,'сут':86400,'нед':604800,'год':31557600}},
'Данные':{d:['ГБ','МБ'],u:{'бит':.125,'байт':1,'КБ':1024,'МБ':1048576,'ГБ':1073741824,'ТБ':1099511627776}},
'Углы':{d:['градусы','радианы'],u:{'градусы':1,'радианы':180/Math.PI,'грады':.9,'обороты':360}},
'Температура':{d:['°C','°F'],u:{'°C':1,'°F':1,'K':1}},
'Системы счисления':{d:['Десятичная (10)','Двоичная (2)'],u:{'Двоичная (2)':2,'Восьмеричная (8)':8,'Десятичная (10)':10,'Шестнадцатеричная (16)':16}}
};
let cat='Длина';
const nf=x=>String(parseFloat(x.toPrecision(10)));
function convert(v,f,t){
const u=U[cat].u;
if(cat==='Системы счисления'){
const b=u[f];if(!v||![...v].every(ch=>parseInt(ch,b)<b))return '—';
return parseInt(v,b).toString(u[t]).toUpperCase();
}
const n=Number(v);if(v===''||isNaN(n))return '—';
if(cat==='Температура'){
const C=f==='°C'?n:f==='°F'?(n-32)*5/9:n-273.15;
return nf(t==='°C'?C:t==='°F'?C*9/5+32:C+273.15);
}
return nf(n*u[f]/u[t]);
}
function upd(){
const v=$('inp').value.trim().replace(',','.'),f=$('sf').value,t=$('st').value;
$('out').value=convert(v,f,t);
$('hint').textContent=v?v+' '+f+' равно:':'Введите значение';
$('list').innerHTML=Object.keys(U[cat].u).map(x=>`<div class="row"><span>${x}</span><b>${v?convert(v,f,x):'—'}</b></div>`).join('');
}
function setCat(c){
cat=c;
document.querySelectorAll('.chip').forEach(x=>x.classList.toggle('on',x.textContent===c));
const keys=Object.keys(U[c].u),opts=keys.map(k=>`<option>${k}</option>`).join('');
$('sf').innerHTML=opts;$('st').innerHTML=opts;
$('sf').value=U[c].d[0];$('st').value=U[c].d[1];
$('inp').value=c==='Системы счисления'?'10':'1';
$('inp').inputMode=/Темп|Сист/.test(c)?'text':'decimal';
upd();
}
Object.keys(U).forEach(c=>{const b=document.createElement('button');b.className='chip';b.textContent=c;b.onclick=()=>setCat(c);$('chips').appendChild(b)});
$('inp').oninput=upd;$('sf').onchange=upd;$('st').onchange=upd;
$('sw').onclick=()=>{
const a=$('sf').value,o=$('out').value;$('sf').value=$('st').value;$('st').value=a;
if(o&&o!=='—')$('inp').value=o;upd();
};
setCat('Длина');
</script>
</body>
</html>