/
mubarak
/
MB
Обзор
Документация
Войти
/
mubarak
/
MB
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
app.js
314 строк
12 KB
mubarak
Update: app.js
25 июл 2026, 11:59
Верифицирован
25 июл 2026, 11:59
5239b14
Код
Авторство
О чём код?
(function() { const MIN_PRICE = 6000; const MAX_PRICE = 500000; const MAX_PRICE_EXTENDED = 5000000; const MIN_TERM = 3; const MAX_TERM = 12; const R_STANDARD = 0.04; const R_EXCLUSIVE = 0.025 / 0.75; const DP_MAX_PCT = 80; const priceInput = document.getElementById('price-input'); const priceSlider = document.getElementById('price-slider'); const monthsSlider = document.getElementById('months-slider'); const monthsVal = document.getElementById('months-val'); const dpInput = document.getElementById('dp-input'); const dpSlider = document.getElementById('dp-slider'); const dpMinLbl = document.getElementById('dp-min-lbl'); const dpBadge = document.getElementById('dp-badge'); const dpPctBtns = document.querySelectorAll('#dp-pct-btns .pct-btn'); const warning = document.getElementById('warning'); const tariffCard = document.getElementById('tariffCard'); const tariffName = document.getElementById('tariffName'); const tariffEffRate = document.getElementById('tariffEffRate'); const resMonthly = document.getElementById('res-monthly'); const resDpRow = document.getElementById('res-dp-row'); const resDpRub = document.getElementById('res-dp-rub'); const resMarkup = document.getElementById('res-markup'); const resTotal = document.getElementById('res-total'); const infoBtn = document.getElementById('infoBtn'); const modalOverlay = document.getElementById('infoModal'); const shareBtn = document.getElementById('shareBtn'); let currentPrice = 110000; let currentTerm = 3; let currentDP = 27500; let fillColor, trackColor; let prevTariff = 'standard'; function updateColors() { const style = getComputedStyle(document.documentElement); fillColor = style.getPropertyValue('--range-fill').trim(); trackColor = style.getPropertyValue('--range-track').trim(); } updateColors(); function fillSlider(slider, value) { const min = parseFloat(slider.min); const max = parseFloat(slider.max); const pct = ((value - min) / (max - min)) * 100; slider.style.background = `linear-gradient(to right, ${fillColor} 0%, ${fillColor} ${pct}%, ${trackColor} ${pct}%)`; } function parseNumber(value) { if (typeof value === 'string') { return parseInt(value.replace(/\s/g, '')) || 0; } return value || 0; } function formatMoney(v) { return v.toLocaleString('ru-RU').replace(/\s/g, ' ') + ' ₽'; } function getTariff(p) { return p >= 100000 ? 'exclusive' : 'standard'; } function getMinDP(p) { if (getTariff(p) === 'standard') return 0; if (p < 200000) return 0; if (p <= 250000) return p - 200000; return Math.floor(p * 0.25); } function getMaxDP(p) { return Math.floor(p * 0.8); } function clampDP(dp, p) { return Math.max(getMinDP(p), Math.min(getMaxDP(p), dp)); } function round100(v) { return Math.round(v / 100) * 100; } function getSliderMax() { return parseInt(priceSlider.max); } function compute() { const p = currentPrice; const t = currentTerm; const d = currentDP; const minDp = getMinDP(p); const valid = d >= minDp && d <= getMaxDP(p) && p >= MIN_PRICE && p <= getSliderMax(); const tariff = getTariff(p); const r = tariff === 'exclusive' ? R_EXCLUSIVE : R_STANDARD; let effRate = null; if (valid) { const F = p - d; const debt = F * (1 + r * t); const M = Math.ceil(debt / t / 50) * 50; const rem = M * t; const markup = rem - F; const total = rem + d; effRate = r * (1 - d / p) * 100; resMonthly.textContent = formatMoney(M); resMarkup.textContent = formatMoney(markup); resTotal.textContent = formatMoney(total); resDpRow.style.display = d > 0 ? 'flex' : 'none'; if (d > 0) resDpRub.textContent = formatMoney(d); } else { resMonthly.textContent = '—'; resMarkup.textContent = '—'; resTotal.textContent = formatMoney(p); resDpRow.style.display = d > 0 ? 'flex' : 'none'; if (d > 0) resDpRub.textContent = formatMoney(d); } updateTariffUI(p, effRate); warning.style.display = (p >= 200000 && d < getMinDP(p)) ? 'block' : 'none'; warning.textContent = `ℹ️ Минимальный взнос: ${formatMoney(getMinDP(p))}`; } function updateTariffUI(price, effRate) { const tariff = getTariff(price); const tariffChanged = tariff !== prevTariff; prevTariff = tariff; if (tariff === 'exclusive') { tariffCard.className = 'tariff-card exclusive'; tariffName.textContent = 'Эксклюзив'; } else { tariffCard.className = 'tariff-card standard'; tariffName.textContent = 'Стандарт'; } tariffEffRate.textContent = effRate ? (Math.round(effRate * 10) / 10).toFixed(1).replace('.0', '') + '%' : '—'; if (tariffChanged) { tariffEffRate.classList.add('pulse'); setTimeout(() => tariffEffRate.classList.remove('pulse'), 350); } } function updateUI(opts = {}) { const p = currentPrice; const minDp = getMinDP(p); priceSlider.value = p; fillSlider(priceSlider, p); if (!opts.skipPriceInput) { priceInput.value = p.toLocaleString('ru-RU').replace(/\s/g, ' '); } monthsSlider.value = currentTerm; monthsVal.textContent = currentTerm + ' мес.'; fillSlider(monthsSlider, currentTerm); const sliderMinPct = (getTariff(p) === 'exclusive' && p >= 250000) ? 25 : 0; dpSlider.min = sliderMinPct; dpSlider.max = DP_MAX_PCT; let sPct = Math.round((currentDP / p) * 100); if (sPct < sliderMinPct) sPct = sliderMinPct; if (sPct > DP_MAX_PCT) sPct = DP_MAX_PCT; dpSlider.value = sPct; fillSlider(dpSlider, sPct); dpBadge.textContent = sPct + '%'; dpMinLbl.textContent = sliderMinPct + '%'; if (!opts.skipDpInput) { dpInput.value = currentDP.toLocaleString('ru-RU').replace(/\s/g, ' '); } const minDpPct = p > 0 ? Math.round((minDp / p) * 100) : 0; dpPctBtns.forEach(btn => { const pctVal = parseInt(btn.dataset.pct); let target = (pctVal === 0 && minDp > 0) ? minDp : clampDP(round100(Math.floor(p * pctVal / 100)), p); const active = target === currentDP; btn.classList.toggle('active', active); if (pctVal === 0) { if (p >= 250000 && getTariff(p) === 'exclusive') { btn.textContent = '25%'; btn.classList.add('semi-disabled'); btn.classList.remove('active'); } else if (minDpPct > 0) { btn.textContent = minDpPct + '%'; btn.classList.remove('semi-disabled'); } else { btn.textContent = '0%'; btn.classList.remove('semi-disabled'); } } else { btn.textContent = pctVal + '%'; btn.classList.remove('semi-disabled'); } }); compute(); } priceSlider.addEventListener('input', () => { const val = parseInt(priceSlider.value); if (val === currentPrice) return; currentPrice = val; fillSlider(priceSlider, val); priceInput.value = val.toLocaleString('ru-RU').replace(/\s/g, ' '); currentDP = clampDP(round100(Math.floor(currentPrice * 0.25)), currentPrice); updateUI({ skipPriceInput: true }); }); priceInput.addEventListener('input', () => { const raw = priceInput.value; const num = parseInt(raw.replace(/\s/g, '')) || 0; if (num === currentPrice) return; if (num > getSliderMax()) { currentPrice = getSliderMax(); priceInput.value = getSliderMax().toLocaleString('ru-RU').replace(/\s/g, ' '); } else { currentPrice = num; } if (currentPrice < MIN_PRICE) currentPrice = MIN_PRICE; currentDP = clampDP(round100(Math.floor(currentPrice * 0.25)), currentPrice); updateUI({ skipPriceInput: true }); }); priceInput.addEventListener('blur', () => { priceInput.value = currentPrice.toLocaleString('ru-RU').replace(/\s/g, ' '); }); monthsSlider.addEventListener('input', () => { currentTerm = parseInt(monthsSlider.value); fillSlider(monthsSlider, currentTerm); monthsVal.textContent = currentTerm + ' мес.'; compute(); }); dpSlider.addEventListener('input', () => { const pct = parseInt(dpSlider.value); let target = round100(Math.floor(currentPrice * pct / 100)); const clamped = clampDP(target, currentPrice); if (clamped < getMinDP(currentPrice) && currentPrice < 250000) { currentDP = getMinDP(currentPrice); dpSlider.value = Math.ceil((currentDP / currentPrice) * 100); } else { currentDP = clamped; } fillSlider(dpSlider, dpSlider.value); dpBadge.textContent = dpSlider.value + '%'; dpInput.value = currentDP.toLocaleString('ru-RU').replace(/\s/g, ' '); updateUI({ skipDpInput: true, keepDP: true }); }); dpInput.addEventListener('input', () => { const raw = dpInput.value; const num = parseInt(raw.replace(/\s/g, '')) || 0; currentDP = num; updateUI({ skipDpInput: true, keepDP: true }); }); dpInput.addEventListener('blur', () => { let val = parseInt(dpInput.value.replace(/\s/g, '')) || 0; val = round100(val); currentDP = clampDP(val, currentPrice); dpInput.value = currentDP.toLocaleString('ru-RU').replace(/\s/g, ' '); updateUI({ keepDP: true }); }); dpPctBtns.forEach(btn => { btn.addEventListener('click', () => { const pctVal = parseInt(btn.dataset.pct); if (btn.classList.contains('semi-disabled')) return; const minDp = getMinDP(currentPrice); let target = (pctVal === 0 && minDp > 0) ? minDp : round100(Math.floor(currentPrice * pctVal / 100)); currentDP = clampDP(target, currentPrice); dpInput.value = currentDP.toLocaleString('ru-RU').replace(/\s/g, ' '); updateUI({ keepDP: true }); }); }); shareBtn.addEventListener('click', () => { const text = `📋 Рассрочка «Мубарака»\n\n` + `💰 Стоимость: ${formatMoney(currentPrice)}\n` + `📅 Срок: ${currentTerm} мес.\n` + `💎 Взнос: ${formatMoney(currentDP)} (${dpBadge.textContent})\n` + `📊 Платёж: ${resMonthly.textContent}\n` + `🏷️ Наценка: ${resMarkup.textContent}\n` + `✅ Итого: ${resTotal.textContent}\n\n` + `Тариф: ${tariffName.textContent} (${tariffEffRate.textContent})`; if (navigator.share) { navigator.share({ text }).catch(() => {}); } else { navigator.clipboard.writeText(text).then(() => { shareBtn.textContent = '✅ Скопировано!'; setTimeout(() => shareBtn.textContent = '📤 Поделиться расчётом', 1500); }); } }); infoBtn.addEventListener('click', () => modalOverlay.classList.add('active')); modalOverlay.addEventListener('click', (e) => { if (e.target === modalOverlay) modalOverlay.classList.remove('active'); }); currentPrice = 110000; currentTerm = 3; currentDP = clampDP(round100(Math.floor(currentPrice * 0.25)), currentPrice); dpInput.value = currentDP.toLocaleString('ru-RU').replace(/\s/g, ' '); priceInput.value = currentPrice.toLocaleString('ru-RU').replace(/\s/g, ' '); updateUI(); })();