diff --git a/.htaccess b/.htaccess new file mode 100644 index 0000000..80bfc0e --- /dev/null +++ b/.htaccess @@ -0,0 +1,15 @@ +RewriteEngine On +Options -MultiViews + +# 1) /index.html -> / (только при прямом запросе клиента) +RewriteCond %{THE_REQUEST} \s/+index\.html(?:[\s?]|$) [NC] +RewriteRule ^index\.html$ / [R=301,L,NC] + +# 2) /page.html -> /page (только при прямом запросе клиента) +RewriteCond %{THE_REQUEST} \s/+(prices|portfolio|reviews|privacy|requisites)\.html(?:[\s?]|$) [NC] +RewriteRule ^(prices|portfolio|reviews|privacy|requisites)\.html$ /$1 [R=301,L,NC] + +# 3) /page или /page/ -> /page.html (внутренний роутинг без редиректа) +RewriteCond %{REQUEST_FILENAME} !-f +RewriteCond %{REQUEST_FILENAME} !-d +RewriteRule ^(prices|portfolio|reviews|privacy|requisites)/?$ $1.html [L,NC] diff --git a/12.xlsx b/12.xlsx new file mode 100644 index 0000000..8aa1a65 Binary files /dev/null and b/12.xlsx differ diff --git a/image/1.jpg b/image/1.jpg new file mode 100644 index 0000000..57720b7 Binary files /dev/null and b/image/1.jpg differ diff --git a/image/2.jpg b/image/2.jpg new file mode 100644 index 0000000..d5bfa22 Binary files /dev/null and b/image/2.jpg differ diff --git a/image/3.jpg b/image/3.jpg new file mode 100644 index 0000000..1c6aabc Binary files /dev/null and b/image/3.jpg differ diff --git a/image/4.jpg b/image/4.jpg new file mode 100644 index 0000000..42fbb50 Binary files /dev/null and b/image/4.jpg differ diff --git a/image/5.jpg b/image/5.jpg new file mode 100644 index 0000000..db2e3b8 Binary files /dev/null and b/image/5.jpg differ diff --git a/image/6.jpg b/image/6.jpg new file mode 100644 index 0000000..b2b573a Binary files /dev/null and b/image/6.jpg differ diff --git a/image/7.jpg b/image/7.jpg new file mode 100644 index 0000000..cc85730 Binary files /dev/null and b/image/7.jpg differ diff --git a/image/8.jpg b/image/8.jpg new file mode 100644 index 0000000..fcd569e Binary files /dev/null and b/image/8.jpg differ diff --git a/image/bg-1.png b/image/bg-1.png new file mode 100644 index 0000000..2e47371 Binary files /dev/null and b/image/bg-1.png differ diff --git a/image/bg-2.png b/image/bg-2.png new file mode 100644 index 0000000..3a8f806 Binary files /dev/null and b/image/bg-2.png differ diff --git a/image/bg-3.png b/image/bg-3.png new file mode 100644 index 0000000..d153cc4 Binary files /dev/null and b/image/bg-3.png differ diff --git a/image/bg-4.png b/image/bg-4.png new file mode 100644 index 0000000..980146f Binary files /dev/null and b/image/bg-4.png differ diff --git a/image/bg-5.png b/image/bg-5.png new file mode 100644 index 0000000..a8c6a69 Binary files /dev/null and b/image/bg-5.png differ diff --git a/image/favicon.png b/image/favicon.png new file mode 100644 index 0000000..d4f66d7 Binary files /dev/null and b/image/favicon.png differ diff --git a/image/favicon.svg b/image/favicon.svg new file mode 100644 index 0000000..57a2442 --- /dev/null +++ b/image/favicon.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/image/logo-flat-2.png b/image/logo-flat-2.png new file mode 100644 index 0000000..112c7a1 Binary files /dev/null and b/image/logo-flat-2.png differ diff --git a/image/logo-flat-3.png b/image/logo-flat-3.png new file mode 100644 index 0000000..89f9b21 Binary files /dev/null and b/image/logo-flat-3.png differ diff --git a/image/logo-flat-5.png b/image/logo-flat-5.png new file mode 100644 index 0000000..beafefb Binary files /dev/null and b/image/logo-flat-5.png differ diff --git a/image/logo-interior-1.png b/image/logo-interior-1.png new file mode 100644 index 0000000..8fcb4de Binary files /dev/null and b/image/logo-interior-1.png differ diff --git a/image/logo-interior-6.png b/image/logo-interior-6.png new file mode 100644 index 0000000..d68a02d Binary files /dev/null and b/image/logo-interior-6.png differ diff --git a/image/logo-interior-8.png b/image/logo-interior-8.png new file mode 100644 index 0000000..34b2ac5 Binary files /dev/null and b/image/logo-interior-8.png differ diff --git a/image/logo-interior-9.png b/image/logo-interior-9.png new file mode 100644 index 0000000..62b6983 Binary files /dev/null and b/image/logo-interior-9.png differ diff --git a/image/logo-option-1.png b/image/logo-option-1.png new file mode 100644 index 0000000..4c39e83 Binary files /dev/null and b/image/logo-option-1.png differ diff --git a/image/logo-option-2.png b/image/logo-option-2.png new file mode 100644 index 0000000..df2e665 Binary files /dev/null and b/image/logo-option-2.png differ diff --git a/image/logo-option-3.png b/image/logo-option-3.png new file mode 100644 index 0000000..b964b9a Binary files /dev/null and b/image/logo-option-3.png differ diff --git a/image/logo-option-4.png b/image/logo-option-4.png new file mode 100644 index 0000000..5bb5baf Binary files /dev/null and b/image/logo-option-4.png differ diff --git a/image/logo-option-6.png b/image/logo-option-6.png new file mode 100644 index 0000000..99d52e2 Binary files /dev/null and b/image/logo-option-6.png differ diff --git a/image/logo-right-2.png b/image/logo-right-2.png new file mode 100644 index 0000000..7523f35 Binary files /dev/null and b/image/logo-right-2.png differ diff --git a/image/logo-right-4.png b/image/logo-right-4.png new file mode 100644 index 0000000..43f93cd Binary files /dev/null and b/image/logo-right-4.png differ diff --git a/image/logo-right-5.png b/image/logo-right-5.png new file mode 100644 index 0000000..88a33c8 Binary files /dev/null and b/image/logo-right-5.png differ diff --git a/image/logo-simple-1.png b/image/logo-simple-1.png new file mode 100644 index 0000000..d07152a Binary files /dev/null and b/image/logo-simple-1.png differ diff --git a/image/logo-simple-3.png b/image/logo-simple-3.png new file mode 100644 index 0000000..614fde2 Binary files /dev/null and b/image/logo-simple-3.png differ diff --git a/image/placeholder.png b/image/placeholder.png new file mode 100644 index 0000000..5b7e0b9 Binary files /dev/null and b/image/placeholder.png differ diff --git a/image/Лен 1.jpg b/image/Лен 1.jpg new file mode 100644 index 0000000..eeb8b59 Binary files /dev/null and b/image/Лен 1.jpg differ diff --git a/image/Лен 10.jpg b/image/Лен 10.jpg new file mode 100644 index 0000000..27373fa Binary files /dev/null and b/image/Лен 10.jpg differ diff --git a/image/Лен 2.jpg b/image/Лен 2.jpg new file mode 100644 index 0000000..e0fa247 Binary files /dev/null and b/image/Лен 2.jpg differ diff --git a/image/Лен 3.jpg b/image/Лен 3.jpg new file mode 100644 index 0000000..4976dec Binary files /dev/null and b/image/Лен 3.jpg differ diff --git a/image/Лен 4.jpg b/image/Лен 4.jpg new file mode 100644 index 0000000..3d35c4d Binary files /dev/null and b/image/Лен 4.jpg differ diff --git a/image/Лен 5.jpg b/image/Лен 5.jpg new file mode 100644 index 0000000..525d238 Binary files /dev/null and b/image/Лен 5.jpg differ diff --git a/image/Лен 6.jpg b/image/Лен 6.jpg new file mode 100644 index 0000000..ce802bd Binary files /dev/null and b/image/Лен 6.jpg differ diff --git a/image/Лен 7.jpg b/image/Лен 7.jpg new file mode 100644 index 0000000..3ffb708 Binary files /dev/null and b/image/Лен 7.jpg differ diff --git a/image/Лен 8.jpg b/image/Лен 8.jpg new file mode 100644 index 0000000..823a594 Binary files /dev/null and b/image/Лен 8.jpg differ diff --git a/image/Лен 9.jpg b/image/Лен 9.jpg new file mode 100644 index 0000000..8f4c3dc Binary files /dev/null and b/image/Лен 9.jpg differ diff --git a/index.html b/index.html new file mode 100644 index 0000000..05e387f --- /dev/null +++ b/index.html @@ -0,0 +1,184 @@ + + + + + + + Фриз | Отделочные работы в Санкт-Петербурге + + + + + + + + + +
+ +
+
+ +
+

Отделочные малярные работы
в Санкт-Петербурге

+

+ Работаем в Санкт-Петербурге с 2009 года, чаще ведем объекты по дизайн-проектам, но беремся и за более + простые варианты работ. Мы небольшая команда, специализирующаяся на малярных работах, поэтому + хорошо знаем все нюансы и доводим все до идеального состояния +

+ Оставить заявку +
+
+ +
+ +
+ + + +
+
+

Оставить заявку

+
+ + + + + + +
+
+
+
+ +
+ + + + + + diff --git a/portfolio.html b/portfolio.html new file mode 100644 index 0000000..50f9351 --- /dev/null +++ b/portfolio.html @@ -0,0 +1,58 @@ + + + + + + + Фриз | Портфолио — Санкт-Петербург + + + + + + + + +
+ +
+
+
+

Портфолио работ

+

Несколько примеров реализованных объектов в Санкт-Петербурге и Ленобласти

