(function(){
var DEBUG = true;
function log(){ if(DEBUG) console.log.apply(console, ["[CALC]"].concat([].slice.call(arguments))); }
function warn(){ console.warn.apply(console, ["[CALC]"].concat([].slice.call(arguments))); }
function err(){ console.error.apply(console, ["[CALC]"].concat([].slice.call(arguments))); }
var providers={"QueryParams":{render:(param)=>{
let { item , state } = param;
if (item.type !== "QueryParams") return null;
try {
var _window_location, _window_history;
if (!(window === null || window === void 0 ? void 0 : (_window_location = window.location) === null || _window_location === void 0 ? void 0 : _window_location.href)) return null;
if (!(window === null || window === void 0 ? void 0 : (_window_history = window.history) === null || _window_history === void 0 ? void 0 : _window_history.replaceState)) return null;
const url = new URL(window.location.href);
let changed = false;
(item.data || []).forEach((row)=>{
var _row_;
const paramKey = String((_row_ = row === null || row === void 0 ? void 0 : row[0]) !== null && _row_ !== void 0 ? _row_ : "").trim();
if (!paramKey) return;
const rawValue = row === null || row === void 0 ? void 0 : row[1];
let nextValue = "";
if (typeof rawValue === "string" && Object.prototype.hasOwnProperty.call(state || {}, rawValue)) {
nextValue = state[rawValue];
} else {
nextValue = rawValue;
}
nextValue = nextValue == null ? "" : String(nextValue);
if (url.searchParams.get(paramKey) !== nextValue) {
url.searchParams.set(paramKey, nextValue);
changed = true;
}
});
if (changed) {
window.history.replaceState(null, "", url.toString());
}
} catch (e) {
console.log("[CALC] QueryParams error", e);
}
return null;
}},"Header":{render:(param)=>{
let { item , h } = param;
if (item.type !== "Header") return null;
const style = {};
if (item.bgColor) {
style.background = item.bgColor;
style.padding = ".5rem .6rem";
style.borderRadius = ".6rem";
}
if (item.textColor) {
style.color = item.textColor;
}
return h("div", {
class: "header",
style
}, item.text || "");
}},"SaveResult":{render:(param)=>{
let { item , h } = param;
if (item.type !== "SaveResult") return null;
const style = {};
if (item.bgColor) style.background = item.bgColor;
if (item.textColor) style.color = item.textColor;
return h("button", {
class: "save-btn",
style,
onMouseEnter: (e)=>{
if (item.hoverBg) e.currentTarget.style.background = item.hoverBg;
},
onMouseLeave: (e)=>{
if (item.bgColor) e.currentTarget.style.background = item.bgColor;
else e.currentTarget.style.background = "";
},
onClick: handleClick
}, item.text || "Скачать");
async function handleClick() {
const root = document.querySelector(".cbs-calc-runtime");
if (!root) return;
try {
const html2canvas = await loadHtml2Canvas();
const calcCanvas = await html2canvas(root, {
backgroundColor: "#ffffff",
useCORS: true
});
const topImg = await loadImage(item.topImage);
const bottomImg = await loadImage(item.bottomImage);
const width = calcCanvas.width;
const topH = topImg ? Math.round(topImg.height * (width / topImg.width)) : 0;
const bottomH = bottomImg ? Math.round(bottomImg.height * (width / bottomImg.width)) : 0;
const height = topH + calcCanvas.height + bottomH;
const finalCanvas = document.createElement("canvas");
finalCanvas.width = width;
finalCanvas.height = height;
const ctx = finalCanvas.getContext("2d");
let y = 0;
if (topImg) {
ctx.drawImage(topImg, 0, y, width, topH);
y += topH;
}
ctx.drawImage(calcCanvas, 0, y);
y += calcCanvas.height;
if (bottomImg) {
ctx.drawImage(bottomImg, 0, y, width, bottomH);
}
const link = document.createElement("a");
link.download = "result.png";
link.href = finalCanvas.toDataURL("image/png");
link.click();
} catch (e) {
console.error(e);
alert("Ошибка сохранения");
}
}
function loadImage(src) {
return new Promise((res)=>{
if (!src) return res(null);
const img = new Image();
img.crossOrigin = "anonymous";
img.onload = ()=>res(img);
img.onerror = ()=>res(null);
img.src = src;
});
}
function loadHtml2Canvas() {
if (window.html2canvas) return Promise.resolve(window.html2canvas);
return new Promise((resolve, reject)=>{
const s = document.createElement("script");
s.src = "https://unpkg.com/html2canvas@1.4.1/dist/html2canvas.min.js";
s.onload = ()=>resolve(window.html2canvas);
s.onerror = reject;
document.head.appendChild(s);
});
}
}},"Image":{render:(param)=>{
let { item , h } = param;
if (item.type !== "Image") return null;
if (!item.src) return null;
let aspectClass = "";
if (item.aspect === "wide") aspectClass = "image-wide";
else if (item.aspect === "square") aspectClass = "image-square";
else aspectClass = "image-free";
const imgClass = item.aspect ? "image" : "image";
return h("div", {
class: "image-wrap " + aspectClass
}, h("img", {
class: imgClass,
src: item.src
}));
}},"Text":{render:(param)=>{
let { item , h } = param;
if (item.type !== "Text") return null;
if (!item.text) return null;
const parts = String(item.text).split(/\n+/);
return h("div", {
class: "text-block"
}, parts.map((line)=>h("p", {}, line)));
}},"DataSelect":{render:(param)=>{
let { item , state , set , get , h } = param;
if (item.type !== "DataSelect") return null;
let data = {};
try {
data = JSON.parse(item.data || "{}");
} catch (e) {}
const keys = Object.keys(data || {});
const def = item.default || keys[0] || "";
const value = get(item.key, def);
const row = data[value];
if (row && typeof row === "object") {
Object.entries(row).forEach((param)=>{
let [k, v] = param;
set(k, v);
});
}
return h("div", {
class: "field"
}, item.name ? h("div", {
class: "field-label"
}, item.name) : null, h("select", {
value: value || "",
onChange: (e)=>set(item.key, e.target.value)
}, keys.map((k)=>h("option", {
value: k
}, k))));
}},"Input":{render:(param)=>{
let { item , state , set , get , h } = param;
if (item.type !== "Input") return null;
var _item_default;
const value = get(item.key, (_item_default = item.default) !== null && _item_default !== void 0 ? _item_default : 0);
return h("div", {
class: "field"
}, item.name ? h("div", {
class: "field-label"
}, item.name) : null, h("input", {
value: value !== null && value !== void 0 ? value : "",
onInput: (e)=>set(item.key, parseFloat(e.target.value) || 0)
}));
}},"Select":{render:(param)=>{
let { item , state , set , get , h } = param;
var _item_variants, _item_variants_;
if (item.type !== "Select") return null;
var _item_variants__value, _item_default;
const def = (_item_default = item.default) !== null && _item_default !== void 0 ? _item_default : (_item_variants__value = (_item_variants = item.variants) === null || _item_variants === void 0 ? void 0 : (_item_variants_ = _item_variants[0]) === null || _item_variants_ === void 0 ? void 0 : _item_variants_.value) !== null && _item_variants__value !== void 0 ? _item_variants__value : 0;
const value = get(item.key, def);
return h("div", {
class: "field"
}, item.name ? h("div", {
class: "field-label"
}, item.name) : null, h("select", {
value,
onChange: (e)=>set(item.key, parseFloat(e.target.value) || 0)
}, (item.variants || []).map((v)=>h("option", {
value: v.value
}, v.name))));
}},"Radio":{render:(param)=>{
let { item , state , set , get , h } = param;
var _item_variants, _item_variants_;
if (item.type !== "Radio") return null;
var _item_variants__value, _item_default;
const def = (_item_default = item.default) !== null && _item_default !== void 0 ? _item_default : (_item_variants__value = (_item_variants = item.variants) === null || _item_variants === void 0 ? void 0 : (_item_variants_ = _item_variants[0]) === null || _item_variants_ === void 0 ? void 0 : _item_variants_.value) !== null && _item_variants__value !== void 0 ? _item_variants__value : 0;
const value = get(item.key, def);
return h("div", {}, item.name ? h("div", {
class: "field-label"
}, item.name) : null, h("div", {
class: "radio-group",
style: {
gridTemplateColumns: "repeat(" + (item.cols || 4) + ",1fr)"
}
}, (item.variants || []).map((v)=>{
const ratioClass = v.imageRatio === "wide" ? "radio-ratio-wide" : v.imageRatio === "square" ? "radio-ratio-square" : "";
return h("div", {
class: "radio-card" + (value === v.value ? " active" : ""),
onClick: ()=>set(item.key, v.value)
}, v.image ? h("img", {
class: "radio-img " + ratioClass,
src: v.image
}) : null, h("div", {
class: "radio-card-label"
}, v.name));
})));
}},"Calc":{render:(param)=>{
let { item , calc , set } = param;
if (item.type !== "Calc") return null;
const v = calc(item.formula || "");
set(item.key, v);
return null;
}},"Range":{render:(param)=>{
let { item , state , set , get , h } = param;
if (item.type !== "Range") return null;
var _item_min;
const min = Number((_item_min = item.min) !== null && _item_min !== void 0 ? _item_min : 0);
var _item_max;
const max = Number((_item_max = item.max) !== null && _item_max !== void 0 ? _item_max : 100);
var _item_step;
const step = Number((_item_step = item.step) !== null && _item_step !== void 0 ? _item_step : 1);
var _item_default;
const def = (_item_default = item.default) !== null && _item_default !== void 0 ? _item_default : min;
const stateValue = get(item.key, def);
const snapped = normalize(stateValue, min, max, step);
const inputValue = clampEdge(snapped, min, max, step);
return h("div", {
class: "field"
}, h("div", {
class: "range-head"
}, item.name ? h("div", {
class: "field-label"
}, item.name) : h("div", {}), h("div", {
class: "range-value"
}, inputValue)), h("input", {
type: "range",
class: "range-input",
min: min,
max: max,
step: "any",
value: inputValue,
onInput: (e)=>{
const raw = parseFloat(e.target.value) || 0;
const v = normalize(raw, min, max, step);
set(item.key, v);
}
}));
function normalize(v, min, max, step) {
if (v <= min) return min;
if (v >= max) return max;
const steps = Math.round((v - min) / step);
let snapped = min + steps * step;
return Number(snapped.toFixed(6));
}
function clampEdge(v, min, max, step) {
if (Math.abs(v - min) <= step) return min;
if (Math.abs(v - max) <= step) return max;
return v;
}
}},"Result":{render:(param)=>{
let { item , state , h } = param;
if (item.type !== "Result") return null;
const rows = [];
(item.data || []).forEach((row)=>{
const cols = (row || []).map((cell)=>{
const val = state[cell];
if (typeof val === "number" && isFinite(val)) {
const out = Number.isInteger(val) ? val : Number(val.toFixed(2));
return h("td", {}, out);
}
if (val !== undefined && val !== null) {
return h("td", {}, String(val));
}
return h("td", {}, cell);
});
rows.push(h("tr", {}, cols));
});
return h("div", {
class: "results-card"
}, item.title ? h("div", {
class: "header"
}, item.title) : null, h("table", {}, rows));
}}};
var PREACT_URL='https://unpkg.com/preact@10.19.3/dist/preact.umd.js';
var HOOKS_URL='https://unpkg.com/preact@10.19.3/hooks/dist/hooks.umd.js';
var CSS_TEXT = `
.cbs-calc-runtime{padding:1.25rem;border-radius:1rem;font-family:Arial,sans-serif}
.cbs-calc-runtime .grid{display:grid;grid-template-columns:repeat(3,1fr);gap:1rem}
.cbs-calc-runtime .col{min-width:0}
.cbs-calc-runtime details{margin-bottom:.5rem}
.cbs-calc-runtime summary{
list-style:none;
cursor:pointer;
font-weight:700;
font-size:1.2rem;
margin-bottom:.5rem;
padding:.5rem .6rem;
border-radius:.6rem;
display:flex;
align-items:center;
justify-content:space-between;
}
.cbs-calc-runtime summary::-webkit-details-marker{display:none}
.cbs-calc-runtime summary::after{
content:"▾";
font-size:.9rem;
opacity:.7;
margin-left:1rem;
}
.cbs-calc-runtime details[open] summary::after{
transform:rotate(180deg);
}
.calc-error{font-family:Arial,sans-serif;border:1px solid #f00;background:#fff5f5;padding:12px;border-radius:8px}
.calc-error-title{font-weight:700;margin-bottom:6px;color:#900}
.calc-error textarea{width:100%;height:300px;font-family:monospace;font-size:12px}
.cbs-calc-runtime .calc-highlight{
outline:2px solid red;
border-radius:.5rem;
transition:.25s;
}
.cbs-calc-runtime .header{font-weight:700;font-size:1.2rem;}
.cbs-calc-runtime .save-btn{
width:100%;
padding:.7rem 1rem;
border-radius:.6rem;
border:1px solid #ddd;
background:#fff;
cursor:pointer;
font-size:1rem;
font-weight:600;
transition:.15s;
display:block;
}
.cbs-calc-runtime .image-wrap{width:100%;margin-bottom:.5rem}
.cbs-calc-runtime .image-square{aspect-ratio:1/1}
.cbs-calc-runtime .image-wide{aspect-ratio:16/9}
.cbs-calc-runtime .image-free img{object-fit:contain;height:auto}
.cbs-calc-runtime .image{width:100%;height:100%;object-fit:cover;border-radius:.75rem;display:block}
.cbs-calc-runtime .text-block{margin-bottom:.5rem;line-height:1.4}
.cbs-calc-runtime .text-block p{margin:.25rem 0}
.cbs-calc-runtime .field{background:#f3f3f3;border-radius:.75rem;padding:.55rem .6rem .6rem .6rem;margin-bottom:.5rem}
.cbs-calc-runtime .field-label{font-size:.7rem;opacity:.7;margin-bottom:.25rem}
.cbs-calc-runtime select{padding:.45rem;border:.0625rem solid #ddd;border-radius:.45rem;font-size:.85rem;width:100%;box-sizing:border-box}
.cbs-calc-runtime .field{background:#f3f3f3;border-radius:.75rem;padding:.55rem .6rem .6rem .6rem;margin-bottom:.5rem}
.cbs-calc-runtime .field-label{font-size:.7rem;opacity:.7;margin-bottom:.25rem}
.cbs-calc-runtime input{padding:.45rem;border:.0625rem solid #ddd;border-radius:.45rem;font-size:.85rem;width:100%;box-sizing:border-box}
.cbs-calc-runtime .radio-group{display:grid;gap:.75rem;margin-top:.75rem}
.cbs-calc-runtime .radio-card{border:.0625rem solid #ddd;border-radius:.75rem;cursor:pointer;background:#fff;overflow:hidden;display:flex;flex-direction:column;text-align:center}
.cbs-calc-runtime .radio-card.active{border-color:#4aa;background:#e6f3f3}
.cbs-calc-runtime .radio-img{width:100%;object-fit:cover;display:block;margin:0}
.cbs-calc-runtime .radio-ratio-square{aspect-ratio:1/1}
.cbs-calc-runtime .radio-ratio-wide{aspect-ratio:16/9}
.cbs-calc-runtime .radio-free img{object-fit:contain;height:auto}
.cbs-calc-runtime .radio-card-label{padding:.5rem .55rem .6rem .55rem;font-size:.85rem;line-height:1.2}
.cbs-calc-runtime .range-input{width:100%}
.cbs-calc-runtime .range-head{display:flex;justify-content:space-between;align-items:center;}
.cbs-calc-runtime .range-value{font-size:.75rem;opacity:.7}
.cbs-calc-runtime .results-card{background:#e6f3f3;border-radius:.9rem;padding:.75rem;margin-top:.5rem}
.cbs-calc-runtime table{width:100%;border-collapse:collapse;font-size:.85rem;margin:0}
.cbs-calc-runtime td{padding:.35rem .45rem;border-bottom:.0625rem solid rgba(0,0,0,.08)}
.cbs-calc-runtime td:last-child{text-align:right}
`;
function showError(container,title,text){
if(!container) return;
container.innerHTML="";
var wrap=document.createElement("div");
wrap.className="calc-error";
var h=document.createElement("div");
h.className="calc-error-title";
h.textContent=title || "Ошибка";
var ta=document.createElement("textarea");
ta.value=text || "";
wrap.appendChild(h);
wrap.appendChild(ta);
container.appendChild(wrap);
}
function load(src){
return new Promise(function(res,rej){
var s=document.createElement('script');
s.src=src;
s.onload=res;
s.onerror=function(e){ rej(e); };
document.head.appendChild(s);
});
}
function calcExpr(expr,state){
try{
if(!expr) return 0;
if(expr[0]==='=') expr = expr.slice(1);
var keys=Object.keys(state||{});
var vals=keys.map(function(k){return state[k];});
return Function.apply(null, keys.concat(["return "+expr])).apply(null, vals);
}catch(e){
err("CALC ERROR", expr, e);
return 0;
}
}
function extractDeps(expr){
if(!expr) return [];
if(expr[0]==='=') expr = expr.slice(1);
var m = expr.match(/[A-Z]+[0-9_]+/g);
return m || [];
}
function detectCycles(elements){
var graph={}, keys={};
for(var i=0;i node.clientHeight) return node;
node = node.parentElement || node.parentNode;
}
return document.scrollingElement || document.documentElement;
}
function highlight(el, q){
if(!el) return;
var prev = q.querySelector('.calc-highlight');
if(prev) prev.classList.remove('calc-highlight');
el.classList.add('calc-highlight');
setTimeout(function(){
el.classList.remove('calc-highlight');
},1200);
}
function start(){
if(!window.preact || !window.preactHooks){
err("Preact not loaded");
return;
}
var h=window.preact.h;
var render=window.preact.render;
var useState=window.preactHooks.useState;
var useEffect=window.preactHooks.useEffect;
var host=document.getElementById('data-calc');
if(!host){
err("Container #data-calc not found");
return;
}
var raw = host.getAttribute('data-calc');
if(!raw){
showError(host,"Ошибка рендера схемы","data-calc attribute пуст");
return;
}
var schema;
try{
var json = decodeURIComponent(escape(atob(raw)));
schema = JSON.parse(json);
}catch(e){
err("SCHEMA PARSE ERROR", e);
showError(host,"Ошибка рендера схемы","Decode/parse error: "+e.message);
return;
}
detectCycles((schema && schema.elements) ? schema.elements : []);
var shadow = null;
var mount = null;
var q = document;
try{
if(host.attachShadow){
shadow = host.shadowRoot || host.attachShadow({mode:'open'});
}
}catch(e){
shadow = null;
}
if(shadow){
q = shadow;
try{ host.classList.add('cbs-calc-runtime'); }catch(e){}
shadow.innerHTML = "";
var st = document.createElement('style');
st.textContent = CSS_TEXT;
shadow.appendChild(st);
mount = document.createElement('div');
shadow.appendChild(mount);
}else{
host.innerHTML = "";
var st2 = document.createElement('style');
st2.textContent = CSS_TEXT;
host.appendChild(st2);
mount = document.createElement('div');
host.appendChild(mount);
q = document;
}
window.addEventListener('message', function(ev){
var msg = ev && ev.data ? ev.data : {};
if(msg.type !== "calc-scroll") return;
var index = msg.index;
try{
var el = (shadow ? shadow.querySelector('[data-el-index="'+index+'"]') : document.querySelector('[data-el-index="'+index+'"]'));
if(!el) return;
var box = findScrollParent(el);
if(box){
box.scrollTo({
top: el.offsetTop - box.clientHeight/2 + el.clientHeight/2,
behavior:'smooth'
});
}
highlight(el, shadow || document);
}catch(e){
err("Scroll message error", e);
}
});
function App(){
var _useState=useState({});
var state=_useState[0];
var setState=_useState[1];
var solverBuffer=null;
var solving=false;
function set(key,val){
if(solverBuffer){
solverBuffer[key]=val;
return;
}
setState(function(prev){
if(prev[key]===val) return prev;
var next={};
for(var k in prev) next[k]=prev[k];
next[key]=val;
return next;
});
}
function get(key,def){
if(key===undefined) return undefined;
if(state[key]===undefined && def!==undefined){
set(key,def);
return def;
}
return state[key];
}
function calc(expr){
return calcExpr(expr,state);
}
function runServiceElements(runtimeState){
var elements = schema && schema.elements ? schema.elements : [];
for(var i=0;i0;
});
if(!cols.length) return null;
var viewMode = block && block.viewMode ? block.viewMode : "full";
if(viewMode === "dropdown"){
var style={};
if(block.dropdownBg) style.background=block.dropdownBg;
if(block.dropdownColor) style.color=block.dropdownColor;
return h('details',{key:i},
h('summary',{style:style},
block.dropdownTitle || "Подробнее"
),
h('div',{class:'grid'},
cols.map(function(col,c){ return renderCol(col,c); })
)
);
}
return h('div',{class:'grid',key:i},
cols.map(function(col,c){ return renderCol(col,c); })
);
}
var layout=schema && schema.layout ? schema.layout : [];
return h('div',{class:'cbs-calc-runtime'},
layout.map(function(b,i){ return renderBlock(b,i); })
);
}
try{
render(h(App),mount);
}catch(e){
err("Render crash", e);
showError(mount,"Ошибка рендера схемы",e.stack || e.message);
}
}
(function init(){
var p = Promise.resolve();
if(!window.preact) p = p.then(function(){ return load(PREACT_URL); });
if(!window.preactHooks) p = p.then(function(){ return load(HOOKS_URL); });
p.then(start).catch(function(e){
var host=document.getElementById('data-calc');
if(host){
try{
if(host.shadowRoot){
var m = host.shadowRoot.querySelector('div') || host.shadowRoot;
showError(m,"Ошибка рендера схемы",e.stack || e.message);
return;
}
}catch(_e){}
showError(host,"Ошибка рендера схемы",e.stack || e.message);
}
err("Init failed", e);
});
})();
})();
Lorem ipsum
test
Показать калькулятор