Files
test/public/prototype.html

514 lines
17 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Прототип</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Manrope:wght@400;600;700&display=swap');
* {
box-sizing: border-box;
}
html,
body {
margin: 0;
padding: 0;
height: 100%;
font-family: 'Manrope', system-ui, sans-serif;
color: #171717;
background: #f4f1eb;
}
.app {
height: 100%;
display: flex;
flex-direction: column;
gap: 12px;
padding: 34px 16px 18px;
background: linear-gradient(160deg, rgba(255, 255, 255, 0.95), rgba(243, 239, 232, 0.92));
}
.status {
display: flex;
align-items: center;
justify-content: space-between;
font-size: 12px;
color: #2d2d2d;
}
.pill {
font-size: 10px;
letter-spacing: 0.18em;
text-transform: uppercase;
padding: 4px 10px;
border-radius: 999px;
background: rgba(26, 77, 122, 0.12);
color: #1a4d7a;
font-weight: 600;
}
.icons {
display: flex;
align-items: center;
gap: 6px;
}
.dot {
width: 6px;
height: 6px;
border-radius: 999px;
background: #1a4d7a;
}
.progress {
position: relative;
height: 6px;
background: rgba(26, 77, 122, 0.12);
border-radius: 999px;
overflow: hidden;
}
.progress__bar {
position: absolute;
inset: 0;
width: 10%;
background: linear-gradient(90deg, #1a4d7a, #3f7fb0);
border-radius: inherit;
transition: width 0.35s ease;
}
.card {
background: rgba(255, 255, 255, 0.92);
border-radius: 18px;
border: 1px solid rgba(23, 23, 23, 0.08);
padding: 14px;
box-shadow: 0 12px 28px rgba(23, 23, 23, 0.08);
}
.card.main {
flex: 1;
display: flex;
flex-direction: column;
gap: 10px;
}
.eyebrow {
font-size: 10px;
letter-spacing: 0.16em;
text-transform: uppercase;
color: rgba(23, 23, 23, 0.5);
}
.title {
font-size: 20px;
font-weight: 700;
}
.subtitle {
font-size: 12px;
color: rgba(23, 23, 23, 0.64);
margin: 0;
}
.list {
display: grid;
gap: 8px;
margin-top: 4px;
}
.item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 10px;
border-radius: 12px;
background: #f0ece6;
font-size: 12px;
}
.item strong {
font-weight: 600;
}
.tag {
padding: 2px 8px;
border-radius: 999px;
font-size: 10px;
font-weight: 600;
}
.tag.success {
background: rgba(31, 129, 84, 0.14);
color: #1f8154;
}
.tag.pending {
background: rgba(211, 135, 32, 0.15);
color: #b7741c;
}
.tag.info {
background: rgba(26, 77, 122, 0.14);
color: #1a4d7a;
}
.info-card {
font-size: 12px;
color: rgba(23, 23, 23, 0.7);
line-height: 1.4;
}
.actions {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
}
.actions .btn {
border: none;
border-radius: 14px;
padding: 10px 12px;
font-size: 12px;
font-weight: 600;
cursor: pointer;
transition: transform 0.2s ease, box-shadow 0.2s ease, opacity 0.2s ease;
}
.actions .btn:active {
transform: scale(0.98);
}
.btn.primary {
background: #1a4d7a;
color: #fff;
box-shadow: 0 10px 20px rgba(26, 77, 122, 0.3);
grid-column: span 2;
}
.btn.secondary {
background: rgba(26, 77, 122, 0.12);
color: #1a4d7a;
}
.btn.ghost {
background: rgba(23, 23, 23, 0.06);
color: rgba(23, 23, 23, 0.75);
}
.btn[disabled] {
opacity: 0.45;
cursor: not-allowed;
transform: none;
box-shadow: none;
}
</style>
</head>
<body>
<div class="app">
<div class="status">
<div>9:41</div>
<div class="pill">Контроль ремонта</div>
<div class="icons">
<span class="dot"></span>
<span class="dot"></span>
<span class="dot"></span>
</div>
</div>
<div class="progress">
<div class="progress__bar" id="progressBar"></div>
</div>
<div class="card main">
<div class="eyebrow" id="stepLabel"></div>
<div class="title" id="stepTitle"></div>
<p class="subtitle" id="stepSubtitle"></p>
<div id="stepContent"></div>
</div>
<div class="card info-card" id="helperCard"></div>
<div class="actions">
<button class="btn ghost" data-action="back">Назад</button>
<button class="btn secondary" data-action="primary">Добавить</button>
<button class="btn primary" data-action="next">Далее</button>
</div>
</div>
<script>
(function () {
var state = {
step: 0,
projectCreated: false,
addressAdded: false,
mediaAdded: false,
quoteApproved: false,
stageStarted: false,
accepted: false
};
function resetState() {
state.step = 0;
state.projectCreated = false;
state.addressAdded = false;
state.mediaAdded = false;
state.quoteApproved = false;
state.stageStarted = false;
state.accepted = false;
}
var steps = [
{
label: 'Шаг 1 из 6',
title: 'Старт проекта',
subtitle: 'Зафиксируйте объект, чтобы запустить сценарий.',
primaryLabel: 'Создать проект',
onPrimary: function () {
state.projectCreated = true;
},
ready: function () {
return state.projectCreated;
},
content: function () {
var status = state.projectCreated ? 'Проект создан' : 'Проект не создан';
var tagClass = state.projectCreated ? 'success' : 'pending';
return (
'<div class="list">' +
'<div class="item"><strong>Объект</strong><span class="tag ' +
tagClass +
'">' +
status +
'</span></div>' +
'<div class="item"><strong>План приемки</strong><span class="tag info">В подготовке</span></div>' +
'</div>'
);
},
helper: function () {
return state.projectCreated
? 'Проект создан. Теперь добавьте адрес и параметры объекта, чтобы расчет стал точным.'
: 'Нажмите «Создать проект», чтобы зафиксировать старт и открыть следующие шаги.';
}
},
{
label: 'Шаг 2 из 6',
title: 'Добавление объекта',
subtitle: 'Адрес и параметры нужны для точной сметы.',
primaryLabel: 'Добавить адрес',
onPrimary: function () {
state.addressAdded = true;
},
ready: function () {
return state.addressAdded;
},
content: function () {
var address = state.addressAdded ? 'Москва, ул. Правды, 12' : 'Адрес не указан';
var tagClass = state.addressAdded ? 'success' : 'pending';
return (
'<div class="list">' +
'<div class="item"><strong>Адрес</strong><span class="tag ' +
tagClass +
'">' +
address +
'</span></div>' +
'<div class="item"><strong>Метраж</strong><span class="tag info">68 м²</span></div>' +
'</div>'
);
},
helper: function () {
return state.addressAdded
? 'Адрес сохранен. Переходим к медиа — это помогает зафиксировать стартовое состояние.'
: 'Добавьте адрес, чтобы расчет сметы учитывал реальный объект.';
}
},
{
label: 'Шаг 3 из 6',
title: 'Фото и видео',
subtitle: 'Фиксируем стартовое состояние объекта.',
primaryLabel: 'Добавить медиа',
onPrimary: function () {
state.mediaAdded = true;
},
ready: function () {
return state.mediaAdded;
},
content: function () {
var text = state.mediaAdded ? '12 фото, 2 видео' : 'Медиа не загружены';
var tagClass = state.mediaAdded ? 'success' : 'pending';
return (
'<div class="list">' +
'<div class="item"><strong>Загрузка</strong><span class="tag ' +
tagClass +
'">' +
text +
'</span></div>' +
'<div class="item"><strong>Комментарий</strong><span class="tag info">Черновая стяжка</span></div>' +
'</div>'
);
},
helper: function () {
return state.mediaAdded
? 'Медиа загружены. Система сформирует смету и этапы на основании материалов.'
: 'Добавьте фото и видео, чтобы у обеих сторон была единая точка отсчета.';
}
},
{
label: 'Шаг 4 из 6',
title: 'Смета и этапы',
subtitle: 'Проверьте расчет и зафиксируйте этапы.',
primaryLabel: 'Согласовать смету',
onPrimary: function () {
state.quoteApproved = true;
},
ready: function () {
return state.quoteApproved;
},
content: function () {
var tagClass = state.quoteApproved ? 'success' : 'pending';
var status = state.quoteApproved ? 'Согласовано' : 'На согласовании';
return (
'<div class="list">' +
'<div class="item"><strong>Смета</strong><span class="tag ' +
tagClass +
'">' +
status +
'</span></div>' +
'<div class="item"><strong>Этапы</strong><span class="tag info">6 этапов</span></div>' +
'</div>'
);
},
helper: function () {
return state.quoteApproved
? 'Смета согласована. Можно запускать работы по первому этапу.'
: 'Согласуйте смету, чтобы зафиксировать финансовую модель и регламент.';
}
},
{
label: 'Шаг 5 из 6',
title: 'Старт этапа',
subtitle: 'Исполнитель следует чек-листу и фиксирует ход.',
primaryLabel: 'Запустить этап 1',
onPrimary: function () {
state.stageStarted = true;
},
ready: function () {
return state.stageStarted;
},
content: function () {
var tagClass = state.stageStarted ? 'success' : 'pending';
var status = state.stageStarted ? 'В работе' : 'Не запущен';
return (
'<div class="list">' +
'<div class="item"><strong>Этап 1</strong><span class="tag ' +
tagClass +
'">' +
status +
'</span></div>' +
'<div class="item"><strong>Чек-лист</strong><span class="tag info">12 пунктов</span></div>' +
'</div>'
);
},
helper: function () {
return state.stageStarted
? 'Этап запущен. Система подготовит уведомление о приемке.'
: 'Запустите этап, чтобы начать работу и фиксировать прогресс.';
}
},
{
label: 'Шаг 6 из 6',
title: 'Приемка и оплата',
subtitle: 'Подтвердите качество, чтобы закрыть этап и оплату.',
primaryLabel: 'Подтвердить качество',
onPrimary: function () {
state.accepted = true;
},
ready: function () {
return state.accepted;
},
content: function () {
var status = state.accepted ? 'Принято' : 'Ожидает';
var tagClass = state.accepted ? 'success' : 'info';
return (
'<div class="list">' +
'<div class="item"><strong>Инспекция</strong><span class="tag ' +
tagClass +
'">' +
status +
'</span></div>' +
'<div class="item"><strong>Эскроу</strong><span class="tag info">1 250 000 ₽</span></div>' +
'</div>'
);
},
helper: function () {
return state.accepted
? 'Качество подтверждено. Средства автоматически переводятся исполнителю.'
: 'Подтвердите качество, чтобы выпустить оплату и закрыть этап.';
}
}
];
var stepLabel = document.getElementById('stepLabel');
var stepTitle = document.getElementById('stepTitle');
var stepSubtitle = document.getElementById('stepSubtitle');
var stepContent = document.getElementById('stepContent');
var helperCard = document.getElementById('helperCard');
var progressBar = document.getElementById('progressBar');
var actions = document.querySelector('.actions');
function render() {
var step = steps[state.step];
stepLabel.textContent = step.label;
stepTitle.textContent = step.title;
stepSubtitle.textContent = step.subtitle;
stepContent.innerHTML = step.content();
helperCard.textContent = step.helper();
progressBar.style.width = ((state.step + 1) / steps.length) * 100 + '%';
var buttons = actions.querySelectorAll('button');
buttons.forEach(function (btn) {
var action = btn.getAttribute('data-action');
if (action === 'back') {
btn.disabled = state.step === 0;
}
if (action === 'primary') {
btn.textContent = step.primaryLabel || 'Действие';
btn.style.display = step.primaryLabel ? 'inline-flex' : 'none';
}
if (action === 'next') {
btn.textContent = state.step === steps.length - 1 ? 'Завершить' : 'Далее';
btn.disabled = !step.ready();
}
});
}
actions.addEventListener('click', function (event) {
var target = event.target;
if (!target || !target.getAttribute) return;
var action = target.getAttribute('data-action');
var step = steps[state.step];
if (action === 'back') {
state.step = Math.max(0, state.step - 1);
render();
return;
}
if (action === 'primary') {
if (step.onPrimary) {
step.onPrimary();
}
render();
return;
}
if (action === 'next') {
if (!step.ready()) return;
if (state.step >= steps.length - 1) {
resetState();
render();
return;
}
state.step += 1;
render();
}
});
render();
})();
</script>
</body>
</html>