+ + +
+
+
+ +
+ + + + + + diff --git a/prices-data.js b/prices-data.js new file mode 100644 index 0000000..95b41fa --- /dev/null +++ b/prices-data.js @@ -0,0 +1,769 @@ +window.PRICES_DATA = [ + { + title: "Демонтажные работы", + children: [ + { + title: "Полы", + items: [ + { name: "Демонтаж плинтуса", price: "от 99 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Удаление слоев из керамзита, песка", price: "от 156 ₽/м²", amount: "1", unit: "м²" }, + { name: "Устранение мелких дифектов пола", price: "от 167 ₽/м²", amount: "1", unit: "м²" }, + { name: "Демонтаж ламината, паркетной доски", price: "от 173 ₽/м²", amount: "1", unit: "м²" }, + { name: "Демонтаж паркета штучного", price: "от 332 ₽/м²", amount: "1", unit: "м²" }, + { name: "Демонтаж паркета щитового", price: "от 332 ₽/м²", amount: "1", unit: "м²" }, + { name: "Демонтаж деревянных полов", price: "от 398 ₽/м²", amount: "1", unit: "м²" }, + { name: "Демонтаж линолеума, ковролина на клею", price: "от 216 ₽/м²", amount: "1", unit: "м²" }, + { name: "Демонтаж линолеума, ковролина", price: "от 138 ₽/м²", amount: "1", unit: "м²" }, + { name: "Демонтаж фанеры, ДВП", price: "от 218 ₽/м²", amount: "1", unit: "м²" }, + { name: "Демонтаж лаг", price: "от 233 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Демонтаж подложки из ДВП обмазанной битумной мастикой", price: "от 294 ₽/м²", amount: "1", unit: "м²" }, + { name: "Демонтаж покрытия из битумной мастики", price: "от 375 ₽/м²", amount: "1", unit: "м²" }, + { name: "Расчистка пола от засыпки и шумоизоляции", price: "от 294 ₽/м²", amount: "1", unit: "м²" }, + { name: "Демонтаж плиточного клея", price: "от 438 ₽/м²", amount: "1", unit: "м²" }, + { name: "Расшивка плиточных швов (пол)", price: "от 381 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Демонтаж плинтуса из плитки", price: "от 449 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Демонтаж порога из плитки", price: "от 596 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Демонтаж плитки", price: "от 449 ₽/м²", amount: "1", unit: "м²" }, + { name: "Демонтаж бетонного бортика", price: "от 953 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Демонтаж стяжки до 4 см", price: "от 597 ₽/м²", amount: "1", unit: "м²" }, + { name: "Демонтаж стяжки до 6 см", price: "от 747 ₽/м²", amount: "1", unit: "м²" }, + { name: "Разборка гидроизоляции", price: "от 261 ₽/м²", amount: "1", unit: "м²" }, + { name: "Сухая уборка полов под метлу, щетку", price: "от 90 ₽/м²", amount: "1", unit: "м²" }, + { name: "Ремонт дощатых полов (протяжка саморезами)", price: "от 383 ₽/м²", amount: "1", unit: "м²" } + ] + }, + { + title: "Стены", + items: [ + { name: "Демонтаж пластикового уголка", price: "от 80 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Снятие обоев (стены)", price: "от 188 ₽/м²", amount: "1", unit: "м²" }, + { name: "Снятие многослойных трудноотделяемых обоев", price: "от 419 ₽/м²", amount: "1", unit: "м²" }, + { name: "Снятие побелки", price: "от 336 ₽/м²", amount: "1", unit: "м²" }, + { name: "Очистка краски со стен ВЭ", price: "от 294 ₽/м²", amount: "1", unit: "м²" }, + { name: "Очистка масляной краски со стен", price: "от 576 ₽/м²", amount: "1", unit: "м²" }, + { name: "Очистка стен от \"жидких\" обоев", price: "от 234 ₽/м²", amount: "1", unit: "м²" }, + { name: "Очистка стен от шпаклевки", price: "от 291 ₽/м²", amount: "1", unit: "м²" }, + { name: "Расшивка трещин/рустов", price: "от 333 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Насечка стен", price: "от 188 ₽/м²", amount: "1", unit: "м²" }, + { name: "Расшивка плиточных швов (стены)", price: "от 383 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Демонтаж керамической плитки", price: "от 449 ₽/м²", amount: "1", unit: "м²" }, + { name: "Отбивка плиточного клея", price: "от 438 ₽/м²", amount: "1", unit: "м²" }, + { name: "Ошкуривание поверхности стен", price: "от 263 ₽/м²", amount: "1", unit: "м²" }, + { name: "Демонтаж панелей ПВХ (без каркаса)", price: "от 117 ₽/м²", amount: "1", unit: "м²" }, + { name: "Демонтаж облицовки стен из ДСП, вагонки", price: "от 366 ₽/м²", amount: "1", unit: "м²" }, + { name: "Демонтаж панелей ПВХ (с каркасом)", price: "от 186 ₽/м²", amount: "1", unit: "м²" }, + { name: "Демонтаж обшивки стен из ГКЛ на металлокарскасе", price: "от 447 ₽/м²", amount: "1", unit: "м²" }, + { name: "Демонтаж \"дранки\" со стен", price: "от 525 ₽/м²", amount: "1", unit: "м²" }, + { name: "Демонтаж утеплителя", price: "от 285 ₽/м²", amount: "1", unit: "м²" }, + { name: "Отбивка штукатурки со стен", price: "от 522 ₽/м²", amount: "1", unit: "м²" }, + { name: "Демонтаж деревянных стен", price: "от 1 148 ₽/м²", amount: "1", unit: "м²" }, + { name: "Демонтаж подоконного блока", price: "от 6 818 ₽/шт", amount: "1", unit: "шт" }, + { name: "Демонтаж стен (перегородок) в 1/2 кирпич", price: "от 1 718 ₽/м²", amount: "1", unit: "м²" }, + { name: "Демонтаж стен (перегородок) в 1 кирпич", price: "от 2 153 ₽/м²", amount: "1", unit: "м²" }, + { name: "Демонтаж стен (перегородок) в 2 кирпича", price: "от 2 768 ₽/м²", amount: "1", unit: "м²" }, + { name: "Демонтаж стен из гипсолита, ПГП", price: "от 924 ₽/м²", amount: "1", unit: "м²" }, + { name: "Демонтаж стен из пеноблока", price: "от 966 ₽/м²", amount: "1", unit: "м²" }, + { name: "Демонтаж стен из ацеида, ГВЛ, ГКЛ", price: "от 747 ₽/м²", amount: "1", unit: "м²" }, + { name: "Демонтаж бетонных стен толщиной до 80 мм", price: "от 5 798 ₽/м²", amount: "1", unit: "м²" } + ] + }, + { + title: "Откосы", + items: [ + { name: "Демонтаж уголка с откосов, углов", price: "от 50 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Демонтаж сэндвич панелей", price: "от 98 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Обрезка пены с откосов, углов", price: "от 51 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Очистка откосов, углов от шпаклевки", price: "от 294 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Очистка от краски откосов, углов", price: "от 279 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Демонтаж бетонного подоконника", price: "от 1 089 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Отбивка штукатурки с откосов, углов", price: "от 381 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Демонтаж откосов из ГКЛв", price: "от 210 ₽/пог.м", amount: "1", unit: "пог.м" } + ] + }, + { + title: "Потолки", + items: [ + { name: "Демонтаж лепнины без сохранения", price: "от 171 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Демонтаж потолочного плинтуса", price: "от 138 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Демонтаж подвесных потолков ГКЛ", price: "от 488 ₽/м²", amount: "1", unit: "м²" }, + { name: "Демонтаж реечного потолка", price: "от 234 ₽/м²", amount: "1", unit: "м²" }, + { name: "Демонтаж кассетного подвесного потолка (армстронг)", price: "от 177 ₽/м²", amount: "1", unit: "м²" }, + { name: "Демонтаж панелей кассетного потолка без профиля (армстронг)", price: "от 98 ₽/м²", amount: "1", unit: "м²" }, + { name: "Демонтаж потолочных панелей (на клею)", price: "от 156 ₽/м²", amount: "1", unit: "м²" }, + { name: "Демонтаж натяжного потолка (включая профиль)", price: "от 180 ₽/м²", amount: "1", unit: "м²" }, + { name: "Снятие полистирольных плиток", price: "от 171 ₽/м²", amount: "1", unit: "м²" }, + { name: "Демонтаж потолков из ацеида, гипсолита", price: "от 923 ₽/м²", amount: "1", unit: "м²" }, + { name: "Очистка от побелки", price: "от 441 ₽/м²", amount: "1", unit: "м²" }, + { name: "Очистка от краски ВЭ", price: "от 351 ₽/м²", amount: "1", unit: "м²" }, + { name: "Очистка потолков от масляной краски", price: "от 936 ₽/м²", amount: "1", unit: "м²" }, + { name: "Снятие обоев (потолок)", price: "от 218 ₽/м²", amount: "1", unit: "м²" }, + { name: "Снятие многослойных трудноотделяемых обоев", price: "от 486 ₽/м²", amount: "1", unit: "м²" }, + { name: "Очистка потолка от шпаклевки", price: "от 426 ₽/м²", amount: "1", unit: "м²" }, + { name: "Отбивка штукатурки", price: "от 486 ₽/м²", amount: "1", unit: "м²" }, + { name: "Расшивка трещин рустов", price: "от 312 ₽/пог.м", amount: "1", unit: "пог.м" } + ] + }, + { + title: "Двери, окна", + items: [ + { name: "Демонтаж наличников", price: "от 98 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Демонтаж дверной ручки / замка", price: "от 318 ₽/шт", amount: "1", unit: "шт" }, + { name: "Демонтаж бетонного подоконника", price: "от 1 089 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Демонтаж деревянной межкомнатной двери", price: "от 480 ₽/шт", amount: "1", unit: "шт" }, + { name: "Демонтаж деревянной межкомнатной двери с сохранением", price: "от 1 332 ₽/шт", amount: "1", unit: "шт" }, + { name: "Демонтаж наличников (с сохранением)", price: "от 482 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Снятие деревянной двери с петель", price: "от 383 ₽/шт", amount: "1", unit: "шт" }, + { name: "Демонтаж металлической двери", price: "от 2 042 ₽/шт", amount: "1", unit: "шт" }, + { name: "Очистка наличников или дверной коробки от краски", price: "от 848 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Демонтаж откосов из сэндвич /ПВХ панелей", price: "от 165 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Демонтаж штукатурных откосов", price: "от 381 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Демонтаж оконного блока (без сохранения)", price: "от 822 ₽/м²", amount: "1", unit: "м²" }, + { name: "Демонтаж оконного блока (с сохранением)", price: "от 1 275 ₽/м²", amount: "1", unit: "м²" }, + { name: "Демонтаж арки дверного проема (ГКЛ, дерево)", price: "от 1 212 ₽/шт", amount: "1", unit: "шт" }, + { name: "Демонтаж подоконника", price: "от 455 ₽/шт", amount: "1", unit: "шт" }, + { name: "Очистка окна от краски", price: "от 1 872 ₽/пог.м", amount: "1", unit: "пог.м" } + ] + }, + { + title: "Прочие", + items: [ + { name: "Демонтаж встроенных шкафов и полок, антресолей", price: "от 1 122 ₽/м²", amount: "1", unit: "м²" }, + { name: "Демонтаж карнизов", price: "от 284 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Демонтаж вентиляционной решетки", price: "от 117 ₽/шт", amount: "1", unit: "шт" }, + { name: "Демонтаж воздуховодов", price: "от 273 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Срез металлоконструкций", price: "от 117 ₽/шт", amount: "1", unit: "шт" } + ] + }, + { + title: "Сантехника", + items: [ + { name: "Демонтаж смесителя", price: "от 683 ₽/шт", amount: "1", unit: "шт" }, + { name: "Демонтаж душевой кабины (без сохранения)", price: "от 1 164 ₽/шт", amount: "1", unit: "шт" }, + { name: "Демонтаж душевой кабины (с сохранением)", price: "от 4 731 ₽/шт", amount: "1", unit: "шт" }, + { name: "Демонтаж ванны (без сохранения)", price: "от 1 347 ₽/шт", amount: "1", unit: "шт" }, + { name: "Демонтаж ванны (с сохранением)", price: "от 2 109 ₽/шт", amount: "1", unit: "шт" }, + { name: "Демонтаж чугунной ванны (без сохранения)", price: "от 1 913 ₽/шт", amount: "1", unit: "шт" }, + { name: "Демонтаж чугунной ванны (с сохранением)", price: "от 4 916 ₽/шт", amount: "1", unit: "шт" }, + { name: "Демонтаж сантехнического шкафа", price: "от 774 ₽/м²", amount: "1", unit: "м²" }, + { name: "Демонтаж раковины", price: "от 674 ₽/шт", amount: "1", unit: "шт" }, + { name: "Демонтаж кухонной мойки", price: "от 773 ₽/шт", amount: "1", unit: "шт" }, + { name: "Демонтаж унитаза", price: "от 966 ₽/шт", amount: "1", unit: "шт" }, + { name: "Демонтаж стиральной машины", price: "от 966 ₽/шт", amount: "1", unit: "шт" }, + { name: "Демонтаж полотенцесушителя (электро)", price: "от 525 ₽/шт", amount: "1", unit: "шт" }, + { name: "Демонтаж водонагревателя", price: "от 1 260 ₽/шт", amount: "1", unit: "шт" }, + { name: "Демонтаж кранов вводных, счетчиков", price: "от 186 ₽/шт", amount: "1", unit: "шт" }, + { name: "Демонтаж кранов шаровых", price: "от 168 ₽/шт", amount: "1", unit: "шт" }, + { name: "Демонтаж труб в/с", price: "от 284 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Демонтаж труб канализации", price: "от 450 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Расчеканка чугунных труб", price: "от 5 168 ₽/шт", amount: "1", unit: "шт" }, + { name: "Демонтаж радиатора", price: "от 773 ₽/шт", amount: "1", unit: "шт" }, + { name: "Очистка стояка от старой краски до 3м", price: "от 1 868 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Очистка радиатора от краски ( до 5 секций)", price: "от 1 934 ₽/шт", amount: "1", unit: "шт" }, + { name: "Очистка радиатора каждая следующая секция", price: "от 653 ₽/шт", amount: "1", unit: "шт" }, + { name: "Демонтаж кабины совмещенного санузла (из ацеида)", price: "от 15 780 ₽/шт", amount: "1", unit: "шт" }, + { name: "Демонтаж кабины раздельного санузла (из ацеида)", price: "от 18 780 ₽/шт", amount: "1", unit: "шт" }, + { name: "Демонтаж бетонного пола сан тех кабины", price: "от 14 793 ₽/шт", amount: "1", unit: "шт" } + ] + }, + { + title: "Электрика", + items: [ + { name: "Демонтаж панели электроточки", price: "от 74 ₽/шт", amount: "1", unit: "шт" }, + { name: "Демонтаж розеток и выключателей", price: "от 263 ₽/шт", amount: "1", unit: "шт" }, + { name: "Демонтаж светильников", price: "от 186 ₽/шт", amount: "1", unit: "шт" }, + { name: "Демонтаж люстры", price: "от 383 ₽/шт", amount: "1", unit: "шт" }, + { name: "Демонтаж электрокабеля", price: "от 39 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Демонтаж автоматического выключателя", price: "от 186 ₽/шт", amount: "1", unit: "шт" }, + { name: "Демонтаж электрического щита", price: "от 1 418 ₽/шт", amount: "1", unit: "шт" }, + { name: "Пробивка отверстия в стене (бетон) ф 16-20", price: "от 1 283 ₽/шт", amount: "1", unit: "шт" }, + { name: "Пробивка отверстия в стене (кирпич) ф 16-20", price: "от 714 ₽/шт", amount: "1", unit: "шт" }, + { name: "Пробивка отверстия в стене (пеноблок, пазогребневый блок) ф 16-20", price: "от 578 ₽/шт", amount: "1", unit: "шт" }, + { name: "Штробление ниши под элщит до 12 модулей (бетон)", price: "от 6 381 ₽/шт", amount: "1", unit: "шт" }, + { name: "Штробление ниши под элщит 12-36 модулей (бетон)", price: "от 9 668 ₽/шт", amount: "1", unit: "шт" }, + { name: "Штробление ниши под элщит до 12 модулей (кирпич/Пено блок/пгп)", price: "от 2 691 ₽/шт", amount: "1", unit: "шт" }, + { name: "Штробление ниши под элщит 12-36 модулей (кирпич/Пено блок/пгп)", price: "от 5 343 ₽/шт", amount: "1", unit: "шт" } + ] + } + ] + }, + { + title: "Черновые отделочные работы", + children: [ + { + title: "Полы", + items: [ + { name: "Грунтовка пола", price: "от 98 ₽/м²", amount: "1", unit: "м²" }, + { name: "Грунтовка пола (второй и последующие слои)", price: "от 98 ₽/м²", amount: "1", unit: "м²" }, + { name: "Грунтовка пола (бетон контактом)", price: "от 128 ₽/м²", amount: "1", unit: "м²" }, + { name: "Обработка анти плесенью(пол)", price: "от 206 ₽/м²", amount: "1", unit: "м²" }, + { name: "Гидроизоляция пола пленкой ПВХ", price: "от 147 ₽/м²", amount: "1", unit: "м²" }, + { name: "Гидроизоляция пола (смесью водостоп или жидким стеклом)", price: "от 461 ₽/м²", amount: "1", unit: "м²" }, + { name: "Гидроизоляция рулонными материалами", price: "от 642 ₽/м²", amount: "1", unit: "м²" }, + { name: "Укладка пароизолирующих материалов", price: "от 186 ₽/м²", amount: "1", unit: "м²" }, + { name: "Гидроизоляционная лента (по периметру стен)", price: "от 176 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Монтаж демпферной ленты (кромочной ленты)", price: "от 147 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Укрытие пленкой пола", price: "от 81 ₽/м²", amount: "1", unit: "м²" }, + { name: "Подсыпка керамзита", price: "от 321 ₽/м²", amount: "1", unit: "м²" }, + { name: "Укладка сетки (для стяжки)", price: "от 177 ₽/м²", amount: "1", unit: "м²" }, + { name: "Железнение стяжки", price: "от 186 ₽/м²", amount: "1", unit: "м²" }, + { name: "Устройство стяжки по маякам до 4 см", price: "от 698 ₽/м²", amount: "1", unit: "м²" }, + { name: "Устройство стяжки по маякам до 6 см", price: "от 1 049 ₽/м²", amount: "1", unit: "м²" }, + { name: "Устройство стяжки по маякам свыше 6 см", price: "от 1 193 ₽/м²", amount: "1", unit: "м²" }, + { name: "Устройство наливных полов до 3 см", price: "от 683 ₽/м²", amount: "1", unit: "м²" }, + { name: "Финишное выравнивание стяжки наливным полом", price: "от 533 ₽/м²", amount: "1", unit: "м²" }, + { name: "Заливка рустов после проведения демонтажа перегородок", price: "от 630 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Устройство \"сухой стяжки\"", price: "от 2 310 ₽/м²", amount: "1", unit: "м²" }, + { name: "Устройство \"полусухой\" (механизированной стяжки)", price: "от 2 775 ₽/м²", amount: "1", unit: "м²" }, + { name: "Устройство деревянной обрешетки пола", price: "от 638 ₽/м²", amount: "1", unit: "м²" }, + { name: "Укладка деревянных лаг", price: "от 533 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Устройство тепло/звукоизоляции(полы)", price: "от 353 ₽/м²", amount: "1", unit: "м²" }, + { name: "Устройство дощатых полов по лагам", price: "от 759 ₽/м²", amount: "1", unit: "м²" }, + { name: "Укладка фанеры на дощатый пол", price: "от 518 ₽/м²", amount: "1", unit: "м²" }, + { name: "Укладка фанеры на бетон", price: "от 863 ₽/м²", amount: "1", unit: "м²" }, + { name: "Распил фанеры", price: "от 90 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Шлифовка фанеры", price: "от 332 ₽/м²", amount: "1", unit: "м²" }, + { name: "Укладка ДВП", price: "от 399 ₽/м²", amount: "1", unit: "м²" }, + { name: "Временная застилка полов оргалитом", price: "от 90 ₽/м²", amount: "1", unit: "м²" }, + { name: "Устройство подиума с обшивкой фанерой до 10 см сложной формы", price: "от 3 518 ₽/м²", amount: "1", unit: "м²" }, + { name: "Добавление фиброволокна в стяжку пола", price: "от 72 ₽/м²", amount: "1", unit: "м²" } + ] + }, + { + title: "Стены", + items: [ + { name: "Кирпичная кладка в 1/2 кирпича (черновой кирпич)", price: "от 1 688 ₽/м²", amount: "1", unit: "м²" }, + { name: "Кирпичная кладка в 1 кирпич (черновой кирпич)", price: "от 2 223 ₽/м²", amount: "1", unit: "м²" }, + { name: "Кирпичная кладка в 1 кирпич (облицовочный кирпич)", price: "от 4 440 ₽/м²", amount: "1", unit: "м²" }, + { name: "Кирпичная кладка в 1/2 кирпича (облицовочный кирпич)", price: "от 3 375 ₽/м²", amount: "1", unit: "м²" }, + { name: "Кладка стен из Пеноблоков", price: "от 1 718 ₽/м²", amount: "1", unit: "м²" }, + { name: "Кладка перегородок из шлакоблоков", price: "от 1 881 ₽/м²", amount: "1", unit: "м²" }, + { name: "Кладка перегородок из пазогребневых блоков", price: "от 1 763 ₽/м²", amount: "1", unit: "м²" }, + { name: "Заужение дверного проема путем наращивания торца стен из ГКЛ до 50 см", price: "от 2 798 ₽/шт", amount: "1", unit: "шт" }, + { name: "Расширение дверного проема из кирпича,пазогреб./Пеноблоков до 10 см ( с одной стороны)", price: "от 1 518 ₽/шт", amount: "1", unit: "шт" }, + { name: "Расширение дверного проема бетонного до 80 мм", price: "от 7 823 ₽/шт", amount: "1", unit: "шт" }, + { name: "Подготовка дверного проема под скрытую дверь", price: "от 6 750 ₽/шт", amount: "1", unit: "шт" }, + { name: "Армирование проемов арматурой (с грунтовкой арматуры)", price: "от 891 ₽/шт", amount: "1", unit: "шт" }, + { name: "Армирование проемов уголком (с грунтовкой уголка)", price: "от 1 079 ₽/шт", amount: "1", unit: "шт" }, + { name: "Монтаж металлической штукатурной сетки (на дюбели)", price: "от 488 ₽/м²", amount: "1", unit: "м²" }, + { name: "Монтаж штукатурной сетки на стенах", price: "от 261 ₽/м²", amount: "1", unit: "м²" }, + { name: "Герметизация стыков, швов монтажной пеной", price: "от 186 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Герметизация стыков, швов силиконом", price: "от 675 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Монтаж отражающей теплоизоляции (Пенофол)", price: "от 201 ₽/м²", amount: "1", unit: "м²" }, + { name: "Устройство теплоизоляции стен пеноплексом", price: "от 891 ₽/м²", amount: "1", unit: "м²" }, + { name: "Устройство звукоизоляции стен системой ЗИПС вектор", price: "от 1 700 ₽/м²", amount: "1", unit: "м²" }, + { name: "Устройство пароизоляции(стен)", price: "от 152 ₽/м²", amount: "1", unit: "м²" }, + { name: "Окраска металических деталей до 100мм", price: "от 180 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Устройство тепло/звукоизоляции стен минеральной ватой", price: "от 480 ₽/м²", amount: "1", unit: "м²" }, + { name: "Устройство перегородок из ГКЛ в 1 слой", price: "от 2 100 ₽/м²", amount: "1", unit: "м²" }, + { name: "Устройство перегородок из ГКЛ в 2 слоя", price: "от 2 468 ₽/м²", amount: "1", unit: "м²" }, + { name: "Усиление ГКЛ перегородки ОСБ/фанерой", price: "от 975 ₽/м²", amount: "1", unit: "м²" }, + { name: "Устройство ниш, ступеней из ГКЛ", price: "от 3 291 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Устройство коробов из ГКЛ", price: "от 2 768 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Устройство криволинейных коробов из ГКЛ на стенах", price: "от 3 960 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Устройство ниш, ступеней из ГКЛ сложной формы", price: "от 4 125 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Устройство арки из ГКЛ", price: "от 5 400 ₽/шт", amount: "1", unit: "шт" }, + { name: "Выравнивание стен листами ГКЛ", price: "от 941 ₽/м²", amount: "1", unit: "м²" }, + { name: "Выравнивание стен листами ГКЛ (1 слой) с устройством каркаса", price: "от 1 650 ₽/м²", amount: "1", unit: "м²" }, + { name: "Выравнивание стен листами ГКЛ (2 слоя) с устройством каркаса", price: "от 1 875 ₽/м²", amount: "1", unit: "м²" }, + { name: "Устройство конструкции ниши ГКЛ с полками на металлокаркасе", price: "от 5 835 ₽/шт", amount: "1", unit: "шт" }, + { name: "Грунтовка стен", price: "от 98 ₽/м²", amount: "1", unit: "м²" }, + { name: "Грунтовка стен (второй и последующие слои )", price: "от 98 ₽/м²", amount: "1", unit: "м²" }, + { name: "Грунтовка стен (бетонконтактом)", price: "от 168 ₽/м²", amount: "1", unit: "м²" }, + { name: "Противогрибковая обработка стен спец. составом", price: "от 255 ₽/м²", amount: "1", unit: "м²" }, + { name: "Гидроизоляция (смесью водостоп или жидким стеклом)", price: "от 461 ₽/м²", amount: "1", unit: "м²" }, + { name: "Штукатурка стен простая (под правило)", price: "от 656 ₽/м²", amount: "1", unit: "м²" }, + { name: "Штукатурка стен по маякам до 3 см гипсовой смесью", price: "от 984 ₽/м²", amount: "1", unit: "м²" }, + { name: "Штукатурка стен по маякам до 5 см гипсовой смесью", price: "от 1 134 ₽/м²", amount: "1", unit: "м²" }, + { name: "Штукатурка стен по маякам свыше 5 см гипсовой смесью", price: "от 1 434 ₽/м²", amount: "1", unit: "м²" }, + { name: "Штукатурка стен по маякам до 3 см цементной смесью", price: "от 1 284 ₽/м²", amount: "1", unit: "м²" }, + { name: "Штукатурка стен по маякам до 5 см цементной смесью", price: "от 1 434 ₽/м²", amount: "1", unit: "м²" }, + { name: "Протяжка углов штукатуркой", price: "от 372 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Протягивание штукатуркой углов, зон плинтусов, потолков (до 30 см)", price: "от 428 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Установка малярного уголка, перфорированного", price: "от 186 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Нанесение малярной сетки", price: "от 180 ₽/м²", amount: "1", unit: "м²" }, + { name: "Базовая шпаклевка стен (с ошкуриванием)", price: "от 488 ₽/м²", amount: "1", unit: "м²" }, + { name: "Сплошная шпаклевка в 1 слой (с ошкуриванием)", price: "от 548 ₽/м²", amount: "1", unit: "м²" }, + { name: "Шпаклевка стен под оклейку обоями (с ошкуриванием)", price: "от 825 ₽/м²", amount: "1", unit: "м²" }, + { name: "Финишная шпаклевка стен под окраску (2 слоя с ошкуриванием)", price: "от 1 200 ₽/м²", amount: "1", unit: "м²" }, + { name: "Шпаклевка стен ГКЛ под оклейку обоями (с ошкуриванием)", price: "от 870 ₽/м²", amount: "1", unit: "м²" }, + { name: "Финишная Шпаклевка стен ГКЛ под окраску (2 слоя с ошкуриванием)", price: "от 900 ₽/м²", amount: "1", unit: "м²" }, + { name: "Шпаклевка коробов, ниш", price: "от 780 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Ошкуривание поверхности стен", price: "от 480 ₽/м²", amount: "1", unit: "м²" }, + { name: "Поклейка стеклохолста", price: "от 435 ₽/м²", amount: "1", unit: "м²" }, + { name: "Устройство ниш глубиной до 7 см в кирпичных стенах", price: "от 2 250 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Устройство деревянной обрешетки стены", price: "от 518 ₽/м²", amount: "1", unit: "м²" } + ] + }, + { + title: "Откосы", + items: [ + { name: "Грунтовка откосов, углов", price: "от 98 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Установка штукатурных уголков ( перфорированных)", price: "от 188 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Штукатурка откосов до 40 см, углов", price: "от 683 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Штукатурка откосов от 40 до 60 см, углов", price: "от 713 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Штукатурка откосов арочных", price: "от 1 193 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Утепление откосов", price: "от 171 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Запенивание стыков", price: "от 189 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Устройство откосов из ГКЛ", price: "от 1 238 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Оклейка стеклохолстом / малярной сеткой откосов", price: "от 323 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Шпаклевка откосов, углов", price: "от 870 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Формирование дверного проема", price: "от 2 250 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Шпаклевка откосов под окраску", price: "от 870 ₽/пог.м", amount: "1", unit: "пог.м" } + ] + }, + { + title: "Потолки", + items: [ + { name: "Грунтовка потолков", price: "от 150 ₽/м²", amount: "1", unit: "м²" }, + { name: "Грунтовка потолков второй и последующие слои", price: "от 150 ₽/м²", amount: "1", unit: "м²" }, + { name: "Грунтовка потолков бетонконтактом", price: "от 225 ₽/м²", amount: "1", unit: "м²" }, + { name: "Гидроизоляция потолков", price: "от 600 ₽/м²", amount: "1", unit: "м²" }, + { name: "Обработка анти плесенью(потолок)", price: "от 294 ₽/м²", amount: "1", unit: "м²" }, + { name: "Монтаж металлической штукатурной сетки на потолке", price: "от 563 ₽/м²", amount: "1", unit: "м²" }, + { name: "Штукатурка потолков простая (под провило)", price: "от 794 ₽/м²", amount: "1", unit: "м²" }, + { name: "Штукатурка улучшенная до 1 см гипсовой смесью", price: "от 987 ₽/м²", amount: "1", unit: "м²" }, + { name: "Штукатурка потолков по маякам до 3 см гипсовой смесью", price: "от 1 265 ₽/м²", amount: "1", unit: "м²" }, + { name: "Заделка трещин рустов", price: "от 213 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Устройство теплоизоляции пеноплексом", price: "от 518 ₽/м²", amount: "1", unit: "м²" }, + { name: "Устройство тепло/звукоизоляции минеральной ватой", price: "от 938 ₽/м²", amount: "1", unit: "м²" }, + { name: "Устройство шумоизоляционного мата на потолоке", price: "от 1 350 ₽/м²", amount: "1", unit: "м²" }, + { name: "Устройство пароизоляции", price: "от 215 ₽/м²", amount: "1", unit: "м²" }, + { name: "Устройство деревянной обрешетки", price: "от 555 ₽/м²", amount: "1", unit: "м²" }, + { name: "Устройство потолков из ГКЛ ( с устройством каркаса)", price: "от 2 475 ₽/м²", amount: "1", unit: "м²" }, + { name: "Устройство потолков из ГКЛ в 2 слоя ( с устройством каркаса)", price: "от 2 918 ₽/м²", amount: "1", unit: "м²" }, + { name: "Устройство коробов из ГКЛ на потолке", price: "от 3 218 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Устройство коробов из ГКЛ на потолке с нишей для скрытой подсветки", price: "от 3 683 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Устройство потолков из ГКЛ в 2 уровня прямолинейного", price: "от 3 975 ₽/м²", amount: "1", unit: "м²" }, + { name: "Устройство потолков из ГКЛ в 2 уровня криволинейного", price: "от 4 860 ₽/м²", amount: "1", unit: "м²" }, + { name: "Монтаж ниши для парящего потолка из гипсокартона", price: "от 3 683 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Монтаж полки из ГКЛ для скрытой подсветки", price: "от 2 468 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Монтаж ниши из ГКЛ для скрытого карниза", price: "от 1 853 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Устройство закладной на потолке", price: "от 300 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Устройство криволинейных коробов из ГКЛ на потолке", price: "от 4 215 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Установка уголков перфорированных арочных", price: "от 338 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Установка уголков перфорированных", price: "от 263 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Проклейка швов ГКЛ серпянкой/малярной лентой", price: "от 188 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Поклейка стеклохолста(потолок)", price: "от 428 ₽/м²", amount: "1", unit: "м²" }, + { name: "Монтаж малярной сетки / серпянки", price: "от 188 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Сплошная шпаклевка в один слой", price: "от 563 ₽/м²", amount: "1", unit: "м²" }, + { name: "Базовая шпаклевка (с ошкуриванием)", price: "от 645 ₽/м²", amount: "1", unit: "м²" }, + { name: "Шпаклевка потолков в два слоя под обои", price: "от 786 ₽/м²", amount: "1", unit: "м²" }, + { name: "Шпаклевка потолков под покраску", price: "от 1 500 ₽/м²", amount: "1", unit: "м²" }, + { name: "Шпаклевка ГКЛ под оклейку обоями (с ошкуриванием)", price: "от 900 ₽/м²", amount: "1", unit: "м²" }, + { name: "Шпаклевка ГКЛ под окраску (2 слоя с ошкуриванием)", price: "от 1 800 ₽/м²", amount: "1", unit: "м²" }, + { name: "Малярные работы по нише для закарнизной конструкции (штукатурка, грунтовка, шпаклевка, окраска", price: "от 1 500 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Шпаклевка криволинейного участка шириной до 250 мм", price: "от 1 011 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Шпаклевка коробов, ниш", price: "от 828 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Шпаклевка полок для скрытой подсветки", price: "от 602 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Оклеивание потолков флизелином под окраску", price: "от 593 ₽/м²", amount: "1", unit: "м²" }, + { name: "Ошкуривание поверхности потолка", price: "от 263 ₽/м²", amount: "1", unit: "м²" }, + { name: "Монтаж фальш балки на потолок", price: "от 1 431 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Грунтовка коробов из ГКЛ", price: "от 83 ₽/пог.м", amount: "1", unit: "пог.м" } + ] + } + ] + }, + { + title: "Чистовые отделочные работы", + children: [ + { + title: "Полы", + items: [ + { name: "Облицовка плиткой пола", price: "от 2 588 ₽/м²", amount: "1", unit: "м²" }, + { name: "Облицовка плиткой гексагон", price: "от 3 518 ₽/м²", amount: "1", unit: "м²" }, + { name: "Подрезка керамической плитки", price: "от 423 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Облицовка плиткой пола с декоративными вставками", price: "от 2 784 ₽/м²", amount: "1", unit: "м²" }, + { name: "Облицовка плиткой пола по диагонали", price: "от 3 488 ₽/м²", amount: "1", unit: "м²" }, + { name: "Облицовка плиткой пола елочкой", price: "от 4 266 ₽/м²", amount: "1", unit: "м²" }, + { name: "Облицовка плиткой пола \"панно\"", price: "от 3 831 ₽/м²", amount: "1", unit: "м²" }, + { name: "Облицовка мозаикой, мелкой плиткой (с затиркой)", price: "от 4 283 ₽/м²", amount: "1", unit: "м²" }, + { name: "Облицовка плиточным бордюром (с затиркой)", price: "от 1 281 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Облицовка плиткой менее 300х300", price: "от 3 218 ₽/м²", amount: "1", unit: "м²" }, + { name: "Облицовка пола разноразмерной плиткой", price: "от 3 159 ₽/м²", amount: "1", unit: "м²" }, + { name: "Облицовка керамогранитом по диагонали", price: "от 3 986 ₽/м²", amount: "1", unit: "м²" }, + { name: "Облицовка керамогранитом до 600 мм по одной стороне", price: "от 3 459 ₽/м²", amount: "1", unit: "м²" }, + { name: "Облицовка керамогранитом до 1200 мм по одной стороне", price: "от 4 838 ₽/м²", amount: "1", unit: "м²" }, + { name: "Облицовка Крупноформатным керамогранитом", price: "от 9 915 ₽/м²", amount: "1", unit: "м²" }, + { name: "Подрезка керамогранита до 600 мм", price: "от 782 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Подрезка керамогранита до 1200 мм", price: "от 1 359 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Подрезка керамогранита крупноформатного", price: "от 2 469 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Укладка плитки с разуклонкой в подиуме в строительном исполнении", price: "от 6 000 ₽/м²", amount: "1", unit: "м²" }, + { name: "Устройство плинтуса из плитки", price: "от 1 380 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Установка порожков из плитки", price: "от 2 775 ₽/шт", amount: "1", unit: "шт" }, + { name: "Облицовка плиткой ступеней (с подрезкой и затиркой)", price: "от 3 161 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Затирка плитки", price: "от 294 ₽/м²", amount: "1", unit: "м²" }, + { name: "Затирка плитки эпоксидной затиркой", price: "от 1 301 ₽/м²", amount: "1", unit: "м²" }, + { name: "Укладка ламината", price: "от 714 ₽/м²", amount: "1", unit: "м²" }, + { name: "Укладка ламината и подложки с проклейкой стыков клеем", price: "от 884 ₽/м²", amount: "1", unit: "м²" }, + { name: "Укладка ламината и подложки по диагонали", price: "от 1 338 ₽/м²", amount: "1", unit: "м²" }, + { name: "Укладка нелакированного модульного паркета на фанеру", price: "от 3 315 ₽/м²", amount: "1", unit: "м²" }, + { name: "Укладка инженерной доски", price: "от 3 183 ₽/м²", amount: "1", unit: "м²" }, + { name: "Укладка паркетной доски", price: "от 2 241 ₽/м²", amount: "1", unit: "м²" }, + { name: "Укладка паркетной доски (на клей)", price: "от 2 691 ₽/м²", amount: "1", unit: "м²" }, + { name: "Укладка паркетной доски по диагонали", price: "от 2 796 ₽/м²", amount: "1", unit: "м²" }, + { name: "Укладка массивной доски", price: "от 2 760 ₽/м²", amount: "1", unit: "м²" }, + { name: "Укладка замкового пробкового пола", price: "от 740 ₽/м²", amount: "1", unit: "м²" }, + { name: "Укладка кварцвиниловой плитки", price: "от 818 ₽/м²", amount: "1", unit: "м²" }, + { name: "Укладка кварцвиниловой плитки на клей", price: "от 1 145 ₽/м²", amount: "1", unit: "м²" }, + { name: "Укладка кварцвинила клеевого елочкой", price: "от 2 700 ₽/м²", amount: "1", unit: "м²" }, + { name: "Укладка кварцвинила замкового по диагонали", price: "от 1 500 ₽/м²", amount: "1", unit: "м²" }, + { name: "Настил линолеума коммерческого", price: "от 467 ₽/м²", amount: "1", unit: "м²" }, + { name: "Настил линолеума бытового , на клей", price: "от 570 ₽/м²", amount: "1", unit: "м²" }, + { name: "Настил линолеума бытового, ковролина", price: "от 440 ₽/м²", amount: "1", unit: "м²" }, + { name: "Устройство ковровых покрытий плиточных", price: "от 833 ₽/м²", amount: "1", unit: "м²" }, + { name: "Установка пробкового компенсатора", price: "от 702 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Монтаж плинтусов пластиковых", price: "от 368 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Монтаж скрытого плинтуса/теневого профиля", price: "от 2 025 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Штробление под плинтус/профиль скрытого монтажа", price: "от 350 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Монтаж микроплинтуса", price: "от 2 028 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Установка плинтусов деревянных, МДФ", price: "от 675 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Установка плинтусов из дюрополимера и полиуриетана", price: "от 825 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Запил углов плинтуса", price: "от 216 ₽/шт", amount: "1", unit: "шт" }, + { name: "Покраска напольного плинтуса", price: "от 353 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Установка порожков", price: "от 923 ₽/шт", amount: "1", unit: "шт" }, + { name: "Добавляется на установку плинтусов к криволинейным стенам", price: "от 297 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Устройство плинтуса из плитки", price: "от 987 ₽/пог.м", amount: "1", unit: "пог.м" } + ] + }, + { + title: "Стены", + items: [ + { name: "Облицовка плиткой стен до 600 мм по одной стороне", price: "от 3 000 ₽/м²", amount: "1", unit: "м²" }, + { name: "Облицовка плиткой гексагон", price: "от 3 698 ₽/м²", amount: "1", unit: "м²" }, + { name: "Облицовка плиткой стен, с декоративными вставками", price: "от 3 104 ₽/м²", amount: "1", unit: "м²" }, + { name: "Облицовка плиткой стен по диагонали", price: "от 3 540 ₽/м²", amount: "1", unit: "м²" }, + { name: "Облицовка плиткой стен по диагонали, со вставками", price: "от 4 650 ₽/м²", amount: "1", unit: "м²" }, + { name: "Облицовка плиткой стен менее 200х200 мм", price: "от 3 750 ₽/м²", amount: "1", unit: "м²" }, + { name: "Облицовка стен мелкой плиткой на подложке (с затиркой)", price: "от 4 200 ₽/м²", amount: "1", unit: "м²" }, + { name: "Облицовка стен мелкой плиткой, мозаикой (с затиркой)", price: "от 4 806 ₽/м²", amount: "1", unit: "м²" }, + { name: "Подрезка керамической плитки", price: "от 675 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Подрезка резного края плитки под 45(стены)", price: "от 2 168 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Облицовка стен плиткой панно*", price: "от 7 425 ₽/м²", amount: "1", unit: "м²" }, + { name: "Облицовка стен композитными материалами (ЛОФТ)", price: "от 4 005 ₽/м²", amount: "1", unit: "м²" }, + { name: "Устройство объемного керамического бордюра с подрезкой в углах", price: "от 1 530 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Устройство бордюра из плитки (с затиркой)", price: "от 1 011 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Облицовка плиткой \"фартук\" (с затиркой и подрезкой)", price: "от 4 680 ₽/м²", amount: "1", unit: "м²" }, + { name: "Облицовка керамогранитом \"фартук\" (с затиркой и подрезкой)", price: "от 5 475 ₽/м²", amount: "1", unit: "м²" }, + { name: "Фартук из декоративного камня", price: "от 5 850 ₽/м²", amount: "1", unit: "м²" }, + { name: "Облицовка клинкерным кирпичом с затиркой", price: "от 4 200 ₽/м²", amount: "1", unit: "м²" }, + { name: "Облицовка Крупноформатным керамогранитом", price: "от 10 800 ₽/м²", amount: "1", unit: "м²" }, + { name: "Облицовка стен керамогранитом до 1200 мм по одной стороне", price: "от 5 166 ₽/м²", amount: "1", unit: "м²" }, + { name: "Облицовка стен керамогранитом до 600 мм по одной стороне", price: "от 2 570 ₽/м²", amount: "1", unit: "м²" }, + { name: "Подрезка керамогранита крупноформатного до 1200 мм", price: "от 1 359 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Подрезка керамогранита крупноформатного", price: "от 2 471 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Подрезка керамогранита крупноформатного под 45 гр.", price: "от 4 286 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Подрезка керамогранита 600", price: "от 782 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Высверливание коронкой отверстия в керамической плитке", price: "от 609 ₽/шт", amount: "1", unit: "шт" }, + { name: "Высверливание отверстий в керамограните", price: "от 668 ₽/шт", amount: "1", unit: "шт" }, + { name: "Облицовка стен декоративным камнем (с затиркой)", price: "от 3 519 ₽/м²", amount: "1", unit: "м²" }, + { name: "Затирка плитки(стены)", price: "от 330 ₽/м²", amount: "1", unit: "м²" }, + { name: "Затирка плитки эпоксидной затиркой", price: "от 1 301 ₽/м²", amount: "1", unit: "м²" }, + { name: "Затирка швов в керамической плитке нестандартного размера (10*10, рзноразмерной плитке , мраморной плитке)", price: "от 750 ₽/м²", amount: "1", unit: "м²" }, + { name: "Вставки из стеклоблоков поштучно в ниши, стены", price: "от 867 ₽/шт", amount: "1", unit: "шт" }, + { name: "Укладка стеклоблоков ( устройство перегородок, стен)", price: "от 5 928 ₽/м²", amount: "1", unit: "м²" }, + { name: "Установка углового профиля (для плитки)", price: "от 294 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Облицовка стеновыми ПВХ панелями (до 10м2)", price: "от 1 260 ₽/м²", amount: "1", unit: "м²" }, + { name: "Облицовка стеновыми ПВХ панелями (свыше 10м2)", price: "от 879 ₽/м²", amount: "1", unit: "м²" }, + { name: "Монтаж стеновых ПВХ панелей с устройством каркаса", price: "от 1 739 ₽/м²", amount: "1", unit: "м²" }, + { name: "Монтаж стеновых 3D панелей на клей", price: "от 3 368 ₽/м²", amount: "1", unit: "м²" }, + { name: "Монтаж молдингов на стену", price: "от 630 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Запил молдингов под 45 градусов", price: "от 81 ₽/шт", amount: "1", unit: "шт" }, + { name: "Монтаж ламината на клей", price: "от 1 868 ₽/м²", amount: "1", unit: "м²" }, + { name: "Облицовка стеновыми панелями (с устройством каркаса)", price: "от 1 761 ₽/м²", amount: "1", unit: "м²" }, + { name: "Облицовка стен деревянной вагонкой с устройством каркаса", price: "от 3 090 ₽/м²", amount: "1", unit: "м²" }, + { name: "Покрытие деревянных поверхностей лаком или тонирующим составом в 1 слой", price: "от 297 ₽/м²", amount: "1", unit: "м²" }, + { name: "Оклейка стен стеклянными/зеркальными элементами (размер менее 20*20)", price: "от 3 090 ₽/м²", amount: "1", unit: "м²" }, + { name: "Оклейка стен стеклянными/зеркальными элементами (размер более 20*20)", price: "от 2 520 ₽/м²", amount: "1", unit: "м²" }, + { name: "Декоративная штукатурка", price: "от 2 280 ₽/м²", amount: "1", unit: "м²" }, + { name: "Фактурная штукатурка", price: "от 981 ₽/м²", amount: "1", unit: "м²" }, + { name: "Венецианская штукатурка", price: "от 3 953 ₽/м²", amount: "1", unit: "м²" }, + { name: "Нанесение жидких обоев", price: "от 1 170 ₽/м²", amount: "1", unit: "м²" }, + { name: "Оклейка стен обоями шириной менее 700мм", price: "от 728 ₽/м²", amount: "1", unit: "м²" }, + { name: "Оклейка стен обоями без подбора", price: "от 720 ₽/м²", amount: "1", unit: "м²" }, + { name: "Оклейка стен обоями с подбором", price: "от 810 ₽/м²", amount: "1", unit: "м²" }, + { name: "Оклейка стен текстильными обоями", price: "от 939 ₽/м²", amount: "1", unit: "м²" }, + { name: "Оклейка стен обоями под окраску", price: "от 630 ₽/м²", amount: "1", unit: "м²" }, + { name: "Оклейка стен флизелиновым холстом (110,130,150 г/м2)", price: "от 465 ₽/м²", amount: "1", unit: "м²" }, + { name: "Оклеивание стен обоями шелкография", price: "от 1 413 ₽/м²", amount: "1", unit: "м²" }, + { name: "Поклейка обойного бордюра", price: "от 362 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Поклейка фотообоев/бумажных обоев", price: "от 3 123 ₽/м²", amount: "1", unit: "м²" }, + { name: "Оклеивание стен шелковыми обоями VIP класса, обоями типа соломка, пробковым покрытием", price: "от 4 296 ₽/м²", amount: "1", unit: "м²" }, + { name: "Оклеивание стены фреской/панно", price: "от 3 150 ₽/м²", amount: "1", unit: "м²" }, + { name: "Окраска молдингов стены", price: "от 368 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Окраска кирпичной /бетонной стены( стиль лофт)", price: "от 672 ₽/м²", amount: "1", unit: "м²" }, + { name: "Окраска стен в 2 слоя", price: "от 518 ₽/м²", amount: "1", unit: "м²" }, + { name: "Окраска стен (цветная) до 3х цветов", price: "от 953 ₽/м²", amount: "1", unit: "м²" }, + { name: "Окраска обоев в 2 слоя", price: "от 413 ₽/м²", amount: "1", unit: "м²" }, + { name: "Установка люка \"невидимка\"", price: "от 4 149 ₽/шт", amount: "1", unit: "шт" }, + { name: "Установка люка (металл/ПВХ)", price: "от 1 113 ₽/шт", amount: "1", unit: "шт" }, + { name: "Установка пластикового декоративного уголка", price: "от 297 ₽/пог.м", amount: "1", unit: "пог.м" } + ] + }, + { + title: "Откосы", + items: [ + { name: "Окраска откосов, углов", price: "от 384 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Оклейка откосов обоями", price: "от 390 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Установка ПВХ уголка", price: "от 218 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Устройство откосов из ПВХ панелей, сэндвич-панелей", price: "от 1 103 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Облицовка откосов плиткой", price: "от 2 018 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Облицовка откосов плиткой (торец 45 град, дополнительно)", price: "от 3 368 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Подрезка резного края плитки под 45 гр.", price: "от 2 168 ₽/пог.м", amount: "1", unit: "пог.м" } + ] + }, + { + title: "Потолки", + items: [ + { name: "Окраска потолка в 2 слоя", price: "от 518 ₽/м²", amount: "1", unit: "м²" }, + { name: "Декоративная штукатурка на потолке", price: "от 2 618 ₽/м²", amount: "1", unit: "м²" }, + { name: "Установка плинтуса потолочного (полиуритан)", price: "от 966 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Установка потолочных розеток декоративных", price: "от 1 155 ₽/шт", amount: "1", unit: "шт" }, + { name: "Установка потолочного плинтуса дюрополимер/полиуретан до 70 мм", price: "от 795 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Установка потолочного плинтуса дюрополимер/полиуретан более 70 мм", price: "от 1 395 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Монтаж плинтуса потолочного (полиуретан, с подбором рисунка)", price: "от 1 248 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Окраска потолочного плинтуса", price: "от 368 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Поклейка обоев на потолок", price: "от 663 ₽/м²", amount: "1", unit: "м²" }, + { name: "Отделка готовых потолков типа \"Венецианская штукатурка\"", price: "от 3 831 ₽/м²", amount: "1", unit: "м²" }, + { name: "Устройство потолков из ПВХ панелей до 10м2", price: "от 1 152 ₽/м²", amount: "1", unit: "м²" }, + { name: "Устройство потолков из ПВХ панелей свыше 10м2", price: "от 1 347 ₽/м²", amount: "1", unit: "м²" }, + { name: "Устройство потолков реечных свыше до 10м2", price: "от 1 454 ₽/м²", amount: "1", unit: "м²" }, + { name: "Обшивка потолка деревянной вагонкой с устройством каркаса", price: "от 3 212 ₽/м²", amount: "1", unit: "м²" }, + { name: "Поклейка панелей потолочных", price: "от 576 ₽/м²", amount: "1", unit: "м²" }, + { name: "Устройство кассетных подвесных потолков (\"Армстронг\")", price: "от 1 133 ₽/м²", amount: "1", unit: "м²" }, + { name: "Сверление отверстия в ГКЛ потолке", price: "от 189 ₽/шт", amount: "1", unit: "шт" }, + { name: "Сверление отверстия в реечном потолке", price: "от 270 ₽/шт", amount: "1", unit: "шт" } + ] + }, + { + title: "Двери, окна", + items: [ + { name: "Окраска окна", price: "от 1 268 ₽/м²", amount: "1", unit: "м²" }, + { name: "Установка подоконников", price: "от 1 413 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Установка каменного подоконника (с подготовкой поверхности)", price: "от 3 840 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Устройство подоконника из керамогранита (включая подготовку поверхности)", price: "от 4 650 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Установка отливов", price: "от 1 187 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Окраска подоконников", price: "от 495 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Подрез деревянной двери", price: "от 780 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Окраска двери", price: "от 1 581 ₽/м²", amount: "1", unit: "м²" }, + { name: "Установка арки в проем", price: "от 4 994 ₽/шт", amount: "1", unit: "шт" }, + { name: "Окраска оконных проемов с подготовкой", price: "от 2 225 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Установка антресольных дверок", price: "от 1 187 ₽/шт", amount: "1", unit: "шт" }, + { name: "Монтаж дверного ограничителя", price: "от 338 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Окраска наличников", price: "от 368 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Установка межкомнатной двери под ключ", price: "от 7 500 ₽/шт", amount: "1", unit: "шт" }, + { name: "Шпаклевка дверей (с ошкуриванием)", price: "от 968 ₽/м²", amount: "1", unit: "м²" }, + { name: "Замуровать окна (между с/у и кухней)", price: "от 3 161 ₽/шт", amount: "1", unit: "шт" } + ] + }, + { + title: "Прочие", + items: [ + { name: "Скрытый карниз для парящих штор", price: "от 2 496 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Установка карнизов", price: "от 818 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Монтаж электрокарниза", price: "от 3 078 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Монтаж антресоли", price: "от 2 964 ₽/м²", amount: "1", unit: "м²" }, + { name: "Монтаж ЖК телевизора на стену", price: "от 3 951 ₽/шт", amount: "1", unit: "шт" }, + { name: "Установка рольставней", price: "от 4 392 ₽/шт", amount: "1", unit: "шт" }, + { name: "Установка полок", price: "от 1 053 ₽/шт", amount: "1", unit: "шт" }, + { name: "Установка зеркал", price: "от 1 685 ₽/шт", amount: "1", unit: "шт" }, + { name: "Установка экрана для радиатора из МДФ (без изготовления)", price: "от 972 ₽/шт", amount: "1", unit: "шт" } + ] + } + ] + }, + { + title: "Электромонтажные работы", + items: [ + { name: "Штробление кирпичных/гипсолитовых/пеноблочных стен под элкабель", price: "от 450 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Штробление бетонных стен под элкабель", price: "от 780 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Заделка штроб", price: "от 108 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Высверливание чаши под подрозетник в бетоне", price: "от 780 ₽/шт", amount: "1", unit: "шт" }, + { name: "Высверливание чаши под подрозетник в гипсолите/Пеноблоке", price: "от 420 ₽/шт", amount: "1", unit: "шт" }, + { name: "Высверливание отверстия под подрозетник в ГКЛ", price: "от 381 ₽/шт", amount: "1", unit: "шт" }, + { name: "Высверливание чаши под подрозетник в кирпиче", price: "от 668 ₽/шт", amount: "1", unit: "шт" }, + { name: "Установка подрозетника", price: "от 186 ₽/шт", amount: "1", unit: "шт" }, + { name: "Установка механизма, панели электроточки", price: "от 638 ₽/шт", amount: "1", unit: "шт" }, + { name: "Установка электроточки накладной", price: "от 488 ₽/шт", amount: "1", unit: "шт" }, + { name: "Установка диммера", price: "от 983 ₽/шт", amount: "1", unit: "шт" }, + { name: "Установка перекрестного выключателя", price: "от 1 028 ₽/шт", amount: "1", unit: "шт" }, + { name: "Установка проходного выключателя", price: "от 713 ₽/шт", amount: "1", unit: "шт" }, + { name: "Установка проходного 2х клавишного выключателя", price: "от 912 ₽/шт", amount: "1", unit: "шт" }, + { name: "Установка проходного 3х клавишного выключателя", price: "от 954 ₽/шт", amount: "1", unit: "шт" }, + { name: "Установка электроточки в ГКЛ", price: "от 612 ₽/шт", amount: "1", unit: "шт" }, + { name: "Установка 3х фазной розетки накладной", price: "от 702 ₽/шт", amount: "1", unit: "шт" }, + { name: "Установка светильника трекового", price: "от 518 ₽/шт", amount: "1", unit: "шт" }, + { name: "Установка 3х фазной розетки внутренней установки", price: "от 1 053 ₽/шт", amount: "1", unit: "шт" }, + { name: "Монтаж встраиваемого линейного освещения", price: "от 5 961 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Установка трекового освещения (без монтажа осветительных приборов)", price: "от 498 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Установка люстры (без сборки)", price: "от 2 168 ₽/шт", amount: "1", unit: "шт" }, + { name: "Установка встраиваемых светильников под окраску", price: "от 2 670 ₽/шт", amount: "1", unit: "шт" }, + { name: "Установка светильников точечных", price: "от 450 ₽/шт", amount: "1", unit: "шт" }, + { name: "Установка светильника настенного,бра", price: "от 956 ₽/шт", amount: "1", unit: "шт" }, + { name: "Установка светодиодной ленты", price: "от 563 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Установка светодиодной ленты в алюминиевом коробе", price: "от 623 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Прокладка кабеля в гофре", price: "от 570 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Прокладка кабеля", price: "от 375 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Прокладка кабеля телефонного/интернет UTP", price: "от 165 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Установка кабель-канала", price: "от 180 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Монтаж кабельного лотка металл", price: "от 261 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Установка контроллера для светодиодной ленты", price: "от 1 757 ₽/шт", amount: "1", unit: "шт" }, + { name: "Установка автоматического выключателя", price: "от 791 ₽/шт", amount: "1", unit: "шт" }, + { name: "Установка дифф автоматического выключателя", price: "от 1 218 ₽/шт", amount: "1", unit: "шт" }, + { name: "Установка УЗО", price: "от 1 389 ₽/шт", amount: "1", unit: "шт" }, + { name: "Монтаж элщита накладного", price: "от 2 636 ₽/шт", amount: "1", unit: "шт" }, + { name: "Подключение к вводному щиту", price: "от 2 265 ₽/шт", amount: "1", unit: "шт" }, + { name: "Соединение медных проводов пайкой+термоусадка", price: "от 510 ₽/шт", amount: "1", unit: "шт" }, + { name: "Наращивание кабеля (спайка + терм усадка)", price: "от 510 ₽/шт", amount: "1", unit: "шт" }, + { name: "Установка распределительной коробки накладной", price: "от 855 ₽/шт", amount: "1", unit: "шт" }, + { name: "Монтаж элщита до 6 автоматов кирпичной стене (ПГП,пеноблок)", price: "от 2 847 ₽/шт", amount: "1", unit: "шт" }, + { name: "Монтаж элщита до 16 автоматов в кирпичной стене (ПГП,пеноблок)", price: "от 3 618 ₽/шт", amount: "1", unit: "шт" }, + { name: "Монтаж элщита до 24 автоматов в кирпичной стене (ПГП,пеноблок)", price: "от 4 392 ₽/шт", amount: "1", unit: "шт" }, + { name: "Монтаж элщита свыше 24 автоматов в кирпичной стене (ПГП,пеноблок)", price: "от 8 700 ₽/шт", amount: "1", unit: "шт" }, + { name: "Монтаж элщита до 6 автоматов в бетонной стене", price: "от 4 317 ₽/шт", amount: "1", unit: "шт" }, + { name: "Монтаж элщита до 12 автоматов в бетонной стене", price: "от 5 013 ₽/шт", amount: "1", unit: "шт" }, + { name: "Монтаж элщита до 18 автоматов в бетонной стене", price: "от 5 718 ₽/шт", amount: "1", unit: "шт" }, + { name: "Монтаж элщита до 24 автоматов в бетонной стене", price: "от 7 674 ₽/шт", amount: "1", unit: "шт" }, + { name: "Монтаж элщита свыше 24 автоматов в бетонной стене", price: "от 15 300 ₽/шт", amount: "1", unit: "шт" }, + { name: "Комплексное расключение слаботочного щита", price: "от 2 310 ₽/шт", amount: "1", unit: "шт" }, + { name: "Прозвонка электрики (точка)", price: "от 249 ₽/шт", amount: "1", unit: "шт" }, + { name: "Установка вентилятора", price: "от 942 ₽/шт", amount: "1", unit: "шт" }, + { name: "Установка конвектора электрического", price: "от 1 779 ₽/шт", amount: "1", unit: "шт" }, + { name: "Установка звонка", price: "от 624 ₽/шт", amount: "1", unit: "шт" }, + { name: "Установка домофона (на старое место)", price: "от 890 ₽/шт", amount: "1", unit: "шт" }, + { name: "Устройство инфракрасного теплого пола", price: "от 1 404 ₽/м²", amount: "1", unit: "м²" }, + { name: "Установка терморегулятора под теплый пол", price: "от 1 758 ₽/шт", amount: "1", unit: "шт" }, + { name: "Устройство теплого пола из карбоновых матов (кабель)", price: "от 1 581 ₽/м²", amount: "1", unit: "м²" }, + { name: "Составление однолинейной схемы до 16 модулей", price: "от 7 500 ₽/шт", amount: "1", unit: "шт" }, + { name: "Составление однолинейной схемы от 16 до 24 модулей", price: "от 15 000 ₽/шт", amount: "1", unit: "шт" }, + { name: "Составление однолинейной схемы от 24 до 36 модулей", price: "от 22 500 ₽/шт", amount: "1", unit: "шт" } + ] + }, + { + title: "Сантехнические работы", + items: [ + { name: "Снятие и установка радиатора, без замены, для проведения работ по стенам при наличие шаровых кранов", price: "от 3 990 ₽/шт", amount: "1", unit: "шт" }, + { name: "Монтаж радиатора на готовое подключение", price: "от 4 868 ₽/шт", amount: "1", unit: "шт" }, + { name: "Монтаж вертикального радиатора с переносом труб ввода/вывода воды на стену", price: "от 9 450 ₽/шт", amount: "1", unit: "шт" }, + { name: "Перенос вывода/ввода воды радиатора на стену", price: "от 5 550 ₽/шт", amount: "1", unit: "шт" }, + { name: "Монтаж конвекторов напольных", price: "от 5 883 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Штробление кирпичных стен до 7 см", price: "от 1 448 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Штробление бетонных стен до 7 см", price: "от 2 768 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Штробление стен для прокладки труб ГВС и ХВС", price: "от 780 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Заделка сантехнических штроб", price: "от 177 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Пробивка отверстия в гипсолитовой стене ф50-110мм до 100 мм в толщине", price: "от 990 ₽/шт", amount: "1", unit: "шт" }, + { name: "Пробивка отверстия в бетонной стене ф50--110мм до 100 мм толщины стен", price: "от 3 233 ₽/шт", amount: "1", unit: "шт" }, + { name: "Пробивка отверстия в кирпичной стене ф50-110 мм до 100 мм в толщине", price: "от 1 673 ₽/шт", amount: "1", unit: "шт" }, + { name: "Утепление труб канализации/водоснабжения/теплоизоляция", price: "от 147 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Устройство шумоизоляции труб стояка", price: "от 1 470 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Устройство сан тех - шкафа (ГКЛ , Пеноблок)", price: "от 2 070 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Установка ванны", price: "от 5 451 ₽/шт", amount: "1", unit: "шт" }, + { name: "Установка ванны (\"джакузи\")", price: "от 7 314 ₽/шт", amount: "1", unit: "шт" }, + { name: "Установка ванны (чугун, камень)", price: "от 7 964 ₽/шт", amount: "1", unit: "шт" }, + { name: "Установка ванны стоимостью свыше 100000р", price: "от 15 900 ₽/шт", amount: "1", unit: "шт" }, + { name: "Монтаж стеклянных шторок на ванну", price: "от 5 618 ₽/шт", amount: "1", unit: "шт" }, + { name: "Монтаж пластиковых шторок на ванну", price: "от 2 898 ₽/шт", amount: "1", unit: "шт" }, + { name: "Установка душевой кабины ( размером 80*80)", price: "от 5 670 ₽/шт", amount: "1", unit: "шт" }, + { name: "Устройства поддона для душевой кабины", price: "от 5 138 ₽/шт", amount: "1", unit: "шт" }, + { name: "Устройство подиума (кладка, стяжка, гидроизоляция)", price: "от 20 760 ₽/шт", amount: "1", unit: "шт" }, + { name: "Монтаж стеклянных шторок для душевого поддона", price: "от 10 320 ₽/шт", amount: "1", unit: "шт" }, + { name: "Установка сливного трапа", price: "от 2 435 ₽/шт", amount: "1", unit: "шт" }, + { name: "Устройства подиума с обшивкой фанерой до 10 см прямолинейного", price: "от 10 419 ₽/шт", amount: "1", unit: "шт" }, + { name: "Устройство подиума с установкой поддона", price: "от 6 279 ₽/шт", amount: "1", unit: "шт" }, + { name: "Установка смесителей/гигиенического душа", price: "от 1 868 ₽/шт", amount: "1", unit: "шт" }, + { name: "Установка встраиваемого смесителя/гигиенического душа", price: "от 5 183 ₽/шт", amount: "1", unit: "шт" }, + { name: "Установка тропического душа", price: "от 3 204 ₽/шт", amount: "1", unit: "шт" }, + { name: "Установка душевой штанги", price: "от 1 418 ₽/шт", amount: "1", unit: "шт" }, + { name: "Установка сифона", price: "от 828 ₽/шт", amount: "1", unit: "шт" }, + { name: "Прокладка труб водоснабжения", price: "от 243 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Прокладка труб канализации", price: "от 743 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Монтаж заглушек на трубопроводе/канализации", price: "от 108 ₽/шт", amount: "1", unit: "шт" }, + { name: "Установка кухонной мойки", price: "от 1 656 ₽/шт", amount: "1", unit: "шт" }, + { name: "Сборка коллектора до 5 выходов", price: "от 3 105 ₽/шт", amount: "1", unit: "шт" }, + { name: "Сборка коллектора до 10 выходов", price: "от 5 175 ₽/шт", amount: "1", unit: "шт" }, + { name: "Сборка коллектора свыше 10 выходов", price: "от 6 210 ₽/шт", amount: "1", unit: "шт" }, + { name: "Установка раковины стоимостью свыше 50000р", price: "от 5 483 ₽/шт", amount: "1", unit: "шт" }, + { name: "Установка раковины", price: "от 2 318 ₽/шт", amount: "1", unit: "шт" }, + { name: "Монтаж фильтра для воды под мойку", price: "от 1 677 ₽/шт", amount: "1", unit: "шт" }, + { name: "Установка фильтра грубой очистки (косой)", price: "от 1 118 ₽/шт", amount: "1", unit: "шт" }, + { name: "Установка магистрального фильтра", price: "от 810 ₽/шт", amount: "1", unit: "шт" }, + { name: "Установка самопромывного фильтра тонкой очистки", price: "от 2 889 ₽/шт", amount: "1", unit: "шт" }, + { name: "Установка манометра", price: "от 300 ₽/шт", amount: "1", unit: "шт" }, + { name: "Установка регулятора давления", price: "от 1 070 ₽/шт", amount: "1", unit: "шт" }, + { name: "Установка компенсатора гидроудара", price: "от 705 ₽/шт", amount: "1", unit: "шт" }, + { name: "Установка полотенцесушителя", price: "от 4 883 ₽/шт", amount: "1", unit: "шт" }, + { name: "Установка полотенцесушителя( электрического)", price: "от 2 018 ₽/шт", amount: "1", unit: "шт" }, + { name: "Монтаж водосчетчика без пломбировки", price: "от 1 869 ₽/шт", amount: "1", unit: "шт" }, + { name: "Установка и подключение стиральной машины", price: "от 2 574 ₽/шт", amount: "1", unit: "шт" }, + { name: "Установка водонагревателя до 100л", price: "от 4 380 ₽/шт", amount: "1", unit: "шт" }, + { name: "Установка водонагревателя свыше 100л", price: "от 6 300 ₽/шт", amount: "1", unit: "шт" }, + { name: "Монтаж проточного водонагревателя", price: "от 2 400 ₽/шт", amount: "1", unit: "шт" }, + { name: "Монтаж экрана под ванну (пластик)", price: "от 2 139 ₽/шт", amount: "1", unit: "шт" }, + { name: "Монтаж экрана под ванну (каркас для плитки)", price: "от 3 533 ₽/шт", amount: "1", unit: "шт" }, + { name: "Замена вводных кранов", price: "от 1 283 ₽/шт", amount: "1", unit: "шт" }, + { name: "Установка системы защиты от протечек воды \"Аквасторож\"", price: "от 7 377 ₽/шт", amount: "1", unit: "шт" }, + { name: "Пайка пропиленового фитинга ( угол, муфта, переход, тройник)", price: "от 201 ₽/шт", amount: "1", unit: "шт" }, + { name: "Установка латунного фитинга резьбового", price: "от 345 ₽/шт", amount: "1", unit: "шт" }, + { name: "Установка латунного фитинга резьбового/уголков металлических/сгонов/ниппелей/бочонка/удлинителя и пр.", price: "от 345 ₽/шт", amount: "1", unit: "шт" }, + { name: "Установка шарового крана/американки", price: "от 732 ₽/шт", amount: "1", unit: "шт" }, + { name: "Установка точки вентиляции", price: "от 702 ₽/шт", amount: "1", unit: "шт" }, + { name: "Устройство вентиляционных каналов из гофрированных труб", price: "от 323 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Устройство вентиляционных каналов из стальных оцинкованных труб", price: "от 873 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Устройство соединений при врезке в существующий вентканал", price: "от 258 ₽/шт", amount: "1", unit: "шт" }, + { name: "Установка Вентрешёток", price: "от 414 ₽/шт", amount: "1", unit: "шт" }, + { name: "Установка коллекторного щита накладного", price: "от 2 745 ₽/шт", amount: "1", unit: "шт" }, + { name: "Снятие-установка радиатора на существующие места (с заменой радиатора)", price: "от 2 745 ₽/шт", amount: "1", unit: "шт" }, + { name: "Установка системы сололифт", price: "от 8 058 ₽/шт", amount: "1", unit: "шт" }, + { name: "Установка кухонного фильтра очистки воды", price: "от 4 520 ₽/шт", amount: "1", unit: "шт" }, + { name: "Установка измельчителя отходов", price: "от 2 712 ₽/шт", amount: "1", unit: "шт" }, + { name: "Комплексная разводка труб водоснабжения (1 точка)", price: "от 2 918 ₽/шт", amount: "1", unit: "шт" }, + { name: "Комплексная разводка труб канализации (1 точка)", price: "от 2 781 ₽/шт", amount: "1", unit: "шт" }, + { name: "Установка точки водоснабжения (водорозетки)", price: "от 939 ₽/шт", amount: "1", unit: "шт" }, + { name: "Комплексная разводка труб водоснабжения из сшитого полиэтилена \"Рехау\" до 10м (1 точка)", price: "от 3 353 ₽/шт", amount: "1", unit: "шт" }, + { name: "Комплексная разводка труб водоснабжения из сшитого полиэтилена \"Рехау\" свыше 10м (1 точка)", price: "от 4 500 ₽/шт", amount: "1", unit: "шт" }, + { name: "Установка точки водоснабжения (водорозетки) \"Рехау\"", price: "от 1 503 ₽/шт", amount: "1", unit: "шт" }, + { name: "Монтаж водяных теплых полов до 5 м кв.", price: "от 2 574 ₽/шт", amount: "1", unit: "шт" }, + { name: "Монтаж водяных теплых полов более 5 м кв.", price: "от 1 656 ₽/шт", amount: "1", unit: "шт" }, + { name: "Комплексная прокладка воздуховодов до 3-х метров", price: "от 2 139 ₽/шт", amount: "1", unit: "шт" }, + { name: "Устройство вентиляционных каналов ПВХ труб", price: "от 683 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Установка \"тюльпана\"", price: "от 2 628 ₽/шт", amount: "1", unit: "шт" }, + { name: "Установка точки канализации", price: "от 1 875 ₽/шт", amount: "1", unit: "шт" }, + { name: "Установка унитаза , биде со сборкой", price: "от 5 202 ₽/шт", amount: "1", unit: "шт" }, + { name: "Установка унитаза без сборки", price: "от 2 829 ₽/шт", amount: "1", unit: "шт" }, + { name: "Установка унитаза без сборки на старые посадочные места", price: "от 1 242 ₽/шт", amount: "1", unit: "шт" }, + { name: "Установка \"инсталляции\"", price: "от 7 161 ₽/шт", amount: "1", unit: "шт" }, + { name: "Установка \"мойдодыра\"", price: "от 3 380 ₽/шт", amount: "1", unit: "шт" }, + { name: "Установка чаши \"инсталляции\"", price: "от 3 255 ₽/шт", amount: "1", unit: "шт" }, + { name: "Герметизация швов ванны /раковины /унитаза", price: "от 306 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Окраска радиатора (до 5 секций)", price: "от 1 071 ₽/шт", amount: "1", unit: "шт" }, + { name: "Окраска радиатора каждая последующая секция", price: "от 291 ₽/шт", amount: "1", unit: "шт" }, + { name: "Окраска труб диаметром до 50мм", price: "от 276 ₽/пог.м", amount: "1", unit: "пог.м" }, + { name: "Окраска труб диаметром свыше 50мм", price: "от 378 ₽/пог.м", amount: "1", unit: "пог.м" } + ] + }, + { + title: "Подготовительные работы", + items: [ + { name: "Укрытие пленкой", price: "от 68 ₽/м²", amount: "1", unit: "м²" }, + { name: "Разборка/сборка мебели", price: "от 3 953 ₽/шт", amount: "1", unit: "шт" }, + { name: "Сборка строительных подмостей", price: "от 2 070 ₽/шт", amount: "1", unit: "шт" }, + { name: "Перестановка мебели (стенка из 5 секций)", price: "от 2 606 ₽/шт", amount: "1", unit: "шт" }, + { name: "Перестановка мебели ( шкаф из 1 секции)", price: "от 1 731 ₽/шт", amount: "1", unit: "шт" }, + { name: "Перестановка мебели (перемещение дивана)", price: "от 1 380 ₽/шт", amount: "1", unit: "шт" }, + { name: "Демонтаж кухонного гарнитура без сохранения", price: "от 3 519 ₽/шт", amount: "1", unit: "шт" }, + { name: "Демонтаж кухонного гарнитура с сохранением", price: "от 11 181 ₽/шт", amount: "1", unit: "шт" }, + { name: "Демонтаж кухонного модуля (тумба/шкафчик)", price: "от 414 ₽/шт", amount: "1", unit: "шт" }, + { name: "Демонтаж кухонного фартука / столешницы", price: "от 414 ₽/м²", amount: "1", unit: "м²" }, + { name: "Установка кухонного модуля (тумба/шкафчик)", price: "от 612 ₽/шт", amount: "1", unit: "шт" }, + { name: "Демонтаж кухонной плиты без сохранения", price: "от 1 380 ₽/шт", amount: "1", unit: "шт" }, + { name: "Демонтаж газовой плиты", price: "от 1 380 ₽/шт", amount: "1", unit: "шт" }, + { name: "Вынос мусора из квартиры на лифте ( при условии наличия контейнера для сбора строительного мусора не далее 50 м от подъезда или места проведения работ) (за 1 кг)", price: "от 9 ₽/кг", amount: "1", unit: "кг" }, + { name: "Уборка лестничных клеток", price: "от 1 380 ₽/шт", amount: "1", unit: "шт" }, + { name: "Вынос чугунной ванны по лестнице за каждый этаж", price: "от 1 380 ₽/шт", amount: "1", unit: "шт" }, + { name: "Сборка мебели 15% от стоимости", price: "от 3 906 ₽/шт", amount: "1", unit: "шт" }, + { name: "Разгрузка материала", price: "от 9 ₽/кг", amount: "1", unit: "кг" }, + { name: "При отсутствии лифта подъем на 1 этаж", price: "от 6 ₽/кг", amount: "1", unit: "кг" }, + { name: "Работа/ час прораба (выезд)", price: "от 1 500 ₽/час", amount: "1", unit: "час" }, + { name: "Комплексные подготовительные работы (Укрытие пленкой дверей/окон, временное освещение, временное водоснабжение, сборка подмостей)", price: "от 234 ₽/м²", amount: "1", unit: "м²" } + ] + } +]; diff --git a/prices.html b/prices.html new file mode 100644 index 0000000..6314107 --- /dev/null +++ b/prices.html @@ -0,0 +1,35 @@ + + + + + + + Фриз | Цены в Санкт-Петербурге + + + + + + + + +
+ +
+
+
+

