(function(){
const providers={
"Header":{
css:".cbs-calc-runtime .header{font-weight:700;font-size:1.2rem;margin-bottom:.5rem}",
render:function(item,ctx){(item)=>"\n if(item.type==='Header'){\n return h('div',{class:'header'},item.text||'');\n }\n "},
init:function(item,state){},
solve:function(item,state,ctx){}
}
,
"Image":{
css:"\n .cbs-calc-runtime .image-wrap{width:100%;margin-bottom:.5rem}\n .cbs-calc-runtime .image-square{aspect-ratio:1/1}\n .cbs-calc-runtime .image-wide{aspect-ratio:16/9}\n .cbs-calc-runtime .image{width:100%;height:100%;object-fit:cover;border-radius:.75rem;display:block}\n ",
render:function(item,ctx){(item)=>"\n if(item.type==='Image'){\n if(!item.src)return null;\n const aspect=item.aspect==='wide'?'image-wide':'image-square';\n return h('div',{class:'image-wrap '+aspect},\n h('img',{class:'image',src:item.src})\n );\n }\n "},
init:function(item,state){},
solve:function(item,state,ctx){}
}
,
"Input":{
css:"\n .cbs-calc-runtime .field{background:#f3f3f3;border-radius:.75rem;padding:.55rem .6rem .6rem .6rem;margin-bottom:.5rem}\n .cbs-calc-runtime .field-label{font-size:.7rem;opacity:.7;margin-bottom:.25rem}\n .cbs-calc-runtime input{padding:.45rem;border:.0625rem solid #ddd;border-radius:.45rem;font-size:.85rem;width:100%;box-sizing:border-box}\n ",
render:function(item,ctx){(item)=>"\n if(item.type==='Input'){\n return h('div',{class:'field'},\n item.name?h('div',{class:'field-label'},item.name):null,\n h('input',{\n value:state[item.key]??'',\n onInput:e=>update(item.key,parseFloat(e.target.value)||0)\n })\n );\n }\n "},
init:function(item,state){(item)=>"\n if(item.type==='Input'){\n if(item.key && state[item.key]===undefined){\n state[item.key]=item.default??0;\n }\n }\n "},
solve:function(item,state,ctx){}
}
,
"Select":{
css:"",
render:function(item,ctx){(item)=>"\n if(item.type==='Select'){\n return h('div',{class:'field'},\n item.name?h('div',{class:'field-label'},item.name):null,\n h('select',{\n value:state[item.key],\n onChange:e=>update(item.key,parseFloat(e.target.value)||0)\n },\n (item.variants||[]).map(v=>h('option',{value:v.value},v.name))\n )\n );\n }\n "},
init:function(item,state){(item)=>"\n if(item.type==='Select'){\n if(item.key && state[item.key]===undefined){\n state[item.key]=item.default??(item.variants?.[0]?.value??0);\n }\n }\n "},
solve:function(item,state,ctx){}
}
,
"Radio":{
css:"\n .cbs-calc-runtime .radio-group{display:grid;grid-template-columns:repeat(4,1fr);gap:.75rem;margin-top:.75rem}\n .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}\n .cbs-calc-runtime .radio-card.active{border-color:#4aa;background:#e6f3f3}\n .cbs-calc-runtime .radio-img{width:100%;object-fit:cover;display:block;margin:0}\n .cbs-calc-runtime .radio-ratio-square{aspect-ratio:1/1}\n .cbs-calc-runtime .radio-ratio-wide{aspect-ratio:16/9}\n .cbs-calc-runtime .radio-card-label{padding:.5rem .55rem .6rem .55rem;font-size:.85rem;line-height:1.2}\n ",
render:function(item,ctx){(item)=>"\n if(item.type==='Radio'){\n return h('div',{},\n item.name?h('div',{class:'field-label'},item.name):null,\n h('div',{\n class:'radio-group',\n style:{gridTemplateColumns:'repeat('+(item.cols||4)+',1fr)'}\n },\n (item.variants||[]).map(v=>{\n const ratioClass=v.imageRatio==='wide'?'radio-ratio-wide':'radio-ratio-square';\n return h('div',{\n class:'radio-card'+(state[item.key]===v.value?' active':''),\n onClick:()=>update(item.key,v.value)\n },\n v.image?h('img',{class:'radio-img '+ratioClass,src:v.image}):null,\n h('div',{class:'radio-card-label'},v.name)\n );\n }))\n );\n }\n "},
init:function(item,state){(item)=>"\n if(item.type==='Radio'){\n if(item.key && state[item.key]===undefined){\n state[item.key]=item.default??(item.variants?.[0]?.value??0);\n }\n }\n "},
solve:function(item,state,ctx){}
}
,
"Calc":{
css:"",
render:function(item,ctx){},
init:function(item,state){},
solve:function(item,state,ctx){(item)=>"\n if(item.type==='Calc'){\n const newVal=calc(item.formula,state);\n if(state[item.key]!==newVal){\n state[item.key]=newVal;\n ctx.changed=true;\n }\n }\n "}
}
,
"Result":{
css:"\n .cbs-calc-runtime .results-card{background:#e6f3f3;border-radius:.9rem;padding:.75rem;margin-top:.5rem}\n .cbs-calc-runtime table{width:100%;border-collapse:collapse;font-size:.85rem;margin:0}\n .cbs-calc-runtime td{padding:.35rem .45rem;border-bottom:.0625rem solid rgba(0,0,0,.08)}\n .cbs-calc-runtime td:last-child{text-align:right}\n ",
render:function(item,ctx){(item)=>"\n if(item.type==='Result'){\n const rows=[];\n (item.data||[]).forEach(row=>{\n const cols=(row||[]).map(cell=>{\n const val=state[cell];\n if(isFinite(val))return h('td',{},val);\n return h('td',{},cell);\n });\n rows.push(h('tr',{},cols));\n });\n\n return h('div',{class:'results-card'},\n item.title?h('div',{class:'header'},item.title):null,\n h('table',{},rows)\n );\n }\n "},
init:function(item,state){},
solve:function(item,state,ctx){}
}
};
const PREACT_URL='https://unpkg.com/preact@10.19.3/dist/preact.umd.js';
const HOOKS_URL='https://unpkg.com/preact@10.19.3/hooks/dist/hooks.umd.js';
function load(src){
return new Promise(res=>{
const s=document.createElement('script');
s.src=src;
s.onload=res;
document.head.appendChild(s);
});
}
(async()=>{
if(!window.preact)await load(PREACT_URL);
if(!window.preactHooks)await load(HOOKS_URL);
const {h,render}=window.preact;
const {useState}=window.preactHooks;
const container=document.getElementById('data-calc');
if(!container)return;
const raw=container.getAttribute('data-calc');
if(!raw)return;
let schema;
try{schema=JSON.parse(raw);}catch(e){return;}
function App(){
const [state,setState]=useState(initState());
return h('div',{class:'cbs-calc-runtime'},
(schema.layout||[]).map((b,i)=>renderBlock(b,i))
);
function renderBlock(block,i){
const cols=(block?.cols||[]).filter(col=>(col?.span??1)>0);
if(!cols.length)return null;
return h('div',{class:'grid',key:i},cols.map((col,c)=>renderCol(col,c)));
}
function renderCol(col,c){
const span=Math.max(1,Math.min(3,col?.span||1));
const style={gridColumn:'span '+span};
return h('div',{class:'col',style,key:c},
(col?.items||[]).map(idx=>renderElement(schema.elements?.[idx]))
);
}
function renderElement(item){
if(!item)return null;
const p=providers[item.type];
if(!p || !p.render)return null;
return p.render(item,{state,update});
}
function update(key,val){
const next={...state};
next[key]=val;
solve(next);
setState(next);
}
function solve(s){
let changed=true,guard=0;
while(changed && guard<1000){
changed=false;
guard++;
(schema.elements||[]).forEach(item=>{
const p=providers[item.type];
if(p && p.solve){
p.solve(item,s,{changed});
}
});
}
}
function initState(){
const s={};
(schema.elements||[]).forEach(item=>{
const p=providers[item.type];
if(p && p.init){
p.init(item,s);
}
});
solve(s);
return s;
}
}
render(h(App),container);
})();
})();
Lorem ipsum
test
Показать калькулятор