// Calculator v3 — feature-based flat-rate pricing. // User picks features → fixed monthly subscription. No volume sliders, no per-call billing. // Bundle discounts auto-apply when user selects all features in a discount group. const { useState: useStateCalc, useMemo: useMemoCalc } = React; function fmtMoney(n) { if (n === 0) return "0 ₽"; return Math.round(n).toLocaleString("ru-RU") + " ₽"; } function CalcIcon({ name }) { const p = { width: 20, height: 20, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 1.6, strokeLinecap: "round", strokeLinejoin: "round" }; switch (name) { case "suggest": return ; case "iploc": return ; case "fuzzy": return ; case "pin": return ; case "reverse": return ; case "tree": return ; case "parse": return ; case "batch": return ; case "webhook": return ; case "sla": return ; default: return null; } } function Calculator() { const { CALC_FEATURES, CALC_BUNDLES } = window.__DATA; const [selected, setSelected] = useStateCalc(new Set(["suggest"])); // suggest is default-on function toggle(id) { const next = new Set(selected); if (next.has(id)) next.delete(id); else next.add(id); setSelected(next); } // Group features by their group label, preserving order const grouped = useMemoCalc(() => { const groups = []; const map = new Map(); CALC_FEATURES.forEach((f) => { if (!map.has(f.group)) { const g = { label: f.group, items: [] }; map.set(f.group, g); groups.push(g); } map.get(f.group).items.push(f); }); return groups; }, [CALC_FEATURES]); // Compute subtotal, applicable bundles, total const subtotal = useMemoCalc(() => { return CALC_FEATURES.filter((f) => selected.has(f.id)).reduce((s, f) => s + f.price, 0); }, [selected, CALC_FEATURES]); const activeBundles = useMemoCalc(() => { return CALC_BUNDLES.filter((b) => b.requires.every((r) => selected.has(r))); }, [selected, CALC_BUNDLES]); const discount = activeBundles.reduce((s, b) => s + b.discount, 0); const total = Math.max(0, subtotal - discount); // Plan recommendation let plan; const sel = selected.size; if (sel === 0) plan = { name: "Старт", note: "Бесплатно · 10 000 запросов в сутки", hint: "Подходит для тестирования и пет-проектов" }; else if (sel <= 3 && total < 4000) plan = { name: "Базовый", note: `${fmtMoney(total)} / мес`, hint: "Для команд до 5 человек, без SLA" }; else if (selected.has("sla") || total >= 8000) plan = { name: "Прод", note: `${fmtMoney(total)} / мес · SLA 99.95%`, hint: "Боевой продакшен с поддержкой 24/7" }; else plan = { name: "Команда", note: `${fmtMoney(total)} / мес`, hint: "Стандартная нагрузка, поддержка в рабочие часы" }; const selectedList = CALC_FEATURES.filter((f) => selected.has(f.id)); return (
{/* Left — feature cards by group */}
{grouped.map((g) => (
{g.label} {g.items.filter((it) => selected.has(it.id)).length} / {g.items.length}
{g.items.map((f) => { const on = selected.has(f.id); return ( ); })}
))}
API
Все фичи доступны через единый REST API.{" "} API для интеграции и тестирования — отдельно не оплачивается, входит в любую фичу.
{/* Right — summary panel */}
); } Object.assign(window, { Calculator });