Примерные цены на штучные работы

+

Итоговая стоимость зависит от состояния основания, объема и доступа к объекту

+ +
+
+
+
+
+ +
+ + + + + diff --git a/prices.xlsx b/prices.xlsx new file mode 100644 index 0000000..afdcdc0 Binary files /dev/null and b/prices.xlsx differ diff --git a/privacy.html b/privacy.html new file mode 100644 index 0000000..657d7c1 --- /dev/null +++ b/privacy.html @@ -0,0 +1,119 @@ + + + + + + + Фриз | Политика обработки персональных данных + + + + + + + + +
+ +
+
+
+

Политика обработки персональных данных

+ +

+ Индивидуальный предприниматель Погребнякова Ольга Юрьевна
+ ИНН [укажите ИНН]
+ ОГРНИП [укажите ОГРНИП]
+ Адрес регистрации: [укажите адрес регистрации ИП] +

+ +

Полные реквизиты: страница «Реквизиты ИП»

+ +

Действует с: «01» января 2026 г.

+ +

1. Общие положения

+

1.1. Настоящая Политика определяет порядок обработки персональных данных и меры по обеспечению безопасности персональных данных у Индивидуального предпринимателя Погребняковой Ольги Юрьевны (далее — Оператор)

+

1.2. Оператор ставит своей важнейшей целью соблюдение прав и свобод человека и гражданина при обработке его персональных данных

