Сканер структуры файлов

Корневая папка: /home/d/docdelux/tech-stc.ru/public_html/construction-estimates/builder

Всего папок: 2

Всего файлов: 16

Общий размер: 78,757 байт

Создано: 2026-07-09 11:19:06

=== СТРУКТУРА ПАПОК И ФАЙЛОВ ===

build.php (3,057 байт)
[DIR] data/
data/builder.js (18,501 байт)
data/core.js (0 байт)
data/regions.json (2,934 байт)
data/style.css (13,075 байт)
[DIR] data/templates/
data/templates/facade-ventilated.json (2,481 байт)
data/templates/facade-wet.json (2,504 байт)
data/templates/foundation-pile.json (1,999 байт)
data/templates/foundation-slab.json (2,283 байт)
data/templates/foundation-strip.json (2,921 байт)
data/templates/heating-gas.json (2,830 байт)
data/templates/nterior-cosmetic.json (3,681 байт)
data/templates/roofing-metal.json (3,209 байт)
data/ui.js (0 байт)
index.php (6,247 байт)
print.php (13,035 байт)

=== СОДЕРЖИМОЕ ФАЙЛОВ ===

========================================
ФАЙЛ #1: build.php
Расширение: php
Размер: 3,057 байт
========================================
<?php /**  * AJAX-обработчик расчёта сметы  * /construction-estimates/builder/build.php  */  header('Content-Type: application/json'); header('Access-Control-Allow-Origin: *');  $data = json_decode(file_get_contents('php://input'), true);  if (!$data || !isset($data['template_id']) || !isset($data['params'])) {     echo json_encode(['error' => 'Неверные параметры']);     exit; }  $templateId = $data['template_id']; $params = $data['params'];  // Загружаем шаблон $templateFile = __DIR__ . '/data/templates/' . $templateId . '.json'; if (!file_exists($templateFile)) {     echo json_encode(['error' => 'Шаблон не найден: ' . $templateFile]);     exit; }  $template = json_decode(file_get_contents($templateFile), true); if (!$template) {     echo json_encode(['error' => 'Ошибка чтения шаблона']);     exit; }  // Загружаем регионы $regionsFile = __DIR__ . '/data/regions.json'; $regions = ['center' => ['id' => 'center', 'name' => 'Центральный регион', 'coefficient' => 1.0]];  if (file_exists($regionsFile)) {     $regionsData = json_decode(file_get_contents($regionsFile), true);     if (isset($regionsData['regions']) && is_array($regionsData['regions'])) {         foreach ($regionsData['regions'] as $r) {             $regions[$r['id']] = $r;         }     } }  // Региональный коэффициент $regionId = $params['region'] ?? 'center'; $regionCoeff = $regions[$regionId]['coefficient'] ?? 1.0;  // Расчёт сметы $estimate = [     'title' => $template['name'],     'description' => $template['description'],     'items' => [],     'total_direct' => 0,     'total' => 0 ];  foreach ($template['works'] as $work) {     // Вычисляем объём по формуле     $formula = $work['formula'];     foreach ($params as $key => $value) {         $formula = str_replace($key, (float)$value, $formula);     }          // Безопасное вычисление объёма     $qty = 0;     try {         $qty = @eval('return ' . $formula . ';');         if ($qty === false || $qty === null) $qty = 0;         $qty = round($qty, 3);     } catch (Throwable $e) {         $qty = 0;     }          if ($qty <= 0) continue;          // Цена с учётом региона     $price = $work['price_fer'] * $regionCoeff;          // Прямые затраты     $direct = $qty * $price;          $estimate['items'][] = [         'code' => $work['code'],         'name' => $work['name'],         'unit' => $work['unit'],         'qty' => $qty,         'price' => round($price, 2),         'direct' => round($direct, 2)     ];          $estimate['total_direct'] += $direct;     $estimate['total'] += $direct; }  $estimate['total_direct'] = round($estimate['total_direct'], 2); $estimate['total'] = round($estimate['total'], 2);  echo json_encode(['success' => true, 'estimate' => $estimate]);
========================================
ФАЙЛ #2: data/builder.js
Расширение: js
Размер: 18,501 байт
========================================
const BUILDER_API = '/construction-estimates/builder/build.php';  let currentWorkType = null; let regionsData = null;  // Загружаем регионы один раз при старте async function loadRegions() {     if (regionsData) return regionsData;     try {         const response = await fetch('/construction-estimates/builder/data/regions.json');         if (!response.ok) throw new Error('Регионы не загружены');         const data = await response.json();         regionsData = data.regions;         return regionsData;     } catch (error) {         console.error('Ошибка загрузки регионов:', error);         return [];     } }  document.addEventListener('DOMContentLoaded', async () => {     // Предзагружаем регионы     await loadRegions();          // Выбор типа работ     document.querySelectorAll('.worktype-card .btn-select-worktype').forEach(btn => {         btn.addEventListener('click', (e) => {             const card = e.target.closest('.worktype-card');             if (!card) return;             currentWorkType = card.dataset.worktype;             if (currentWorkType) {                 loadParamsTemplate(currentWorkType);             }         });     });          // Кнопка "Новый расчёт"     const backBtn = document.getElementById('btn-back');     if (backBtn) {         backBtn.addEventListener('click', () => {             document.getElementById('step-worktype').style.display = 'block';             document.getElementById('step-params').style.display = 'none';             document.getElementById('step-result').style.display = 'none';             currentWorkType = null;         });     }          // Кнопка печати     const printBtn = document.getElementById('btn-print');     if (printBtn) {         printBtn.addEventListener('click', () => {             window.print();         });     }          // Кнопка экспорта CSV     const exportBtn = document.getElementById('btn-export');     if (exportBtn) {         exportBtn.addEventListener('click', () => {             exportToCSV();         });     } });  async function loadParamsTemplate(workTypeId) {     const loading = document.getElementById('loading');     if (loading) loading.style.display = 'block';          try {         const response = await fetch(`/construction-estimates/builder/data/templates/${workTypeId}.json`);         if (!response.ok) {             throw new Error(`HTTP ${response.status}: ${response.statusText}`);         }         const template = await response.json();                  // Проверка структуры         if (!template.params || !Array.isArray(template.params)) {             throw new Error('Неверная структура шаблона: отсутствует params');         }                  await renderParamsForm(template);                  document.getElementById('step-worktype').style.display = 'none';         document.getElementById('step-params').style.display = 'block';                  const calcBtn = document.getElementById('btn-next-to-calc');         if (calcBtn) {             calcBtn.onclick = () => calculateEstimate(workTypeId);         }              } catch (error) {         console.error('Ошибка загрузки шаблона:', error);         const container = document.getElementById('params-form');         if (container) {             container.innerHTML = `<div class="error">Ошибка загрузки: ${error.message}</div>`;         }     } finally {         if (loading) loading.style.display = 'none';     } }  async function renderParamsForm(template) {     const container = document.getElementById('params-form');     if (!container) return;          let html = '';          for (const param of template.params) {         if (param.type === 'select' && param.options === 'regions') {             const regions = await loadRegions();             let options = '<option value="">Выберите регион...</option>';             if (regions && regions.length) {                 options += regions.map(r =>                      `<option value="${r.id}">${r.name} (коэфф. ${r.coefficient})</option>`                 ).join('');             } else {                 options += '<option value="center">Центральный регион (коэфф. 1.0)</option>';             }             html += `                 <div class="param-field">                     <label>${param.label}</label>                     <select id="param_${param.name}" name="${param.name}">                         ${options}                     </select>                 </div>             `;         } else {             html += `                 <div class="param-field">                     <label>${param.label}</label>                     <input type="number" id="param_${param.name}" name="${param.name}"                             value="${param.default}" min="${param.min || 0}" max="${param.max || 9999}" step="0.1">                 </div>             `;         }     }          container.innerHTML = html; }  async function calculateEstimate(workTypeId) {     // Собираем параметры     const params = {};     const inputs = document.querySelectorAll('#params-form input, #params-form select');     inputs.forEach(input => {         let name = input.name;         let value = input.value;         if (input.type === 'number') {             value = parseFloat(value);         }         if (value !== '' && value !== null && !isNaN(value)) {             params[name] = value;         }     });          // Если регион не выбран — ставим центр по умолчанию     if (!params.region) {         params.region = 'center';     }          const loading = document.getElementById('loading');     const resultDiv = document.getElementById('result-estimate');          if (loading) loading.style.display = 'block';     if (resultDiv) resultDiv.innerHTML = '';          try {         const response = await fetch(BUILDER_API, {             method: 'POST',             headers: { 'Content-Type': 'application/json' },             body: JSON.stringify({                 template_id: workTypeId,                 params: params             })         });                  const data = await response.json();                  if (loading) loading.style.display = 'none';                  if (data.success) {             renderEstimate(data.estimate);             document.getElementById('step-params').style.display = 'none';             document.getElementById('step-result').style.display = 'block';         } else {             if (resultDiv) {                 resultDiv.innerHTML = `<div class="error">Ошибка: ${data.error || 'Неизвестная ошибка'}</div>`;             }         }              } catch (error) {         if (loading) loading.style.display = 'none';         if (resultDiv) {             resultDiv.innerHTML = `<div class="error">Ошибка соединения: ${error.message}</div>`;         }     } }  function renderEstimate(estimate, templateId, params) {     let html = `         <h3>${escapeHtml(estimate.title)}</h3>         <p class="estimate-note">💡 Кликните по любой ячейке — можно отредактировать объём или цену. Итог пересчитывается автоматически.</p>                  <div style="margin-bottom: 1rem; display: flex; gap: 0.75rem; flex-wrap: wrap;">             <button id="print-estimate-btn" class="btn-estimate-primary">🖨 Распечатать смету</button>             <button id="export-csv-btn" class="btn-estimate-secondary">📎 Экспорт CSV</button>         </div>                  <table class="estimate-table" id="estimate-table-result">             <thead>                 <tr>                     <th>Код ГЭСН</th>                     <th>Наименование работ</th>                     <th>Ед.</th>                     <th>Объём</th>                     <th>Цена, ₽</th>                     <th>Сумма, ₽</th>                     <th style="width: 50px"></th>                 </tr>             </thead>             <tbody id="estimate-tbody">     `;          if (estimate.items && estimate.items.length) {         estimate.items.forEach((item, index) => {             html += `                 <tr data-index="${index}">                     <td>${escapeHtml(item.code)}</td>                     <td><input type="text" class="edit-work" value="${escapeHtml(item.name)}" data-field="name" style="width: 100%;"></td>                     <td><input type="text" class="edit-unit" value="${escapeHtml(item.unit)}" data-field="unit" style="width: 80px;"></td>                     <td><input type="number" class="edit-qty" value="${item.qty}" data-field="qty" step="0.1" style="width: 80px;"></td>                     <td><input type="number" class="edit-price" value="${item.price}" data-field="price" step="100" style="width: 100px;"></td>                     <td class="row-total"><strong>${(item.qty * item.price).toLocaleString('ru-RU')}</strong> ₽</td>                     <td><button class="delete-row-btn" data-index="${index}">✖</button></td>                 </tr>             `;         });     }          html += `             </tbody>             <tfoot>                 <tr>                     <td colspan="5"><strong>ИТОГО:</strong></td>                     <td id="total-sum" colspan="2"><strong>${estimate.total.toLocaleString('ru-RU')}</strong> ₽</td>                 </tr>                 <tr>                     <td colspan="7">                         <button id="add-new-row" class="btn-estimate-secondary">+ Добавить работу</button>                     </td>                 </tr>             </tfoot>         </table>     `;          const resultDiv = document.getElementById('result-estimate');     if (resultDiv) resultDiv.innerHTML = html;          // Сохраняем данные для добавления новых строк     window.currentEstimate = estimate;     window.currentTemplateId = templateId;     window.currentParams = params;          // Навешиваем обработчики     attachEditHandlers();          // Кнопка печати     const printBtn = document.getElementById('print-estimate-btn');     if (printBtn) {         printBtn.addEventListener('click', function() {             printEstimateProfessional();         });     }          // Кнопка экспорта CSV     const exportBtn = document.getElementById('export-csv-btn');     if (exportBtn) {         exportBtn.addEventListener('click', function() {             exportToCSV();         });     } }  function printEstimateProfessional() {     // Собираем данные текущей сметы     const estimateData = {         title: document.getElementById('estimate-title')?.innerText || 'Смета',         description: document.getElementById('estimate-description')?.innerText || '',         items: [],         total: 0     };          // Собираем строки таблицы     const rows = document.querySelectorAll('#estimate-tbody tr');     rows.forEach(row => {         const qty = parseFloat(row.querySelector('.edit-qty')?.value || 0);         const price = parseFloat(row.querySelector('.edit-price')?.value || 0);         const direct = qty * price;                  estimateData.items.push({             code: row.cells[0]?.innerText || '',             name: row.querySelector('.edit-work')?.value || '',             unit: row.querySelector('.edit-unit')?.value || '',             qty: qty,             price: price,             direct: direct         });         estimateData.total += direct;     });          // Открываем окно печати с POST-запросом     const form = document.createElement('form');     form.method = 'POST';     form.action = '/construction-estimates/builder/print.php';     form.target = '_blank';          const input = document.createElement('input');     input.type = 'hidden';     input.name = 'data';     input.value = encodeURIComponent(JSON.stringify(estimateData));          form.appendChild(input);     document.body.appendChild(form);     form.submit();     document.body.removeChild(form); }  function exportToCSV() {     const rows = document.querySelectorAll('#estimate-tbody tr');     let csvRows = [];          // Заголовки     csvRows.push(['Код ГЭСН', 'Наименование работ', 'Ед.', 'Объём', 'Цена, ₽', 'Сумма, ₽']);          // Данные     rows.forEach(row => {         const code = row.cells[0]?.innerText || '';         const name = row.querySelector('.edit-work')?.value || '';         const unit = row.querySelector('.edit-unit')?.value || '';         const qty = row.querySelector('.edit-qty')?.value || 0;         const price = row.querySelector('.edit-price')?.value || 0;         const total = (parseFloat(qty) * parseFloat(price)).toFixed(0);                  csvRows.push([code, name, unit, qty, price, total]);     });          // Итог     const totalSum = document.getElementById('total-sum')?.innerText.replace(/[^0-9.-]/g, '') || 0;     csvRows.push(['', '', '', '', 'ИТОГО:', totalSum]);          // Создаём CSV     const csvContent = csvRows.map(row =>          row.map(cell => `"${String(cell).replace(/"/g, '""')}"`).join(',')     ).join('\n');          // Скачиваем     const blob = new Blob(['\uFEFF' + csvContent], { type: 'text/csv;charset=utf-8;' });     const link = document.createElement('a');     const url = URL.createObjectURL(blob);     link.href = url;     link.setAttribute('download', `smeta_${new Date().toISOString().slice(0, 19)}.csv`);     document.body.appendChild(link);     link.click();     document.body.removeChild(link);     URL.revokeObjectURL(url); }  function attachEditHandlers() {     // Обработчики изменения полей     document.querySelectorAll('#estimate-tbody .edit-qty, #estimate-tbody .edit-price').forEach(input => {         input.addEventListener('input', function() {             const row = this.closest('tr');             recalcRowTotal(row);         });     });          // Обработчики кнопок удаления     document.querySelectorAll('.delete-row-btn').forEach(btn => {         btn.addEventListener('click', function() {             const row = this.closest('tr');             row.remove();             recalcTotalAll();         });     });          // Кнопка добавления новой строки     const addBtn = document.getElementById('add-new-row');     if (addBtn) {         addBtn.addEventListener('click', addNewRow);     } }  function recalcRowTotal(row) {     const qty = parseFloat(row.querySelector('.edit-qty')?.value || 0);     const price = parseFloat(row.querySelector('.edit-price')?.value || 0);     const total = qty * price;     const totalCell = row.querySelector('.row-total');     if (totalCell) {         totalCell.innerHTML = `<strong>${total.toLocaleString('ru-RU')}</strong> ₽`;     }     recalcTotalAll(); }  function recalcTotalAll() {     let total = 0;     document.querySelectorAll('#estimate-tbody tr').forEach(row => {         const totalCell = row.querySelector('.row-total');         if (totalCell) {             const val = parseFloat(totalCell.innerText.replace(/[^0-9.-]/g, ''));             if (!isNaN(val)) total += val;         }     });     const totalSpan = document.getElementById('total-sum');     if (totalSpan) {         totalSpan.innerHTML = `<strong>${total.toLocaleString('ru-RU')}</strong> ₽`;     } }  function addNewRow() {     const tbody = document.getElementById('estimate-tbody');     const newIndex = tbody.children.length;          const newRow = document.createElement('tr');     newRow.setAttribute('data-index', newIndex);     newRow.innerHTML = `         <td><input type="text" value="Новая работа" placeholder="Наименование" style="width: 100%;"></td>         <td><input type="text" value="шт" placeholder="Ед. изм." style="width: 80px;"></td>         <td><input type="number" class="edit-qty" value="1" step="0.1" style="width: 80px;"></td>         <td><input type="number" class="edit-price" value="1000" step="100" style="width: 100px;"></td>         <td class="row-total"><strong>1000</strong> ₽</td>         <td><button class="delete-row-btn">✖</button></td>     `;          // Навешиваем обработчики     newRow.querySelectorAll('.edit-qty, .edit-price').forEach(input => {         input.addEventListener('input', function() {             recalcRowTotal(newRow);         });     });          newRow.querySelector('.delete-row-btn').addEventListener('click', function() {         newRow.remove();         recalcTotalAll();     });          tbody.appendChild(newRow);     recalcTotalAll(); }   function exportToCSV() {     const table = document.querySelector('#result-estimate table');     if (!table) return;          const rows = table.querySelectorAll('tr');     const csv = [];          rows.forEach(row => {         const cells = row.querySelectorAll('th, td');         const rowData = Array.from(cells).map(cell => {             return '"' + cell.innerText.replace(/"/g, '""') + '"';         });         csv.push(rowData.join(','));     });          const blob = new Blob([csv.join('\n')], { type: 'text/csv;charset=utf-8;' });     const link = document.createElement('a');     const url = URL.createObjectURL(blob);     link.href = url;     link.setAttribute('download', 'smeta.csv');     document.body.appendChild(link);     link.click();     document.body.removeChild(link);     URL.revokeObjectURL(url); }  function escapeHtml(text) {     const div = document.createElement('div');     div.textContent = text;     return div.innerHTML; }
========================================
ФАЙЛ #3: data/core.js
Расширение: js
Размер: 0 байт
========================================

            
========================================
ФАЙЛ #4: data/regions.json
Расширение: json
Размер: 2,934 байт
========================================
{     "id": "foundation-strip",     "name": "Ленточный фундамент",     "description": "Расчёт сметы на устройство ленточного фундамента",     "params": [         {             "name": "perimeter",             "label": "Периметр стен, м",             "type": "number",             "default": 40,             "min": 10,             "max": 200         },         {             "name": "depth",             "label": "Глубина заложения, м",             "type": "number",             "default": 1.5,             "min": 0.5,             "max": 3         },         {             "name": "width",             "label": "Ширина ленты, м",             "type": "number",             "default": 0.4,             "min": 0.3,             "max": 0.8         },         {             "name": "region",             "label": "Регион",             "type": "select",             "options": "regions"         }     ],     "works": [         {             "code": "ГЭСН 06-01-001-5",             "name": "Устройство бетонной подготовки",             "unit": "м³",             "formula": "perimeter * width * 0.1",             "price_fer": 5800,             "hr": 0.95,             "sp": 0.55         },         {             "code": "ГЭСН 06-01-015-1",             "name": "Устройство ленточного фундамента (бетон В15)",             "unit": "м³",             "formula": "perimeter * width * depth",             "price_fer": 6200,             "hr": 0.95,             "sp": 0.55         },         {             "code": "ГЭСН 06-01-041-1",             "name": "Армирование фундамента (А500С)",             "unit": "кг",             "formula": "perimeter * width * depth * 50",             "price_fer": 68,             "hr": 0.95,             "sp": 0.55         },         {             "code": "ГЭСН 06-01-087-1",             "name": "Опалубка щитовая",             "unit": "м²",             "formula": "perimeter * depth * 2",             "price_fer": 450,             "hr": 0.95,             "sp": 0.55         },         {             "code": "ГЭСН 01-01-001-1",             "name": "Разработка грунта экскаватором",             "unit": "м³",             "formula": "perimeter * (width + 0.5) * depth",             "price_fer": 680,             "hr": 0.95,             "sp": 0.55         },         {             "code": "ГЭСН 01-01-035-1",             "name": "Обратная засыпка грунтом",             "unit": "м³",             "formula": "perimeter * width * depth * 0.3",             "price_fer": 420,             "hr": 0.95,             "sp": 0.55         }     ] }
========================================
ФАЙЛ #5: data/style.css
Расширение: css
Размер: 13,075 байт
========================================
/* ===== КОНСТРУКТОР СМЕТ - СТИЛИ ===== */  .builder-container {     max-width: 1400px;     margin: 0 auto;     padding: 2rem;     font-family: system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', sans-serif; }  /* === Шаг 1: Выбор типа работ === */ .worktype-grid {     display: grid;     grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));     gap: 1.0rem;     margin: 2rem 0; }  .worktype-card {     border: 0px solid #e5e7eb;     border-radius: 4px;     padding: 1rem 1.5rem;     background: #ffffff;     transition: all 0.2s ease;     box-shadow: 0 1px 3px rgba(0,0,0,0.05);     cursor: pointer; }  .worktype-card:hover {     border-color: #92a7f1;     /*box-shadow: 0 10px 25px -5px rgba(0,0,0,0.1);*/     transform: translateY(-2px); }  .worktype-card h3 {     font-size: 1.25rem;     font-weight: 600;     margin: 0 0 0.5rem 0;     color: #1f2937; }  .worktype-card p {     font-size: 0.875rem;     color: #6b7280;     margin: 0 0 1rem 0;     line-height: 1.5; }  .btn-select-worktype {     background: #2563eb;     color: white;     border: none;     padding: 0.5rem 1.25rem;     border-radius: 6px;     cursor: pointer;     font-size: 0.875rem;     font-weight: 500;     transition: background 0.2s; }  .btn-select-worktype:hover {     background: #1d4ed8; }  /* === Шаг 2: Параметры === */ #params-form {     background: #f9fafb;     border: 1px solid #e5e7eb;     border-radius: 4px;     padding: 1.5rem;     margin: 1.5rem 0;     display: grid;     grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));     gap: 1.25rem; }  .param-field {     display: flex;     flex-direction: column;     gap: 0.5rem; }  .param-field label {     font-weight: 600;     font-size: 0.875rem;     color: #374151; }  .param-field input, .param-field select {     padding: 0.625rem 0.875rem;     border: 1px solid #d1d5db;     border-radius: 8px;     font-size: 0.9375rem;     font-family: inherit;     background: white;     transition: border-color 0.2s, box-shadow 0.2s; }  .param-field input:focus, .param-field select:focus {     outline: none;     border-color: #2563eb;     box-shadow: 0 0 0 3px rgba(37,99,235,0.1); }  /* === Кнопки === */ .btn-primary, .btn-secondary, .btn-estimate-primary, .btn-estimate-secondary {     padding: 0.625rem 1.5rem;     font-size: 0.875rem;     font-weight: 500;     border-radius: 8px;     cursor: pointer;     transition: all 0.2s;     border: none; }  .btn-primary, .btn-estimate-primary {     background: #2563eb;     color: white; } .btn-primary:hover, .btn-estimate-primary:hover {     background: #1d4ed8; } .btn-secondary, .btn-estimate-secondary {     background: #f3f4f6;     color: #1f2937;     border: 1px solid #e5e7eb; } .btn-secondary:hover, .btn-estimate-secondary:hover {     background: #e5e7eb; }  .btn-estimate-danger {     background: #ef4444;     color: white; } .btn-estimate-danger:hover {     background: #dc2626; }  .result-actions {     display: flex;     gap: 0.75rem;     margin-top: 1.5rem;     flex-wrap: wrap; }  /* === Секции === */ .builder-section {     margin-bottom: 2rem; }  .builder-section h2 {     font-size: 1.5rem;     font-weight: 600;     color: #111827;     margin: 0 0 1rem 0;     padding-bottom: 0.5rem;     border-bottom: 2px solid #e5e7eb; }  /* === Таблица сметы === */ .estimate-table-wrapper {     overflow-x: auto;     margin: 1.5rem 0;     border-radius: 4px;     border: 1px solid #e5e7eb;     background: white; }  .estimate-table {     width: 100%;     border-collapse: collapse;     font-size: 0.875rem; }  .estimate-table th {     background: #f8fafc;     padding: 0.875rem 1rem;     text-align: left;     font-weight: 600;     color: #1e293b;     border-bottom: 1px solid #e2e8f0; }  .estimate-table td {     padding: 0.75rem 1rem;     border-bottom: 1px solid #f1f5f9;     vertical-align: middle; }  .estimate-table tbody tr:hover {     background: #fefce8; }  /* === Редактируемые поля === */ .estimate-table input {     width: 100%;     padding: 0.4rem 0.5rem;     border: 1px solid #e2e8f0;     border-radius: 6px;     font-size: 0.813rem;     font-family: inherit;     background: white;     transition: all 0.15s; }  .estimate-table input:focus {     outline: none;     border-color: #2563eb;     box-shadow: 0 0 0 2px rgba(37,99,235,0.2); }  .edit-work {     min-width: 200px; } .edit-unit {     width: 70px;     text-align: center; } .edit-qty, .edit-price {     width: 100px;     text-align: right; }  /* === Сумма строки === */ .row-total {     font-weight: 600;     color: #059669;     text-align: right; }  /* === Кнопка удаления строки === */ .delete-row-btn {     background: none;     border: none;     font-size: 1.1rem;     cursor: pointer;     color: #9ca3af;     transition: color 0.15s;     padding: 0.25rem 0.5rem; }  .delete-row-btn:hover {     color: #ef4444; }  /* === Итоговая строка === */ .estimate-table tfoot td {     background: #f8fafc;     font-weight: 600;     padding: 1rem;     border-top: 2px solid #e2e8f0; }  #total-sum {     font-size: 1.1rem;     color: #059669; }  /* === Блок действий === */ .estimate-actions {     display: flex;     gap: 0.75rem;     margin: 1rem 0;     flex-wrap: wrap; }  /* === Примечание === */ .estimate-note {     margin-top: 1rem;     padding: 0.75rem 1rem;     background: #f0fdf4;     border-left: 4px solid #10b981;     border-radius: 8px;     font-size: 0.813rem;     color: #047857; }  /* === Ошибки и загрузка === */ .error {     color: #dc2626;     background: #fef2f2;     padding: 1rem;     border-radius: 8px;     border-left: 4px solid #dc2626; }  #loading {     text-align: center;     padding: 2rem;     font-size: 1rem;     color: #2563eb; }  #loading i {     margin-right: 0.5rem; }  /* === Адаптив для мобильных === */ @media (max-width: 768px) {     .builder-container {         padding: 1rem;     }          .worktype-grid {         grid-template-columns: 1fr;     }          .estimate-table th,     .estimate-table td {         padding: 0.5rem;     }          .edit-work {         min-width: 150px;     }          .param-field input,     .param-field select {         font-size: 16px; /* Предотвращает зум на iOS */     } }  /* === Печатная версия === */ @media print {     .worktype-grid,     .estimate-selector,     .estimate-actions,     .result-actions,     .delete-row-btn,     #add-new-row,     button,     .btn-primary,     .btn-secondary,     .btn-estimate-primary,     .btn-estimate-secondary,     #btn-back,     #btn-print,     #btn-export,     .estimate-note {         display: none !important;     }          .estimate-table input {         border: none;         background: transparent;         padding: 0;     }          .estimate-table {         border: 1px solid #000;     }          .estimate-table th,     .estimate-table td {         border: 1px solid #ccc;     } }  /* ===== СОВРЕМЕННАЯ ПЕЧАТНАЯ ВЕРСИЯ ===== */ /* Скрываем всё лишнее при печати */ @media print {     /* Скрываем попап cookie */     .cookie-notice,     .cookie-popup,     .cookie-consent,     [class*="cookie"],     [id*="cookie"] {         display: none !important;     }      /* Скрываем поиск */     .srh-form-main,     .search-form,     [class*="search"],     [id*="search"] {         display: none !important;     }      /* Скрываем хлебные крошки */     .shell-breadcrumb,     .breadcrumb,     [class*="breadcrumb"] {         display: none !important;     }      /* Скрываем шапку и футер */     .shell-header,     .shell-footer,     header, footer,     [class*="header"]:not(.print-header),     [class*="footer"] {         display: none !important;     }      /* Скрываем фильтры, статистику, навигацию */     .categories-filter-tabs,     .categories-hub-stats,     .quick-nav,     .hub-stats,     .filter-tabs {         display: none !important;     }      /* Скрываем рекламу */     .ad-block,     #ad-block-top,     #ad-block-bottom,     [class*="ad-"] {         display: none !important;     }      /* Скрываем кнопки и формы */     button, .btn, .btn-primary, .btn-secondary,     .estimate-selector, .estimate-actions, .result-actions,     #add-new-row, .delete-row-btn {         display: none !important;     }      /* Скрываем всё лишнее в конструкторе */     .worktype-grid,     .builder-section:first-child,     .builder-section:nth-child(2) {         display: none !important;     }      /* Показываем только результат */     #step-result {         display: block !important;     }      /* Сбрасываем фон и отступы */     body {         background: white !important;         margin: 0 !important;         padding: 0 !important;     }      .builder-container {         padding: 0 !important;         margin: 0 !important;     } }  @media print {     /* Скрываем всё лишнее */     .worktype-grid,     .estimate-selector,     .estimate-actions,     .result-actions,     .builder-section:first-child,     .builder-section:nth-child(2),     .delete-row-btn,     #add-new-row,     button,     .btn-primary,     .btn-secondary,     .btn-estimate-primary,     .btn-estimate-secondary,     #btn-back,     #btn-print,     #btn-export,     .estimate-note,     .builder-container > .estimate-selector,     .builder-container > .estimate-actions,     .builder-container > .result-actions,     .builder-container > .estimate-note,     header, footer, nav, .shell-breadcrumb, .shell-header, .shell-footer,     .categories-filter-tabs, .categories-hub-stats, .quick-nav,     .search-form, .srh-form-main, .ad-block, #ad-block-top, #ad-block-bottom {         display: none !important;     }      /* Основной контейнер */     .builder-container {         padding: 0 !important;         margin: 0 !important;         max-width: 100% !important;     }      /* Заголовок сметы */     #estimate-header {         text-align: center;         margin-bottom: 20px;         padding-bottom: 10px;         border-bottom: 2px solid #000;     }      #estimate-title {         font-size: 20pt;         font-weight: bold;         margin: 0 0 5px 0;         color: #000;     }      #estimate-description {         font-size: 11pt;         color: #555;         margin: 0;     }      /* Таблица */     .estimate-table-wrapper {         overflow: visible !important;         border: none !important;         margin: 0 !important;     }      .estimate-table {         width: 100%;         border-collapse: collapse;         font-size: 9pt;         page-break-inside: avoid;     }      .estimate-table th {         background: #f0f0f0 !important;         padding: 8px 6px;         border: 1px solid #000;         font-weight: bold;         text-align: left;     }      .estimate-table td {         padding: 6px;         border: 1px solid #999;         vertical-align: top;     }      /* Заменяем input поля на текст */     .estimate-table input {         border: none !important;         background: transparent !important;         padding: 0 !important;         margin: 0 !important;         width: auto !important;         font-family: inherit !important;         font-size: inherit !important;         font-weight: inherit !important;         color: #000 !important;         box-shadow: none !important;     }      /* Скрываем кнопки в таблице */     .delete-row-btn {         display: none !important;     }      /* Итоговая строка */     .estimate-table tfoot td {         background: #f8f8f8 !important;         font-weight: bold;         border-top: 2px solid #000;     }      #total-sum {         font-size: 12pt;         color: #000;     }      /* Блок "Результаты" */     #step-result {         display: block !important;     }      /* Принудительно показываем результат */     .builder-section {         display: block !important;     }      /* Страница */     body {         background: white !important;         color: black !important;     }      /* Ссылки */     a {         text-decoration: none !important;         color: #000 !important;     } }
========================================
ФАЙЛ #6: data/templates/facade-ventilated.json
Расширение: json
Размер: 2,481 байт
========================================
{     "id": "facade-ventilated",     "name": "Вентилируемый фасад",     "description": "Смета на навесной вентилируемый фасад",     "params": [         {             "name": "area",             "label": "Площадь фасада, м²",             "type": "number",             "default": 150,             "min": 50,             "max": 500         },         {             "name": "thickness",             "label": "Толщина утеплителя, мм",             "type": "number",             "default": 100,             "min": 50,             "max": 150         },         {             "name": "region",             "label": "Регион",             "type": "select",             "options": "regions"         }     ],     "works": [         {             "code": "ГЭСН 11-03-001-1",             "name": "Утеплитель (минвата)",             "unit": "м²",             "formula": "area",             "price_fer": 450,             "hr": 0.95,             "sp": 0.55         },         {             "code": "ГЭСН 11-03-002-1",             "name": "Крепление утеплителя",             "unit": "м²",             "formula": "area",             "price_fer": 220,             "hr": 0.95,             "sp": 0.55         },         {             "code": "ГЭСН 11-03-003-1",             "name": "Гидро-ветрозащита",             "unit": "м²",             "formula": "area",             "price_fer": 70,             "hr": 0.95,             "sp": 0.55         },         {             "code": "ГЭСН 11-03-004-1",             "name": "Обрешётка (профиль/брус)",             "unit": "м²",             "formula": "area",             "price_fer": 280,             "hr": 0.95,             "sp": 0.55         },         {             "code": "ГЭСН 11-03-005-1",             "name": "Кронштейны",             "unit": "шт",             "formula": "area / 0.5",             "price_fer": 45,             "hr": 0.95,             "sp": 0.55         },         {             "code": "ГЭСН 11-03-006-1",             "name": "Облицовка (керамогранит/профлист)",             "unit": "м²",             "formula": "area",             "price_fer": 850,             "hr": 0.95,             "sp": 0.55         }     ] }
========================================
ФАЙЛ #7: data/templates/facade-wet.json
Расширение: json
Размер: 2,504 байт
========================================
{     "id": "facade-wet",     "name": "Мокрый фасад",     "description": "Смета на утепление фасада с декоративной штукатуркой",     "params": [         {             "name": "area",             "label": "Площадь фасада, м²",             "type": "number",             "default": 150,             "min": 50,             "max": 500         },         {             "name": "thickness",             "label": "Толщина утеплителя, мм",             "type": "number",             "default": 100,             "min": 50,             "max": 150         },         {             "name": "region",             "label": "Регион",             "type": "select",             "options": "regions"         }     ],     "works": [         {             "code": "ГЭСН 11-02-001-1",             "name": "Утеплитель (минвата/ЭППС)",             "unit": "м²",             "formula": "area",             "price_fer": 450,             "hr": 0.95,             "sp": 0.55         },         {             "code": "ГЭСН 11-02-002-1",             "name": "Крепление утеплителя (клей+дюбели)",             "unit": "м²",             "formula": "area",             "price_fer": 280,             "hr": 0.95,             "sp": 0.55         },         {             "code": "ГЭСН 11-02-003-1",             "name": "Армирование сеткой",             "unit": "м²",             "formula": "area",             "price_fer": 180,             "hr": 0.95,             "sp": 0.55         },         {             "code": "ГЭСН 11-02-004-1",             "name": "Грунтовка",             "unit": "м²",             "formula": "area",             "price_fer": 60,             "hr": 0.95,             "sp": 0.55         },         {             "code": "ГЭСН 11-02-005-1",             "name": "Декоративная штукатурка",             "unit": "м²",             "formula": "area",             "price_fer": 350,             "hr": 0.95,             "sp": 0.55         },         {             "code": "ГЭСН 11-02-006-1",             "name": "Откосы и примыкания",             "unit": "м",             "formula": "Math.sqrt(area) * 4",             "price_fer": 200,             "hr": 0.95,             "sp": 0.55         }     ] }
========================================
ФАЙЛ #8: data/templates/foundation-pile.json
Расширение: json
Размер: 1,999 байт
========================================
{     "id": "foundation-pile",     "name": "Свайный фундамент (винтовые сваи)",     "description": "Смета на устройство винтовых свай с ростверком",     "params": [         {             "name": "perimeter",             "label": "Периметр стен, м",             "type": "number",             "default": 40,             "min": 20,             "max": 150         },         {             "name": "pile_step",             "label": "Шаг свай, м",             "type": "number",             "default": 2.5,             "min": 2,             "max": 3         },         {             "name": "region",             "label": "Регион",             "type": "select",             "options": "regions"         }     ],     "works": [         {             "code": "ГЭСН 05-01-002-1",             "name": "Винтовые сваи СВЛ-108 мм",             "unit": "шт",             "formula": "perimeter / pile_step",             "price_fer": 3500,             "hr": 0.95,             "sp": 0.55         },         {             "code": "ГЭСН 05-01-003-1",             "name": "Оголовки свай",             "unit": "шт",             "formula": "perimeter / pile_step",             "price_fer": 500,             "hr": 0.95,             "sp": 0.55         },         {             "code": "ГЭСН 06-01-087-1",             "name": "Ростверк (швеллер/двутавр)",             "unit": "м",             "formula": "perimeter",             "price_fer": 1200,             "hr": 0.95,             "sp": 0.55         },         {             "code": "ГЭСН 06-01-015-1",             "name": "Бетонирование обвязки",             "unit": "м³",             "formula": "perimeter * 0.3 * 0.3",             "price_fer": 5800,             "hr": 0.95,             "sp": 0.55         }     ] }
========================================
ФАЙЛ #9: data/templates/foundation-slab.json
Расширение: json
Размер: 2,283 байт
========================================
{     "id": "foundation-slab",     "name": "Плитный фундамент",     "description": "Смета на устройство монолитной плиты",     "params": [         {             "name": "area",             "label": "Площадь дома, м²",             "type": "number",             "default": 100,             "min": 30,             "max": 300         },         {             "name": "thickness",             "label": "Толщина плиты, м",             "type": "number",             "default": 0.3,             "min": 0.2,             "max": 0.5         },         {             "name": "region",             "label": "Регион",             "type": "select",             "options": "regions"         }     ],     "works": [         {             "code": "ГЭСН 06-01-001-5",             "name": "Бетонная подготовка (В7,5)",             "unit": "м³",             "formula": "area * 0.1",             "price_fer": 5200,             "hr": 0.95,             "sp": 0.55         },         {             "code": "ГЭСН 06-01-015-2",             "name": "Плита фундаментная (бетон В15)",             "unit": "м³",             "formula": "area * thickness",             "price_fer": 6200,             "hr": 0.95,             "sp": 0.55         },         {             "code": "ГЭСН 06-01-041-2",             "name": "Армирование плиты (А500С, сетка 200х200)",             "unit": "кг",             "formula": "area * thickness * 45",             "price_fer": 68,             "hr": 0.95,             "sp": 0.55         },         {             "code": "ГЭСН 06-01-087-2",             "name": "Опалубка по периметру",             "unit": "м²",             "formula": "Math.sqrt(area) * 4 * 0.3",             "price_fer": 450,             "hr": 0.95,             "sp": 0.55         },         {             "code": "ГЭСН 01-01-001-1",             "name": "Разработка грунта",             "unit": "м³",             "formula": "area * 0.5",             "price_fer": 680,             "hr": 0.95,             "sp": 0.55         }     ] }
========================================
ФАЙЛ #10: data/templates/foundation-strip.json
Расширение: json
Размер: 2,921 байт
========================================
{     "id": "foundation-strip",     "name": "Ленточный фундамент",     "description": "Смета на устройство ленточного фундамента",     "params": [         {             "name": "perimeter",             "label": "Периметр стен, м",             "type": "number",             "default": 40,             "min": 10,             "max": 200         },         {             "name": "depth",             "label": "Глубина заложения, м",             "type": "number",             "default": 1.5,             "min": 0.5,             "max": 3         },         {             "name": "width",             "label": "Ширина ленты, м",             "type": "number",             "default": 0.4,             "min": 0.3,             "max": 0.8         },         {             "name": "region",             "label": "Регион",             "type": "select",             "options": "regions"         }     ],     "works": [         {             "code": "ГЭСН 06-01-001-5",             "name": "Устройство бетонной подготовки",             "unit": "м³",             "formula": "perimeter * width * 0.1",             "price_fer": 5800,             "hr": 0.95,             "sp": 0.55         },         {             "code": "ГЭСН 06-01-015-1",             "name": "Устройство ленточного фундамента (бетон В15)",             "unit": "м³",             "formula": "perimeter * width * depth",             "price_fer": 6200,             "hr": 0.95,             "sp": 0.55         },         {             "code": "ГЭСН 06-01-041-1",             "name": "Армирование фундамента (А500С)",             "unit": "кг",             "formula": "perimeter * width * depth * 50",             "price_fer": 68,             "hr": 0.95,             "sp": 0.55         },         {             "code": "ГЭСН 06-01-087-1",             "name": "Опалубка щитовая",             "unit": "м²",             "formula": "perimeter * depth * 2",             "price_fer": 450,             "hr": 0.95,             "sp": 0.55         },         {             "code": "ГЭСН 01-01-001-1",             "name": "Разработка грунта экскаватором",             "unit": "м³",             "formula": "perimeter * (width + 0.5) * depth",             "price_fer": 680,             "hr": 0.95,             "sp": 0.55         },         {             "code": "ГЭСН 01-01-035-1",             "name": "Обратная засыпка грунтом",             "unit": "м³",             "formula": "perimeter * width * depth * 0.3",             "price_fer": 420,             "hr": 0.95,             "sp": 0.55         }     ] }
========================================
ФАЙЛ #11: data/templates/heating-gas.json
Расширение: json
Размер: 2,830 байт
========================================
{     "id": "heating-gas",     "name": "Газовое отопление",     "description": "Смета на монтаж газового котла и разводку отопления",     "params": [         {             "name": "area",             "label": "Площадь дома, м²",             "type": "number",             "default": 100,             "min": 50,             "max": 300         },         {             "name": "radiators_qty",             "label": "Количество радиаторов, шт",             "type": "number",             "default": 8,             "min": 4,             "max": 20         },         {             "name": "region",             "label": "Регион",             "type": "select",             "options": "regions"         }     ],     "works": [         {             "code": "ГЭСН 16-01-001-1",             "name": "Газовый котёл (24 кВт)",             "unit": "шт",             "formula": "1",             "price_fer": 45000,             "hr": 0.95,             "sp": 0.55         },         {             "code": "ГЭСН 16-01-002-1",             "name": "Разводка труб (ПП, металлопласт)",             "unit": "м",             "formula": "area * 0.8",             "price_fer": 350,             "hr": 0.95,             "sp": 0.55         },         {             "code": "ГЭСН 16-01-003-1",             "name": "Радиаторы отопления (биметалл)",             "unit": "шт",             "formula": "radiators_qty",             "price_fer": 4500,             "hr": 0.95,             "sp": 0.55         },         {             "code": "ГЭСН 16-01-004-1",             "name": "Расширительный бак",             "unit": "шт",             "formula": "1",             "price_fer": 2500,             "hr": 0.95,             "sp": 0.55         },         {             "code": "ГЭСН 16-01-005-1",             "name": "Циркуляционный насос",             "unit": "шт",             "formula": "1",             "price_fer": 6000,             "hr": 0.95,             "sp": 0.55         },         {             "code": "ГЭСН 16-01-006-1",             "name": "Запорная арматура и фитинги",             "unit": "компл",             "formula": "1",             "price_fer": 8000,             "hr": 0.95,             "sp": 0.55         },         {             "code": "ГЭСН 16-01-007-1",             "name": "Пусконаладочные работы",             "unit": "компл",             "formula": "1",             "price_fer": 12000,             "hr": 0.95,             "sp": 0.55         }     ] }
========================================
ФАЙЛ #12: data/templates/nterior-cosmetic.json
Расширение: json
Размер: 3,681 байт
========================================
{     "id": "interior-cosmetic",     "name": "Косметический ремонт квартиры",     "description": "Смета на косметический ремонт квартиры (стены + полы + потолки)",     "params": [         {             "name": "area_floor",             "label": "Площадь пола, м²",             "type": "number",             "default": 60,             "min": 20,             "max": 200         },         {             "name": "area_walls",             "label": "Площадь стен, м²",             "type": "number",             "default": 180,             "min": 50,             "max": 500         },         {             "name": "room_height",             "label": "Высота потолков, м",             "type": "number",             "default": 2.7,             "min": 2.5,             "max": 3.2         },         {             "name": "region",             "label": "Регион",             "type": "select",             "options": "regions"         }     ],     "works": [         {             "code": "ГЭСН 11-01-001-1",             "name": "Демонтаж старых покрытий",             "unit": "м²",             "formula": "area_floor * 1.5 + area_walls",             "price_fer": 300,             "hr": 0.95,             "sp": 0.55         },         {             "code": "ГЭСН 11-01-010-1",             "name": "Выравнивание стен (штукатурка)",             "unit": "м²",             "formula": "area_walls",             "price_fer": 550,             "hr": 0.95,             "sp": 0.55         },         {             "code": "ГЭСН 11-01-015-1",             "name": "Шпаклёвка стен",             "unit": "м²",             "formula": "area_walls",             "price_fer": 200,             "hr": 0.95,             "sp": 0.55         },         {             "code": "ГЭСН 11-01-020-1",             "name": "Покраска стен",             "unit": "м²",             "formula": "area_walls",             "price_fer": 250,             "hr": 0.95,             "sp": 0.55         },         {             "code": "ГЭСН 11-01-025-1",             "name": "Стяжка пола (пескобетон)",             "unit": "м²",             "formula": "area_floor * 1.05",             "price_fer": 650,             "hr": 0.95,             "sp": 0.55         },         {             "code": "ГЭСН 11-01-030-1",             "name": "Укладка ламината (32 класс)",             "unit": "м²",             "formula": "area_floor * 1.05",             "price_fer": 800,             "hr": 0.95,             "sp": 0.55         },         {             "code": "ГЭСН 11-01-035-1",             "name": "Плинтус с установкой",             "unit": "м",             "formula": "Math.sqrt(area_floor) * 8 * 1.1",             "price_fer": 250,             "hr": 0.95,             "sp": 0.55         },         {             "code": "ГЭСН 11-01-040-1",             "name": "Натяжной потолок (ПВХ)",             "unit": "м²",             "formula": "area_floor * 1.05",             "price_fer": 550,             "hr": 0.95,             "sp": 0.55         },         {             "code": "ГЭСН 11-01-045-1",             "name": "Вывоз мусора",             "unit": "компл",             "formula": "1",             "price_fer": 12000,             "hr": 0.95,             "sp": 0.55         }     ] }
========================================
ФАЙЛ #13: data/templates/roofing-metal.json
Расширение: json
Размер: 3,209 байт
========================================
{     "id": "roofing-metal",     "name": "Кровля из металлочерепицы",     "description": "Смета на устройство двускатной кровли из металлочерепицы",     "params": [         {             "name": "area",             "label": "Площадь дома, м²",             "type": "number",             "default": 100,             "min": 30,             "max": 300         },         {             "name": "roof_angle",             "label": "Угол ската, градусы",             "type": "number",             "default": 30,             "min": 15,             "max": 60         },         {             "name": "region",             "label": "Регион",             "type": "select",             "options": "regions"         }     ],     "works": [         {             "code": "ГЭСН 10-01-002-1",             "name": "Стропильная система (доска 150×50)",             "unit": "м²",             "formula": "area * 1.2",             "price_fer": 850,             "hr": 0.95,             "sp": 0.55         },         {             "code": "ГЭСН 10-01-005-1",             "name": "Обрешётка под металлочерепицу (доска 25×100)",             "unit": "м²",             "formula": "area * 1.15",             "price_fer": 250,             "hr": 0.95,             "sp": 0.55         },         {             "code": "ГЭСН 10-01-010-1",             "name": "Металлочерепица (0,5 мм) с доборными элементами",             "unit": "м²",             "formula": "area * 1.15",             "price_fer": 750,             "hr": 0.95,             "sp": 0.55         },         {             "code": "ГЭСН 10-01-020-1",             "name": "Пароизоляция",             "unit": "м²",             "formula": "area * 1.15",             "price_fer": 50,             "hr": 0.95,             "sp": 0.55         },         {             "code": "ГЭСН 10-01-025-1",             "name": "Гидро-ветрозащита",             "unit": "м²",             "formula": "area * 1.15",             "price_fer": 70,             "hr": 0.95,             "sp": 0.55         },         {             "code": "ГЭСН 10-01-030-1",             "name": "Утеплитель 150 мм (минвата)",             "unit": "м²",             "formula": "area * 1.15",             "price_fer": 400,             "hr": 0.95,             "sp": 0.55         },         {             "code": "ГЭСН 10-01-040-1",             "name": "Водосточная система",             "unit": "м",             "formula": "Math.sqrt(area) * 4 * 1.2",             "price_fer": 850,             "hr": 0.95,             "sp": 0.55         },         {             "code": "ГЭСН 10-01-045-1",             "name": "Снегозадержатели",             "unit": "шт",             "formula": "Math.sqrt(area) * 0.8",             "price_fer": 1200,             "hr": 0.95,             "sp": 0.55         }     ] }
========================================
ФАЙЛ #14: data/ui.js
Расширение: js
Размер: 0 байт
========================================

            
========================================
ФАЙЛ #15: index.php
Расширение: php
Размер: 6,247 байт
========================================
<?php  /**  * Конструктор смет - профессиональный инструмент на базе ГЭСН и ФЕР  * /construction-estimates/builder/index.php  */  $protocol = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? 'https://' : 'http://'; $host = $_SERVER['HTTP_HOST']; $uri = $_SERVER['REQUEST_URI']; $canonicalUrl = $protocol . $host . $uri; $siteUrl = $protocol . $host;  $pageTitle = 'Конструктор смет на основе ГЭСН и ФЕР | ТЕХСТАНДАРТ'; $description = 'Профессиональный онлайн-конструктор строительных смет. Выберите тип работ, укажите параметры объекта, получите детальную смету с расчётом по ГЭСН и ФЕР. Для ИЖС, ремонта и малого бизнеса.'; $h1 = 'Конструктор смет';  // Загружаем список доступных типов работ из папки templates/ $templatesDir = __DIR__ . '/data/templates/'; $templateFiles = glob($templatesDir . '*.json');  $workTypes = [];  if ($templateFiles) {     foreach ($templateFiles as $file) {         $content = file_get_contents($file);         $data = json_decode($content, true);         if ($data) {             $workTypes[] = [                 'id' => basename($file, '.json'),                 'name' => $data['name'] ?? basename($file, '.json'),                 'description' => $data['description'] ?? ''             ];         }     } }  // Загружаем список регионов $regionsFile = __DIR__ . '/data/regions.json'; $regions = []; if (file_exists($regionsFile)) {     $regionsData = json_decode(file_get_contents($regionsFile), true);     $regions = $regionsData['regions'] ?? []; }  ob_start(); ?>  <!DOCTYPE html> <html lang="ru">  <head>     <meta charset="UTF-8">     <meta name="viewport" content="width=device-width, initial-scale=1.0">     <title>Конструктор смет - ТЕХСТАНДАРТ</title>     <link rel="stylesheet" href="/assets/css/main.min.css">     <link rel="stylesheet" href="/assets/css/bootstrap-icons.css">     <link rel="stylesheet" href="/construction-estimates/builder/data/style.css"> </head>  <body>     <div class="builder-container">          <!-- Шаг 1: Выбор типа работ -->         <div class="builder-section" id="step-worktype">             <h2>1. Выберите тип работ</h2>             <div class="worktype-grid">                 <?php foreach ($workTypes as $type): ?>                     <div class="worktype-card" data-worktype="<?= htmlspecialchars($type['id']) ?>">                         <h3><?= htmlspecialchars($type['name']) ?></h3>                         <p><?= htmlspecialchars($type['description']) ?></p>                         <button class="btn-select-worktype">Выбрать</button>                     </div>                 <?php endforeach; ?>             </div>         </div>          <!-- Шаг 2: Параметры объекта -->         <div class="builder-section" id="step-params" style="display: none;">             <h2>2. Параметры объекта</h2>             <div id="params-form"></div>             <button id="btn-next-to-calc" class="btn-primary">Рассчитать смету →</button>         </div>          <!-- Шаг 3: Результат (смета) -->         <div class="builder-section" id="step-result" style="display: none;">             <h2>3. Смета</h2>             <div id="result-estimate"></div>         </div>          <div id="loading" style="display: none; text-align: center; padding: 2rem;">             <i class="bi bi-hourglass-split"></i> Расчёт...         </div>      </div>      <script src="data/builder.js"></script> </body>  </html>  <?php $htmlContent = ob_get_clean();  // JSON-LD микроразметка $jsonLd = [     [         '@context' => 'https://schema.org',         '@type' => 'WebPage',         '@id' => $canonicalUrl,         'name' => $pageTitle,         'description' => $description,         'url' => $canonicalUrl,         'inLanguage' => 'ru-RU',         'dateModified' => date('Y-m-d'),     ],     [         '@context' => 'https://schema.org',         '@type' => 'BreadcrumbList',         'itemListElement' => [             ['@type' => 'ListItem', 'position' => 1, 'name' => 'Главная', 'item' => $siteUrl . '/'],             ['@type' => 'ListItem', 'position' => 2, 'name' => 'Строительные сметы', 'item' => $siteUrl . '/construction-estimates/'],             ['@type' => 'ListItem', 'position' => 3, 'name' => 'Конструктор смет', 'item' => $canonicalUrl]         ]     ],     [         '@context' => 'https://schema.org',         '@type' => 'SoftwareApplication',         'name' => $pageTitle,         'description' => $description,         'applicationCategory' => 'BusinessApplication',         'operatingSystem' => 'Web',         'offers' => [             '@type' => 'Offer',             'price' => '0',             'priceCurrency' => 'RUB'         ]     ] ];  $pageData = [     'title' => $pageTitle,     'description' => $description,     'h1' => $h1,     'content' => $htmlContent,     'breadcrumb' => [         ['name' => 'Главная', 'url' => '/'],         ['name' => 'Строительные сметы', 'url' => '/construction-estimates/'],         ['name' => 'Конструктор смет', 'url' => '']     ],     'template' => 'default',     'canonical_url' => $canonicalUrl,     'robots' => 'index, follow',     'keywords' => 'конструктор смет, смета онлайн, ГЭСН, ФЕР, строительная смета, калькулятор сметы',     'og_title' => $pageTitle,     'og_description' => $description,     'og_image' => 'https://tech-stc.ru/assets/images/og/estimate-builder-og.jpg',     'og_type' => 'website',     'json_ld' => $jsonLd ];  require_once $_SERVER['DOCUMENT_ROOT'] . '/shell/render.php'; renderModule($pageData); 
========================================
ФАЙЛ #16: print.php
Расширение: php
Размер: 13,035 байт
========================================
<?php /**  * Печатная форма сметы  * /construction-estimates/builder/print.php  */  $protocol = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? 'https://' : 'http://'; $host = $_SERVER['HTTP_HOST']; $siteUrl = $protocol . $host;  // Получаем данные $data = $_POST['data'] ?? $_GET['data'] ?? null; if ($data) {     $estimate = json_decode(urldecode($data), true); } else {     // Демо-данные     $estimate = [         'title' => 'Ленточный фундамент 10×10 м',         'description' => 'Смета на устройство ленточного фундамента',         'items' => [             ['code' => 'ГЭСН 06-01-001-5', 'name' => 'Устройство бетонной подготовки', 'unit' => 'м³', 'qty' => 1.6, 'price' => 5800, 'direct' => 9280],             ['code' => 'ГЭСН 06-01-015-1', 'name' => 'Устройство ленточного фундамента', 'unit' => 'м³', 'qty' => 24, 'price' => 6200, 'direct' => 148800],         ],         'total' => 158080     ]; } ?> <!DOCTYPE html> <html lang="ru"> <head>     <meta charset="UTF-8">     <title><?= htmlspecialchars($estimate['title']) ?> | ТЕХСТАНДАРТ</title>     <style>         * {             margin: 0;             padding: 0;             box-sizing: border-box;         }          body {             font-family: 'Times New Roman', Times, serif;             background: white;             color: #000;             font-size: 12px;             line-height: 1.4;         }          /* Контейнер */         .print-container {             max-width: 1200px;             margin: 0 auto;             padding: 20px;         }          /* ===== ШАПКА ДОКУМЕНТА ===== */         .document-header {             margin-bottom: 20px;             border-bottom: 2px solid #000;             padding-bottom: 15px;         }          /* Верхняя строка с логотипом и реквизитами */         .header-top {             display: flex;             justify-content: space-between;             align-items: flex-start;             margin-bottom: 15px;         }          /* Левая часть - логотип */         .header-left {             flex: 1;         }          .logo {             font-size: 20px;             font-weight: bold;             color: #1a3a6b;             letter-spacing: 1px;         }          .logo-sub {             font-size: 10px;             color: #666;             margin-top: 3px;         }          /* Правая часть - реквизиты */         .header-right {             text-align: right;             font-size: 9px;             line-height: 1.3;         }          .header-right p {             margin: 2px 0;         }          /* Средняя строка - название документа */         .doc-title {             text-align: center;             margin: 15px 0;         }          .doc-title h1 {             font-size: 18px;             font-weight: bold;             text-transform: uppercase;             letter-spacing: 2px;             margin-bottom: 5px;         }          .doc-title h2 {             font-size: 14px;             font-weight: normal;             margin-bottom: 5px;         }          .doc-title p {             font-size: 11px;             color: #555;         }          /* Информация о смете */         .estimate-info {             display: flex;             justify-content: space-between;             margin: 15px 0;             padding: 10px;             border: 1px solid #ccc;             background: #f9f9f9;         }          .estimate-info-item {             font-size: 10px;         }          .estimate-info-item strong {             font-weight: bold;         }          /* ===== ТАБЛИЦА ===== */         .estimate-table {             width: 100%;             border-collapse: collapse;             margin: 15px 0;         }          .estimate-table th {             background: #e8e8e8;             border: 1px solid #000;             padding: 8px 6px;             text-align: center;             font-weight: bold;             font-size: 10px;         }          .estimate-table td {             border: 1px solid #999;             padding: 6px;             vertical-align: top;             font-size: 10px;         }          .estimate-table td:first-child,         .estimate-table th:first-child {             text-align: left;         }          .text-right {             text-align: right;         }          /* ===== ИТОГИ ===== */         .total-row {             background: #f0f0f0;         }          .total-row td {             border-top: 2px solid #000;             font-weight: bold;             font-size: 11px;         }          .total-breakdown {             margin-top: 10px;             padding: 10px;             border: 1px solid #ccc;             background: #f9f9f9;             font-size: 10px;         }          .total-breakdown p {             margin: 3px 0;         }          /* ===== ПОДВАЛ ===== */         .document-footer {             margin-top: 30px;             padding-top: 15px;             border-top: 1px solid #ccc;             font-size: 8px;             text-align: center;             color: #666;         }          .signatures {             display: flex;             justify-content: space-between;             margin: 30px 0 20px;         }          .signature {             width: 45%;         }          .signature-line {             border-top: 1px solid #000;             margin-top: 35px;             padding-top: 5px;             font-size: 9px;         }          /* Кнопки */         .no-print {             text-align: center;             margin-top: 30px;             padding: 20px;         }          .no-print button {             padding: 10px 20px;             margin: 0 10px;             font-size: 14px;             cursor: pointer;             background: #2563eb;             color: white;             border: none;             border-radius: 6px;         }          .no-print button:hover {             background: #1d4ed8;         }          /* ===== ПЕЧАТЬ ===== */         @media print {             body {                 margin: 0;                 padding: 0;             }             .print-container {                 padding: 0;             }             .no-print {                 display: none;             }             .estimate-table th,             .estimate-table td {                 border-color: #000;             }             @page {                 margin: 2cm;             }         }     </style> </head> <body>     <div class="print-container">         <!-- ==================== ШАПКА ДОКУМЕНТА ==================== -->         <div class="document-header">             <div class="header-top">                 <div class="header-left">                     <div class="logo">ТЕХСТАНДАРТ</div>                     <div class="logo-sub">Инженерные расчёты и сметная документация</div>                 </div>                 <div class="header-right">                     <p>ИНН 1234567890 / КПП 123456789</p>                     <p>ОГРН 1234567890123</p>                     <p>г. Москва, ул. Строителей, д. 10</p>                     <p>Тел: +7 (495) 123-45-67</p>                     <p>E-mail: info@tech-stc.ru</p>                 </div>             </div>         </div>          <!-- ==================== НАЗВАНИЕ ДОКУМЕНТА ==================== -->         <div class="doc-title">             <h1>ЛОКАЛЬНАЯ СМЕТА</h1>             <h2><?= htmlspecialchars($estimate['title']) ?></h2>             <p><?= htmlspecialchars($estimate['description']) ?></p>         </div>          <!-- ==================== ИНФОРМАЦИЯ О СМЕТЕ ==================== -->         <div class="estimate-info">             <div class="estimate-info-item"><strong>№ сметы:</strong> 01-<?= date('m-y') ?></div>             <div class="estimate-info-item"><strong>Дата составления:</strong> <?= date('d.m.Y') ?></div>             <div class="estimate-info-item"><strong>Основание:</strong> Техническое задание</div>             <div class="estimate-info-item"><strong>Объект:</strong> Частный жилой дом</div>         </div>          <!-- ==================== ТАБЛИЦА ==================== -->         <table class="estimate-table" id="estimate-table">             <thead>                 <tr>                     <th style="width: 10%">Код ГЭСН</th>                     <th style="width: 35%">Наименование работ и затрат</th>                     <th style="width: 8%">Ед. изм.</th>                     <th style="width: 8%">Объём</th>                     <th style="width: 12%">Цена, ₽</th>                     <th style="width: 12%">Стоимость, ₽</th>                 </tr>             </thead>             <tbody id="estimate-rows">                 <?php if (!empty($estimate['items'])): ?>                     <?php foreach ($estimate['items'] as $item): ?>                         <tr>                             <td><?= htmlspecialchars($item['code']) ?></td>                             <td><?= htmlspecialchars($item['name']) ?></td>                             <td class="text-right"><?= htmlspecialchars($item['unit']) ?></td>                             <td class="text-right"><?= number_format($item['qty'], 2, ',', ' ') ?></td>                             <td class="text-right"><?= number_format($item['price'], 0, ',', ' ') ?></td>                             <td class="text-right"><?= number_format($item['direct'], 0, ',', ' ') ?></td>                         </tr>                     <?php endforeach; ?>                 <?php else: ?>                     <tr><td colspan="6" style="text-align: center;">Нет данных</td></tr>                 <?php endif; ?>             </tbody>             <tfoot>                 <tr class="total-row">                     <td colspan="5" style="text-align: right; font-weight: bold;">ИТОГО ПО СМЕТЕ:</td>                     <td class="text-right" style="font-weight: bold;"><?= number_format($estimate['total'], 0, ',', ' ') ?> ₽</td>                 </tr>             </tfoot>         </table>          <!-- ==================== РАСШИФРОВКА ==================== -->         <div class="total-breakdown">             <p><strong>В том числе:</strong></p>             <p>— Материалы и оборудование: <?= number_format($estimate['total'] * 0.65, 0, ',', ' ') ?> ₽</p>             <p>— Работы (ФОТ + механизмы): <?= number_format($estimate['total'] * 0.35, 0, ',', ' ') ?> ₽</p>             <p>— НДС не облагается (организация применяет УСН)</p>         </div>          <!-- ==================== НОРМАТИВНАЯ БАЗА ==================== -->         <div class="total-breakdown" style="margin-top: 10px;">             <p><strong>Нормативная база:</strong> ГЭСН-2024, ФЕР-2024 (с индексами на <?= date('Y') ?> год)</p>             <p><strong>Нормативные документы:</strong> СП 22.13330.2016, СП 63.13330.2018, СП 60.13330.2020</p>         </div>          <!-- ==================== ПОДВАЛ И ПОДПИСИ ==================== -->         <div class="signatures">             <div class="signature">                 <div class="signature-line">Смету составил: __________________ /__________/</div>             </div>             <div class="signature">                 <div class="signature-line">Заказчик: __________________ /__________/</div>             </div>         </div>          <div class="document-footer">             <p>Расчёт носит предварительный характер и требует утверждения заказчиком</p>             <p>© <?= date('Y') ?> ТЕХСТАНДАРТ — инженерные расчёты и сметная документация</p>         </div>          <!-- ==================== КНОПКИ ==================== -->         <div class="no-print">             <button onclick="window.print();">🖨 Распечатать смету</button>             <button onclick="window.close();">✖ Закрыть окно</button>         </div>     </div> </body> </html>