300 lines
13 KiB
HTML
300 lines
13 KiB
HTML
<!DOCTYPE html>
|
||
<!-- Дизайн: Стандартный дизайн. Сохраняй все пять CSS; подробности в design.md. -->
|
||
<html lang="ru" data-theme="light">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||
<title>Калькулятор отпускных</title>
|
||
<link rel="stylesheet" href="design-system/kt-ai-fonts.css">
|
||
<link rel="stylesheet" href="design-system/kt-ai-tokens.css">
|
||
<link rel="stylesheet" href="design-system/kt-ai-components.css">
|
||
<link rel="stylesheet" href="design-system/kt-ai-page.css">
|
||
<link rel="stylesheet" href="design-system/vibe-theme.css">
|
||
<style>
|
||
a.kt-ai-btn { text-decoration: none; }
|
||
.calc-grid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--kt-ai-space-5); margin-top: var(--kt-ai-space-6); }
|
||
.calc-actions { display: flex; gap: var(--kt-ai-space-3); margin-top: var(--kt-ai-space-2); flex-wrap: wrap; }
|
||
.result-block { display: flex; flex-direction: column; gap: var(--kt-ai-space-2); }
|
||
.result-formula { font-size: var(--kt-ai-text-2xs); color: var(--kt-ai-fg-faint); font-family: var(--kt-ai-font-mono); }
|
||
.examples-head { display: flex; align-items: center; gap: var(--kt-ai-space-3); margin-top: var(--kt-ai-space-6); }
|
||
.examples-grid { display: grid; gap: var(--kt-ai-space-4); grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); margin-top: var(--kt-ai-space-3); }
|
||
.example-card .cap { font-size: var(--kt-ai-text-2xs); color: var(--kt-ai-fg-muted); }
|
||
.example-card .num { font-size: var(--kt-ai-text-lg); font-weight: var(--kt-ai-weight-semibold); font-variant-numeric: tabular-nums; }
|
||
.example-card .sum { font-size: var(--kt-ai-text-xl); font-weight: var(--kt-ai-weight-semibold); color: var(--kt-ai-primary); font-variant-numeric: tabular-nums; }
|
||
.howto-grid { display: grid; gap: var(--kt-ai-space-4); grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); margin-top: var(--kt-ai-space-4); }
|
||
[hidden] { display: none !important; }
|
||
@media (max-width: 640px) {
|
||
.calc-grid { grid-template-columns: 1fr; }
|
||
}
|
||
</style>
|
||
</head>
|
||
<body class="kt-ai-app" data-kt-data-mode="demo">
|
||
|
||
<header class="kt-ai-pagehead">
|
||
<div class="kt-ai-wrap kt-ai-pagehead-in">
|
||
<img src="design-system/brand/kazakhtelecom-logo.png" alt="Казахтелеком" height="28" style="display:block">
|
||
<span style="font-weight:var(--kt-ai-weight-semibold)">Калькулятор отпускных</span>
|
||
<span class="kt-ai-spacer"></span>
|
||
<span class="kt-ai-chip" data-tone="orange">Демо-данные</span>
|
||
</div>
|
||
</header>
|
||
|
||
<section class="kt-ai-hero" data-align="center">
|
||
<div class="kt-ai-wrap">
|
||
<h1>Сколько выплатят за отпуск</h1>
|
||
<p class="kt-ai-hero-sub">Введите оклад и количество дней — калькулятор сразу покажет итоговую сумму. Расчёт: оклад ÷ 30 × дни.</p>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="kt-ai-section" data-divider="true">
|
||
<div class="kt-ai-wrap">
|
||
<div class="kt-ai-card">
|
||
<div class="kt-ai-section-head">
|
||
<h2>Расчёт</h2>
|
||
<p>Заполните два поля и нажмите «Рассчитать». Последний расчёт сохраняется в этом браузере.</p>
|
||
</div>
|
||
|
||
<div class="calc-grid">
|
||
<div class="kt-ai-field">
|
||
<label for="salary">Оклад, тг</label>
|
||
<input id="salary" class="kt-ai-input" type="number" min="0" step="1" inputmode="numeric" placeholder="Например, 200000">
|
||
<span class="hint">Месячная зарплата без надбавок</span>
|
||
<span class="error" id="salary-err" hidden>Введите оклад больше нуля</span>
|
||
</div>
|
||
<div class="kt-ai-field">
|
||
<label for="days">Дни отпуска</label>
|
||
<input id="days" class="kt-ai-input" type="number" min="0" max="120" step="1" inputmode="numeric" placeholder="Например, 14">
|
||
<span class="hint">Целое количество дней, от 1 до 120</span>
|
||
<span class="error" id="days-err" hidden>Введите число дней от 1 до 120</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="calc-actions">
|
||
<button id="calc-btn" class="kt-ai-btn" data-variant="primary" type="button">Рассчитать</button>
|
||
<button id="clear-btn" class="kt-ai-btn" data-variant="ghost" type="button">Очистить</button>
|
||
</div>
|
||
|
||
<div id="result" class="kt-ai-card" data-glow="strong" hidden style="margin-top:var(--kt-ai-space-5)">
|
||
<div class="result-block">
|
||
<span class="kt-ai-chip" data-tone="blue">Результат</span>
|
||
<div class="kt-ai-kpi">
|
||
<span class="value" id="result-total">0 тг</span>
|
||
<span class="label">итого к выплате за отпуск</span>
|
||
</div>
|
||
<div class="kt-ai-kpi-strip">
|
||
<div class="kt-ai-kpi">
|
||
<span class="value" id="result-perday">0 тг</span>
|
||
<span class="label">средний дневной оклад</span>
|
||
</div>
|
||
<div class="kt-ai-kpi">
|
||
<span class="value" id="result-days">0</span>
|
||
<span class="label">дней в отпуске</span>
|
||
</div>
|
||
<div class="kt-ai-kpi">
|
||
<span class="value" id="result-salary">0 тг</span>
|
||
<span class="label">оклад, за основу</span>
|
||
</div>
|
||
</div>
|
||
<div class="result-formula" id="result-formula"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="kt-ai-empty" id="empty" hidden>
|
||
<div class="icon-spot"><svg width="20" height="20"><use href="design-system/icons/kt-ai-lucide-sprite.svg#tool"></use></svg></div>
|
||
<div class="title">Пока ничего не рассчитано</div>
|
||
<div class="hint">Заполните поля выше или выберите демо-пример ниже — блок результата появится здесь.</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="examples-head">
|
||
<h2 style="font-size:var(--kt-ai-text-md);font-weight:var(--kt-ai-weight-semibold)">Демо-примеры</h2>
|
||
<span class="kt-ai-chip" data-tone="orange">Демо-данные</span>
|
||
</div>
|
||
<p style="font-size:var(--kt-ai-text-sm);color:var(--kt-ai-fg-muted)">Нажмите на карточку, чтобы подставить значения в форму.</p>
|
||
<div class="examples-grid" id="examples"></div>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="kt-ai-section" data-divider="true">
|
||
<div class="kt-ai-wrap">
|
||
<div class="kt-ai-section-head">
|
||
<h2>Как считается</h2>
|
||
<p>Формула калькулятора — демонстрационная и не заменяет расчёт по трудовому законодательству Казахстана.</p>
|
||
</div>
|
||
<div class="howto-grid">
|
||
<article class="kt-ai-tile">
|
||
<h3>1. Дневной оклад</h3>
|
||
<p>Оклад делим на 30 — условная длительность месяца в рабочих расчётах.</p>
|
||
</article>
|
||
<article class="kt-ai-tile">
|
||
<h3>2. Умножаем на дни</h3>
|
||
<p>Дневной оклад умножаем на количество дней отпуска.</p>
|
||
</article>
|
||
<article class="kt-ai-tile">
|
||
<h3>3. Итог в тг</h3>
|
||
<p>Сумма округляется до целых тенге и показывается в блоке результата.</p>
|
||
</article>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<footer class="kt-ai-wrap kt-ai-pagefoot">
|
||
<span>© 2026 Калькулятор отпускных</span>
|
||
<span class="kt-ai-spacer"></span>
|
||
<span>Демо-прототип, демонстрационные данные</span>
|
||
</footer>
|
||
|
||
<script>
|
||
(function () {
|
||
"use strict";
|
||
|
||
var LS_KEY = "vacation-calc:state:v1";
|
||
|
||
var EXAMPLES = [
|
||
{ id: "demo-14", name: "Стандартный отпуск", salary: 200000, days: 14 },
|
||
{ id: "demo-7", name: "Короткий отпуск", salary: 250000, days: 7 },
|
||
{ id: "demo-30", name: "Полный месяц", salary: 180000, days: 30 }
|
||
];
|
||
|
||
var salEl = document.getElementById("salary");
|
||
var daysEl = document.getElementById("days");
|
||
var salErr = document.getElementById("salary-err");
|
||
var daysErr = document.getElementById("days-err");
|
||
var calcBtn = document.getElementById("calc-btn");
|
||
var clearBtn = document.getElementById("clear-btn");
|
||
var resultBox = document.getElementById("result");
|
||
var emptyBox = document.getElementById("empty");
|
||
var totalEl = document.getElementById("result-total");
|
||
var perdayEl = document.getElementById("result-perday");
|
||
var daysResEl = document.getElementById("result-days");
|
||
var salaryResEl = document.getElementById("result-salary");
|
||
var formulaEl = document.getElementById("result-formula");
|
||
var examplesBox = document.getElementById("examples");
|
||
|
||
var nf = new Intl.NumberFormat("ru-RU");
|
||
|
||
function fmt(n) { return nf.format(Math.round(n)) + " тг"; }
|
||
|
||
function compute(salary, days) {
|
||
var perDay = salary / 30;
|
||
return {
|
||
salary: salary,
|
||
days: days,
|
||
perDay: perDay,
|
||
total: perDay * days
|
||
};
|
||
}
|
||
|
||
function save(state) {
|
||
try { localStorage.setItem(LS_KEY, JSON.stringify(state)); } catch (e) { /* приватный режим */ }
|
||
}
|
||
|
||
function load() {
|
||
try {
|
||
var raw = localStorage.getItem(LS_KEY);
|
||
return raw ? JSON.parse(raw) : null;
|
||
} catch (e) { return null; }
|
||
}
|
||
|
||
function showError(field, errEl, bad) {
|
||
errEl.hidden = !bad;
|
||
field.style.borderColor = bad ? "var(--kt-ai-status-risk-fg)" : "";
|
||
}
|
||
|
||
function validate(salary, days) {
|
||
var badSal = !(salary > 0);
|
||
var badDays = !(Number.isInteger(days) && days >= 1 && days <= 120);
|
||
showError(salEl, salErr, badSal);
|
||
showError(daysEl, daysErr, badDays);
|
||
return !badSal && !badDays;
|
||
}
|
||
|
||
function renderResult(r) {
|
||
resultBox.hidden = false;
|
||
emptyBox.hidden = true;
|
||
totalEl.textContent = fmt(r.total);
|
||
perdayEl.textContent = fmt(r.perDay);
|
||
daysResEl.textContent = String(r.days);
|
||
salaryResEl.textContent = fmt(r.salary);
|
||
formulaEl.textContent = nf.format(r.salary) + " ÷ 30 × " + r.days + " = " + nf.format(Math.round(r.total)) + " тг";
|
||
}
|
||
|
||
function renderEmpty() {
|
||
resultBox.hidden = true;
|
||
emptyBox.hidden = false;
|
||
}
|
||
|
||
function calc() {
|
||
var salary = Math.round(Number(salEl.value)) || 0;
|
||
var days = Math.round(Number(daysEl.value)) || 0;
|
||
if (!validate(salary, days)) return;
|
||
var r = compute(salary, days);
|
||
renderResult(r);
|
||
save({ last: r, inputs: { salary: salary, days: days } });
|
||
}
|
||
|
||
function clear() {
|
||
salEl.value = "";
|
||
daysEl.value = "";
|
||
showError(salEl, salErr, false);
|
||
showError(daysEl, daysErr, false);
|
||
renderEmpty();
|
||
save(null);
|
||
}
|
||
|
||
function applyExample(ex) {
|
||
salEl.value = String(ex.salary);
|
||
daysEl.value = String(ex.days);
|
||
showError(salEl, salErr, false);
|
||
showError(daysEl, daysErr, false);
|
||
var r = compute(ex.salary, ex.days);
|
||
renderResult(r);
|
||
save({ last: r, inputs: { salary: ex.salary, days: ex.days } });
|
||
resultBox.scrollIntoView({ behavior: "smooth", block: "nearest" });
|
||
}
|
||
|
||
function buildExamples() {
|
||
examplesBox.innerHTML = "";
|
||
EXAMPLES.forEach(function (ex) {
|
||
var r = compute(ex.salary, ex.days);
|
||
var card = document.createElement("article");
|
||
card.className = "kt-ai-card example-card";
|
||
card.setAttribute("data-clickable", "true");
|
||
card.innerHTML =
|
||
'<div class="cap">' + ex.name + "</div>" +
|
||
'<div class="num">' + r.days + " дн. · " + nf.format(ex.salary) + " тг</div>" +
|
||
'<div class="sum">' + fmt(r.total) + "</div>" +
|
||
'<button class="kt-ai-btn" data-variant="ghost" data-size="sm" type="button" style="margin-top:var(--kt-ai-space-3)">Подставить</button>';
|
||
card.querySelector("button").addEventListener("click", function () { applyExample(ex); });
|
||
card.addEventListener("click", function (e) {
|
||
if (e.target.tagName !== "BUTTON") applyExample(ex);
|
||
});
|
||
examplesBox.appendChild(card);
|
||
});
|
||
}
|
||
|
||
calcBtn.addEventListener("click", calc);
|
||
clearBtn.addEventListener("click", clear);
|
||
[salEl, daysEl].forEach(function (el) {
|
||
el.addEventListener("keydown", function (e) { if (e.key === "Enter") calc(); });
|
||
el.addEventListener("input", function () {
|
||
if (!el.value) showError(el === salEl ? salEl : daysEl, el === salEl ? salErr : daysErr, false);
|
||
});
|
||
});
|
||
|
||
buildExamples();
|
||
|
||
var saved = load();
|
||
if (saved && saved.inputs) {
|
||
salEl.value = String(saved.inputs.salary);
|
||
daysEl.value = String(saved.inputs.days);
|
||
if (saved.last) renderResult(saved.last);
|
||
} else {
|
||
renderEmpty();
|
||
}
|
||
})();
|
||
</script>
|
||
|
||
</body>
|
||
</html>
|