+

1.3. Политика действует в отношении всех персональных данных, которые Оператор может получить от посетителей сайта https://frieze-spb.ru (далее — Сайт)

+ +

2. Какие персональные данные собирает Оператор

+

2.1. Оператор собирает только следующие персональные данные:

+
    +
  • Фамилия, имя, отчество (или просто имя, которое указал посетитель)
  • +
  • Номер контактного телефона
  • +
  • Адрес объекта (квартиры, дома), где планируются отделочные работы — только после того, как посетитель сам сообщит его Оператору для записи на замер
  • +
+

2.2. Оператор НЕ собирает и НЕ обрабатывает: паспортные данные, СНИЛС, ИНН, данные банковских карт, биометрические данные (отпечатки пальцев, фото лица и т.п.)

+ +

3. Цели сбора персональных данных

+

3.1. Оператор собирает и обрабатывает персональные данные только для следующих целей:

+
    +
  • Связь с посетителем сайта (звонок или сообщение) для уточнения деталей заявки
  • +
  • Согласование даты и времени выезда специалиста на замер помещения
  • +
  • Составление сметы на отделочные работы
  • +
  • Заключение и исполнение договора подряда (если стороны приняли решение о сотрудничестве)
  • +
+

3.2. Оператор НЕ использует персональные данные для: рассылки рекламы (без отдельного согласия), передачи третьим лицам, продажи, публикации в открытом доступе

