/** * NexterCaptcha — risk-adaptive виджет капчи. * * NexterCaptcha.render(container, { * site: 'crm', action: 'register', * onVerified: ({token, mode, riskScore}) => { ... }, * }); * * Низкий риск → сервер сразу выдаёт токен (невидимая проверка). * Высокий риск → интерактивный proof-of-work (sha256 с ведущими нулями). */ (function () { 'use strict'; var STYLE = document.createElement('style'); STYLE.textContent = [ '.nc-box{display:flex;align-items:center;gap:14px;width:100%;padding:12px 0;border-top:1px solid var(--ink,#111);border-bottom:1px solid var(--ink,#111);border-radius:0;background:transparent;font:14px/1.4 var(--font,system-ui),sans-serif;color:var(--ink,#111)}', '.nc-btn{display:inline-flex;align-items:center;gap:8px;flex:none;background:var(--ink,#111);color:var(--bg,#efefec);border:none;border-radius:0;padding:10px 14px;font:600 14px var(--font,system-ui);cursor:pointer}', '.nc-btn:disabled{opacity:.45;cursor:wait}', '.nc-state{flex:1;min-width:0}', '.nc-status{font-weight:600;letter-spacing:-.02em}', '.nc-sub{color:var(--muted,#5f5f5c);font-size:12px;margin-top:2px}', '.nc-ok{color:var(--ok,#0d6b3d)}.nc-err{color:var(--bad,#9f1d14)}.nc-warn{color:var(--warn,#8a5a00)}', '.nc-powbar{height:2px;border-radius:0;background:var(--line-soft,rgba(17,17,17,.14));overflow:hidden;margin-top:8px}', '.nc-powfill{height:100%;width:0;background:var(--ink,#111);transition:width .2s}', '.nc-spin{width:14px;height:14px;border:2px solid rgba(239,239,236,.35);border-top-color:var(--bg,#efefec);border-radius:50%;animation:nc-rot .8s linear infinite;display:none}', '.nc-box.busy .nc-spin{display:inline-block}', '@keyframes nc-rot{to{transform:rotate(360deg)}}' ].join('\n'); document.head.appendChild(STYLE); function sha256Hex(text) { var te = new TextEncoder(); return crypto.subtle.digest('SHA-256', te.encode(text)).then(function (d) { return Array.prototype.map.call(new Uint8Array(d), function (b) { return b.toString(16).padStart(2, '0'); }).join(''); }); } function leadingZeroBits(hex) { var bits = 0; for (var i = 0; i < hex.length; i++) { var v = parseInt(hex[i], 16); if (v === 0) { bits += 4; continue; } if (v < 2) bits += 3; else if (v < 4) bits += 2; else if (v < 8) bits += 1; break; } return bits; } // PoW-решатель: перебор с прогрессом, ~50k хэшей/с в WebCrypto. function solvePow(nonce, difficulty, onProgress) { var batch = 400; var i = 0; return new Promise(function (resolve, reject) { function step() { var chain = Promise.resolve(); var start = i; for (var j = 0; j < batch; j++, i++) { (function (k) { chain = chain.then(function () { return sha256Hex(nonce + k).then(function (h) { if (leadingZeroBits(h) >= difficulty) throw { solved: String(k) }; }); }); })(i); } chain.then(function () { onProgress(i); if (i > 5e6) return reject(new Error('pow: exceeded attempts')); setTimeout(step, 0); }, function (early) { if (early && early.solved) resolve(early.solved); else reject(early); }); void start; } step(); }); } function render(container, opts) { opts = opts || {}; var box = document.createElement('div'); box.className = 'nc-box'; box.innerHTML = '' + '