Загрузка данных


from pathlib import Path
import re, subprocess, textwrap, os, shutil
widget_code = r'''
<style>
.calc{--key:64px;width:min(100%,420px);margin:0 auto;padding:16px;border:1px solid var(--kimi-color-border);border-radius:16px;background:var(--kimi-color-surface-raised);font-family:var(--kimi-font-sans)}
.calc button{font:inherit}.calc *{box-sizing:border-box}.top{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}.title{font-size:17px;font-weight:500;line-height:24px}.sub{font-size:12px;line-height:16px;color:var(--kimi-color-text-tertiary)}.mem-badge{min-width:28px;height:28px;padding:0 8px;border-radius:999px;display:inline-flex;align-items:center;justify-content:center;border:1px solid color-mix(in srgb, var(--kimi-color-warning) 42%, transparent);color:var(--kimi-color-warning);background:color-mix(in srgb, var(--kimi-color-warning) 12%, transparent);font-size:12px;font-weight:500}.mem-badge[hidden]{display:none}.pulse{animation:pulse var(--t-normal) var(--ease-out)}@keyframes pulse{0%{transform:scale(.92)}100%{transform:scale(1)}}.screen{margin-top:16px;min-height:116px;padding:16px;border-radius:12px;background:var(--kimi-color-surface-strong);border:1px solid var(--kimi-color-border);display:flex;flex-direction:column;justify-content:flex-end;align-items:flex-end;gap:4px}.expr{min-height:20px;font-size:14px;line-height:20px;color:var(--kimi-color-text-tertiary);font-variant-numeric:tabular-nums;font-feature-settings:"tnum" 1;overflow-wrap:anywhere;text-align:right}.result{max-width:100%;font-size:44px;line-height:1.08;font-weight:500;color:var(--kimi-color-text-primary);font-variant-numeric:tabular-nums;font-feature-settings:"tnum" 1;overflow-wrap:anywhere;text-align:right}.result.small{font-size:34px}.result.tiny{font-size:28px}.result.error{color:var(--kimi-color-danger)}.memory{margin-top:12px;display:grid;grid-template-columns:repeat(4,1fr);gap:8px}.memory button{height:28px;border-radius:999px;border:1px solid var(--kimi-color-border);background:transparent;color:var(--kimi-color-text-secondary);font-size:12px;cursor:pointer;transition:transform var(--t-fast) var(--ease-out),color var(--t-fast),border-color var(--t-fast)}.memory button:hover{color:var(--kimi-color-text-primary);border-color:var(--kimi-color-text-tertiary)}.memory button:active{transform:scale(.96)}.grid{margin-top:12px;display:grid;grid-template-columns:repeat(4,1fr);gap:12px}.key{aspect-ratio:1;border:0;border-radius:999px;display:flex;align-items:center;justify-content:center;font-size:24px;font-weight:400;color:var(--kimi-color-text-primary);background:var(--kimi-color-surface-muted);cursor:pointer;user-select:none;-webkit-tap-highlight-color:transparent;transition:transform var(--t-fast) var(--ease-out),background var(--t-fast),border-color var(--t-fast),color var(--t-fast)}.key:hover{background:var(--kimi-color-surface-strong)}.key:active{transform:scale(.96)}.key:focus-visible{outline:2px solid var(--kimi-color-text-primary);outline-offset:2px}.util{background:transparent;border:1px solid var(--kimi-color-border);color:var(--kimi-color-text-secondary)}.util:hover{background:var(--kimi-color-surface-muted)}.ac{color:var(--kimi-color-danger);background:color-mix(in srgb, var(--kimi-color-danger) 10%, transparent);border-color:color-mix(in srgb, var(--kimi-color-danger) 22%, transparent)}.op{background:color-mix(in srgb, var(--kimi-color-accent) 14%, transparent);border:1px solid color-mix(in srgb, var(--kimi-color-accent) 28%, transparent);color:var(--kimi-color-text-primary)}.op:hover{background:color-mix(in srgb, var(--kimi-color-accent) 22%, transparent)}.eq{background:var(--kimi-color-text-primary);border:1px solid var(--kimi-color-text-primary);color:var(--kimi-color-surface);font-weight:500}.eq:hover{background:var(--kimi-color-text-secondary)}.history{margin-top:12px;display:grid;gap:8px}.hist{display:flex;align-items:center;justify-content:space-between;gap:12px;width:100%;padding:8px 12px;border-radius:10px;border:1px solid var(--kimi-color-border);background:transparent;color:var(--kimi-color-text-secondary);font-size:14px;line-height:20px;text-align:left;cursor:pointer;transition:transform var(--t-fast) var(--ease-out),border-color var(--t-fast),color var(--t-fast)}.hist strong{font-weight:500;color:var(--kimi-color-text-primary);font-variant-numeric:tabular-nums;font-feature-settings:"tnum" 1}.hist:hover{border-color:var(--kimi-color-text-tertiary);color:var(--kimi-color-text-primary)}.hist:active{transform:scale(.99)}.empty{padding:8px 12px;border:1px dashed var(--kimi-color-border);border-radius:10px;color:var(--kimi-color-text-quaternary);font-size:12px;line-height:16px}@media (max-width:360px){.calc{padding:12px}.screen{min-height:104px;padding:12px}.grid{gap:8px}.result{font-size:38px}.result.small{font-size:30px}.result.tiny{font-size:25px}}
</style>
<div class="calc" id="calc">
  <div class="top">
    <div>
      <div class="title">Калькулятор</div>
      <div class="sub">Apple-style · полный функционал</div>
    </div>
    <div class="mem-badge" id="memBadge" hidden>M</div>
  </div>
  <div class="screen" aria-live="polite">
    <div class="expr" id="expr"></div>
    <div class="result" id="result">0</div>
  </div>
  <div class="memory" aria-label="Память">
    <button type="button" data-m="mc">MC</button>
    <button type="button" data-m="mr">MR</button>
    <button type="button" data-m="m+">M+</button>
    <button type="button" data-m="m-">M−</button>
  </div>
  <div class="grid" id="grid">
    <button type="button" class="key util ac" data-action="clear" aria-label="Очистить">AC</button>
    <button type="button" class="key util" data-action="back" aria-label="Стереть">⌫</button>
    <button type="button" class="key util" data-action="percent" aria-label="Процент">%</button>
    <button type="button" class="key op" data-op="/" aria-label="Разделить">÷</button>
    <button type="button" class="key" data-digit="7">7</button>
    <button type="button" class="key" data-digit="8">8</button>
    <button type="button" class="key" data-digit="9">9</button>
    <button type="button" class="key op" data-op="*" aria-label="Умножить">×</button>
    <button type="button" class="key" data-digit="4">4</button>
    <button type="button" class="key" data-digit="5">5</button>
    <button type="button" class="key" data-digit="6">6</button>
    <button type="button" class="key op" data-op="-" aria-label="Вычесть">−</button>
    <button type="button" class="key" data-digit="1">1</button>
    <button type="button" class="key" data-digit="2">2</button>
    <button type="button" class="key" data-digit="3">3</button>
    <button type="button" class="key op" data-op="+" aria-label="Сложить">+</button>
    <button type="button" class="key util" data-action="sign" aria-label="Сменить знак">±</button>
    <button type="button" class="key" data-digit="0">0</button>
    <button type="button" class="key" data-action="dot">.</button>
    <button type="button" class="key eq" data-action="equals" aria-label="Равно">=</button>
  </div>
  <div class="history" id="history"></div>
</div>
<script>
const resultEl=document.getElementById("result");
const exprEl=document.getElementById("expr");
const historyEl=document.getElementById("history");
const memBadge=document.getElementById("memBadge");
let current="0";
let previous=null;
let operator=null;
let waiting=false;
let justEvaluated=false;
let memory=0;
let history=[];
const symbols={"/":"÷","*":"×","-":"−","+":"+"};
function round(n){return Math.round((n+Number.EPSILON)*1e10)/1e10}
function fmt(n){if(!isFinite(n))return"Ошибка";const a=Math.abs(n);if(a!==0&&(a>=1e12||a<1e-8)){return n.toExponential(6).replace(".",",").replace("e+","e").replace("e-","e−")}return new Intl.NumberFormat("ru-RU",{maximumFractionDigits:10}).format(n)}
function shown(){if(current==="Ошибка")return current;return justEvaluated?fmt(Number(current)):current.replace(".",",")}
function setError(){current="Ошибка";previous=null;operator=null;waiting=false;justEvaluated=true;update()}
function update(){const text=shown();resultEl.textContent=text;resultEl.classList.toggle("error",current==="Ошибка");const len=text.length;resultEl.classList.toggle("small",len>12&&len<=18);resultEl.classList.toggle("tiny",len>18);exprEl.textContent=operator!==null?fmt(previous)+" "+symbols[operator]:"";memBadge.hidden=memory===0;renderHistory()}
function renderHistory(){historyEl.innerHTML="";if(!history.length){const d=document.createElement("div");d.className="empty";d.textContent="История появится после расчёта";historyEl.appendChild(d);return}history.forEach(item=>{const b=document.createElement("button");b.type="button";b.className="hist";const s=document.createElement("span");s.textContent=item.expr;const strong=document.createElement("strong");strong.textContent="= "+item.res;b.append(s,strong);b.addEventListener("click",()=>{current=item.raw;previous=null;operator=null;waiting=false;justEvaluated=true;update()});historyEl.appendChild(b)})}
function pushHistory(a,op,b,r){history.unshift({expr:fmt(a)+" "+symbols[op]+" "+fmt(b),res:fmt(r),raw:String(round(r))});history=history.slice(0,4)}
function inputDigit(d){if(current==="Ошибка")current="0";if(waiting||justEvaluated){current=d;waiting=false;justEvaluated=false}else{if(current==="0"){current=d}else if(current.replace("-","").replace(".","").length<12){current+=d}}update()}
function inputDot(){if(current==="Ошибка")current="0";if(waiting||justEvaluated){current="0.";waiting=false;justEvaluated=false}else if(!current.includes(".")){current+="."}update()}
function toggleSign(){if(current==="Ошибка"||current==="0")return;current=current.startsWith("-")?current.slice(1):"-"+current;if(current==="-0")current="0";update()}
function percent(){let x=Number(current);if(!isFinite(x))return;if(previous!==null&&(operator==="+"||operator==="-")){x=previous*x/100}else{x=x/100}x=round(x);current=String(x);waiting=false;justEvaluated=false;update()}
function resolve(b){if(previous===null||operator===null||!isFinite(b))return false;let r;switch(operator){case"+":r=previous+b;break;case"-":r=previous-b;break;case"*":r=previous*b;break;case"/":if(b===0){setError();return false}r=previous/b;break;default:return false}r=round(r);if(!isFinite(r)||Math.abs(r)>1e15){setError();return false}pushHistory(previous,operator,b,r);current=String(r);previous=null;operator=null;waiting=false;justEvaluated=true;update();return true}
function setOperator(op){if(current==="Ошибка")return;if(operator!==null&&!waiting){if(!resolve(Number(current)))return}else{justEvaluated=false}previous=Number(current);operator=op;waiting=true;update()}
function equals(){if(current==="Ошибка"||operator===null||previous===null)return;resolve(waiting?previous:Number(current))}
function backspace(){if(current==="Ошибка"){clearAll();return}if(justEvaluated){clearAll();return}if(waiting){operator=null;previous=null;waiting=false;update();return}current=current.length>1?current.slice(0,-1):"0";if(current==="-"||current==="-0")current="0";update()}
function clearAll(){current="0";previous=null;operator=null;waiting=false;justEvaluated=false;update()}
function pulse(el){el.classList.remove("pulse");void el.offsetWidth;el.classList.add("pulse")}
function memoryAdd(sign){const x=Number(current);if(!isFinite(x))return;memory=round(memory+sign*x);pulse(memBadge);update()}
document.getElementById("grid").addEventListener("click",e=>{const btn=e.target.closest("button");if(!btn)return;if(btn.dataset.digit!==undefined)inputDigit(btn.dataset.digit);else if(btn.dataset.op)setOperator(btn.dataset.op);else{const a=btn.dataset.action;if(a==="dot")inputDot();if(a==="clear")clearAll();if(a==="back")backspace();if(a==="percent")percent();if(a==="sign")toggleSign();if(a==="equals")equals()}});
document.querySelector(".memory").addEventListener("click",e=>{const btn=e.target.closest("button");if(!btn)return;const m=btn.dataset.m;if(m==="mc"){memory=0;update()}if(m==="mr"){if(isFinite(memory)){current=String(memory);previous=null;operator=null;waiting=false;justEvaluated=true;update()}}if(m==="m+")memoryAdd(1);if(m==="m-")memoryAdd(-1)});
document.addEventListener("keydown",e=>{const k=e.key;if(/^[0-9]$/.test(k)){inputDigit(k);e.preventDefault()}else if(k==="."||k===","){inputDot();e.preventDefault()}else if(k==="+"||k==="-"||k==="*"||k==="/"||k==="x"||k==="X"){setOperator(k.toLowerCase()==="x"?"*":k);e.preventDefault()}else if(k==="Enter"||k==="="){equals();e.preventDefault()}else if(k==="Backspace"){backspace();e.preventDefault()}else if(k==="Escape"||k==="Delete"){clearAll();e.preventDefault()}else if(k==="%"){percent();e.preventDefault()}});
update();
</script>
'''
script = re.search(r'<script>([\s\S]*)</script>', widget_code).group(1)
Path('/tmp/calc-widget.js').write_text(script)
print('widget chars', len(widget_code), 'script chars', len(script))
if shutil.which('node'):
    cp = subprocess.run(['node','--check','/tmp/calc-widget.js'], capture_output=True, text=True)
    print('node rc', cp.returncode)
    print(cp.stdout)
    print(cp.stderr)
else:
    print('node not found')