+ +

4. Как и где хранятся персональные данные

+

4.1. Заявки, отправленные через форму на Сайте, передаются на хостинг-площадку REG.RU и доставляются Оператору по электронной почте. Обработка выполняется только в объеме, необходимом для связи с клиентом

+

4.2. Персональные данные могут храниться: в почтовом ящике Оператора (на серверах REG.RU или почтового провайдера, используемого Оператором), а также в мобильном телефоне Оператора (в списке контактов и истории звонков/сообщений). Доступ к устройствам защищен паролем/биометрией

+

4.3. Оператор не использует CRM-системы, автоматические рассылки и не передает персональные данные в открытый доступ. Данные используются только для обработки заявок и связи с клиентом

+ +

5. Как долго хранятся данные

+

5.1. Персональные данные хранятся:

+
    +
  • Если договор не заключён (позвонили, уточнили, но не стали заказывать услуги) — до 30 дней с момента последнего контакта. После этого номер телефона удаляется из телефона Оператора
  • +
  • Если договор заключён и работы выполнены — персональные данные хранятся в течение 3 лет после окончания действия договора (требование налогового законодательства). После этого — удаляются
  • +
+

5.2. В любой момент вы можете потребовать удалить ваши персональные данные досрочно (см. раздел 7)

+ +

6. Передача персональных данных третьим лицам

+

6.1. Оператор не передает персональные данные третьим лицам, за исключением случаев, прямо предусмотренных законом (например, по требованию суда, полиции, прокуратуры)

+

6.2. Для технической работы Сайта и приема заявок Оператор использует инфраструктуру REG.RU (хостинг/почтовые сервисы). Такая передача ограничивается только данными, которые пользователь сам указывает в форме

+

6.3. Оператор не привлекает колл-центры, сторонние сервисы обзвона и рекламные платформы для обработки заявок. Все звонки и встречи осуществляются лично Оператором

+ +

7. Ваши права

+

Вы, как субъект персональных данных, имеете право:

+

7.1. Узнать, какие персональные данные о вас хранятся у Оператора. Для этого достаточно позвонить Оператору по телефону: +7 (921) 741-26-46 и назвать себя

+

7.2. Потребовать исправить неверные или неточные данные

+

7.3. Потребовать удалить ваши персональные данные полностью. Для этого позвоните Оператору или напишите на электронную почту: [укажите email] (если есть). Оператор обязан удалить данные в течение 10 рабочих дней

+

7.4. Отозвать согласие на обработку персональных данных (например, вы передумали и не хотите больше получать звонки). После отзыва согласия Оператор обязан прекратить обработку и удалить ваши данные

+ +

8. Как даётся согласие на обработку персональных данных

+

8.1. На Сайте в форме заявки (поля «Имя» и «Телефон») размещён чекбокс (галочка) с текстом: «Я даю согласие на обработку персональных данных в соответствии с Политикой обработки персональных данных» (словосочетание «Политикой обработки персональных данных» оформлено как ссылка на полный текст Политики)

+

8.2. Отправляя заявку через Сайт с отмеченной галочкой, вы даёте письменное согласие на обработку ваших персональных данных в объёме и на условиях, изложенных в настоящей Политике

+

8.3. Если вы заполняете заявку по телефону (диспетчер записывает ваш номер вручную), согласие считается полученным в устной форме и фиксируется фактом вашего обращения

+ +

9. Меры защиты персональных данных

+

9.1. Оператор принимает следующие меры для защиты ваших данных:

+
    +
  • Телефон, где хранятся номера клиентов, защищён паролем или биометрией (отпечаток пальца / Face ID)
  • +
  • Телефон не передаётся третьим лицам
  • +
  • Доступ к панели хостинга и почте защищен сложным паролем, доступ предоставлен только Оператору
  • +
  • Для сайта и почты регулярно обновляются пароли, используется актуальное программное окружение хостинга
  • +
  • При утере или краже телефона Оператор немедленно блокирует его удалённо
  • +
+ +

10. Изменение Политики

+

10.1. Оператор может изменять настоящую Политику. Актуальная версия всегда размещается по этому адресу: https://frieze-spb.ru/privacy

+

10.2. Если изменения существенные (например, смена хостинг-провайдера, подключение новых каналов приема заявок или изменение состава обрабатываемых данных), на Сайте появится уведомление об этом

+ +

11. Контактная информация

+

По всем вопросам, связанным с обработкой персональных данных, вы можете обратиться к Оператору:

+

+ Индивидуальный предприниматель Погребнякова Ольга Юрьевна
+ Телефон: +7 (921) 741-26-46
+ Электронная почта: [укажите email, при появлении]
+ Режим работы: ежедневно с 9:00 до 18:00 по московскому времени +

+

Дата публикации: 09 апреля 2026 г.

+
+
+
+ +
+ + + + diff --git a/requisites.html b/requisites.html new file mode 100644 index 0000000..14f10fa --- /dev/null +++ b/requisites.html @@ -0,0 +1,44 @@ + + + + + + + Фриз | Реквизиты ИП + + + + + + + + +
+ +
+
+
+

Реквизиты индивидуального предпринимателя

+ +

+ Индивидуальный предприниматель Погребнякова Ольга Юрьевна
+ ИНН [укажите ИНН]
+ ОГРНИП [укажите ОГРНИП]
+ Адрес регистрации: [укажите адрес регистрации ИП] +

+ +

Телефон: +7 (921) 741-26-46
+ Электронная почта: [укажите email, при появлении]

+ +

Сайт носит информационный характер. Услуги оказываются на территории Санкт-Петербурга и Ленинградской области

+ +

Политика обработки персональных данных

+
+
+
+ +
+ + + + diff --git a/reviews.html b/reviews.html new file mode 100644 index 0000000..2d56e2d --- /dev/null +++ b/reviews.html @@ -0,0 +1,46 @@ + + + + + + + Фриз | Отзывы — Санкт-Петербург + + + + + + + + +
+ +
+
+
+

Отзывы клиентов

+

Реальные отклики после завершения ремонта и сдачи объектов в городе и области

+ +
+
+

Мария, Петроградский район

+

"Сделали ванную под ключ за 12 дней, всё аккуратно и в оговоренный бюджет"

+
+
+

Алексей, Васильевский остров

+

"Отлично подготовили стены в старом фонде, после покраски всё выглядит идеально"

+
+
+

Ирина, Приморский район

+

"Помогли с закупкой материалов и вывозом мусора, очень удобно и без лишних хлопот"

