feat: add toolbar, autosave, URL sharing, preview panel, dynamic sections/fields
This commit is contained in:
+194
-135
@@ -7,143 +7,202 @@
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<header>
|
||||
<h1>Sunset Pass</h1>
|
||||
<p class="subtitle">Опросник для разработки системы бронирования парка</p>
|
||||
<div class="progress-bar">
|
||||
<div class="progress-fill" id="progressFill"></div>
|
||||
</div>
|
||||
<p class="progress-text" id="progressText">Вопрос 1 из 12</p>
|
||||
</header>
|
||||
|
||||
<form id="questionnaireForm" action="https://formspree.io/f/xrejjoav" method="POST">
|
||||
<input type="hidden" name="_replyto" id="_replyto">
|
||||
|
||||
<!-- О парке -->
|
||||
<section class="section">
|
||||
<h2>О парке</h2>
|
||||
|
||||
<div class="question" data-index="1">
|
||||
<label for="park_size">1. Какой размер парка? (площадь, примерно)</label>
|
||||
<textarea id="park_size" name="park_size" placeholder="Например: 2000 кв.м, примерно 50x40 метров"></textarea>
|
||||
<textarea id="park_size_comment" name="park_size_comment" class="comment" placeholder="Комментарий (необязательно)"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="question" data-index="2">
|
||||
<label for="max_people">2. Сколько человек максимум могут быть в парке одновременно?</label>
|
||||
<textarea id="max_people" name="max_people" placeholder="Например: 5 человек с собаками"></textarea>
|
||||
<textarea id="max_people_comment" name="max_people_comment" class="comment" placeholder="Комментарий (необязательно)"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="question" data-index="3">
|
||||
<label for="slot_duration">3. Какова минимальная и максимальная длительность одного слота?</label>
|
||||
<textarea id="slot_duration" name="slot_duration" placeholder="Например: минимум 30 минут, максимум 2 часа"></textarea>
|
||||
<textarea id="slot_duration_comment" name="slot_duration_comment" class="comment" placeholder="Комментарий (необязательно)"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="question" data-index="4">
|
||||
<label for="working_hours">4. Часы работы парка (по дням недели)?</label>
|
||||
<textarea id="working_hours" name="working_hours" placeholder="Например: пн-пт 8:00-20:00, сб-вс 9:00-21:00"></textarea>
|
||||
<textarea id="working_hours_comment" name="working_hours_comment" class="comment" placeholder="Комментарий (необязательно)"></textarea>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- О бронировании -->
|
||||
<section class="section">
|
||||
<h2>О бронировании</h2>
|
||||
|
||||
<div class="question" data-index="5">
|
||||
<label for="payment">5. Как вы хотите принимать оплату?</label>
|
||||
<textarea id="payment" name="payment" placeholder="Например: онлайн картой, наличными на месте, выставлять счёт"></textarea>
|
||||
<textarea id="payment_comment" name="payment_comment" class="comment" placeholder="Комментарий (необязательно)"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="question" data-index="6">
|
||||
<label for="cancellation">6. Нужна ли возможность отмены бронирования? За сколько часов до начала?</label>
|
||||
<textarea id="cancellation" name="cancellation" placeholder="Например: да, за 24 часа до начала / нет, отмена невозможна"></textarea>
|
||||
<textarea id="cancellation_comment" name="cancellation_comment" class="comment" placeholder="Комментарий (необязательно)"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="question" data-index="7">
|
||||
<label for="registration">7. Нужна ли регистрация пользователя или достаточно имя + email?</label>
|
||||
<textarea id="registration" name="registration" placeholder="Например: достаточно имени и email / нужна полная регистрация с паролем"></textarea>
|
||||
<textarea id="registration_comment" name="registration_comment" class="comment" placeholder="Комментарий (необязательно)"></textarea>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- О воротах -->
|
||||
<section class="section">
|
||||
<h2>О воротах</h2>
|
||||
|
||||
<div class="question" data-index="8">
|
||||
<label for="gate_equipment">8. Какое оборудование уже есть на воротах или нужно закупить?</label>
|
||||
<textarea id="gate_equipment" name="gate_equipment" placeholder="Например: уже есть электронный замок марки X / нужно закупить всё с нуля"></textarea>
|
||||
<textarea id="gate_equipment_comment" name="gate_equipment_comment" class="comment" placeholder="Комментарий (необязательно)"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="question" data-index="9">
|
||||
<label for="pin_generation">9. PIN-код — кто его генерирует: вы вручную или система автоматически?</label>
|
||||
<textarea id="pin_generation" name="pin_generation" placeholder="Например: система должна генерировать автоматически после оплаты"></textarea>
|
||||
<textarea id="pin_generation_comment" name="pin_generation_comment" class="comment" placeholder="Комментарий (необязательно)"></textarea>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- О дизайне -->
|
||||
<section class="section">
|
||||
<h2>О дизайне и языке</h2>
|
||||
|
||||
<div class="question" data-index="10">
|
||||
<label for="branding">10. Есть ли логотип, фирменные цвета, название парка?</label>
|
||||
<textarea id="branding" name="branding" placeholder="Например: название 'Koira Park', цвета зелёный и белый, логотип есть (могу прислать)"></textarea>
|
||||
<textarea id="branding_comment" name="branding_comment" class="comment" placeholder="Комментарий (необязательно)"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="question" data-index="11">
|
||||
<label for="language">11. На каком языке должен быть сайт/приложение?</label>
|
||||
<textarea id="language" name="language" placeholder="Например: только финский / финский + английский / финский + русский"></textarea>
|
||||
<textarea id="language_comment" name="language_comment" class="comment" placeholder="Комментарий (необязательно)"></textarea>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- О сроках -->
|
||||
<section class="section">
|
||||
<h2>О сроках</h2>
|
||||
|
||||
<div class="question" data-index="12">
|
||||
<label for="deadline">12. Когда нужен готовый продукт?</label>
|
||||
<textarea id="deadline" name="deadline" placeholder="Например: через 2 месяца / к лету 2026 / как можно скорее"></textarea>
|
||||
<textarea id="deadline_comment" name="deadline_comment" class="comment" placeholder="Комментарий (необязательно)"></textarea>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Контактные данные -->
|
||||
<section class="section">
|
||||
<h2>Ваши контактные данные</h2>
|
||||
<div class="question">
|
||||
<label for="client_name">Имя</label>
|
||||
<input type="text" id="client_name" name="client_name" required placeholder="Ваше имя">
|
||||
</div>
|
||||
<div class="question">
|
||||
<label for="client_email">Email</label>
|
||||
<input type="email" id="client_email" name="client_email" required placeholder="ваш@email.com">
|
||||
</div>
|
||||
<div class="question">
|
||||
<label for="client_phone">Телефон (необязательно)</label>
|
||||
<input type="tel" id="client_phone" name="client_phone" placeholder="+358 XX XXX XXXX">
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<button type="submit" id="submitBtn">Отправить ответы</button>
|
||||
<p class="submit-note">После отправки мы свяжемся с вами в течение 1-2 рабочих дней.</p>
|
||||
|
||||
</form>
|
||||
<!-- Тулбар -->
|
||||
<div class="toolbar">
|
||||
<div class="toolbar-left">
|
||||
<span class="toolbar-title">Sunset Pass — Опросник</span>
|
||||
<span class="save-status" id="saveStatus"></span>
|
||||
</div>
|
||||
<div class="toolbar-right">
|
||||
<button class="toolbar-btn" id="btnReset" title="Очистить форму">↺ Заново</button>
|
||||
<button class="toolbar-btn" id="btnSave" title="Сохранить">💾 Сохранить</button>
|
||||
<button class="toolbar-btn primary" id="btnShare" title="Поделиться ссылкой">🔗 Поделиться</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.getElementById('client_email').addEventListener('input', function() {
|
||||
document.getElementById('_replyto').value = this.value;
|
||||
});
|
||||
</script>
|
||||
<!-- Flash сообщение -->
|
||||
<div class="flash" id="flash"></div>
|
||||
|
||||
<!-- Основной макет -->
|
||||
<div class="app-layout">
|
||||
|
||||
<!-- Колонка с формой -->
|
||||
<div class="form-column">
|
||||
<header>
|
||||
<div class="progress-bar">
|
||||
<div class="progress-fill" id="progressFill"></div>
|
||||
</div>
|
||||
<p class="progress-text" id="progressText">Заполнено: 0 из 12</p>
|
||||
</header>
|
||||
|
||||
<form id="questionnaireForm" action="https://formspree.io/f/xrejjoav" method="POST">
|
||||
<input type="hidden" name="_replyto" id="_replyto">
|
||||
|
||||
<!-- О парке -->
|
||||
<section class="section" data-section-id="park">
|
||||
<div class="section-header">
|
||||
<h2 contenteditable="false">О парке</h2>
|
||||
</div>
|
||||
|
||||
<div class="question" data-index="1">
|
||||
<label for="park_size">1. Какой размер парка? (площадь, примерно)</label>
|
||||
<textarea id="park_size" name="park_size" placeholder="Например: 2000 кв.м, примерно 50x40 метров"></textarea>
|
||||
<textarea id="park_size_comment" name="park_size_comment" class="comment" placeholder="Комментарий (необязательно)"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="question" data-index="2">
|
||||
<label for="max_people">2. Сколько человек максимум могут быть в парке одновременно?</label>
|
||||
<textarea id="max_people" name="max_people" placeholder="Например: 5 человек с собаками"></textarea>
|
||||
<textarea id="max_people_comment" name="max_people_comment" class="comment" placeholder="Комментарий (необязательно)"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="question" data-index="3">
|
||||
<label for="slot_duration">3. Какова минимальная и максимальная длительность одного слота?</label>
|
||||
<textarea id="slot_duration" name="slot_duration" placeholder="Например: минимум 30 минут, максимум 2 часа"></textarea>
|
||||
<textarea id="slot_duration_comment" name="slot_duration_comment" class="comment" placeholder="Комментарий (необязательно)"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="question" data-index="4">
|
||||
<label for="working_hours">4. Часы работы парка (по дням недели)?</label>
|
||||
<textarea id="working_hours" name="working_hours" placeholder="Например: пн-пт 8:00-20:00, сб-вс 9:00-21:00"></textarea>
|
||||
<textarea id="working_hours_comment" name="working_hours_comment" class="comment" placeholder="Комментарий (необязательно)"></textarea>
|
||||
</div>
|
||||
|
||||
<button type="button" class="add-field-btn" onclick="addField(this)">+ Добавить поле</button>
|
||||
</section>
|
||||
|
||||
<button type="button" class="add-section-btn" onclick="addSection(this)">+ Добавить раздел</button>
|
||||
|
||||
<!-- О бронировании -->
|
||||
<section class="section" data-section-id="booking">
|
||||
<div class="section-header">
|
||||
<h2 contenteditable="false">О бронировании</h2>
|
||||
</div>
|
||||
|
||||
<div class="question" data-index="5">
|
||||
<label for="payment">5. Как вы хотите принимать оплату?</label>
|
||||
<textarea id="payment" name="payment" placeholder="Например: онлайн картой, наличными на месте, выставлять счёт"></textarea>
|
||||
<textarea id="payment_comment" name="payment_comment" class="comment" placeholder="Комментарий (необязательно)"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="question" data-index="6">
|
||||
<label for="cancellation">6. Нужна ли возможность отмены бронирования? За сколько часов до начала?</label>
|
||||
<textarea id="cancellation" name="cancellation" placeholder="Например: да, за 24 часа до начала / нет, отмена невозможна"></textarea>
|
||||
<textarea id="cancellation_comment" name="cancellation_comment" class="comment" placeholder="Комментарий (необязательно)"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="question" data-index="7">
|
||||
<label for="registration">7. Нужна ли регистрация пользователя или достаточно имя + email?</label>
|
||||
<textarea id="registration" name="registration" placeholder="Например: достаточно имени и email / нужна полная регистрация с паролем"></textarea>
|
||||
<textarea id="registration_comment" name="registration_comment" class="comment" placeholder="Комментарий (необязательно)"></textarea>
|
||||
</div>
|
||||
|
||||
<button type="button" class="add-field-btn" onclick="addField(this)">+ Добавить поле</button>
|
||||
</section>
|
||||
|
||||
<button type="button" class="add-section-btn" onclick="addSection(this)">+ Добавить раздел</button>
|
||||
|
||||
<!-- О воротах -->
|
||||
<section class="section" data-section-id="gates">
|
||||
<div class="section-header">
|
||||
<h2 contenteditable="false">О воротах</h2>
|
||||
</div>
|
||||
|
||||
<div class="question" data-index="8">
|
||||
<label for="gate_equipment">8. Какое оборудование уже есть на воротах или нужно закупить?</label>
|
||||
<textarea id="gate_equipment" name="gate_equipment" placeholder="Например: уже есть электронный замок марки X / нужно закупить всё с нуля"></textarea>
|
||||
<textarea id="gate_equipment_comment" name="gate_equipment_comment" class="comment" placeholder="Комментарий (необязательно)"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="question" data-index="9">
|
||||
<label for="pin_generation">9. PIN-код — кто его генерирует: вы вручную или система автоматически?</label>
|
||||
<textarea id="pin_generation" name="pin_generation" placeholder="Например: система должна генерировать автоматически после оплаты"></textarea>
|
||||
<textarea id="pin_generation_comment" name="pin_generation_comment" class="comment" placeholder="Комментарий (необязательно)"></textarea>
|
||||
</div>
|
||||
|
||||
<button type="button" class="add-field-btn" onclick="addField(this)">+ Добавить поле</button>
|
||||
</section>
|
||||
|
||||
<button type="button" class="add-section-btn" onclick="addSection(this)">+ Добавить раздел</button>
|
||||
|
||||
<!-- О дизайне -->
|
||||
<section class="section" data-section-id="design">
|
||||
<div class="section-header">
|
||||
<h2 contenteditable="false">О дизайне и языке</h2>
|
||||
</div>
|
||||
|
||||
<div class="question" data-index="10">
|
||||
<label for="branding">10. Есть ли логотип, фирменные цвета, название парка?</label>
|
||||
<textarea id="branding" name="branding" placeholder="Например: название 'Koira Park', цвета зелёный и белый, логотип есть (могу прислать)"></textarea>
|
||||
<textarea id="branding_comment" name="branding_comment" class="comment" placeholder="Комментарий (необязательно)"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="question" data-index="11">
|
||||
<label for="language">11. На каком языке должен быть сайт/приложение?</label>
|
||||
<textarea id="language" name="language" placeholder="Например: только финский / финский + английский / финский + русский"></textarea>
|
||||
<textarea id="language_comment" name="language_comment" class="comment" placeholder="Комментарий (необязательно)"></textarea>
|
||||
</div>
|
||||
|
||||
<button type="button" class="add-field-btn" onclick="addField(this)">+ Добавить поле</button>
|
||||
</section>
|
||||
|
||||
<button type="button" class="add-section-btn" onclick="addSection(this)">+ Добавить раздел</button>
|
||||
|
||||
<!-- О сроках -->
|
||||
<section class="section" data-section-id="deadlines">
|
||||
<div class="section-header">
|
||||
<h2 contenteditable="false">О сроках</h2>
|
||||
</div>
|
||||
|
||||
<div class="question" data-index="12">
|
||||
<label for="deadline">12. Когда нужен готовый продукт?</label>
|
||||
<textarea id="deadline" name="deadline" placeholder="Например: через 2 месяца / к лету 2026 / как можно скорее"></textarea>
|
||||
<textarea id="deadline_comment" name="deadline_comment" class="comment" placeholder="Комментарий (необязательно)"></textarea>
|
||||
</div>
|
||||
|
||||
<button type="button" class="add-field-btn" onclick="addField(this)">+ Добавить поле</button>
|
||||
</section>
|
||||
|
||||
<button type="button" class="add-section-btn" onclick="addSection(this)">+ Добавить раздел</button>
|
||||
|
||||
<!-- Контактные данные -->
|
||||
<section class="section" data-section-id="contacts">
|
||||
<div class="section-header">
|
||||
<h2 contenteditable="false">Ваши контактные данные</h2>
|
||||
</div>
|
||||
<div class="question">
|
||||
<label for="client_name">Имя</label>
|
||||
<input type="text" id="client_name" name="client_name" required placeholder="Ваше имя">
|
||||
</div>
|
||||
<div class="question">
|
||||
<label for="client_email">Email</label>
|
||||
<input type="email" id="client_email" name="client_email" required placeholder="ваш@email.com">
|
||||
</div>
|
||||
<div class="question">
|
||||
<label for="client_phone">Телефон (необязательно)</label>
|
||||
<input type="tel" id="client_phone" name="client_phone" placeholder="+358 XX XXX XXXX">
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<button type="submit" id="submitBtn">Отправить ответы</button>
|
||||
<p class="submit-note">После отправки мы свяжемся с вами в течение 1-2 рабочих дней.</p>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Панель предпросмотра -->
|
||||
<div class="preview-column">
|
||||
<div class="preview-panel">
|
||||
<div class="preview-header">Предпросмотр</div>
|
||||
<div class="preview-a4" id="previewContent">
|
||||
<p class="preview-empty">Начните заполнять форму — здесь появится предпросмотр</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,363 @@
|
||||
// ─── Состояние ───────────────────────────────────────────────────────────────
|
||||
let saveTimer = null;
|
||||
let dynamicSectionCount = 0;
|
||||
let dynamicFieldCount = 0;
|
||||
|
||||
// ─── Инициализация ────────────────────────────────────────────────────────────
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
loadFromURL();
|
||||
setupAutosave();
|
||||
updatePreview();
|
||||
updateProgress();
|
||||
|
||||
document.getElementById('client_email').addEventListener('input', function () {
|
||||
document.getElementById('_replyto').value = this.value;
|
||||
});
|
||||
|
||||
document.getElementById('btnReset').addEventListener('click', resetForm);
|
||||
document.getElementById('btnSave').addEventListener('click', saveManual);
|
||||
document.getElementById('btnShare').addEventListener('click', shareForm);
|
||||
});
|
||||
|
||||
// ─── Прогресс ─────────────────────────────────────────────────────────────────
|
||||
function updateProgress() {
|
||||
const staticFields = ['park_size','max_people','slot_duration','working_hours',
|
||||
'payment','cancellation','registration','gate_equipment','pin_generation',
|
||||
'branding','language','deadline'];
|
||||
let filled = 0;
|
||||
staticFields.forEach(name => {
|
||||
const el = document.getElementById(name);
|
||||
if (el && el.value.trim()) filled++;
|
||||
});
|
||||
const total = staticFields.length;
|
||||
const pct = Math.round((filled / total) * 100);
|
||||
document.getElementById('progressFill').style.width = pct + '%';
|
||||
document.getElementById('progressText').textContent = `Заполнено: ${filled} из ${total}`;
|
||||
}
|
||||
|
||||
// ─── Превью ───────────────────────────────────────────────────────────────────
|
||||
function updatePreview() {
|
||||
const container = document.getElementById('previewContent');
|
||||
const sections = document.querySelectorAll('.section');
|
||||
let html = '';
|
||||
|
||||
sections.forEach(section => {
|
||||
const title = section.querySelector('h2');
|
||||
const questions = section.querySelectorAll('.question');
|
||||
let sectionHtml = '';
|
||||
|
||||
questions.forEach(q => {
|
||||
const inputs = q.querySelectorAll('textarea, input[type="text"], input[type="email"], input[type="tel"]');
|
||||
const label = q.querySelector('label, .dynamic-question-header input.field-title');
|
||||
let labelText = '';
|
||||
let mainVal = '';
|
||||
let commentVal = '';
|
||||
|
||||
if (label) {
|
||||
labelText = label.tagName === 'LABEL' ? label.textContent.trim() : label.value.trim();
|
||||
}
|
||||
|
||||
inputs.forEach((inp, i) => {
|
||||
const val = inp.value.trim();
|
||||
if (i === 0) mainVal = val;
|
||||
if (inp.classList.contains('comment')) commentVal = val;
|
||||
});
|
||||
|
||||
if (mainVal) {
|
||||
sectionHtml += `<div class="preview-item">
|
||||
<div class="preview-item-label">${labelText}</div>
|
||||
<div class="preview-item-value">${escapeHtml(mainVal)}</div>
|
||||
${commentVal ? `<div class="preview-item-comment">↳ ${escapeHtml(commentVal)}</div>` : ''}
|
||||
</div>`;
|
||||
}
|
||||
});
|
||||
|
||||
if (sectionHtml && title) {
|
||||
html += `<div class="preview-section">
|
||||
<div class="preview-section-title">${title.textContent.trim()}</div>
|
||||
${sectionHtml}
|
||||
</div>`;
|
||||
}
|
||||
});
|
||||
|
||||
container.innerHTML = html || '<p class="preview-empty">Начните заполнять форму — здесь появится предпросмотр</p>';
|
||||
}
|
||||
|
||||
function escapeHtml(str) {
|
||||
return str.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
|
||||
}
|
||||
|
||||
// ─── Автосохранение ───────────────────────────────────────────────────────────
|
||||
function setupAutosave() {
|
||||
document.getElementById('questionnaireForm').addEventListener('input', function () {
|
||||
updatePreview();
|
||||
updateProgress();
|
||||
clearTimeout(saveTimer);
|
||||
saveTimer = setTimeout(() => {
|
||||
saveToStorage();
|
||||
updateURL();
|
||||
}, 10000);
|
||||
});
|
||||
}
|
||||
|
||||
function saveToStorage() {
|
||||
const data = collectFormData();
|
||||
localStorage.setItem('sunsetpass_form', JSON.stringify(data));
|
||||
document.getElementById('saveStatus').textContent = 'Сохранено ' + formatTime();
|
||||
}
|
||||
|
||||
function saveManual() {
|
||||
saveToStorage();
|
||||
updateURL();
|
||||
showFlash('Сохранено!');
|
||||
}
|
||||
|
||||
function formatTime() {
|
||||
const d = new Date();
|
||||
return d.getHours().toString().padStart(2,'0') + ':' + d.getMinutes().toString().padStart(2,'0');
|
||||
}
|
||||
|
||||
// ─── Сбор данных формы ────────────────────────────────────────────────────────
|
||||
function collectFormData() {
|
||||
const form = document.getElementById('questionnaireForm');
|
||||
const data = { fields: {}, dynamicSections: [] };
|
||||
|
||||
// Статичные поля
|
||||
form.querySelectorAll('textarea[name], input[name]').forEach(el => {
|
||||
if (el.name && el.name !== '_replyto') {
|
||||
data.fields[el.name] = el.value;
|
||||
}
|
||||
});
|
||||
|
||||
// Динамические секции
|
||||
form.querySelectorAll('.section.dynamic-section').forEach(sec => {
|
||||
const title = sec.querySelector('h2') ? sec.querySelector('h2').textContent.trim() : '';
|
||||
const fields = [];
|
||||
sec.querySelectorAll('.dynamic-question').forEach(q => {
|
||||
const titleInput = q.querySelector('input.field-title');
|
||||
const textarea = q.querySelector('textarea');
|
||||
fields.push({
|
||||
title: titleInput ? titleInput.value : '',
|
||||
value: textarea ? textarea.value : ''
|
||||
});
|
||||
});
|
||||
data.dynamicSections.push({ title, fields });
|
||||
});
|
||||
|
||||
// Дополнительные поля в статичных секциях
|
||||
data.extraFields = [];
|
||||
form.querySelectorAll('.section:not(.dynamic-section) .dynamic-question').forEach(q => {
|
||||
const sectionId = q.closest('.section').dataset.sectionId;
|
||||
const titleInput = q.querySelector('input.field-title');
|
||||
const textarea = q.querySelector('textarea');
|
||||
data.extraFields.push({
|
||||
sectionId,
|
||||
title: titleInput ? titleInput.value : '',
|
||||
value: textarea ? textarea.value : ''
|
||||
});
|
||||
});
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
// ─── Загрузка / URL ───────────────────────────────────────────────────────────
|
||||
function updateURL() {
|
||||
const data = collectFormData();
|
||||
const json = JSON.stringify(data);
|
||||
const b64 = btoa(unescape(encodeURIComponent(json)));
|
||||
const url = new URL(window.location.href);
|
||||
url.searchParams.set('d', b64);
|
||||
window.history.replaceState(null, '', url.toString());
|
||||
}
|
||||
|
||||
function loadFromURL() {
|
||||
const params = new URL(window.location.href).searchParams;
|
||||
const b64 = params.get('d');
|
||||
if (b64) {
|
||||
try {
|
||||
const json = decodeURIComponent(escape(atob(b64)));
|
||||
const data = JSON.parse(json);
|
||||
restoreFormData(data);
|
||||
document.getElementById('saveStatus').textContent = 'Загружено из ссылки';
|
||||
return;
|
||||
} catch (e) {}
|
||||
}
|
||||
// Попробовать localStorage
|
||||
const saved = localStorage.getItem('sunsetpass_form');
|
||||
if (saved) {
|
||||
try {
|
||||
restoreFormData(JSON.parse(saved));
|
||||
document.getElementById('saveStatus').textContent = 'Восстановлено из сохранения';
|
||||
} catch (e) {}
|
||||
}
|
||||
}
|
||||
|
||||
function restoreFormData(data) {
|
||||
if (!data) return;
|
||||
const form = document.getElementById('questionnaireForm');
|
||||
|
||||
// Статичные поля
|
||||
if (data.fields) {
|
||||
Object.entries(data.fields).forEach(([name, value]) => {
|
||||
const el = form.querySelector(`[name="${name}"]`);
|
||||
if (el) el.value = value;
|
||||
});
|
||||
}
|
||||
|
||||
// Дополнительные поля в статичных секциях
|
||||
if (data.extraFields) {
|
||||
data.extraFields.forEach(item => {
|
||||
const section = form.querySelector(`.section[data-section-id="${item.sectionId}"]`);
|
||||
if (section) {
|
||||
const addBtn = section.querySelector('.add-field-btn');
|
||||
addField(addBtn, item.title, item.value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Динамические секции
|
||||
if (data.dynamicSections) {
|
||||
data.dynamicSections.forEach(sec => {
|
||||
const lastAddSectionBtn = [...form.querySelectorAll('.add-section-btn')].pop();
|
||||
if (lastAddSectionBtn) {
|
||||
const newSection = addSection(lastAddSectionBtn, sec.title);
|
||||
if (newSection && sec.fields) {
|
||||
sec.fields.forEach(field => {
|
||||
const addBtn = newSection.querySelector('.add-field-btn');
|
||||
addField(addBtn, field.title, field.value);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
updatePreview();
|
||||
updateProgress();
|
||||
}
|
||||
|
||||
// ─── Поделиться ───────────────────────────────────────────────────────────────
|
||||
function shareForm() {
|
||||
saveToStorage();
|
||||
updateURL();
|
||||
const url = window.location.href;
|
||||
|
||||
if (navigator.share) {
|
||||
navigator.share({ title: 'Sunset Pass — Опросник', url }).catch(() => copyToClipboard(url));
|
||||
} else {
|
||||
copyToClipboard(url);
|
||||
}
|
||||
}
|
||||
|
||||
function copyToClipboard(text) {
|
||||
if (navigator.clipboard) {
|
||||
navigator.clipboard.writeText(text).then(() => showFlash('Ссылка скопирована!')).catch(() => fallbackCopy(text));
|
||||
} else {
|
||||
fallbackCopy(text);
|
||||
}
|
||||
}
|
||||
|
||||
function fallbackCopy(text) {
|
||||
const ta = document.createElement('textarea');
|
||||
ta.value = text;
|
||||
ta.style.position = 'fixed';
|
||||
ta.style.opacity = '0';
|
||||
document.body.appendChild(ta);
|
||||
ta.select();
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(ta);
|
||||
showFlash('Ссылка скопирована!');
|
||||
}
|
||||
|
||||
// ─── Заново ───────────────────────────────────────────────────────────────────
|
||||
function resetForm() {
|
||||
if (!confirm('Очистить всю форму? Это действие нельзя отменить.')) return;
|
||||
|
||||
const form = document.getElementById('questionnaireForm');
|
||||
|
||||
// Очистить статичные поля
|
||||
form.querySelectorAll('textarea, input[type="text"], input[type="email"], input[type="tel"]').forEach(el => {
|
||||
el.value = '';
|
||||
});
|
||||
|
||||
// Удалить динамические поля
|
||||
form.querySelectorAll('.dynamic-question').forEach(el => el.remove());
|
||||
|
||||
// Удалить динамические секции
|
||||
form.querySelectorAll('.section.dynamic-section').forEach(el => el.remove());
|
||||
// Удалить add-section-btn дублей (оставить только оригинальные)
|
||||
const allAddBtns = form.querySelectorAll('.add-section-btn');
|
||||
allAddBtns.forEach((btn, i) => { if (i >= 6) btn.remove(); });
|
||||
|
||||
localStorage.removeItem('sunsetpass_form');
|
||||
const url = new URL(window.location.href);
|
||||
url.searchParams.delete('d');
|
||||
window.history.replaceState(null, '', url.toString());
|
||||
|
||||
document.getElementById('saveStatus').textContent = '';
|
||||
updatePreview();
|
||||
updateProgress();
|
||||
showFlash('Форма очищена');
|
||||
}
|
||||
|
||||
// ─── Flash ────────────────────────────────────────────────────────────────────
|
||||
function showFlash(msg) {
|
||||
const el = document.getElementById('flash');
|
||||
el.textContent = msg;
|
||||
el.classList.add('show');
|
||||
setTimeout(() => el.classList.remove('show'), 2500);
|
||||
}
|
||||
|
||||
// ─── Добавить поле ────────────────────────────────────────────────────────────
|
||||
function addField(btn, titleVal, textVal) {
|
||||
dynamicFieldCount++;
|
||||
const id = 'dyn_field_' + dynamicFieldCount;
|
||||
const div = document.createElement('div');
|
||||
div.className = 'dynamic-question';
|
||||
div.innerHTML = `
|
||||
<div class="dynamic-question-header">
|
||||
<input class="field-title" type="text" placeholder="Название поля..." value="${escapeAttr(titleVal || '')}">
|
||||
<button type="button" class="remove-btn" onclick="this.closest('.dynamic-question').remove(); updatePreview(); updateProgress();" title="Удалить поле">×</button>
|
||||
</div>
|
||||
<textarea id="${id}" name="${id}" placeholder="Ответ...">${escapeAttr(textVal || '')}</textarea>
|
||||
`;
|
||||
btn.parentNode.insertBefore(div, btn);
|
||||
|
||||
div.querySelector('input.field-title').addEventListener('input', updatePreview);
|
||||
div.querySelector('textarea').addEventListener('input', updatePreview);
|
||||
|
||||
return div;
|
||||
}
|
||||
|
||||
// ─── Добавить раздел ──────────────────────────────────────────────────────────
|
||||
function addSection(btn, titleVal) {
|
||||
dynamicSectionCount++;
|
||||
const section = document.createElement('section');
|
||||
section.className = 'section dynamic-section';
|
||||
section.dataset.sectionId = 'dyn_' + dynamicSectionCount;
|
||||
section.innerHTML = `
|
||||
<div class="section-header" style="display:flex;align-items:center;gap:8px;">
|
||||
<h2 contenteditable="true" style="flex:1;outline:none;border-bottom:2px solid #2d6a4f;padding-bottom:4px;" placeholder="Название раздела...">${escapeAttr(titleVal || '')}</h2>
|
||||
<button type="button" class="remove-btn" onclick="this.closest('.section').nextElementSibling?.classList.contains('add-section-btn') && this.closest('.section').nextElementSibling.remove(); this.closest('.section').remove(); updatePreview();" title="Удалить раздел">×</button>
|
||||
</div>
|
||||
<button type="button" class="add-field-btn" onclick="addField(this)">+ Добавить поле</button>
|
||||
`;
|
||||
|
||||
section.querySelector('h2').addEventListener('input', updatePreview);
|
||||
|
||||
// Вставить перед кнопкой добавления раздела
|
||||
btn.parentNode.insertBefore(section, btn);
|
||||
|
||||
// Добавить новую кнопку добавления раздела после секции
|
||||
const newBtn = document.createElement('button');
|
||||
newBtn.type = 'button';
|
||||
newBtn.className = 'add-section-btn';
|
||||
newBtn.textContent = '+ Добавить раздел';
|
||||
newBtn.onclick = function() { addSection(this); };
|
||||
section.parentNode.insertBefore(newBtn, btn);
|
||||
|
||||
return section;
|
||||
}
|
||||
|
||||
function escapeAttr(str) {
|
||||
return str.replace(/"/g, '"').replace(/</g, '<').replace(/>/g, '>');
|
||||
}
|
||||
@@ -11,27 +11,126 @@ body {
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 720px;
|
||||
/* ─── Тулбар ─── */
|
||||
.toolbar {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
background: #fff;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
padding: 10px 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
box-shadow: 0 1px 4px rgba(0,0,0,0.06);
|
||||
}
|
||||
|
||||
.toolbar-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.toolbar-title {
|
||||
font-weight: 600;
|
||||
font-size: 0.95rem;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.save-status {
|
||||
font-size: 0.8rem;
|
||||
color: #888;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.toolbar-right {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.toolbar-btn {
|
||||
padding: 7px 14px;
|
||||
border: 1.5px solid #ddd;
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
font-size: 0.85rem;
|
||||
font-family: inherit;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s, border-color 0.15s;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.toolbar-btn:hover {
|
||||
background: #f0f0f0;
|
||||
border-color: #bbb;
|
||||
}
|
||||
|
||||
.toolbar-btn.primary {
|
||||
background: #2d6a4f;
|
||||
color: #fff;
|
||||
border-color: #2d6a4f;
|
||||
}
|
||||
|
||||
.toolbar-btn.primary:hover {
|
||||
background: #1b4332;
|
||||
border-color: #1b4332;
|
||||
}
|
||||
|
||||
/* ─── Flash ─── */
|
||||
.flash {
|
||||
position: fixed;
|
||||
top: 60px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%) translateY(-20px);
|
||||
background: #1a1a1a;
|
||||
color: #fff;
|
||||
padding: 10px 20px;
|
||||
border-radius: 8px;
|
||||
font-size: 0.9rem;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.2s, transform 0.2s;
|
||||
z-index: 200;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.flash.show {
|
||||
opacity: 1;
|
||||
transform: translateX(-50%) translateY(0);
|
||||
}
|
||||
|
||||
/* ─── Макет ─── */
|
||||
.app-layout {
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
max-width: 1300px;
|
||||
margin: 0 auto;
|
||||
padding: 40px 20px 80px;
|
||||
padding: 32px 24px 80px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.form-column {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
max-width: 720px;
|
||||
}
|
||||
|
||||
.preview-column {
|
||||
width: 360px;
|
||||
flex-shrink: 0;
|
||||
position: sticky;
|
||||
top: 72px;
|
||||
max-height: calc(100vh - 80px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* ─── Прогресс ─── */
|
||||
header {
|
||||
text-align: center;
|
||||
margin-bottom: 48px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 8px;
|
||||
color: #1a1a1a;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
color: #666;
|
||||
font-size: 1rem;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
@@ -41,7 +140,7 @@ h1 {
|
||||
background: #e0e0e0;
|
||||
border-radius: 3px;
|
||||
overflow: hidden;
|
||||
margin-bottom: 8px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.progress-fill {
|
||||
@@ -53,48 +152,53 @@ h1 {
|
||||
}
|
||||
|
||||
.progress-text {
|
||||
font-size: 0.85rem;
|
||||
font-size: 0.82rem;
|
||||
color: #888;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* ─── Секции ─── */
|
||||
.section {
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
padding: 32px;
|
||||
margin-bottom: 24px;
|
||||
padding: 28px 32px 20px;
|
||||
margin-bottom: 8px;
|
||||
box-shadow: 0 1px 4px rgba(0,0,0,0.06);
|
||||
}
|
||||
|
||||
.section-header {
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 2px solid #e8f5e9;
|
||||
}
|
||||
|
||||
.section h2 {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
color: #2d6a4f;
|
||||
margin-bottom: 24px;
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 2px solid #e8f5e9;
|
||||
}
|
||||
|
||||
.question {
|
||||
margin-bottom: 28px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.question:last-child {
|
||||
margin-bottom: 0;
|
||||
.question:last-of-type {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
font-weight: 500;
|
||||
margin-bottom: 8px;
|
||||
font-size: 0.95rem;
|
||||
margin-bottom: 6px;
|
||||
font-size: 0.92rem;
|
||||
}
|
||||
|
||||
textarea, input[type="text"], input[type="email"], input[type="tel"] {
|
||||
width: 100%;
|
||||
padding: 12px 14px;
|
||||
padding: 10px 12px;
|
||||
border: 1.5px solid #ddd;
|
||||
border-radius: 8px;
|
||||
font-size: 0.95rem;
|
||||
font-size: 0.92rem;
|
||||
font-family: inherit;
|
||||
color: #1a1a1a;
|
||||
background: #fafafa;
|
||||
@@ -103,7 +207,7 @@ textarea, input[type="text"], input[type="email"], input[type="tel"] {
|
||||
}
|
||||
|
||||
textarea {
|
||||
min-height: 80px;
|
||||
min-height: 72px;
|
||||
}
|
||||
|
||||
textarea:focus, input:focus {
|
||||
@@ -113,9 +217,9 @@ textarea:focus, input:focus {
|
||||
}
|
||||
|
||||
textarea.comment {
|
||||
min-height: 50px;
|
||||
margin-top: 8px;
|
||||
font-size: 0.88rem;
|
||||
min-height: 44px;
|
||||
margin-top: 6px;
|
||||
font-size: 0.85rem;
|
||||
color: #555;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
@@ -125,62 +229,244 @@ textarea.comment::placeholder {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* ─── Кнопки добавления ─── */
|
||||
.add-field-btn {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
margin-top: 12px;
|
||||
background: none;
|
||||
border: 1.5px dashed #c8e6c9;
|
||||
border-radius: 8px;
|
||||
color: #2d6a4f;
|
||||
font-size: 0.85rem;
|
||||
font-family: inherit;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s, border-color 0.15s;
|
||||
}
|
||||
|
||||
.add-field-btn:hover {
|
||||
background: #f0faf4;
|
||||
border-color: #2d6a4f;
|
||||
}
|
||||
|
||||
.add-section-btn {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
margin-bottom: 8px;
|
||||
background: none;
|
||||
border: 1.5px dashed #ddd;
|
||||
border-radius: 10px;
|
||||
color: #888;
|
||||
font-size: 0.85rem;
|
||||
font-family: inherit;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s, border-color 0.15s, color 0.15s;
|
||||
}
|
||||
|
||||
.add-section-btn:hover {
|
||||
background: #f8f8f8;
|
||||
border-color: #aaa;
|
||||
color: #444;
|
||||
}
|
||||
|
||||
/* Динамические поля */
|
||||
.dynamic-question {
|
||||
margin-top: 16px;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.dynamic-question-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.dynamic-question input.field-title {
|
||||
flex: 1;
|
||||
font-weight: 500;
|
||||
font-size: 0.92rem;
|
||||
border: none;
|
||||
border-bottom: 1.5px solid #ddd;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
.dynamic-question input.field-title:focus {
|
||||
border-bottom-color: #2d6a4f;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.remove-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
color: #ccc;
|
||||
font-size: 1.1rem;
|
||||
cursor: pointer;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
line-height: 1;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.remove-btn:hover {
|
||||
color: #e53935;
|
||||
background: #fff0f0;
|
||||
}
|
||||
|
||||
/* Динамические секции */
|
||||
.dynamic-section .section-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.dynamic-section h2[contenteditable="true"] {
|
||||
flex: 1;
|
||||
outline: none;
|
||||
border-bottom: 2px solid #2d6a4f;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
|
||||
/* ─── Submit ─── */
|
||||
button[type="submit"] {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 16px;
|
||||
padding: 15px;
|
||||
background: #2d6a4f;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
font-size: 1.1rem;
|
||||
font-size: 1.05rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
font-family: inherit;
|
||||
transition: background 0.2s, transform 0.1s;
|
||||
margin-top: 32px;
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
button[type="submit"]:hover {
|
||||
background: #1b4332;
|
||||
}
|
||||
|
||||
button[type="submit"]:active {
|
||||
transform: scale(0.99);
|
||||
}
|
||||
|
||||
button[type="submit"]:disabled {
|
||||
background: #aaa;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
button[type="submit"]:hover { background: #1b4332; }
|
||||
button[type="submit"]:active { transform: scale(0.99); }
|
||||
button[type="submit"]:disabled { background: #aaa; cursor: not-allowed; }
|
||||
|
||||
.submit-note {
|
||||
text-align: center;
|
||||
color: #888;
|
||||
font-size: 0.82rem;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
/* ─── Превью панель ─── */
|
||||
.preview-panel {
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 1px 4px rgba(0,0,0,0.06);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.preview-header {
|
||||
padding: 12px 20px;
|
||||
background: #f0faf4;
|
||||
border-bottom: 1px solid #e8f5e9;
|
||||
font-size: 0.85rem;
|
||||
margin-top: 12px;
|
||||
font-weight: 600;
|
||||
color: #2d6a4f;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
background: #fff3f3;
|
||||
border: 1px solid #f5c6cb;
|
||||
color: #721c24;
|
||||
padding: 12px 16px;
|
||||
border-radius: 8px;
|
||||
margin-top: 16px;
|
||||
font-size: 0.9rem;
|
||||
display: none;
|
||||
.preview-a4 {
|
||||
padding: 24px 20px;
|
||||
min-height: 400px;
|
||||
font-size: 0.88rem;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.preview-empty {
|
||||
color: #bbb;
|
||||
font-style: italic;
|
||||
text-align: center;
|
||||
padding: 40px 0;
|
||||
}
|
||||
|
||||
.preview-section {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.preview-section-title {
|
||||
font-weight: 700;
|
||||
font-size: 0.95rem;
|
||||
color: #2d6a4f;
|
||||
border-bottom: 1.5px solid #e8f5e9;
|
||||
padding-bottom: 4px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.preview-item {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.preview-item-label {
|
||||
font-weight: 600;
|
||||
font-size: 0.82rem;
|
||||
color: #555;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.preview-item-value {
|
||||
color: #1a1a1a;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.preview-item-comment {
|
||||
font-size: 0.8rem;
|
||||
color: #888;
|
||||
font-style: italic;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
/* ─── Планшет ─── */
|
||||
@media (max-width: 1024px) {
|
||||
.app-layout {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.form-column {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.preview-column {
|
||||
width: 100%;
|
||||
position: static;
|
||||
max-height: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* ─── Телефон ─── */
|
||||
@media (max-width: 600px) {
|
||||
.container {
|
||||
padding: 24px 16px 60px;
|
||||
.toolbar {
|
||||
padding: 8px 16px;
|
||||
}
|
||||
|
||||
.toolbar-title {
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.toolbar-btn {
|
||||
padding: 6px 10px;
|
||||
font-size: 0.78rem;
|
||||
}
|
||||
|
||||
.app-layout {
|
||||
padding: 16px 16px 60px;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.section {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 1.5rem;
|
||||
padding: 20px 16px 14px;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user