+
+
+
+
+
+ +
+ + + + diff --git a/robots.txt b/robots.txt new file mode 100644 index 0000000..ee93fae --- /dev/null +++ b/robots.txt @@ -0,0 +1,5 @@ +User-agent: * +Allow: / + +Host: frieze-spb.ru +Sitemap: https://frieze-spb.ru/sitemap.xml diff --git a/script.js b/script.js new file mode 100644 index 0000000..885d6e0 --- /dev/null +++ b/script.js @@ -0,0 +1,897 @@ +function getCurrentPageName() { + const normalizedPath = window.location.pathname.replace(/\/+$/, ""); + const raw = (normalizedPath.split("/").pop() || "").toLowerCase(); + const normalized = raw.replace(/\.html$/, ""); + return normalized || "index"; +} + +const isLocalDevHost = + window.location.hostname === "localhost" || + window.location.hostname === "127.0.0.1"; + +function routeHref(page) { + if (page === "index") { + return isLocalDevHost ? "index.html" : "/"; + } + return isLocalDevHost ? `${page}.html` : `/${page}`; +} + +function renderSharedLayout() { + const currentPage = getCurrentPageName(); + const headerMount = document.querySelector("[data-site-header]"); + const footerMount = document.querySelector("[data-site-footer]"); + + if (headerMount) { + const navItems = [ + { page: "prices", href: routeHref("prices"), label: "Цены" }, + { page: "portfolio", href: routeHref("portfolio"), label: "Портфолио" }, + { page: "reviews", href: routeHref("reviews"), label: "Отзывы" }, + ]; + + const navHtml = navItems + .map((item) => { + const isCurrent = currentPage === item.page; + return `${item.label}`; + }) + .join(""); + + headerMount.outerHTML = ` +
+
+
+ + +
+
+
+ +7 (921) 741-26-46 + Ежедневно 10:00-18:00 + Санкт-Петербург +
+ VK +
+
+
`; + } + + if (footerMount) { + const isPrivacyPage = currentPage === "privacy"; + const isRequisitesPage = currentPage === "requisites"; + + footerMount.outerHTML = ` + Позвонить + `; + } +} + +renderSharedLayout(); + +const currentYear = document.getElementById("year"); +if (currentYear) { + currentYear.textContent = new Date().getFullYear(); +} + +const reduceMotionMql = window.matchMedia("(prefers-reduced-motion: reduce)"); + +function easeInOutCubic(t) { + return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2; +} + +function smoothScrollTo(targetY) { + const startY = window.scrollY || document.documentElement.scrollTop; + const change = targetY - startY; + if (reduceMotionMql.matches) { + window.scrollTo(0, targetY); + return; + } + if (Math.abs(change) < 2) { + return; + } + const duration = Math.min(980, Math.max(480, Math.abs(change) * 0.62)); + const start = performance.now(); + + const step = (now) => { + const t = Math.min(1, (now - start) / duration); + window.scrollTo(0, startY + change * easeInOutCubic(t)); + if (t < 1) { + requestAnimationFrame(step); + } + }; + requestAnimationFrame(step); +} + +document.addEventListener( + "click", + (e) => { + if (document.documentElement.classList.contains("modal-open")) { + return; + } + const a = e.target.closest("a"); + if (!a) { + return; + } + const href = a.getAttribute("href"); + if (!href || href === "#" || href.charAt(0) !== "#") { + return; + } + if (a.hasAttribute("download")) { + return; + } + const el = document.querySelector(href); + if (!el) { + return; + } + e.preventDefault(); + const header = document.querySelector(".header"); + const headerH = header ? header.getBoundingClientRect().height : 0; + const gap = 16; + const y = el.getBoundingClientRect().top + window.scrollY - headerH - gap; + smoothScrollTo(Math.max(0, y)); + history.pushState(null, "", href); + }, + true +); + +const isHomePage = () => { + return getCurrentPageName() === "index"; +}; + +document.querySelectorAll("a.logo").forEach((link) => { + const href = (link.getAttribute("href") || "").trim(); + const goesHome = + href === "index.html" || + href === "./index.html" || + href === "/" || + href === "/index" || + href.endsWith("/index.html"); + if (!goesHome) { + return; + } + link.addEventListener("click", (event) => { + if (!isHomePage()) { + return; + } + event.preventDefault(); + if (window.scrollY > 0) { + smoothScrollTo(0); + } + if (window.location.hash) { + history.replaceState(null, "", window.location.pathname + window.location.search); + } + }); +}); + +const form = document.getElementById("contactForm"); +if (form) { + form.setAttribute("novalidate", "novalidate"); + const nameInput = form.querySelector('input[name="name"]'); + const phoneInput = form.querySelector('input[name="phone"]'); + const honeypotInput = form.querySelector('input[name="contact_fax"]'); + const startedAtInput = form.querySelector('input[name="started_at"]'); + const submitButton = form.querySelector('button[type="submit"]'); + + if (startedAtInput) { + startedAtInput.value = String(Date.now()); + } + + if (phoneInput) { + const formatPhone = (rawValue) => { + const digits = rawValue.replace(/\D/g, ""); + const localDigits = digits.startsWith("7") ? digits.slice(1) : digits; + const trimmed = localDigits.slice(0, 10); + + if (!trimmed.length) { + return "+7"; + } + + let result = "+7"; + if (trimmed.length > 0) { + result += ` (${trimmed.slice(0, 3)}`; + } + if (trimmed.length >= 3) { + result += ")"; + } + if (trimmed.length > 3) { + result += ` ${trimmed.slice(3, 6)}`; + } + if (trimmed.length > 6) { + result += `-${trimmed.slice(6, 8)}`; + } + if (trimmed.length > 8) { + result += `-${trimmed.slice(8, 10)}`; + } + + return result; + }; + + phoneInput.addEventListener("focus", () => { + if (!phoneInput.value.trim()) { + phoneInput.value = "+7"; + } else { + phoneInput.value = formatPhone(phoneInput.value); + } + }); + + phoneInput.addEventListener("input", () => { + phoneInput.value = formatPhone(phoneInput.value); + phoneInput.setSelectionRange(phoneInput.value.length, phoneInput.value.length); + }); + + phoneInput.addEventListener("keydown", (event) => { + const selectionStart = phoneInput.selectionStart; + const selectionEnd = phoneInput.selectionEnd; + const hasSelection = + selectionStart !== null && + selectionEnd !== null && + selectionStart !== selectionEnd; + const fullSelection = + selectionStart === 0 && selectionEnd === phoneInput.value.length; + + if ( + fullSelection && + (event.key === "Backspace" || event.key === "Delete") + ) { + event.preventDefault(); + phoneInput.value = ""; + return; + } + + if ( + (event.key === "Backspace" || event.key === "Delete") && + selectionStart !== null && + selectionStart <= 2 && + !hasSelection + ) { + event.preventDefault(); + } + }); + + phoneInput.addEventListener("blur", () => { + if (phoneInput.value === "+7" || phoneInput.value === "+7 (") { + phoneInput.value = ""; + } + }); + } + + const consentInput = form.querySelector('input[name="consent"]'); + const consentLabel = form.querySelector(".contact-form__consent"); + const nameValidationRegex = /^[A-Za-zА-Яа-яЁё\s-]{2,80}$/; + + const showFormToast = (message, variant = "warning") => { + let toast = document.getElementById("formToast"); + if (!toast) { + toast = document.createElement("div"); + toast.id = "formToast"; + toast.className = "form-toast"; + document.body.appendChild(toast); + } + toast.textContent = message; + toast.classList.remove("form-toast--warning", "form-toast--success"); + toast.classList.add(`form-toast--${variant}`); + toast.setAttribute("role", variant === "success" ? "status" : "alert"); + toast.setAttribute("aria-live", "polite"); + toast.classList.add("is-visible"); + clearTimeout(toast._hideTimer); + const hideMs = variant === "success" ? 6000 : 5000; + toast._hideTimer = window.setTimeout(() => { + toast.classList.remove("is-visible"); + }, hideMs); + toast.onclick = () => { + clearTimeout(toast._hideTimer); + toast.classList.remove("is-visible"); + }; + }; + + form.addEventListener("submit", (event) => { + event.preventDefault(); + + if (nameInput) { + const normalizedName = nameInput.value.trim().replace(/\s+/g, " "); + nameInput.value = normalizedName; + if (!normalizedName) { + showFormToast("Пожалуйста, укажите ваше имя", "warning"); + nameInput.focus(); + return; + } + if (!nameValidationRegex.test(normalizedName)) { + showFormToast( + "Имя должно содержать только буквы, пробелы и дефис (2-80 символов)", + "warning" + ); + nameInput.focus(); + return; + } + } + + if (phoneInput) { + const phoneDigits = phoneInput.value.replace(/\D/g, ""); + const isPhoneValid = phoneDigits.length === 11 && phoneDigits.startsWith("7"); + if (!isPhoneValid) { + showFormToast("Пожалуйста, укажите корректный номер телефона", "warning"); + phoneInput.focus(); + return; + } + } + + if (consentInput && !consentInput.checked) { + consentLabel?.classList.add("contact-form__consent--error"); + consentLabel?.scrollIntoView({ behavior: "smooth", block: "center" }); + showFormToast( + "Чтобы отправить заявку, отметьте согласие на обработку персональных данных", + "warning" + ); + consentInput.focus(); + return; + } + consentLabel?.classList.remove("contact-form__consent--error"); + + if (startedAtInput && !startedAtInput.value) { + startedAtInput.value = String(Date.now()); + } + + const formData = new FormData(form); + // Явно чистим honeypot: защищает от автозаполнения браузером. + if (honeypotInput) { + honeypotInput.value = ""; + } + formData.delete("website"); + formData.set("contact_fax", ""); + if (!formData.get("consent")) { + formData.set("consent", "1"); + } + + if (submitButton) { + submitButton.disabled = true; + submitButton.setAttribute("aria-busy", "true"); + } + + fetch("submit.php", { + method: "POST", + body: formData, + headers: { + Accept: "application/json", + }, + }) + .then(async (response) => { + let payload = null; + try { + payload = await response.json(); + } catch (error) { + payload = null; + } + + if (!response.ok || !payload?.ok) { + const errorMessage = + payload?.message || + "Не удалось отправить заявку. Позвоните нам, пожалуйста, по телефону +7 (921) 741-26-46"; + throw new Error(errorMessage); + } + + showFormToast("Спасибо! Заявка принята — скоро мы с вами свяжемся", "success"); + form.reset(); + if (startedAtInput) { + startedAtInput.value = String(Date.now()); + } + }) + .catch((error) => { + showFormToast(error.message, "warning"); + }) + .finally(() => { + if (submitButton) { + submitButton.disabled = false; + submitButton.removeAttribute("aria-busy"); + } + }); + }); + + if (consentInput) { + consentInput.addEventListener("change", () => { + if (consentInput.checked) { + consentLabel?.classList.remove("contact-form__consent--error"); + } + }); + } +} + +const revealItems = document.querySelectorAll(".reveal"); +const revealObserver = new IntersectionObserver( + (entries, observer) => { + entries.forEach((entry) => { + if (!entry.isIntersecting) { + return; + } + entry.target.classList.add("is-visible"); + observer.unobserve(entry.target); + }); + }, + { + threshold: 0.15, + } +); + +revealItems.forEach((item) => revealObserver.observe(item)); + +const priceAccordion = document.getElementById("priceAccordion"); + +const fallbackPriceData = [ + { + title: "Электромонтажные работы", + items: [ + { name: "Установка розетки", price: "от 650 ₽/шт" }, + { name: "Установка выключателя", price: "от 550 ₽/шт" }, + { name: "Монтаж подрозетника", price: "от 450 ₽/шт" }, + { name: "Монтаж точки освещения", price: "от 1 200 ₽/шт" }, + { name: "Монтаж потолочного светильника", price: "от 900 ₽/шт" }, + ], + }, + { + title: "Плиточные работы", + items: [ + { name: "Укладка плитки 20x30", price: "от 350 ₽/шт" }, + { name: "Укладка керамогранита 60x60", price: "от 900 ₽/шт" }, + { name: "Запил плитки под 45 градусов", price: "от 500 ₽/пог.м" }, + { name: "Затирка эпоксидная", price: "от 300 ₽/м²" }, + ], + }, + { + title: "Сантехнические работы", + items: [ + { name: "Установка смесителя", price: "от 1 500 ₽/шт" }, + { name: "Монтаж унитаза", price: "от 2 500 ₽/шт" }, + { name: "Установка раковины с тумбой", price: "от 3 000 ₽/шт" }, + { name: "Подключение полотенцесушителя", price: "от 2 800 ₽/шт" }, + ], + }, +]; + +function escapeHtml(value) { + const text = String(value ?? ""); + return text + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """) + .replace(/'/g, "'"); +} + +function normalizePriceItems(items) { + if (!Array.isArray(items)) { + return []; + } + return items.map((item) => ({ + name: typeof item?.name === "string" ? item.name : "", + price: typeof item?.price === "string" ? item.price : "", + amount: typeof item?.amount === "string" ? item.amount : "", + unit: typeof item?.unit === "string" ? item.unit : "", + total: typeof item?.total === "string" ? item.total : "", + })); +} + +function normalizePriceData(data) { + if (!Array.isArray(data)) { + return JSON.parse(JSON.stringify(fallbackPriceData)); + } + + return data.map((category) => { + const children = Array.isArray(category?.children) + ? category.children.map((child) => ({ + title: typeof child?.title === "string" ? child.title : "", + items: normalizePriceItems(child?.items), + })) + : []; + + return { + title: typeof category?.title === "string" ? category.title : "", + items: normalizePriceItems(category?.items), + children, + }; + }); +} + +async function loadPriceData() { + const externalData = + typeof window.PRICES_DATA !== "undefined" ? window.PRICES_DATA : null; + if (externalData) { + return normalizePriceData(externalData); + } + return JSON.parse(JSON.stringify(fallbackPriceData)); +} + +function buildPriceList(items) { + return items + .map( + (item) => { + const rawPrice = String(item.price || "").trim(); + const desktopPrice = rawPrice + .replace(/\s*\/\s*[^\s]+$/u, "") + .trim(); + const mobilePrice = rawPrice + .replace(/^от\s+/iu, "") + .replace(/\s*\/\s*[^\s]+$/u, "") + .trim(); + const derivedUnitMatch = rawPrice.match(/\/\s*([^\s]+)$/); + const derivedUnit = derivedUnitMatch ? derivedUnitMatch[1] : ""; + const amountValue = item.amount || "1"; + const unitValue = item.unit || derivedUnit || ""; + const normalizedName = String(item.name || "").trim().replace(/\s+/g, " "); + const fullName = escapeHtml(normalizedName); + return ` +
  • + ${fullName} + + ${escapeHtml(desktopPrice || rawPrice)} + ${escapeHtml(mobilePrice || rawPrice)} + + ${escapeHtml(amountValue)} + ${escapeHtml(unitValue || "—")} +
  • + `; + } + ) + .join(""); +} + +function buildPriceTable(items) { + return ` + + `; +} + +function slugifyCategory(title) { + const map = { + а: "a", + б: "b", + в: "v", + г: "g", + д: "d", + е: "e", + ё: "e", + ж: "zh", + з: "z", + и: "i", + й: "y", + к: "k", + л: "l", + м: "m", + н: "n", + о: "o", + п: "p", + р: "r", + с: "s", + т: "t", + у: "u", + ф: "f", + х: "h", + ц: "ts", + ч: "ch", + ш: "sh", + щ: "sch", + ъ: "", + ы: "y", + ь: "", + э: "e", + ю: "yu", + я: "ya", + }; + + return String(title || "") + .toLowerCase() + .split("") + .map((ch) => map[ch] ?? ch) + .join("") + .replace(/[^a-z0-9]+/g, "-") + .replace(/^-+|-+$/g, ""); +} + +function openPriceCategoryFromHash() { + if (!priceAccordion) { + return; + } + + const hash = decodeURIComponent(location.hash.replace(/^#/, "")).trim(); + if (!hash) { + return; + } + + const category = priceAccordion.querySelector( + `.price-category#${CSS.escape(hash)}` + ); + if (!(category instanceof HTMLElement)) { + return; + } + + const toggle = category.querySelector(".price-category__toggle"); + const listWrap = toggle?.nextElementSibling; + if ( + toggle instanceof HTMLElement && + listWrap instanceof HTMLElement && + listWrap.classList.contains("price-list-wrap") + ) { + toggle.setAttribute("aria-expanded", "true"); + listWrap.hidden = false; + } + + requestAnimationFrame(() => { + const header = document.querySelector(".header"); + const offset = + header instanceof HTMLElement ? header.getBoundingClientRect().height + 12 : 12; + const top = category.getBoundingClientRect().top + window.scrollY - offset; + window.scrollTo({ top: Math.max(0, top), behavior: "smooth" }); + }); +} + +function renderPriceAccordion(priceData) { + if (!priceAccordion) { + return; + } + + priceAccordion.innerHTML = priceData + .map((category) => { + const hasChildren = + Array.isArray(category.children) && category.children.length > 0; + const categoryId = slugifyCategory(category.title || "kategoriya"); + + if (hasChildren) { + const childrenHtml = category.children + .map( + (child) => ` +
    + + +
    + ` + ) + .join(""); + + return ` +
    + + +
    + `; + } + + return ` +
    + + +
    + `; + }) + .join(""); + + openPriceCategoryFromHash(); +} + +if (priceAccordion) { + loadPriceData().then(renderPriceAccordion); + + window.addEventListener("hashchange", openPriceCategoryFromHash); + + priceAccordion.addEventListener("click", (event) => { + const target = event.target; + if (!(target instanceof HTMLElement)) { + return; + } + + const toggle = target.closest( + ".price-category__toggle, .price-subcategory__toggle" + ); + if (!toggle || !(toggle instanceof HTMLElement)) { + return; + } + + const listWrap = toggle.nextElementSibling; + if (!(listWrap instanceof HTMLElement) || !listWrap.classList.contains("price-list-wrap")) { + return; + } + + const expanded = toggle.getAttribute("aria-expanded") === "true"; + toggle.setAttribute("aria-expanded", String(!expanded)); + listWrap.hidden = expanded; + }); +} + +const LOGO_KEY = "remont-spb-logo"; +const DEFAULT_LOGO = "logo-interior-1.png"; +const logoOptions = [ + { id: "logo-option-1.png", label: "1" }, + { id: "logo-option-2.png", label: "2" }, + { id: "logo-option-3.png", label: "3" }, + { id: "logo-option-4.png", label: "4" }, + { id: "logo-option-6.png", label: "5" }, + { id: "logo-right-2.png", label: "6" }, + { id: "logo-right-4.png", label: "7" }, + { id: "logo-right-5.png", label: "8" }, + { id: "logo-simple-1.png", label: "9" }, + { id: "logo-simple-3.png", label: "10" }, + { id: "logo-interior-1.png", label: "11" }, + { id: "logo-interior-6.png", label: "12" }, + { id: "logo-interior-8.png", label: "13" }, + { id: "logo-interior-9.png", label: "14" }, + { id: "logo-flat-2.png", label: "15" }, + { id: "logo-flat-3.png", label: "16" }, + { id: "logo-flat-5.png", label: "17" }, +]; + +const BG_KEY = "remont-spb-bg"; +const DEFAULT_BG = "bg-4.png"; +const bgOptions = [ + { id: "none", label: "Без фона" }, + { id: "bg-1.png", label: "Фон 1" }, + { id: "bg-2.png", label: "Фон 2" }, + { id: "bg-3.png", label: "Фон 3" }, + { id: "bg-4.png", label: "Фон 4" }, + { id: "bg-5.png", label: "Фон 5" }, +]; + +function applyLogo(logoId) { + localStorage.setItem(LOGO_KEY, logoId); + const logoPath = `image/${logoId}`; + + const logoImgs = document.querySelectorAll(".logo img"); + logoImgs.forEach((img) => { + img.src = logoPath; + }); +} + +function applyBg(bgId) { + localStorage.setItem(BG_KEY, bgId); + const heroBg = document.getElementById("heroBg"); + const heroSection = document.getElementById("hero"); + + if (heroBg && heroSection) { + if (bgId === "none") { + heroBg.style.display = "none"; + heroBg.src = ""; + } else { + heroBg.style.display = "block"; + heroBg.src = `image/${bgId}`; + } + } +} + +function applyBrandingDefaults() { + // Применяем зафиксированные значения брендинга без UI-переключателя. + applyLogo(DEFAULT_LOGO); + applyBg(DEFAULT_BG); +} + +applyBrandingDefaults(); + +/* Portfolio Gallery Logic */ +const workCards = document.querySelectorAll('.work-card[data-images]'); +const portfolioModal = document.getElementById('portfolioModal'); +const portfolioTrack = document.getElementById('portfolioTrack'); + +if (portfolioModal && portfolioTrack) { + const closeButtons = portfolioModal.querySelectorAll('[data-close]'); + + const openModal = (images) => { + portfolioTrack.innerHTML = images + .map( + (src) => + `
    Фото объекта
    ` + ) + .join(''); + portfolioModal.classList.add('is-open'); + document.documentElement.classList.add('modal-open'); + document.body.classList.add('modal-open'); + }; + + const closeModal = () => { + portfolioModal.classList.remove('is-open'); + document.documentElement.classList.remove('modal-open'); + document.body.classList.remove('modal-open'); + }; + + workCards.forEach(card => { + card.addEventListener('click', () => { + try { + const images = JSON.parse(card.dataset.images); + if (images && images.length) { + openModal(images); + } + } catch (e) { + console.error('Ошибка при чтении картинок галереи', e); + } + }); + }); + + closeButtons.forEach(btn => btn.addEventListener('click', closeModal)); + + document.addEventListener('keydown', (e) => { + if (e.key === 'Escape' && portfolioModal.classList.contains('is-open')) { + closeModal(); + } + }); + + // Логика перетаскивания (Drag-to-scroll) + let isDown = false; + let startX; + let scrollLeft; + + portfolioTrack.addEventListener('mousedown', (e) => { + isDown = true; + portfolioTrack.classList.add('is-dragging'); + startX = e.pageX - portfolioTrack.offsetLeft; + scrollLeft = portfolioTrack.scrollLeft; + }); + + portfolioTrack.addEventListener('mouseleave', () => { + isDown = false; + portfolioTrack.classList.remove('is-dragging'); + }); + + portfolioTrack.addEventListener('mouseup', () => { + isDown = false; + portfolioTrack.classList.remove('is-dragging'); + }); + + portfolioTrack.addEventListener('mousemove', (e) => { + if (!isDown) return; + e.preventDefault(); + const x = e.pageX - portfolioTrack.offsetLeft; + const walk = (x - startX) * 2; // Скорость прокрутки при перетаскивании + portfolioTrack.scrollLeft = scrollLeft - walk; + }); + + // Прокрутка колесиком мыши (Wheel scroll) + portfolioTrack.addEventListener('wheel', (e) => { + if (e.deltaY !== 0) { + e.preventDefault(); + portfolioTrack.scrollLeft += e.deltaY; + } + }); + + // Блокируем прокрутку страницы, пока открыта модалка + portfolioModal.addEventListener( + 'wheel', + (e) => { + if (!portfolioModal.classList.contains('is-open')) { + return; + } + e.preventDefault(); + }, + { passive: false } + ); +} diff --git a/sitemap.xml b/sitemap.xml new file mode 100644 index 0000000..4952022 --- /dev/null +++ b/sitemap.xml @@ -0,0 +1,21 @@ + + + + https://frieze-spb.ru/ + + + https://frieze-spb.ru/prices + + + https://frieze-spb.ru/portfolio + + + https://frieze-spb.ru/reviews + + + https://frieze-spb.ru/privacy + + + https://frieze-spb.ru/requisites + + diff --git a/styles.css b/styles.css new file mode 100644 index 0000000..a630c86 --- /dev/null +++ b/styles.css @@ -0,0 +1,1465 @@ +:root { + --bg: #f7f9fe; + --surface: #ffffff; + --text: #2a3447; + --heading: #3b4f70; + --muted: #606c80; + --border: #e1e8f5; + --accent: #5f7fb8; + --accent-dark: #4f6fa8; + --accent-soft: rgba(95, 127, 184, 0.16); + --shadow: 0 10px 30px rgba(15, 23, 42, 0.08); + --header-bg: #e8eefb; + --footer-bg: #dde6f8; + --header-text: #2d4b84; + --header-muted: #3d4f70; + --header-hover: rgba(95, 127, 184, 0.14); + --header-btn-border: rgba(45, 75, 132, 0.28); + --header-btn-bg: rgba(255, 255, 255, 0.92); + --header-btn-text: #2d4b84; + --header-btn-hover-bg: rgba(95, 127, 184, 0.14); + --header-btn-hover-text: #2d4b84; + --header-btn-hover-border: rgba(95, 127, 184, 0.38); + --scrollbar-thumb: rgba(95, 127, 184, 0.28); + --scrollbar-thumb-hover: rgba(95, 127, 184, 0.42); +} + +*, +*::before, +*::after { + box-sizing: border-box; +} + +html { + scroll-behavior: smooth; + scroll-padding-top: 6.25rem; + overflow-y: scroll; + scrollbar-width: thin; + scrollbar-color: var(--scrollbar-thumb) var(--bg); +} + +@media (prefers-reduced-motion: reduce) { + html { + scroll-behavior: auto; + } +} + +html::-webkit-scrollbar { + width: 10px; +} + +html::-webkit-scrollbar-button { + display: none; + width: 0; + height: 0; +} + +html::-webkit-scrollbar-track { + background: var(--bg); +} + +html::-webkit-scrollbar-thumb { + background: var(--scrollbar-thumb); + border-radius: 100px; + border: 3px solid var(--bg); +} + +html::-webkit-scrollbar-thumb:hover { + background: var(--scrollbar-thumb-hover); +} + +html.modal-open, +body.modal-open { + overflow: hidden; +} + +body { + margin: 0; + font-family: "Inter", Arial, sans-serif; + background: var(--bg); + color: var(--text); + line-height: 1.5; + min-height: 100vh; + display: flex; + flex-direction: column; +} + +main { + flex: 1; +} + +img { + display: block; + width: 100%; + height: auto; +} + +.container { + width: min(1120px, 100% - 2rem); + margin: 0 auto; +} + +.section { + padding: 3.5rem 0; +} + +h1, +h2, +h3 { + margin: 0 0 1rem; + line-height: 1.2; + color: var(--heading); +} + +h1 { + font-size: clamp(1.9rem, 7vw, 3rem); +} + +h2 { + font-size: clamp(1.5rem, 4vw, 2rem); +} + +p { + margin: 0; + color: var(--muted); +} + +.btn { + display: inline-flex; + align-items: center; + justify-content: center; + border-radius: 0.75rem; + padding: 0.75rem 1.15rem; + text-decoration: none; + font-weight: 600; + border: 1px solid transparent; + transition: 0.2s ease; +} + +.btn--primary { + background: var(--accent); + color: #fff; +} + +.btn--primary:hover { + background: var(--accent-dark); +} + +.btn--outline { + border-color: var(--accent); + color: var(--accent); + background: #fff; +} + +.btn--outline:hover { + color: #fff; + background: var(--accent); +} + +.header { + position: sticky; + top: 0; + z-index: 20; + background: var(--header-bg); + border-bottom: 1px solid rgba(255, 255, 255, 0.12); + overflow: hidden; +} + +.header__inner { + display: flex; + align-items: center; + justify-content: space-between; + gap: 0.75rem; + min-height: auto; + flex-wrap: wrap; + padding: 0.75rem 0; +} + +.header__left { + display: flex; + align-items: center; + gap: 0.75rem; + width: 100%; +} + +.header__nav { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 0.4rem; + flex: 1; + min-width: 0; +} + +.logo { + text-decoration: none; + display: inline-flex; + align-items: center; + height: 52px; + margin-right: 0; + flex-shrink: 0; +} + +.logo img { + width: auto; + height: 52px; + object-fit: contain; + mix-blend-mode: darken; + transform: scale(1); + transform-origin: left center; +} + +.header-link { + display: inline-flex; + align-items: center; + justify-content: center; + width: 100%; + color: var(--header-text); + text-decoration: none; + font-weight: 600; + font-size: 0.98rem; + padding: 0.45rem 0.4rem; + border: 1px solid transparent; + border-radius: 0.55rem; + transform-origin: center; + transition: background 0.2s ease, box-shadow 0.2s ease, transform 0.2s ease; + white-space: nowrap; +} + +.header-link:hover { + background: var(--header-hover); + transform: scale(1.02); +} + +.header-link--current { + color: var(--header-text); + background: rgba(95, 127, 184, 0.1); + font-weight: 600; + box-shadow: inset 0 -2px 0 rgba(95, 127, 184, 0.35); + transform: scale(1.03); + animation: header-link-current-in 0.22s ease-out; +} + +.header-link--current:hover { + background: rgba(95, 127, 184, 0.14); + box-shadow: inset 0 -2px 0 rgba(95, 127, 184, 0.42); + transform: scale(1.035); +} + +@media (max-width: 719px) { + .header-link--current, + .header-link--current:hover { + transform: none; + animation: none; + } +} + +@keyframes header-link-current-in { + from { + transform: scale(1); + } + to { + transform: scale(1.03); + } +} + +.header__actions { + display: flex; + align-items: center; + gap: 0.6rem; + margin-left: 0; + flex-wrap: nowrap; + justify-content: space-between; + width: 100%; +} + +.header__contact { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 0.12rem; + line-height: 1.2; + min-width: 0; +} + +.header__geo { + font-size: 0.78rem; + font-weight: 600; + color: var(--header-muted); + letter-spacing: 0.01em; + white-space: nowrap; + opacity: 0.92; +} + +.header__meta { + font-size: 0.78rem; + font-weight: 600; + color: var(--header-muted); + letter-spacing: 0.01em; + white-space: nowrap; + opacity: 0.92; +} + +.phone-link { + color: var(--header-muted); + text-decoration: none; + font-weight: 600; + font-size: 1.02rem; + white-space: nowrap; +} + +.header .btn--outline { + border-color: var(--header-btn-border); + color: var(--header-btn-text); + background: var(--header-btn-bg); + font-size: 1rem; + padding: 0.6rem 0.9rem; +} + +.header .btn--outline:hover { + border-color: var(--header-btn-hover-border); + color: var(--header-btn-hover-text); + background: var(--header-btn-hover-bg); + box-shadow: 0 2px 10px rgba(45, 75, 132, 0.06); +} + +.hero { + padding: clamp(3.2rem, 6vw, 4.2rem) 0; + position: relative; + overflow: hidden; +} + +.hero__bg { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + object-fit: cover; + filter: blur(6px) brightness(0.8); + z-index: 0; +} + +.hero .container { + position: relative; + z-index: 1; + background: linear-gradient( + 180deg, + rgba(255, 255, 255, 0.9) 0%, + rgba(255, 255, 255, 0.84) 100% + ); + border: 1px solid rgba(255, 255, 255, 0.62); + border-radius: 1.1rem; + box-shadow: + 0 18px 42px rgba(15, 23, 42, 0.12), + 0 1px 0 rgba(255, 255, 255, 0.45) inset; + backdrop-filter: blur(2px); + -webkit-backdrop-filter: blur(2px); + padding: 2rem 1.2rem; + display: grid; + justify-items: center; + gap: 0.85rem; + width: min(850px, 100%); + margin: 0 auto; +} + +.hero h1 { + margin: 0; + max-width: none; + width: 100%; + text-wrap: balance; + text-align: center; + font-size: clamp(1.8rem, 3.2vw, 2.7rem); +} + +.hero p { + max-width: 62ch; + margin: 0 auto; + color: var(--header-muted); + font-size: 1.02rem; + line-height: 1.8; + text-align: center; + text-wrap: pretty; + letter-spacing: 0.003em; +} + +.hero .btn--primary { + margin-top: 0.45rem; +} + +/* Вводные абзацы под h1/h2 (не затрагиваем privacy-doc — там свои цвета) */ +main .section:not(.privacy-doc) .container > p { + color: var(--header-muted); + line-height: 1.6; +} + +.services__grid, +.reviews__grid, +.gallery__grid, +.prices-grid { + margin-top: 1.25rem; + display: grid; + gap: 1rem; +} + +.card, +.specific .container, +.contact-form, +.work-card, +.price-item { + background: var(--surface); + border: 1px solid var(--border); + border-radius: 1rem; + box-shadow: var(--shadow); +} + +.card { + padding: 1.2rem; +} + +.card p { + color: var(--header-muted); + line-height: 1.55; +} + +.services .card { + text-align: left; +} + +.card--service { + display: flex; + flex-direction: column; + height: 100%; + color: inherit; + text-decoration: none !important; + transition: border-color 0.2s ease, box-shadow 0.2s ease, transform 0.2s ease; +} + +.card--service:link, +.card--service:visited, +.card--service:hover, +.card--service:active, +.card--service:focus-visible { + color: inherit; + text-decoration: none !important; +} + +.card--service:hover, +.card--service:focus-visible { + border-color: color-mix(in srgb, var(--accent) 35%, var(--border)); + box-shadow: var(--shadow); + transform: translateY(-2px); + outline: none; +} + +.card--service h3 { + margin-bottom: 0.55rem; +} + +.card--service p { + flex: 1; + margin: 0; +} + +.card__link { + display: inline-block; + margin-top: 1rem; + color: var(--accent); + font-weight: 600; + font-size: 0.95rem; + text-decoration: none !important; +} + +.price { + color: var(--accent); + font-weight: 700; + margin-bottom: 0.6rem; +} + +.specific .container { + padding: 1.5rem 1.2rem; +} + +.work-card { + overflow: hidden; + cursor: pointer; + transition: transform 0.2s ease, box-shadow 0.2s ease; +} + +.work-card:hover { + transform: translateY(-4px); + box-shadow: 0 15px 35px rgba(15, 23, 42, 0.12); +} + +.work-card img { + aspect-ratio: 16 / 10; + object-fit: cover; +} + +.work-card__label { + padding: 0.85rem 1rem; + color: var(--heading); + font-weight: 600; + font-size: 0.95rem; + line-height: 1.35; +} + +/* Portfolio Modal */ +.portfolio-modal { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 1000; + display: flex; + align-items: center; + justify-content: center; + opacity: 0; + pointer-events: none; + transition: opacity 0.3s ease; +} + +.portfolio-modal.is-open { + opacity: 1; + pointer-events: auto; +} + +.portfolio-modal__overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(15, 23, 42, 0.85); + backdrop-filter: blur(4px); +} + +.portfolio-modal__container { + position: relative; + width: 100%; + max-width: 1400px; + padding: 2rem; + z-index: 1001; +} + +.portfolio-modal__close { + position: absolute; + top: -1.5rem; + right: 2rem; + background: none; + border: none; + color: #fff; + font-size: 3rem; + line-height: 1; + cursor: pointer; + z-index: 1002; + transition: color 0.2s ease; +} + +.portfolio-modal__close:hover { + color: var(--accent); +} + +.portfolio-modal__track { + display: flex; + align-items: flex-start; + gap: 1.5rem; + overflow-x: auto; + cursor: grab; + /* Поля под тень: при overflow-x: auto браузер обрезает всё за пределами области прокрутки прямоугольником */ + padding: 1rem 1.75rem 2.75rem; + scrollbar-width: none; /* Firefox */ +} + +.portfolio-modal__track::-webkit-scrollbar { + display: none; /* Chrome/Safari */ +} + +.portfolio-modal__track.is-dragging { + cursor: grabbing; +} + +.portfolio-modal__slide { + flex-shrink: 0; + line-height: 0; +} + +.portfolio-modal__track img { + height: 70vh; + max-height: 700px; + width: auto; + display: block; + object-fit: contain; + border-radius: 0.5rem; + clip-path: inset(0 round 0.5rem); + -webkit-clip-path: inset(0 round 0.5rem); + filter: drop-shadow(0 14px 28px rgba(0, 0, 0, 0.28)) + drop-shadow(0 4px 10px rgba(0, 0, 0, 0.18)); + user-select: none; + pointer-events: none; +} + +.price-accordion { + margin-top: 1.25rem; + display: grid; + gap: 1rem; +} + +.owner-tools { + margin-top: 1.25rem; + display: flex; + flex-wrap: wrap; + gap: 0.6rem; +} + +.price-category { + background: var(--surface); + border: 1px solid var(--border); + border-radius: 0.8rem; + box-shadow: var(--shadow); + overflow: hidden; +} + +.price-category__toggle { + width: 100%; + border: none; + background: #fff; + color: var(--heading); + text-align: left; + font: inherit; + font-weight: 700; + font-size: 1rem; + padding: 1rem 1.1rem; + cursor: pointer; + display: flex; + justify-content: space-between; + align-items: center; +} + +.price-category__toggle::after { + content: "+"; + color: var(--accent); + font-size: 1.2rem; + line-height: 1; +} + +.price-category__toggle[aria-expanded="true"]::after { + content: "-"; +} + +.price-list-wrap { + border-top: 1px solid var(--border); + overflow-x: auto; +} + +.price-list-wrap--nested { + overflow: visible; + padding: 0.55rem 0.7rem 0.85rem; + background: #f5f8fd; +} + +.price-subcategories { + display: grid; + gap: 0.55rem; +} + +.price-subcategory { + background: var(--surface); + border: 1px solid var(--border); + border-radius: 0.7rem; + overflow: hidden; +} + +.price-subcategory__toggle { + width: 100%; + border: none; + background: #fff; + color: var(--heading); + text-align: left; + font: inherit; + font-weight: 600; + font-size: 0.95rem; + padding: 0.85rem 1rem; + cursor: pointer; + display: flex; + justify-content: space-between; + align-items: center; +} + +.price-subcategory__toggle::after { + content: "+"; + color: var(--accent); + font-size: 1.1rem; + line-height: 1; +} + +.price-subcategory__toggle[aria-expanded="true"]::after { + content: "-"; +} + +.price-subcategory .price-list-wrap { + border-top: 1px solid var(--border); + background: #fff; +} + +.price-list { + list-style: none; + margin: 0; + padding: 0 1.1rem 1rem; +} + +.price-list li { + display: grid; + grid-template-columns: minmax(240px, 1fr) 9.5rem 5.2rem 6rem; + gap: 0; + padding: 0; + border-bottom: 1px solid var(--border); +} + +.price-list li:last-child { + border-bottom: none; +} + +.price-list li span { + min-width: 0; + padding: 0.62rem 0.7rem; +} + +.price-list li > span + span { + border-left: 1px solid var(--border); +} + +.price-list__head { + font-weight: 700; + color: var(--heading); + background: #eef3fc; +} + +.price-list li span:first-child { + text-align: left; +} + +.price-list li span:nth-child(n + 2) { + text-align: right; + white-space: nowrap; +} + +.price-list__head span:nth-child(n + 2) { + text-align: center; +} + +.price-list li:not(.price-list__head) span:nth-child(2) { + color: var(--accent); + font-weight: 700; +} + +.price-list li:not(.price-list__head) > span:first-child, +.price-list li:not(.price-list__head) > span:nth-child(3), +.price-list li:not(.price-list__head) > span:nth-child(4) { + color: var(--header-muted); + font-weight: 500; +} + +.price-value__mobile { + display: none; +} + +.contact-form { + padding: 1.25rem; + margin-top: 1.1rem; + display: grid; + gap: 0.9rem; +} + +.contact-form label { + display: grid; + gap: 0.35rem; + font-weight: 600; + color: var(--heading); +} + +.contact-form input { + border: 1px solid var(--border); + border-radius: 0.7rem; + padding: 0.8rem 0.9rem; + font-size: 1rem; + color: var(--text); + background: var(--surface); +} + +.contact-form input::placeholder { + color: rgba(61, 79, 112, 0.48); + opacity: 1; +} + +.contact-form input:focus { + outline: 2px solid var(--accent-soft); + border-color: var(--accent); +} + +.contact-form input:-webkit-autofill, +.contact-form input:-webkit-autofill:hover, +.contact-form input:-webkit-autofill:focus { + -webkit-text-fill-color: var(--text); + box-shadow: 0 0 0 1000px var(--surface) inset; + transition: background-color 9999s ease-out; +} + +.contact-form__consent { + display: block; +} + +.contact-form__consent-inner { + display: flex; + gap: 0.65rem; + align-items: flex-start; + font-weight: 400; + font-size: 0.9rem; + line-height: 1.5; + color: var(--heading); +} + +.contact-form__consent input[type="checkbox"] { + width: auto; + height: auto; + margin-top: 0.2rem; + flex-shrink: 0; + accent-color: var(--accent); +} + +.contact-form__consent a { + color: var(--header-text); + font-weight: 600; + text-decoration: underline; + text-decoration-color: rgba(45, 75, 132, 0.38); + text-underline-offset: 0.15em; +} + +.contact-form__consent a:hover { + color: var(--accent-dark); + text-decoration-color: var(--accent); +} + +.contact-form__consent--error .contact-form__consent-inner { + outline: 2px solid var(--accent-soft); + outline-offset: 0.25rem; + border-radius: 0.65rem; + background: rgba(95, 127, 184, 0.06); +} + +.form-toast { + position: fixed; + top: calc(7rem + env(safe-area-inset-top, 0px)); + bottom: auto; + left: 50%; + z-index: 45; + max-width: min(26rem, calc(100% - 2rem)); + padding: 1rem 1.35rem; + margin: 0; + background: var(--surface); + color: var(--text); + font-size: 0.95rem; + line-height: 1.45; + font-weight: 500; + border-radius: 1rem; + border: 1px solid var(--border); + box-shadow: var(--shadow), 0 0 0 1px rgba(95, 127, 184, 0.12); + border-left: 4px solid var(--accent); + cursor: pointer; + transform: translate(-50%, calc(-100% - 1.25rem)); + opacity: 0; + visibility: hidden; + transition: transform 0.38s ease, opacity 0.38s ease, visibility 0.38s ease; +} + +.form-toast.is-visible { + transform: translate(-50%, 0); + opacity: 1; + visibility: visible; +} + +.form-toast--warning { + border-left-color: var(--accent); + box-shadow: var(--shadow), 0 0 0 1px rgba(95, 127, 184, 0.12); +} + +.form-toast--success { + border-left-color: #4a9078; + background: linear-gradient(180deg, #ffffff 0%, #f2f7f5 100%); + border-color: rgba(74, 144, 120, 0.22); + box-shadow: var(--shadow), 0 0 0 1px rgba(74, 144, 120, 0.1); + color: var(--heading); +} + +.footer { + border-top: 1px solid rgba(255, 255, 255, 0.12); + background: var(--footer-bg); + padding: 1.2rem 0; +} + +.footer__inner { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: space-between; + gap: 0.75rem 1.25rem; +} + +.footer__brand { + display: flex; + flex-direction: column; + gap: 0.2rem; + color: var(--header-muted); +} + +.footer__copy { + margin: 0; + font-size: 0.95rem; +} + +.footer__geo, +.footer__ip { + margin: 0; + font-size: 0.85rem; + font-weight: 500; +} + +.footer__disclaimer { + flex-basis: 100%; + margin: 0.2rem 0 0; + font-size: 0.82rem; + font-weight: 500; + line-height: 1.45; + color: var(--muted); +} + +.footer__legal { + display: flex; + flex-direction: column; + align-items: flex-end; + gap: 0.35rem; +} + +.footer__link { + color: var(--header-text); + font-size: 0.9rem; + font-weight: 600; + text-decoration: none; + border-bottom: 1px solid rgba(45, 75, 132, 0.35); + transition: color 0.2s ease, border-color 0.2s ease; +} + +.footer__link:hover { + color: var(--accent-dark); + border-bottom-color: var(--accent); +} + +.privacy-doc { + padding-bottom: 3rem; +} + +.privacy-doc h1 { + margin-bottom: 1.25rem; +} + +.privacy-doc h2 { + margin-top: 2rem; + margin-bottom: 0.75rem; + font-size: 1.15rem; +} + +.privacy-doc p, +.privacy-doc li { + color: var(--text); + font-size: 0.95rem; + line-height: 1.55; + margin: 0 0 0.65rem; +} + +.privacy-doc ul { + margin: 0 0 0.85rem; + padding-left: 1.35rem; +} + +.privacy-doc li { + margin-bottom: 0.4rem; +} + +.privacy-doc__lead { + font-weight: 500; +} + +.privacy-doc__meta { + color: var(--muted); + font-size: 0.9rem; +} + +.privacy-doc a { + color: var(--accent); + font-weight: 600; +} + +.logo-switcher { + position: fixed; + left: 1rem; + bottom: 1rem; + z-index: 35; + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 0.5rem; +} + +.logo-switcher__toggle { + background: var(--surface); + color: var(--text); + border: 1px solid var(--border); + border-radius: 999px; + padding: 0.4rem 0.8rem; + font: inherit; + font-size: 0.85rem; + font-weight: 600; + cursor: pointer; + box-shadow: var(--shadow); +} + +.switcher-tabs { + display: flex; + gap: 0.5rem; + width: 100%; +} + +.switcher-tab { + flex: 1; + background: var(--surface); + border: 1px solid var(--border); + padding: 0.4rem; + border-radius: 0.5rem; + font-weight: 600; + font-size: 0.8rem; + cursor: pointer; + color: var(--text); +} + +.switcher-tab.is-active { + background: var(--accent); + color: #fff; + border-color: var(--accent); +} + +.switcher-panels { + display: flex; + flex-direction: column; + width: 100%; +} + +.logo-switcher__list { + display: none; + flex-wrap: wrap; + max-width: 90vw; + max-height: 35vh; + overflow-y: auto; + gap: 0.35rem; + padding: 0.6rem; + border-radius: 1rem; + background: rgba(255, 255, 255, 0.95); + border: 1px solid var(--border); + box-shadow: var(--shadow); +} + +.logo-switcher__list.is-active { + display: flex; +} + +.logo-switcher.is-collapsed .switcher-tabs, +.logo-switcher.is-collapsed .switcher-panels { + display: none; +} + +.logo-switcher__btn { + border: 1px solid var(--border); + background: #fff; + color: var(--text); + border-radius: 999px; + padding: 0.4rem 0.65rem; + font: inherit; + font-size: 0.8rem; + cursor: pointer; +} + +.logo-switcher__btn.is-active { + background: var(--accent); + border-color: var(--accent); + color: #fff; +} + +.floating-call { + position: fixed; + right: 1rem; + bottom: 1rem; + z-index: 30; + background: var(--accent); + color: #fff; + text-decoration: none; + font-weight: 700; + padding: 0.8rem 1rem; + border-radius: 999px; + box-shadow: var(--shadow); +} + +.floating-call:hover { + background: var(--accent-dark); +} + +.reveal { + opacity: 0; + transform: translateY(24px); + transition: opacity 0.6s ease, transform 0.6s ease; +} + +.reveal.is-visible { + opacity: 1; + transform: translateY(0); +} + +/* Первый экран: без translateY — иначе при липкой шапке на время анимации видна полоска фона */ +.hero.reveal { + opacity: 0; + transform: none; + transition: opacity 0.6s ease; +} + +.hero.reveal.is-visible { + opacity: 1; + transform: none; +} + +@media (min-width: 720px) { + .header__inner { + min-height: 5.5rem; + padding: 1.2rem 0; + } + + .header__left { + width: auto; + gap: 0.75rem; + flex-wrap: wrap; + } + + .header__nav { + display: flex; + gap: 0.55rem; + flex: initial; + } + + .logo { + height: 60px; + margin-right: 150px; + } + + .logo img { + height: 60px; + transform: scale(2.2); + } + + .header-link { + font-size: 1.1rem; + padding: 0.5rem 0.8rem; + } + + .header__actions { + width: auto; + margin-left: auto; + flex-wrap: wrap; + justify-content: flex-end; + } + + .header__contact { + align-items: flex-end; + } + + .phone-link { + font-size: 1.1rem; + } + + .header .btn--outline { + font-size: 1.05rem; + padding: 0.75rem 1.15rem; + } + + .hero h1 { + white-space: nowrap; + line-height: 1.15; + } + + .hero .container { + padding: 2.4rem 2rem; + } + + .gallery__grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .services__grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .reviews__grid { + grid-template-columns: repeat(3, minmax(0, 1fr)); + } + + .prices-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .header__actions { + gap: 0.9rem; + } + + .price-list li { + grid-template-columns: minmax(300px, 1fr) 10rem 5.4rem 6.2rem; + align-items: center; + } + + .contact-form { + grid-template-columns: 1fr 1fr auto; + align-items: end; + } + + .contact-form > label:nth-of-type(1) { + grid-column: 1; + grid-row: 1; + } + + .contact-form > label:nth-of-type(2) { + grid-column: 2; + grid-row: 1; + } + + .contact-form > label.contact-form__consent { + grid-column: 1 / -1; + grid-row: 2; + align-self: start; + } + + .contact-form > .btn--primary { + grid-column: 3; + grid-row: 1; + } + + .floating-call { + display: none; + } + + .logo-switcher { + left: auto; + right: 1rem; + align-items: flex-end; + } + + .logo-switcher__list { + max-width: 300px; + max-height: 60vh; + justify-content: flex-end; + } + + .switcher-tabs { + justify-content: flex-end; + } + +} + +@media (max-width: 390px) { + .logo { + height: 50px; + } + + .logo img { + height: 50px; + transform: scale(1); + } + + .header__nav { + gap: 0.25rem; + } + + .header-link { + font-size: 0.9rem; + padding: 0.4rem 0.3rem; + } + + .header-link--current { + transform: none; + animation: none; + } +} + +@media (max-width: 560px) { + .container { + width: min(1120px, calc(100% - 2rem)); + } + + .section { + padding: 2.45rem 0; + } + + .header__inner { + gap: 0.55rem; + padding: 0.62rem 0; + } + + .header__left { + gap: 0.5rem; + } + + .header__nav { + gap: 0.2rem; + } + + .header-link { + min-height: 2.1rem; + font-size: 0.88rem; + padding: 0.34rem 0.22rem; + letter-spacing: -0.01em; + line-height: 1.1; + } + + .header__actions { + gap: 0.45rem; + } + + .phone-link { + font-size: 0.98rem; + } + + .header__meta, + .header__geo { + font-size: 0.74rem; + } + + .header .btn--outline { + padding: 0.52rem 0.82rem; + font-size: 0.95rem; + flex-shrink: 0; + } + + .hero { + padding: 2rem 0; + } + + .hero .container { + width: min(850px, calc(100% - 1.4rem)); + padding: 1.35rem 0.95rem; + gap: 0.7rem; + } + + .hero h1 { + font-size: clamp(1.45rem, 6.4vw, 1.95rem); + } + + .hero p { + font-size: 0.95rem; + line-height: 1.62; + } + + .contact-form { + padding: 1rem; + } + + .price-list { + padding: 0 0.75rem 0.75rem; + } + + .price-category__toggle { + font-size: 0.94rem; + padding: 0.85rem 0.95rem; + } + + .price-list li { + grid-template-columns: minmax(0, 1fr) 5.1rem 3.6rem 3.9rem; + gap: 0; + border-bottom: 1px solid var(--border); + } + + .price-list li span { + border-left: none; + padding: 0.46rem 0.3rem; + font-size: 0.88rem; + } + + .price-list li > span + span { + border-left: 1px solid var(--border); + } + + .price-list li span:first-child { + text-align: left; + white-space: normal; + } + + .price-list li > span:nth-child(n + 2) { + text-align: right; + white-space: nowrap; + } + + .price-list__head > span { + white-space: nowrap; + } + + .price-value__full { + display: none; + } + + .price-value__mobile { + display: inline; + } + + .price-list__head > span:nth-child(n + 2) { + text-align: center; + font-size: 0.82rem; + } + + .price-list li:not(.price-list__head) span:nth-child(2) { + color: var(--accent); + font-weight: 700; + } + + main { + padding-bottom: calc(4.6rem + env(safe-area-inset-bottom, 0px)); + } + + .floating-call { + right: 0.75rem; + bottom: calc(0.75rem + env(safe-area-inset-bottom, 0px)); + padding: 0.72rem 0.92rem; + font-size: 0.94rem; + } +} + +@media (max-width: 390px) { + .container { + width: min(1120px, calc(100% - 1.7rem)); + } + + .hero .container { + width: min(850px, calc(100% - 1rem)); + } + + .price-list { + padding: 0 0.55rem 0.65rem; + } + + .price-list li span { + padding: 0.44rem 0.24rem; + font-size: 0.83rem; + } + + .price-list__head > span { + font-size: 0.74rem; + letter-spacing: -0.01em; + } + + .price-list li > span:nth-child(n + 2), + .price-list__head > span:nth-child(n + 2) { + text-align: center; + } +} diff --git a/submit-config.php b/submit-config.php new file mode 100644 index 0000000..6ec300c --- /dev/null +++ b/submit-config.php @@ -0,0 +1,10 @@ + false, + "message" => "Метод не поддерживается", + ], JSON_UNESCAPED_UNICODE); + exit; +} + +function respondError(string $message, int $code = 400): void +{ + http_response_code($code); + echo json_encode([ + "ok" => false, + "message" => $message, + ], JSON_UNESCAPED_UNICODE); + exit; +} + +function sanitizeInput(string $value): string +{ + $clean = trim($value); + return preg_replace("/[\\x00-\\x1F\\x7F]/u", "", $clean) ?? ""; +} + +function respondSuccess(): void +{ + echo json_encode([ + "ok" => true, + ], JSON_UNESCAPED_UNICODE); + exit; +} + +function loadRateLimitState(string $filePath): array +{ + if (!is_file($filePath)) { + return []; + } + $raw = file_get_contents($filePath); + if ($raw === false || $raw === "") { + return []; + } + $data = json_decode($raw, true); + return is_array($data) ? $data : []; +} + +function storeRateLimitState(string $filePath, array $state): void +{ + $encoded = json_encode($state, JSON_UNESCAPED_UNICODE); + if ($encoded === false) { + return; + } + @file_put_contents($filePath, $encoded, LOCK_EX); +} + +function isRateLimited(string $ip, int $nowTs): bool +{ + $limitWindowSeconds = 10 * 60; // 10 минут + $limitAttempts = 8; + $rateFile = rtrim(sys_get_temp_dir(), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . "frieze_spb_rate_limit.json"; + + $state = loadRateLimitState($rateFile); + $freshState = []; + + foreach ($state as $storedIp => $timestamps) { + if (!is_array($timestamps)) { + continue; + } + $validTs = []; + foreach ($timestamps as $ts) { + $intTs = (int)$ts; + if ($intTs >= $nowTs - $limitWindowSeconds) { + $validTs[] = $intTs; + } + } + if ($validTs) { + $freshState[$storedIp] = $validTs; + } + } + + $currentIpHits = $freshState[$ip] ?? []; + if (count($currentIpHits) >= $limitAttempts) { + storeRateLimitState($rateFile, $freshState); + return true; + } + + $currentIpHits[] = $nowTs; + $freshState[$ip] = $currentIpHits; + storeRateLimitState($rateFile, $freshState); + return false; +} + +$name = sanitizeInput((string)($_POST["name"] ?? "")); +$phoneRaw = sanitizeInput((string)($_POST["phone"] ?? "")); +$consent = (string)($_POST["consent"] ?? ""); +$honeypot = sanitizeInput((string)($_POST["contact_fax"] ?? ($_POST["website"] ?? ""))); +$startedAtRaw = sanitizeInput((string)($_POST["started_at"] ?? "")); + +if ($honeypot !== "") { + respondError("Не удалось отправить заявку. Пожалуйста, попробуйте еще раз.", 400); +} + +$nowTs = time(); +$formMinFillSeconds = 0.4; +$startedAtMs = (int)$startedAtRaw; +if ($startedAtMs > 0) { + $elapsedSeconds = ((int)floor(microtime(true) * 1000) - $startedAtMs) / 1000; + if ($elapsedSeconds < $formMinFillSeconds) { + respondError("Слишком быстрая отправка формы. Попробуйте еще раз.", 429); + } +} + +$clientIp = (string)($_SERVER["REMOTE_ADDR"] ?? "unknown"); +if (isRateLimited($clientIp, $nowTs)) { + respondError("Слишком много заявок за короткое время. Повторите попытку позже.", 429); +} + +if ($name === "") { + respondError("Укажите имя"); +} + +// Нормализуем имя: убираем лишние пробелы внутри. +$name = preg_replace("/\\s+/u", " ", $name) ?? $name; + +if (mb_strlen($name) < 2 || mb_strlen($name) > 80) { + respondError("Имя должно быть от 2 до 80 символов"); +} + +if (!preg_match("/^[\\p{L}\\s-]+$/u", $name)) { + respondError("Имя может содержать только буквы, пробелы и дефис"); +} + +$phoneDigits = preg_replace("/\\D+/", "", $phoneRaw) ?? ""; +if (strlen($phoneDigits) !== 11 || strpos($phoneDigits, "7") !== 0) { + respondError("Укажите корректный телефон"); +} + +if ($consent !== "1") { + respondError("Необходимо согласие на обработку данных"); +} + +// Укажите email в файле submit-config.php (предпочтительно для REG.RU) +// или в переменной окружения FORM_RECIPIENT_EMAIL. +$recipientEmail = ""; +$senderEmail = ""; +$configFile = __DIR__ . "/submit-config.php"; +if (is_file($configFile)) { + require $configFile; +} + +if ($recipientEmail === "") { + $recipientEmail = getenv("FORM_RECIPIENT_EMAIL") ?: "you@example.com"; +} + +if (substr($recipientEmail, -11) === "@example.com") { + respondError("Не настроен email получателя заявок. Укажите адрес в submit-config.php.", 500); +} + +$host = strtolower((string)($_SERVER["HTTP_HOST"] ?? "frieze-spb.ru")); +$host = preg_replace("/:\\d+$/", "", $host) ?? "frieze-spb.ru"; +$host = preg_replace("/[^a-z0-9\\.-]/", "", $host) ?? "frieze-spb.ru"; +if ($host === "") { + $host = "frieze-spb.ru"; +} +$defaultSenderEmail = "noreply@" . $host; +$fromEmail = $senderEmail !== "" ? $senderEmail : $defaultSenderEmail; +if (!filter_var($fromEmail, FILTER_VALIDATE_EMAIL)) { + $fromEmail = $defaultSenderEmail; +} + +$subject = "Новая заявка с сайта frieze-spb.ru"; +$encodedSubject = "=?UTF-8?B?" . base64_encode($subject) . "?="; + +$messageLines = [ + "Новая заявка с сайта", + "", + "Имя: " . $name, + "Телефон: " . $phoneRaw, + "Телефон (цифры): +" . $phoneDigits, + "Дата/время: " . date("d.m.Y H:i:s"), + "IP: " . (string)($_SERVER["REMOTE_ADDR"] ?? "не определен"), + "User-Agent: " . (string)($_SERVER["HTTP_USER_AGENT"] ?? "не определен"), +]; + +$body = implode("\r\n", $messageLines); + +$headers = [ + "MIME-Version: 1.0", + "Content-Type: text/plain; charset=UTF-8", + "From: Frieze Site <" . $fromEmail . ">", + "Reply-To: " . $recipientEmail, + "X-Mailer: PHP/" . PHP_VERSION, +]; + +$sent = mail($recipientEmail, $encodedSubject, $body, implode("\r\n", $headers), "-f" . $fromEmail); + +if (!$sent) { + respondError("Не удалось отправить заявку. Попробуйте позже или позвоните по телефону.", 500); +} + +respondSuccess(); diff --git a/update-prices.py b/update-prices.py new file mode 100644 index 0000000..d9b9506 --- /dev/null +++ b/update-prices.py @@ -0,0 +1,465 @@ +# -*- coding: utf-8 -*- +""" +Синхронизация прайса сайта с Excel. + +Как пользоваться: +1. Откройте prices.xlsx и правьте таблицу на листе "Цены" +2. Запустите файл "обновить-цены.bat" (двойной клик) +3. Залейте на хостинг обновлённый prices-data.js + +Колонки Excel: +A Раздел | B Подкатегория | C Название | D Цена (число) | E Ед.изм | F Кол-во | G Показывать (Да/Нет) +""" + +from __future__ import annotations + +import json +import re +import sys +from pathlib import Path + +try: + import openpyxl + from openpyxl.styles import Alignment, Font, PatternFill, Border, Side + from openpyxl.utils import get_column_letter + from openpyxl.worksheet.datavalidation import DataValidation +except ImportError: + print("Нужна библиотека openpyxl. Установите: pip install openpyxl") + sys.exit(1) + +ROOT = Path(__file__).resolve().parent +XLSX_PATH = ROOT / "prices.xlsx" +JS_PATH = ROOT / "prices-data.js" + +YES_VALUES = {"да", "yes", "y", "1", "true", "+", "д"} + + +def js_str(value: str) -> str: + return json.dumps(value, ensure_ascii=False) + + +def parse_prices_js(text: str) -> list[dict]: + """Parse controlled prices-data.js format into nested Python structure.""" + cats: list[dict] = [] + + # Top-level categories: either with children or with items + # Split by top-level objects roughly via brace matching after window assignment + body = re.sub(r"^[\s\S]*?window\.PRICES_DATA\s*=\s*", "", text.strip()) + body = re.sub(r";\s*$", "", body).strip() + if not (body.startswith("[") and body.endswith("]")): + raise ValueError("Некорректный формат prices-data.js") + + # Tokenize top-level category objects by scanning braces + inner = body[1:-1] + objects: list[str] = [] + depth = 0 + start = None + for i, ch in enumerate(inner): + if ch == "{": + if depth == 0: + start = i + depth += 1 + elif ch == "}": + depth -= 1 + if depth == 0 and start is not None: + objects.append(inner[start : i + 1]) + start = None + + def parse_items(blob: str) -> list[dict]: + items = [] + for item in re.finditer( + r'\{\s*name:\s*("(?:\\.|[^"\\])*")\s*,\s*price:\s*("(?:\\.|[^"\\])*")\s*,\s*amount:\s*("(?:\\.|[^"\\])*")\s*,\s*unit:\s*("(?:\\.|[^"\\])*")\s*\}', + blob, + ): + items.append( + { + "name": json.loads(item.group(1)), + "price": json.loads(item.group(2)), + "amount": json.loads(item.group(3)), + "unit": json.loads(item.group(4)), + } + ) + return items + + for obj in objects: + title_m = re.search(r'title:\s*("(?:\\.|[^"\\])*")', obj) + if not title_m: + continue + title = json.loads(title_m.group(1)) + children_m = re.search(r"children:\s*\[([\s\S]*)\]\s*$", obj.strip().rstrip("}")) + # Better: find children array bounds + children_idx = obj.find("children:") + + if children_idx != -1: + ch_start = obj.find("[", children_idx) + # find matching closing bracket for children array + depth = 0 + ch_end = None + for i in range(ch_start, len(obj)): + if obj[i] == "[": + depth += 1 + elif obj[i] == "]": + depth -= 1 + if depth == 0: + ch_end = i + break + children_blob = obj[ch_start + 1 : ch_end] if ch_end else "" + children = [] + # child objects + depth = 0 + start = None + for i, ch in enumerate(children_blob): + if ch == "{": + if depth == 0: + start = i + depth += 1 + elif ch == "}": + depth -= 1 + if depth == 0 and start is not None: + child_obj = children_blob[start : i + 1] + ctitle_m = re.search(r'title:\s*("(?:\\.|[^"\\])*")', child_obj) + if not ctitle_m: + continue + children.append( + { + "title": json.loads(ctitle_m.group(1)), + "items": parse_items(child_obj), + } + ) + start = None + cats.append({"title": title, "children": children}) + else: + cats.append({"title": title, "items": parse_items(obj)}) + + return cats + + +def fmt_price_number(value) -> str: + try: + num = float(value) + if num.is_integer(): + num = int(num) + return f"{num:,}".replace(",", " ") + except Exception: + return str(value).strip() + + +def extract_number_from_price(price: str): + """'от 1 200 ₽/м²' -> 1200""" + if price is None: + return "" + if isinstance(price, (int, float)): + return price + s = str(price) + s = re.sub(r"^от\s+", "", s, flags=re.I) + s = re.sub(r"\s*₽.*$", "", s) + s = s.replace(" ", "").replace("\u00a0", "") + s = s.replace(",", ".") + try: + num = float(s) + return int(num) if num.is_integer() else num + except Exception: + return s + + +def build_price_string(number, unit: str) -> str: + unit = (unit or "").strip() + num = fmt_price_number(number) + if unit and unit != "—": + return f"от {num} ₽/{unit}" + return f"от {num} ₽" + + +def flatten_for_excel(data: list[dict]) -> list[dict]: + rows = [] + for cat in data: + section = cat.get("title", "") + children = cat.get("children") or [] + if children: + for child in children: + sub = child.get("title", "") + for item in child.get("items") or []: + rows.append( + { + "section": section, + "subcategory": sub, + "name": item.get("name", ""), + "price": extract_number_from_price(item.get("price", "")), + "unit": item.get("unit", ""), + "amount": item.get("amount", "1"), + "show": "Да", + } + ) + else: + for item in cat.get("items") or []: + rows.append( + { + "section": section, + "subcategory": "", + "name": item.get("name", ""), + "price": extract_number_from_price(item.get("price", "")), + "unit": item.get("unit", ""), + "amount": item.get("amount", "1"), + "show": "Да", + } + ) + return rows + + +def export_xlsx_from_js() -> None: + if not JS_PATH.exists(): + print(f"Не найден {JS_PATH.name}") + sys.exit(1) + + data = parse_prices_js(JS_PATH.read_text(encoding="utf-8")) + rows = flatten_for_excel(data) + + wb = openpyxl.Workbook() + ws = wb.active + ws.title = "Цены" + + headers = ["Раздел", "Подкатегория", "Название работы", "Цена", "Ед.изм", "Кол-во", "Показывать"] + header_fill = PatternFill("solid", fgColor="1F4E79") + header_font = Font(color="FFFFFF", bold=True) + thin = Border( + left=Side(style="thin", color="BDD7EE"), + right=Side(style="thin", color="BDD7EE"), + top=Side(style="thin", color="BDD7EE"), + bottom=Side(style="thin", color="BDD7EE"), + ) + + for col, title in enumerate(headers, start=1): + cell = ws.cell(1, col, title) + cell.fill = header_fill + cell.font = header_font + cell.alignment = Alignment(horizontal="center", vertical="center", wrap_text=True) + + for i, row in enumerate(rows, start=2): + values = [ + row["section"], + row["subcategory"], + row["name"], + row["price"], + row["unit"], + row["amount"], + row["show"], + ] + for col, value in enumerate(values, start=1): + cell = ws.cell(i, col, value) + cell.border = thin + cell.alignment = Alignment(vertical="center", wrap_text=True) + if col == 4: + cell.alignment = Alignment(horizontal="right", vertical="center") + + widths = [34, 18, 70, 12, 10, 10, 12] + for i, width in enumerate(widths, start=1): + ws.column_dimensions[get_column_letter(i)].width = width + + ws.freeze_panes = "A2" + ws.auto_filter.ref = f"A1:G{len(rows) + 1}" + + dv = DataValidation(type="list", formula1='"Да,Нет"', allow_blank=False) + dv.error = "Выберите Да или Нет" + dv.errorTitle = "Показывать" + ws.add_data_validation(dv) + dv.add(f"G2:G{max(2, len(rows) + 200)}") + + # Instruction sheet + info = wb.create_sheet("Инструкция", 0) + info["A1"] = "Как обновлять цены на сайте" + info["A1"].font = Font(bold=True, size=14) + lines = [ + "", + "1) Редактируйте только лист «Цены».", + "2) Можно менять названия, цены, единицы, количество.", + "3) Чтобы временно скрыть позицию с сайта — поставьте в колонке «Показывать» значение «Нет».", + "4) Чтобы удалить позицию навсегда — удалите строку.", + "5) Чтобы добавить новую позицию — добавьте новую строку в конец таблицы.", + "6) Для разделов с подкатегориями заполняйте и «Раздел», и «Подкатегория»", + " (например: Раздел = Демонтажные работы, Подкатегория = Полы).", + "7) Для разделов без подкатегорий оставьте «Подкатегория» пустой", + " (например: Электромонтажные работы).", + "8) В колонке «Цена» пишите только число, например: 650", + " На сайте автоматически станет: от 650 ₽/шт (если ед.изм = шт).", + "9) После правок закройте Excel и запустите файл «обновить-цены.bat».", + "10) На хостинг REG.RU залейте обновлённый файл prices-data.js", + " (и при желании сам prices.xlsx — для вашего удобного хранения).", + "", + "Важно: сайт сам Excel не читает. Сначала bat обновляет prices-data.js, потом этот файл идёт на сайт.", + ] + for idx, line in enumerate(lines, start=2): + info[f"A{idx}"] = line + info.column_dimensions["A"].width = 110 + + wb.save(XLSX_PATH) + print(f"Создан/обновлён Excel: {XLSX_PATH.name} ({len(rows)} строк)") + + +def nest_from_rows(rows: list[dict]) -> list[dict]: + """Build nested PRICES_DATA from flat excel rows, preserving order.""" + result: list[dict] = [] + section_map: dict[str, dict] = {} + child_map: dict[tuple[str, str], dict] = {} + + for row in rows: + section = row["section"] + sub = row["subcategory"] + item = { + "name": row["name"], + "price": build_price_string(row["price"], row["unit"]), + "amount": str(row["amount"] or "1"), + "unit": row["unit"] or "—", + } + + if section not in section_map: + section_obj = {"title": section} + section_map[section] = section_obj + result.append(section_obj) + + section_obj = section_map[section] + + if sub: + key = (section, sub) + if key not in child_map: + if "children" not in section_obj: + section_obj["children"] = [] + child = {"title": sub, "items": []} + child_map[key] = child + section_obj["children"].append(child) + child_map[key]["items"].append(item) + else: + if "items" not in section_obj: + section_obj["items"] = [] + section_obj["items"].append(item) + + # Cleanup: if a section has children, drop empty items key + for section_obj in result: + if section_obj.get("children"): + section_obj.pop("items", None) + elif "items" not in section_obj: + section_obj["items"] = [] + + return result + + +def write_prices_js(data: list[dict]) -> None: + parts = ["window.PRICES_DATA = ["] + for ci, cat in enumerate(data): + parts.append(" {") + parts.append(f" title: {js_str(cat['title'])},") + if cat.get("children"): + parts.append(" children: [") + for si, sub in enumerate(cat["children"]): + parts.append(" {") + parts.append(f" title: {js_str(sub['title'])},") + parts.append(" items: [") + for ii, item in enumerate(sub["items"]): + comma = "," if ii < len(sub["items"]) - 1 else "" + parts.append( + " { " + f"name: {js_str(item['name'])}, " + f"price: {js_str(item['price'])}, " + f"amount: {js_str(item['amount'])}, " + f"unit: {js_str(item['unit'])} " + f"}}{comma}" + ) + parts.append(" ]") + parts.append(" }" + ("," if si < len(cat["children"]) - 1 else "")) + parts.append(" ]") + else: + parts.append(" items: [") + for ii, item in enumerate(cat.get("items") or []): + comma = "," if ii < len(cat["items"]) - 1 else "" + parts.append( + " { " + f"name: {js_str(item['name'])}, " + f"price: {js_str(item['price'])}, " + f"amount: {js_str(item['amount'])}, " + f"unit: {js_str(item['unit'])} " + f"}}{comma}" + ) + parts.append(" ]") + parts.append(" }" + ("," if ci < len(data) - 1 else "")) + parts.append("];") + parts.append("") + JS_PATH.write_text("\n".join(parts), encoding="utf-8") + + +def import_xlsx_to_js() -> None: + if not XLSX_PATH.exists(): + print(f"Не найден {XLSX_PATH.name}. Сначала создайте его командой: python update-prices.py --init") + sys.exit(1) + + wb = openpyxl.load_workbook(XLSX_PATH, data_only=True) + if "Цены" not in wb.sheetnames: + print("В Excel нет листа «Цены»") + sys.exit(1) + ws = wb["Цены"] + + rows = [] + skipped = 0 + for r in range(2, ws.max_row + 1): + section = ws.cell(r, 1).value + sub = ws.cell(r, 2).value + name = ws.cell(r, 3).value + price = ws.cell(r, 4).value + unit = ws.cell(r, 5).value + amount = ws.cell(r, 6).value + show = ws.cell(r, 7).value + + section = str(section).strip() if section is not None else "" + sub = str(sub).strip() if sub is not None else "" + name = str(name).strip() if name is not None else "" + unit = str(unit).strip() if unit is not None else "" + amount = str(amount).strip() if amount is not None else "1" + show_s = str(show).strip().lower() if show is not None else "да" + + if not section and not name: + continue + if not name: + skipped += 1 + continue + if show_s not in YES_VALUES: + skipped += 1 + continue + if price in (None, ""): + skipped += 1 + continue + + rows.append( + { + "section": section or "Прочее", + "subcategory": sub, + "name": name, + "price": price, + "unit": unit, + "amount": amount or "1", + } + ) + + data = nest_from_rows(rows) + write_prices_js(data) + total_items = sum( + len(c.get("items") or []) + + sum(len(ch.get("items") or []) for ch in (c.get("children") or [])) + for c in data + ) + print(f"Готово: {JS_PATH.name}") + print(f"Разделов: {len(data)}") + print(f"Позиций на сайте: {total_items}") + if skipped: + print(f"Пропущено строк (скрытые/пустые): {skipped}") + print("Теперь залейте prices-data.js на хостинг REG.RU") + + +def main() -> None: + args = sys.argv[1:] + if "--init" in args or "--export" in args or not XLSX_PATH.exists(): + export_xlsx_from_js() + if "--init" in args or "--export" in args: + return + import_xlsx_to_js() + + +if __name__ == "__main__": + main() diff --git a/обновить-цены.bat b/обновить-цены.bat new file mode 100644 index 0000000..552ab3b --- /dev/null +++ b/обновить-цены.bat @@ -0,0 +1,60 @@ +@echo off +chcp 65001 >nul +cd /d "%~dp0" + +echo ================================ +echo Обновление прайса сайта из Excel +echo ================================ +echo. + +where python >nul 2>nul +if errorlevel 1 ( + echo [Ошибка] Python не найден. + echo Установите Python с https://www.python.org/downloads/ + echo и отметьте галочку "Add python.exe to PATH". + echo. + pause + exit /b 1 +) + +python -c "import openpyxl" >nul 2>nul +if errorlevel 1 ( + echo Устанавливаю библиотеку openpyxl... + python -m pip install openpyxl + if errorlevel 1 ( + echo [Ошибка] Не удалось установить openpyxl. + pause + exit /b 1 + ) +) + +if not exist "prices.xlsx" ( + echo Excel ещё нет — создаю prices.xlsx из текущего прайса сайта... + python update-prices.py --init + if errorlevel 1 ( + echo [Ошибка] Не удалось создать prices.xlsx + pause + exit /b 1 + ) + echo. + echo Создан файл prices.xlsx + echo Откройте его, отредактируйте и снова запустите этот bat. + echo. + pause + exit /b 0 +) + +python update-prices.py +if errorlevel 1 ( + echo. + echo [Ошибка] Обновление не выполнено. + pause + exit /b 1 +) + +echo. +echo Готово. +echo 1^) Проверьте сайт локально на странице Цены +echo 2^) Залейте на REG.RU файл prices-data.js +echo. +pause