Kgshop commited on
Commit
26922f2
·
verified ·
1 Parent(s): d95148f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +309 -195
app.py CHANGED
@@ -144,131 +144,155 @@ LANDING_TEMPLATE = '''
144
  <title>ОсОО "Раина" - Вентиляция и Кондиционирование</title>
145
  <meta name="description" content="Профессиональные услуги по проектированию, монтажу и обслуживанию систем вентиляции и кондиционирования в Кыргызстане. 15 лет опыта, более 1000 проектов.">
146
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
147
- <link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;600;700&display=swap" rel="stylesheet">
148
  <style>
149
  :root {
150
- --dark-bg: #12121c; --card-bg: #1a1a2e; --primary-color: #a955ff;
151
- --secondary-color: #6a0dad; --text-color: #e0e0e0; --text-muted: #a0a0b0;
152
- --accent-glow: rgba(169, 85, 255, 0.3);
 
 
 
 
 
153
  }
154
  * { margin: 0; padding: 0; box-sizing: border-box; scroll-behavior: smooth; }
155
  body { font-family: 'Montserrat', sans-serif; background-color: var(--dark-bg); color: var(--text-color); line-height: 1.7; font-size: 16px; }
156
  .container { max-width: 1200px; margin: 0 auto; padding: 0 20px; }
157
- section { padding: clamp(3.5rem, 8vw, 5rem) 0; overflow: hidden; }
158
- h1, h2, h3 { font-weight: 700; color: #fff; line-height: 1.3; }
159
- h1 { font-size: clamp(2.2rem, 6vw, 4rem); }
160
- h2 { font-size: clamp(2rem, 5vw, 3rem); text-align: center; margin-bottom: 60px; position: relative; }
161
- h2::after { content: ''; display: block; width: 80px; height: 4px; background: linear-gradient(90deg, var(--primary-color), var(--secondary-color)); margin: 15px auto 0; border-radius: 2px; }
162
- h3 { font-size: clamp(1.2rem, 3vw, 1.5rem); color: var(--primary-color); margin-bottom: 15px; }
163
- p { margin-bottom: 1rem; color: var(--text-muted); }
164
- .btn { display: inline-block; padding: 12px 28px; background: linear-gradient(90deg, var(--primary-color), var(--secondary-color)); color: #fff; border-radius: 50px; text-decoration: none; font-weight: 600; transition: all 0.3s ease; box-shadow: 0 4px 15px var(--accent-glow); }
165
- .btn:hover { transform: translateY(-3px) scale(1.05); box-shadow: 0 8px 25px var(--accent-glow); }
166
- .header { position: fixed; top: 0; left: 0; width: 100%; z-index: 1000; padding: 15px 0; background-color: rgba(18, 18, 28, 0.85); backdrop-filter: blur(10px); transition: all 0.3s ease; }
167
- .header.scrolled { padding: 10px 0; box-shadow: 0 2px 10px rgba(0,0,0,0.3); }
168
  .navbar { display: flex; justify-content: space-between; align-items: center; }
169
- .logo { font-size: clamp(1.5rem, 4vw, 1.8rem); font-weight: 700; color: #fff; text-decoration: none; }
170
- .nav-links { display: flex; gap: 30px; list-style: none; }
171
- .nav-links a { color: var(--text-color); text-decoration: none; font-weight: 600; transition: color 0.3s ease; }
172
- .nav-links a:hover { color: var(--primary-color); }
173
- .menu-toggle { display: none; font-size: 1.5rem; cursor: pointer; border: none; background: none; color: white; }
174
- #hero { min-height: 100vh; display: flex; align-items: center; background-image: linear-gradient(rgba(18, 18, 28, 0.7), rgba(18, 18, 28, 1)), url(https://images.unsplash.com/photo-1558221639-2c7158995165?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1740&q=80); background-size: cover; background-position: center; }
175
- .hero-content { text-align: center; max-width: 800px; margin: 0 auto; }
176
- .hero-content p { font-size: clamp(1rem, 2.5vw, 1.2rem); margin: 30px 0; max-width: 600px; margin-left: auto; margin-right: auto;}
177
- .grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 60px; align-items: center; }
178
- .about-img { width: 100%; border-radius: 15px; box-shadow: 0 10px 30px rgba(0,0,0,0.4); }
179
- .services-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 30px; }
180
- .service-card, .turnkey-card { background-color: var(--card-bg); padding: 30px; border-radius: 15px; border: 1px solid #2a2a4a; transition: all 0.3s ease; }
181
- .service-card:hover, .turnkey-card:hover { transform: translateY(-5px); border-color: var(--primary-color); box-shadow: 0 8px 25px var(--accent-glow); }
182
- .service-card i { font-size: 2.5rem; color: var(--primary-color); margin-bottom: 20px; }
 
 
 
 
183
  .turnkey-card { padding: 0; display: flex; flex-direction: column; }
184
- .turnkey-img { width: 100%; height: 200px; object-fit: cover; border-radius: 15px 15px 0 0; }
185
- .turnkey-content { padding: 30px; flex-grow: 1;}
186
- .equipment-filters { display: flex; justify-content: center; flex-wrap: wrap; gap: 15px; margin-bottom: 40px; }
187
- .filter-btn { padding: 8px 20px; border: 1px solid var(--primary-color); background-color: transparent; color: var(--primary-color); border-radius: 20px; cursor: pointer; transition: all 0.3s; }
188
- .filter-btn.active, .filter-btn:hover { background-color: var(--primary-color); color: #fff; }
189
- .equipment-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 30px; }
190
- .equipment-card { background-color: var(--card-bg); border-radius: 15px; overflow: hidden; text-align: center; padding: 20px; border: 1px solid #2a2a4a; transition: all 0.3s ease; cursor: pointer; }
191
- .equipment-card:hover { transform: translateY(-5px); border-color: var(--primary-color); box-shadow: 0 8px 25px var(--accent-glow); }
192
- .equipment-card img { width: 100%; height: 180px; object-fit: contain; margin-bottom: 15px; }
193
- .equipment-card h3 { font-size: 1.2rem; }
194
- .equipment-card .price { font-size: 1.3rem; font-weight: 700; color: #fff; margin: 10px 0; }
195
- .projects-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 30px; }
196
- .project-card { position: relative; border-radius: 15px; overflow: hidden; min-height: 400px; cursor: pointer; }
197
- .project-card img { width: 100%; height: 100%; object-fit: cover; display: block; transition: transform 0.4s ease; }
198
- .project-overlay { position: absolute; bottom: 0; left: 0; right: 0; background: linear-gradient(to top, rgba(18,18,28,1) 0%, rgba(18,18,28,0) 100%); padding: 40px 20px 20px; }
199
- .project-card h3 { margin-bottom: 5px; font-size: 1.3rem; }
200
- .project-card p { margin-bottom: 0; transition: opacity 0.4s ease; opacity: 0; max-height: 0; overflow: hidden; }
201
- .project-card:hover img { transform: scale(1.05); }
202
- .project-card:hover p { opacity: 1; max-height: 200px; }
203
- #contact { background-color: var(--card-bg); }
 
 
204
  .contact-content { text-align: center; }
205
- .contact-info { margin-top: 40px; display: flex; flex-direction: column; align-items: center; gap: 20px; }
206
- .contact-info p { font-size: 1.2rem; margin-bottom: 0; }
207
  .contact-info a { color: var(--primary-color); text-decoration: none; font-weight: 600; }
208
- .footer { text-align: center; padding: 30px 0; background-color: #0d0d14; }
209
  @media (max-width: 992px) {
210
- .grid-2 { grid-template-columns: 1fr; text-align: center; }
211
  .about-img { margin-bottom: 30px; max-width: 500px; margin-left: auto; margin-right: auto;}
 
 
212
  }
213
  @media (max-width: 768px) {
214
- .nav-links { position: fixed; top: 0; right: -100%; width: min(75vw, 300px); height: 100vh; background-color: var(--card-bg); flex-direction: column; justify-content: center; align-items: center; transition: right 0.4s ease-in-out; box-shadow: -5px 0 15px rgba(0,0,0,0.2); }
215
  .nav-links.active { right: 0; }
216
  .menu-toggle { display: block; z-index: 1001; }
217
- h2 { margin-bottom: 40px; }
218
- .projects-grid { grid-template-columns: 1fr; }
 
 
 
219
  }
 
 
220
  .modal {
221
  display: none;
222
  position: fixed;
223
  z-index: 1001;
224
  left: 0; top: 0; width: 100%; height: 100%;
225
  overflow: auto;
226
- background-color: rgba(0,0,0,0.8);
227
  padding-top: 60px;
 
228
  }
229
  .modal-content {
230
  position: relative;
231
  margin: 5% auto;
232
- padding: 20px;
233
  width: 90%;
234
  max-width: 800px;
235
  background-color: var(--card-bg);
236
- border-radius: 15px;
237
  text-align: center;
 
238
  }
239
  .modal-content img {
240
  max-width: 100%;
241
  max-height: 70vh;
242
- border-radius: 10px;
243
  margin-bottom: 20px;
 
244
  }
245
- .modal-content h3 { margin-bottom: 10px; }
246
- .modal-content p { color: var(--text-muted); font-size: 1.1rem;}
 
247
  .close-button {
248
  position: absolute;
249
- top: 15px;
250
- right: 25px;
251
- font-size: 2.5rem;
252
  font-weight: bold;
253
- color: #fff;
254
  cursor: pointer;
255
  background: none;
256
  border: none;
 
257
  }
258
- .close-button:hover, .close-button:focus { color: var(--primary-color); text-decoration: none; }
259
- .carousel-nav { margin-top: 15px; }
260
  .carousel-nav button {
261
- background-color: var(--primary-color);
262
  color: white;
263
  border: none;
264
- padding: 10px 15px;
265
  border-radius: 50px;
266
- margin: 0 5px;
267
  cursor: pointer;
268
  transition: all 0.3s ease;
269
- font-size: 1.2rem;
 
270
  }
271
- .carousel-nav button:hover { background-color: var(--secondary-color); transform: scale(1.1); }
272
  </style>
273
  </head>
274
  <body>
@@ -289,9 +313,9 @@ LANDING_TEMPLATE = '''
289
 
290
  <section id="hero">
291
  <div class="container hero-content">
292
- <h1>ОсОО "Раина": Ваш Партнер в Вентиляции и Кондиционировании</h1>
293
- <p>15 лет опыта, более 1000 реализованных проектов. Мы создаем комфорт и здоровье в любом помещении с помощью самых современных климатических систем.</p>
294
- <a href="#contact" class="btn">Получить консультацию</a>
295
  </div>
296
  </section>
297
 
@@ -301,12 +325,12 @@ LANDING_TEMPLATE = '''
301
  <div class="grid-2">
302
  <img src="https://images.unsplash.com/photo-1542744173-8e7e53415bb0?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1740&q=80" alt="Команда Раина" class="about-img">
303
  <div>
304
- <h3>Основание и История</h3>
305
- <p>Компания "Раина" была основана в 2009 году. За эти годы мы зарекомендовали себя как надежный партнер, стремящийся к инновациям и совершенству в области климатических решений.</p>
306
- <h3>Наша Миссия</h3>
307
- <p>Наша миссия — создание оптимального микроклимата для наших клиентов, обеспечивающего комфорт, здоровье и высокую производительность.</p>
308
- <h3>Профессиональная Команда</h3>
309
- <p>Наша команда состоит из высококвалифицированных инженеров и техников, обладающих глубокими знаниями и опытом в области HVAC.</p>
310
  </div>
311
  </div>
312
  </div>
@@ -314,19 +338,19 @@ LANDING_TEMPLATE = '''
314
 
315
  <section id="services">
316
  <div class="container">
317
- <h2>Наши Услуги</h2>
318
  <div class="services-grid">
319
- <div class="service-card"><i class="fas fa-drafting-compass"></i><h3>Проектирование</h3><p>Точные расчеты, 3D-модели и вся необходимая проектная документация.</p></div>
320
- <div class="service-card"><i class="fas fa-tools"></i><h3>Монтаж</h3><p>Профессиональная установка всех типов систем HVAC, от бытовых до промышленных.</p></div>
321
- <div class="service-card"><i class="fas fa-headset"></i><h3>Сервис 24/7</h3><p>Плановое обслуживание и оперативный аварийный ремонт в любое время.</p></div>
322
- <div class="service-card"><i class="fas fa-sync-alt"></i><h3>Модернизация</h3><p>Повышение энергоэффективности и снижение расходов на эксплуатацию.</p></div>
323
  </div>
324
  </div>
325
  </section>
326
 
327
  <section id="turnkey" style="background-color: var(--card-bg);">
328
  <div class="container">
329
- <h2>Услуги "под ключ"</h2>
330
  {% if services %}
331
  <div class="services-grid">
332
  {% for service in services %}
@@ -335,21 +359,21 @@ LANDING_TEMPLATE = '''
335
  <img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/services/{{ service.photo }}" alt="{{ service.title }}" class="turnkey-img">
336
  {% endif %}
337
  <div class="turnkey-content">
338
- <h3><i class="{{ service.icon }} fa-fw" style="margin-right: 8px; color: var(--primary-color);"></i>{{ service.title }}</h3>
339
  <p>{{ service.description }}</p>
340
  </div>
341
  </div>
342
  {% endfor %}
343
  </div>
344
  {% else %}
345
- <p style="text-align: center;">Информация об услугах "под ключ" скоро появится на сайте.</p>
346
  {% endif %}
347
  </div>
348
  </section>
349
 
350
  <section id="equipment">
351
  <div class="container">
352
- <h2>Наше Оборудование</h2>
353
  {% if equipment %}
354
  <div class="equipment-filters">
355
  <button class="filter-btn active" data-filter="all">Все</button>
@@ -360,26 +384,28 @@ LANDING_TEMPLATE = '''
360
  <div class="equipment-grid">
361
  {% for item in equipment %}
362
  <div class="equipment-card" data-category="{{ item.get('category', 'all') }}" onclick="showDetailsModal('equipment', {{ loop.index0 }})">
363
- {% if item.photo %}
364
- <img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/equipment/{{ item.photo }}" alt="{{ item.name }}">
365
- {% else %}
366
- <img src="https://via.placeholder.com/250x180.png?text=No+Image" alt="No Image">
367
- {% endif %}
368
- <h3>{{ item.name }}</h3>
369
- <p class="price">{{ "%.2f"|format(item.price) }} KGS</p>
370
- <a href="https://api.whatsapp.com/send?phone={{ whatsapp_phone }}&text=Здравствуйте, интересует оборудование: {{ item.name }}" target="_blank" class="btn" style="padding: 8px 20px; font-size: 0.9rem;">Запросить</a>
 
 
371
  </div>
372
  {% endfor %}
373
  </div>
374
  {% else %}
375
- <p style="text-align: center;">Каталог оборудования скоро будет доступен.</p>
376
  {% endif %}
377
  </div>
378
  </section>
379
 
380
  <section id="projects">
381
  <div class="container">
382
- <h2>Реализованные Проекты</h2>
383
  {% if projects %}
384
  <div class="projects-grid">
385
  {% for project in projects %}
@@ -393,17 +419,17 @@ LANDING_TEMPLATE = '''
393
  {% endfor %}
394
  </div>
395
  {% else %}
396
- <p style="text-align: center;">Информация о реализованных проектах скоро появится на сайте.</p>
397
  {% endif %}
398
  </div>
399
  </section>
400
 
401
  <section id="contact">
402
  <div class="container contact-content">
403
- <h2>Контакты</h2>
404
- <p>Готовы стать вашим надежным партнером в создании идеального климата.</p>
405
  <div class="contact-info">
406
- <p><strong>Свяжитесь с нами:</strong> <a href="tel:{{ contact_phone }}">{{ contact_phone }}</a></p>
407
  <a href="https://api.whatsapp.com/send?phone={{ whatsapp_phone }}&text=Здравствуйте, я хотел(а) бы получить консультацию по вашим услугам." target="_blank" class="btn"><i class="fab fa-whatsapp"></i> Написать в WhatsApp</a>
408
  </div>
409
  <div style="margin-top: 40px; font-size: 0.9rem; color: var(--text-muted);">
@@ -467,7 +493,7 @@ LANDING_TEMPLATE = '''
467
  ${item.photo ? `<img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/equipment/${item.photo}" alt="${item.name}">` : ''}
468
  <h3>${item.name}</h3>
469
  <p><strong>Категория:</strong> ${item.category || 'Не указана'}</p>
470
- <p class="price" style="font-size: 1.5rem; color: var(--primary-color);">${item.price.toFixed(2)} KGS</p>
471
  <a href="https://api.whatsapp.com/send?phone={{ whatsapp_phone }}&text=Здравствуйте, интересует оборудование: ${encodeURIComponent(item.name)}" target="_blank" class="btn" style="padding: 12px 28px; font-size: 1rem;">Запросить</a>
472
  `;
473
  } else if (currentType === 'project') {
@@ -537,7 +563,7 @@ LANDING_TEMPLATE = '''
537
  e.target.classList.add('active');
538
  const filter = e.target.dataset.filter;
539
  document.querySelectorAll('.equipment-card').forEach(card => {
540
- card.style.display = (filter === 'all' || card.dataset.category === filter) ? 'block' : 'none';
541
  });
542
  });
543
  }
@@ -554,75 +580,121 @@ ADMIN_TEMPLATE = '''
554
  <meta charset="UTF-8">
555
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
556
  <title>Админ-панель - Раина</title>
557
- <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600&display=swap" rel="stylesheet">
558
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
559
  <style>
560
- body { font-family: 'Poppins', sans-serif; background-color: #f4f7f9; color: #333; padding: 20px; line-height: 1.6; }
561
- .container { max-width: 1200px; margin: 0 auto; background-color: #fff; padding: 25px; border-radius: 10px; box-shadow: 0 3px 10px rgba(0,0,0,0.05); }
562
- .header { padding-bottom: 15px; margin-bottom: 25px; border-bottom: 1px solid #e0e0e0; display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 10px;}
563
- h1, h2, h3 { font-weight: 600; color: #6a0dad; margin-bottom: 15px; }
564
- h1 { font-size: 1.8rem; }
565
- h2 { font-size: 1.5rem; margin-top: 30px; display: flex; align-items: center; gap: 8px; }
566
- .section { margin-bottom: 30px; padding: 20px; background-color: #fafafa; border: 1px solid #e9e9e9; border-radius: 8px; }
 
 
 
 
 
 
 
 
 
 
 
 
 
567
  form { margin-bottom: 20px; }
568
- label { font-weight: 500; margin-top: 10px; display: block; color: #555; font-size: 0.9rem;}
569
- input[type="text"], input[type="number"], textarea, select { width: 100%; padding: 10px 12px; margin-top: 5px; border: 1px solid #ddd; border-radius: 6px; font-size: 0.95rem; box-sizing: border-box; }
570
- input[type="file"] { padding: 8px; cursor: pointer; border: 1px solid #ddd;}
571
- button, .button { padding: 10px 18px; border: none; border-radius: 6px; background-color: #9b59b6; color: white; font-weight: 500; cursor: pointer; transition: all 0.3s ease; margin-top: 15px; text-decoration: none; }
572
- button:hover, .button:hover { background-color: #8e44ad; }
573
- .delete-button { background-color: #e74c3c; }
574
- .delete-button:hover { background-color: #c0392b; }
 
 
575
  .item-list { display: grid; gap: 20px; }
576
- .item { background: #fff; padding: 15px 20px; border-radius: 8px; border: 1px solid #eee; }
577
- .item-actions { margin-top: 15px; display: flex; gap: 10px; flex-wrap: wrap; }
578
- .edit-form-container { margin-top: 15px; padding: 20px; background: #fdf9ff; border: 1px dashed #ddd; border-radius: 6px; display: none; }
579
- details { background-color: #fafafa; border: 1px solid #e9e9e9; border-radius: 8px; margin-bottom: 20px; }
580
- details > summary { cursor: pointer; font-weight: 600; color: #8e44ad; display: block; padding: 15px; position: relative; list-style: none; }
581
- details > summary::after { content: '\\f078'; font-family: 'Font Awesome 6 Free'; font-weight: 900; position: absolute; right: 20px; top: 50%; transform: translateY(-50%); }
 
 
 
582
  details[open] > summary::after { transform: translateY(-50%) rotate(180deg); }
583
- .photo-preview img { max-width: 70px; max-height: 70px; border-radius: 5px; margin: 5px 5px 0 0; object-fit: cover;}
584
- .message { padding: 10px 15px; border-radius: 6px; margin-bottom: 15px; }
585
- .message.success { background-color: #d4edda; color: #155724; }
586
- .message.error { background-color: #f8d7da; color: #721c24; }
587
- .message.warning { background-color: #fff3cd; color: #856404; }
 
588
  </style>
589
  </head>
590
  <body>
591
  <div class="container">
592
- <div class="header"><h1><i class="fas fa-tools"></i> Админ-панель "Раина"</h1><a href="{{ url_for('landing') }}" class="button"><i class="fas fa-home"></i> Перейти на сайт</a></div>
593
- {% with messages = get_flashed_messages(with_categories=true) %}{% if messages %}{% for category, message in messages %}<div class="message {{ category }}">{{ message }}</div>{% endfor %}{% endif %}{% endwith %}
 
 
 
 
 
 
 
 
 
 
594
 
595
  <div class="section">
596
- <h2><i class="fas fa-sync-alt"></i> Синхронизация</h2>
597
- <form method="POST" action="{{ url_for('force_upload') }}" style="display: inline;"><button type="submit" class="button">Загрузить на сервер</button></form>
598
- <form method="POST" action="{{ url_for('force_download') }}" style="display: inline;"><button type="submit" class="button">Скачать с сервера</button></form>
 
 
 
 
 
 
599
  </div>
600
 
601
  <div class="section">
602
- <h2><i class="fas fa-star"></i> Реализованные проекты</h2>
603
- <details><summary>Добавить проект</summary>
604
- <form method="POST" enctype="multipart/form-data"><input type="hidden" name="action" value="add_project">
605
- <label>Название*:</label><input type="text" name="title" required>
606
- <label>Описание*:</label><textarea name="description" rows="3" required></textarea>
607
- <label>Фото*:</label><input type="file" name="photo" accept="image/*" required>
608
- <button type="submit">Добавить проект</button>
 
609
  </form>
610
  </details>
611
  <div class="item-list">
612
  {% for project in projects %}
613
  <div class="item">
614
  <p><strong>{{ project.title }}</strong>: {{ project.description }}</p>
615
- {% if project.photo %}<div class="photo-preview"><img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/projects/{{ project.photo }}" alt="Project Photo"></div>{% endif %}
 
 
 
 
616
  <div class="item-actions">
617
- <button onclick="toggleEditForm('edit-project-{{ loop.index0 }}')">Редактировать</button>
618
- <form method="POST" style="margin:0;"><input type="hidden" name="action" value="delete_project"><input type="hidden" name="index" value="{{ loop.index0 }}"><button type="submit" class="delete-button">Удалить</button></form>
 
 
 
 
619
  </div>
620
  <div id="edit-project-{{ loop.index0 }}" class="edit-form-container">
621
- <form method="POST" enctype="multipart/form-data"><input type="hidden" name="action" value="edit_project"><input type="hidden" name="index" value="{{ loop.index0 }}">
622
- <label>Название*:</label><input type="text" name="title" value="{{ project.title }}" required>
623
- <label>Описание*:</label><textarea name="description" rows="3" required>{{ project.description }}</textarea>
 
 
624
  <label>Заменить фото:</label><input type="file" name="photo" accept="image/*">
625
- <button type="submit">Сохранить</button>
626
  </form>
627
  </div>
628
  </div>
@@ -631,32 +703,43 @@ ADMIN_TEMPLATE = '''
631
  </div>
632
 
633
  <div class="section">
634
- <h2><i class="fas fa-concierge-bell"></i> Услуги "под ключ"</h2>
635
- <details><summary>Добавить услугу</summary>
636
- <form method="POST" enctype="multipart/form-data"><input type="hidden" name="action" value="add_service">
637
- <label>Заголовок*:</label><input type="text" name="title" required>
638
- <label>Иконка (FontAwesome)*:</label><input type="text" name="icon" placeholder="fas fa-tools" required>
639
- <label>Описание*:</label><textarea name="description" rows="3" required></textarea>
640
- <label>Фото:</label><input type="file" name="photo" accept="image/*">
641
- <button type="submit">Добавить услугу</button>
 
642
  </form>
643
  </details>
644
  <div class="item-list">
645
  {% for service in services %}
646
  <div class="item">
647
- <p><i class="{{ service.icon }} fa-fw"></i> <strong>{{ service.title }}</strong>: {{ service.description }}</p>
648
- {% if service.photo %}<div class="photo-preview"><img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/services/{{ service.photo }}" alt="Service Photo"></div>{% endif %}
 
 
 
 
649
  <div class="item-actions">
650
- <button onclick="toggleEditForm('edit-service-{{ loop.index0 }}')">Редактировать</button>
651
- <form method="POST" style="margin:0;"><input type="hidden" name="action" value="delete_service"><input type="hidden" name="index" value="{{ loop.index0 }}"><button type="submit" class="delete-button">Удалить</button></form>
 
 
 
 
652
  </div>
653
  <div id="edit-service-{{ loop.index0 }}" class="edit-form-container">
654
- <form method="POST" enctype="multipart/form-data"><input type="hidden" name="action" value="edit_service"><input type="hidden" name="index" value="{{ loop.index0 }}">
655
- <label>Заголовок*:</label><input type="text" name="title" value="{{ service.title }}" required>
 
 
656
  <label>Иконка*:</label><input type="text" name="icon" value="{{ service.icon }}" required>
657
- <label>Описание*:</label><textarea name="description" rows="3" required>{{ service.description }}</textarea>
658
  <label>Заменить фото:</label><input type="file" name="photo" accept="image/*">
659
- <button type="submit">Сохранить</button>
660
  </form>
661
  </div>
662
  </div>
@@ -666,52 +749,82 @@ ADMIN_TEMPLATE = '''
666
 
667
  <div class="section">
668
  <h2><i class="fas fa-box-open"></i> Оборудование</h2>
669
- <details><summary>Добавить категорию</summary>
670
- <form method="POST"><input type="hidden" name="action" value="add_category"><label>Название:</label><input type="text" name="category_name" required><button type="submit">Добавить</button></form>
 
 
 
 
 
671
  </details>
672
  <div class="item-list">
673
  {% for category in categories %}
674
- <div class="item" style="display: flex; justify-content: space-between; align-items: center;">
675
- <span>{{ category }}</span>
676
- <form method="POST" style="margin: 0;"><input type="hidden" name="action" value="delete_category"><input type="hidden" name="category_name" value="{{ category }}"><button type="submit" class="delete-button" style="margin:0;">Удалить</button></form>
 
 
 
 
677
  </div>
678
  {% endfor %}
679
  </div>
680
 
681
- <details style="margin-top:20px;"><summary>Добавить оборудование</summary>
682
- <form method="POST" enctype="multipart/form-data"><input type="hidden" name="action" value="add_equipment">
683
- <label>Название*:</label><input type="text" name="name" required>
 
684
  <label>Цена (KGS)*:</label><input type="number" name="price" step="0.01" min="0" required>
685
- <label>Категория:</label><select name="category"><option value="Без категории">Без категории</option>{% for cat in categories %}<option value="{{ cat }}">{{ cat }}</option>{% endfor %}</select>
686
- <label>Фото:</label><input type="file" name="photo" accept="image/*">
687
- <button type="submit">Добавить</button>
 
 
 
 
 
688
  </form>
689
  </details>
690
  <div class="item-list">
691
  {% for item in equipment %}
692
  <div class="item">
693
- <p><strong>{{ item.name }}</strong> ({{ item.category }}) - {{ "%.2f"|format(item.price) }} KGS</p>
694
- {% if item.photo %}<div class="photo-preview"><img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/equipment/{{ item.photo }}" alt="Equipment Photo"></div>{% endif %}
 
 
 
 
695
  <div class="item-actions">
696
- <button onclick="toggleEditForm('edit-eq-{{ loop.index0 }}')">Редактировать</button>
697
- <form method="POST" style="margin:0;"><input type="hidden" name="action" value="delete_equipment"><input type="hidden" name="index" value="{{ loop.index0 }}"><button type="submit" class="delete-button">Удалить</button></form>
 
 
 
 
698
  </div>
699
  <div id="edit-eq-{{ loop.index0 }}" class="edit-form-container">
700
- <form method="POST" enctype="multipart/form-data"><input type="hidden" name="action" value="edit_equipment"><input type="hidden" name="index" value="{{ loop.index0 }}">
701
- <label>Название*:</label><input type="text" name="name" value="{{ item.name }}" required>
 
 
702
  <label>Цена (KGS)*:</label><input type="number" name="price" value="{{ item.price }}" step="0.01" min="0" required>
703
- <label>Категория:</label><select name="category">{% for cat in categories %}<option value="{{ cat }}" {% if item.category == cat %}selected{% endif %}>{{ cat }}</option>{% endfor %}</select>
 
 
 
 
704
  <label>Заменить фото:</label><input type="file" name="photo" accept="image/*">
705
- <button type="submit">Сохранить</button>
706
  </form>
707
  </div>
708
  </div>
709
  {% endfor %}
710
  </div>
711
  </div>
712
- <script>function toggleEditForm(id) { document.getElementById(id).style.display = document.getElementById(id).style.display === 'block' ? 'none' : 'block'; }</script>
713
- </body>
714
- </html>
 
715
  '''
716
 
717
  @app.route('/')
@@ -864,7 +977,6 @@ def upload_photo_to_hf(photo, item_name, folder):
864
  ext = os.path.splitext(photo.filename)[1].lower()
865
  photo_filename = f"{safe_name}_{datetime.now().strftime('%Y%m%d%H%M%S%f')}{ext}"
866
 
867
- # Ensure the file object is in binary mode
868
  photo_file_obj = io.BytesIO(photo.read())
869
 
870
  api.upload_file(
@@ -897,13 +1009,15 @@ def delete_photo_from_hf(photo_filename, folder):
897
  @app.route('/force_upload', methods=['POST'])
898
  def force_upload():
899
  upload_db_to_hf()
900
- flash("Данные загружены на сервер.", 'success')
901
  return redirect(url_for('admin'))
902
 
903
  @app.route('/force_download', methods=['POST'])
904
  def force_download():
905
- download_db_from_hf()
906
- flash("Данные скачаны с сервера.", 'success')
 
 
907
  return redirect(url_for('admin'))
908
 
909
  if __name__ == '__main__':
@@ -912,4 +1026,4 @@ if __name__ == '__main__':
912
  if HF_TOKEN_WRITE:
913
  threading.Thread(target=periodic_backup, daemon=True).start()
914
  port = int(os.environ.get('PORT', 7860))
915
- app.run(debug=False, host='0.0.0.0', port=port)
 
144
  <title>ОсОО "Раина" - Вентиляция и Кондиционирование</title>
145
  <meta name="description" content="Профессиональные услуги по проектированию, монтажу и обслуживанию систем вентиляции и кондиционирования в Кыргызстане. 15 лет опыта, более 1000 проектов.">
146
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
147
+ <link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;600;700&family=Poppins:wght@400;600;700&display=swap" rel="stylesheet">
148
  <style>
149
  :root {
150
+ --dark-bg: #0a0a10;
151
+ --card-bg: #1a1e28;
152
+ --primary-color: #6a0dad;
153
+ --secondary-color: #a955ff;
154
+ --text-color: #e0e0e0;
155
+ --text-muted: #b0b0c0;
156
+ --accent-glow: rgba(106, 13, 173, 0.4);
157
+ --card-border: #2a3040;
158
  }
159
  * { margin: 0; padding: 0; box-sizing: border-box; scroll-behavior: smooth; }
160
  body { font-family: 'Montserrat', sans-serif; background-color: var(--dark-bg); color: var(--text-color); line-height: 1.7; font-size: 16px; }
161
  .container { max-width: 1200px; margin: 0 auto; padding: 0 20px; }
162
+ section { padding: clamp(4rem, 10vw, 6rem) 0; overflow: hidden; }
163
+ h1, h2, h3 { font-weight: 700; color: #fff; line-height: 1.3; font-family: 'Poppins', sans-serif;}
164
+ h1 { font-size: clamp(2.5rem, 7vw, 4.5rem); }
165
+ h2 { font-size: clamp(2rem, 6vw, 3.5rem); text-align: center; margin-bottom: 70px; position: relative; font-family: 'Poppins', sans-serif;}
166
+ h2::after { content: ''; display: block; width: 100px; height: 5px; background: linear-gradient(90deg, var(--primary-color), var(--secondary-color)); margin: 20px auto 0; border-radius: 3px; }
167
+ h3 { font-size: clamp(1.3rem, 3.5vw, 1.8rem); color: var(--primary-color); margin-bottom: 15px; font-family: 'Poppins', sans-serif;}
168
+ p { margin-bottom: 1.2rem; color: var(--text-muted); }
169
+ .btn { display: inline-block; padding: 14px 30px; background: linear-gradient(90deg, var(--primary-color), var(--secondary-color)); color: #fff; border-radius: 60px; text-decoration: none; font-weight: 600; font-size: 1.1rem; transition: all 0.4s ease; box-shadow: 0 6px 20px var(--accent-glow); border: none; cursor: pointer;}
170
+ .btn:hover { transform: translateY(-4px) scale(1.05); box-shadow: 0 10px 30px var(--accent-glow); }
171
+ .header { position: fixed; top: 0; left: 0; width: 100%; z-index: 1000; padding: 20px 0; background-color: rgba(10, 10, 16, 0.9); backdrop-filter: blur(12px); transition: all 0.3s ease; }
172
+ .header.scrolled { padding: 15px 0; box-shadow: 0 4px 15px rgba(0,0,0,0.4); }
173
  .navbar { display: flex; justify-content: space-between; align-items: center; }
174
+ .logo { font-size: clamp(1.7rem, 4.5vw, 2.2rem); font-weight: 700; color: #fff; text-decoration: none; letter-spacing: 1px; }
175
+ .nav-links { display: flex; gap: 35px; list-style: none; }
176
+ .nav-links a { color: var(--text-color); text-decoration: none; font-weight: 600; transition: color 0.3s ease; position: relative; }
177
+ .nav-links a::after { content: ''; position: absolute; bottom: -5px; left: 0; width: 0; height: 2px; background: linear-gradient(90deg, var(--primary-color), var(--secondary-color)); transition: width 0.3s; }
178
+ .nav-links a:hover { color: #fff; }
179
+ .nav-links a:hover::after { width: 100%; }
180
+ .menu-toggle { display: none; font-size: 1.8rem; cursor: pointer; border: none; background: none; color: white; z-index: 1001; }
181
+ #hero { min-height: 100vh; display: flex; align-items: center; background-image: linear-gradient(rgba(10, 10, 16, 0.8), rgba(10, 10, 16, 1)), url(https://images.unsplash.com/photo-1558221639-2c7158995165?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1740&q=80); background-size: cover; background-position: center; }
182
+ .hero-content { text-align: center; max-width: 850px; margin: 0 auto; animation: fadeIn 1.5s ease-out; }
183
+ .hero-content p { font-size: clamp(1.1rem, 3vw, 1.3rem); margin: 40px 0; max-width: 700px; margin-left: auto; margin-right: auto; opacity: 0.9;}
184
+ .grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 70px; align-items: center; }
185
+ .about-img { width: 100%; border-radius: 18px; box-shadow: 0 15px 40px rgba(0,0,0,0.5); transition: transform 0.3s ease; }
186
+ .about-img:hover { transform: scale(1.02) translateY(-5px); }
187
+ .services-grid, .equipment-grid, .projects-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(290px, 1fr)); gap: 40px; }
188
+ .service-card, .turnkey-card { background-color: var(--card-bg); padding: 35px; border-radius: 18px; border: 1px solid var(--card-border); transition: all 0.4s ease; }
189
+ .service-card:hover, .turnkey-card:hover { transform: translateY(-8px); border-color: var(--primary-color); box-shadow: 0 12px 35px var(--accent-glow); }
190
+ .service-card i { font-size: 3rem; color: var(--primary-color); margin-bottom: 25px; transition: transform 0.3s ease;}
191
+ .service-card:hover i { transform: scale(1.1); }
192
  .turnkey-card { padding: 0; display: flex; flex-direction: column; }
193
+ .turnkey-img { width: 100%; height: 220px; object-fit: cover; border-radius: 18px 18px 0 0; }
194
+ .turnkey-content { padding: 35px; flex-grow: 1;}
195
+ .equipment-filters { display: flex; justify-content: center; flex-wrap: wrap; gap: 15px; margin-bottom: 50px; }
196
+ .filter-btn { padding: 10px 22px; border: 2px solid var(--primary-color); background-color: transparent; color: var(--primary-color); border-radius: 30px; cursor: pointer; transition: all 0.3s; font-weight: 600; font-size: 1rem;}
197
+ .filter-btn.active, .filter-btn:hover { background-color: var(--primary-color); color: #fff; box-shadow: 0 4px 15px var(--accent-glow);}
198
+ .equipment-card { background-color: var(--card-bg); border-radius: 18px; overflow: hidden; text-align: center; padding: 25px; border: 1px solid var(--card-border); transition: all 0.4s ease; cursor: pointer; display: flex; flex-direction: column; justify-content: space-between;}
199
+ .equipment-card:hover { transform: translateY(-8px); border-color: var(--primary-color); box-shadow: 0 12px 35px var(--accent-glow); }
200
+ .equipment-card img { width: 100%; height: 180px; object-fit: contain; margin-bottom: 20px; transition: transform 0.3s ease;}
201
+ .equipment-card:hover img { transform: scale(1.05); }
202
+ .equipment-card h3 { font-size: 1.3rem; margin-bottom: 10px; }
203
+ .equipment-card .price { font-size: 1.4rem; font-weight: 700; color: #fff; margin: 10px 0 20px;}
204
+ .equipment-card .btn { padding: 10px 20px; font-size: 0.95rem; background: linear-gradient(90deg, #3498db, #2980b9); box-shadow: 0 4px 15px rgba(52, 152, 219, 0.3); }
205
+ .equipment-card .btn:hover { background: linear-gradient(90deg, #2980b9, #3498db); }
206
+ .project-card { position: relative; border-radius: 18px; overflow: hidden; min-height: 350px; cursor: pointer; display: flex; align-items: flex-end; }
207
+ .project-card img { position: absolute; top: 0; left: 0; width: 100%; height: 100%; object-fit: cover; display: block; transition: transform 0.4s ease, filter 0.4s ease; }
208
+ .project-overlay { position: absolute; bottom: 0; left: 0; right: 0; background: linear-gradient(to top, rgba(10, 10, 16, 0.9) 20%, rgba(10, 10, 16, 0) 70%); padding: 50px 25px 25px; z-index: 1; transition: padding 0.3s ease; }
209
+ .project-card:hover .project-overlay { padding-bottom: 40px; }
210
+ .project-card h3 { margin-bottom: 8px; font-size: 1.5rem; color: #fff; }
211
+ .project-card p { margin-bottom: 0; transition: opacity 0.4s ease, max-height 0.4s ease; opacity: 0; max-height: 0; color: var(--text-muted); font-size: 0.95rem;}
212
+ .project-card:hover img { transform: scale(1.03); filter: brightness(0.7); }
213
+ .project-card:hover p { opacity: 1; max-height: 150px; }
214
+ #contact { background: linear-gradient(rgba(26, 30, 40, 0.9), rgba(26, 30, 40, 0.9)), url('https://images.unsplash.com/photo-1592911677971-2684320e94e1?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1740&q=80'); background-size: cover; background-position: center; background-attachment: fixed;}
215
  .contact-content { text-align: center; }
216
+ .contact-info { margin-top: 40px; display: flex; flex-direction: column; align-items: center; gap: 25px; }
217
+ .contact-info p { font-size: 1.25rem; margin-bottom: 0; }
218
  .contact-info a { color: var(--primary-color); text-decoration: none; font-weight: 600; }
219
+ .footer { text-align: center; padding: 40px 0; background-color: #07070d; color: var(--text-muted); font-size: 0.95rem; }
220
  @media (max-width: 992px) {
221
+ .grid-2 { grid-template-columns: 1fr; text-align: center; gap: 50px;}
222
  .about-img { margin-bottom: 30px; max-width: 500px; margin-left: auto; margin-right: auto;}
223
+ h1 { font-size: clamp(2rem, 6vw, 3.5rem); }
224
+ h2 { margin-bottom: 50px; }
225
  }
226
  @media (max-width: 768px) {
227
+ .nav-links { position: fixed; top: 0; right: -100%; width: min(80vw, 350px); height: 100vh; background-color: var(--card-bg); backdrop-filter: blur(15px); flex-direction: column; justify-content: center; align-items: center; transition: right 0.4s ease-in-out; box-shadow: -5px 0 25px rgba(0,0,0,0.3); padding-top: 60px; }
228
  .nav-links.active { right: 0; }
229
  .menu-toggle { display: block; z-index: 1001; }
230
+ .navbar { padding-right: 15px; }
231
+ .hero-content p { font-size: clamp(1rem, 2.5vw, 1.2rem); margin: 30px 0; }
232
+ .btn { padding: 12px 25px; font-size: 1rem; }
233
+ .service-card, .turnkey-card { padding: 30px; }
234
+ .turnkey-img { height: 180px; }
235
  }
236
+ @keyframes fadeIn { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } }
237
+
238
  .modal {
239
  display: none;
240
  position: fixed;
241
  z-index: 1001;
242
  left: 0; top: 0; width: 100%; height: 100%;
243
  overflow: auto;
244
+ background-color: rgba(0,0,0,0.85);
245
  padding-top: 60px;
246
+ backdrop-filter: blur(5px);
247
  }
248
  .modal-content {
249
  position: relative;
250
  margin: 5% auto;
251
+ padding: 30px;
252
  width: 90%;
253
  max-width: 800px;
254
  background-color: var(--card-bg);
255
+ border-radius: 18px;
256
  text-align: center;
257
+ box-shadow: 0 15px 50px rgba(0,0,0,0.5);
258
  }
259
  .modal-content img {
260
  max-width: 100%;
261
  max-height: 70vh;
262
+ border-radius: 12px;
263
  margin-bottom: 20px;
264
+ border: 2px solid var(--primary-color);
265
  }
266
+ .modal-content h3 { margin-bottom: 12px; font-size: 1.8rem; }
267
+ .modal-content p { color: var(--text-muted); font-size: 1.15rem; }
268
+ .modal-content .price { font-size: 1.7rem; color: #fff; font-weight: 700; margin-top: 15px;}
269
  .close-button {
270
  position: absolute;
271
+ top: 20px;
272
+ right: 30px;
273
+ font-size: 2.8rem;
274
  font-weight: bold;
275
+ color: #aaa;
276
  cursor: pointer;
277
  background: none;
278
  border: none;
279
+ transition: color 0.3s ease;
280
  }
281
+ .close-button:hover, .close-button:focus { color: var(--primary-color); }
282
+ .carousel-nav { margin-top: 20px; }
283
  .carousel-nav button {
284
+ background: linear-gradient(90deg, var(--primary-color), var(--secondary-color));
285
  color: white;
286
  border: none;
287
+ padding: 12px 18px;
288
  border-radius: 50px;
289
+ margin: 0 8px;
290
  cursor: pointer;
291
  transition: all 0.3s ease;
292
+ font-size: 1.3rem;
293
+ box-shadow: 0 5px 15px var(--accent-glow);
294
  }
295
+ .carousel-nav button:hover { background: linear-gradient(90deg, var(--secondary-color), var(--primary-color)); transform: scale(1.1); box-shadow: 0 8px 25px var(--accent-glow); }
296
  </style>
297
  </head>
298
  <body>
 
313
 
314
  <section id="hero">
315
  <div class="container hero-content">
316
+ <h1>Ваш Климат - Наша Забота</h1>
317
+ <p>Проектирование, монтаж и сервис систем вентиляции и кондиционирования от ОсОО "Раина". 15 лет опыта, более 1000 довольных клиентов.</p>
318
+ <a href="#contact" class="btn">Заказать консультацию</a>
319
  </div>
320
  </section>
321
 
 
325
  <div class="grid-2">
326
  <img src="https://images.unsplash.com/photo-1542744173-8e7e53415bb0?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1740&q=80" alt="Команда Раина" class="about-img">
327
  <div>
328
+ <h3>Экспертиза и Инновации</h3>
329
+ <p 2009 года ОсОО "Раина" является лидером в сфере климатических решений в Кыргызстане. Мы сочетаем многолетний опыт с передовыми технологиями для создания идеального микроклимата.</p>
330
+ <h3>Миссия и Ценности</h3>
331
+ <p>Наша миссия — обеспечивать максимальный комфорт и безопасность для наших клиентов, предлагая энергоэффективные и надежные системы, отвечающие самым высоким стандартам качества.</p>
332
+ <h3>Квалифицированная Команда</h3>
333
+ <p>Наша команда это опытные инженеры, монтажники и консультанты, готовые решить задачи любой сложности, от бытовых систем до комплексных промышленных решений.</p>
334
  </div>
335
  </div>
336
  </div>
 
338
 
339
  <section id="services">
340
  <div class="container">
341
+ <h2>Наши Ключевые Услуги</h2>
342
  <div class="services-grid">
343
+ <div class="service-card"><i class="fas fa-chart-line"></i><h3>Проектирование</h3><p>Разработка детализированных проектов систем вентиляции и кондиционирования с учетом всех ваших требований и норм.</p></div>
344
+ <div class="service-card"><i class="fas fa-cogs"></i><h3>Монтаж</h3><p>Профессиональная установка климатического оборудования любой сложности с гарантией качества и долговечности.</p></div>
345
+ <div class="service-card"><i class="fas fa-wrench"></i><h3>Сервисное Обслуживание</h3><p>Регулярное техническое обслуживание и оперативный ремонт для бесперебойной работы ваших систем 24/7.</p></div>
346
+ <div class="service-card"><i class="fas fa-redo-alt"></i><h3>Модернизация и Автоматизация</h3><p>Повышение эффективности существующих систем, внедрение современных решений для оптимизации энергопотребления и управления.</p></div>
347
  </div>
348
  </div>
349
  </section>
350
 
351
  <section id="turnkey" style="background-color: var(--card-bg);">
352
  <div class="container">
353
+ <h2>Комплексные Решения "Под Ключ"</h2>
354
  {% if services %}
355
  <div class="services-grid">
356
  {% for service in services %}
 
359
  <img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/services/{{ service.photo }}" alt="{{ service.title }}" class="turnkey-img">
360
  {% endif %}
361
  <div class="turnkey-content">
362
+ <h3><i class="{{ service.icon }} fa-fw" style="margin-right: 8px;"></i>{{ service.title }}</h3>
363
  <p>{{ service.description }}</p>
364
  </div>
365
  </div>
366
  {% endfor %}
367
  </div>
368
  {% else %}
369
+ <p style="text-align: center;">Наши комплексные решения скоро будут представлены здесь.</p>
370
  {% endif %}
371
  </div>
372
  </section>
373
 
374
  <section id="equipment">
375
  <div class="container">
376
+ <h2>Качественное Оборудование</h2>
377
  {% if equipment %}
378
  <div class="equipment-filters">
379
  <button class="filter-btn active" data-filter="all">Все</button>
 
384
  <div class="equipment-grid">
385
  {% for item in equipment %}
386
  <div class="equipment-card" data-category="{{ item.get('category', 'all') }}" onclick="showDetailsModal('equipment', {{ loop.index0 }})">
387
+ <div>
388
+ {% if item.photo %}
389
+ <img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/equipment/{{ item.photo }}" alt="{{ item.name }}">
390
+ {% else %}
391
+ <img src="https://via.placeholder.com/290x180.png?text=No+Image" alt="No Image">
392
+ {% endif %}
393
+ <h3>{{ item.name }}</h3>
394
+ <p class="price">{{ "%.2f"|format(item.price) }} KGS</p>
395
+ </div>
396
+ <a href="https://api.whatsapp.com/send?phone={{ whatsapp_phone }}&text=Здравствуйте, интересует оборудование: {{ item.name }}" target="_blank" class="btn">Запросить</a>
397
  </div>
398
  {% endfor %}
399
  </div>
400
  {% else %}
401
+ <p style="text-align: center;">Наш каталог оборудования будет доступен в ближайшее время.</p>
402
  {% endif %}
403
  </div>
404
  </section>
405
 
406
  <section id="projects">
407
  <div class="container">
408
+ <h2>Наши Реализованные Проекты</h2>
409
  {% if projects %}
410
  <div class="projects-grid">
411
  {% for project in projects %}
 
419
  {% endfor %}
420
  </div>
421
  {% else %}
422
+ <p style="text-align: center;">Информация о наших проектах скоро появится на сайте.</p>
423
  {% endif %}
424
  </div>
425
  </section>
426
 
427
  <section id="contact">
428
  <div class="container contact-content">
429
+ <h2>Свяжитесь с Нами</h2>
430
+ <p>Мы готовы ответить на ваши вопросы и предложить оптимальное решение для вашего объекта.</p>
431
  <div class="contact-info">
432
+ <p><strong>Телефон:</strong> <a href="tel:{{ contact_phone }}">{{ contact_phone }}</a></p>
433
  <a href="https://api.whatsapp.com/send?phone={{ whatsapp_phone }}&text=Здравствуйте, я хотел(а) бы получить консультацию по вашим услугам." target="_blank" class="btn"><i class="fab fa-whatsapp"></i> Написать в WhatsApp</a>
434
  </div>
435
  <div style="margin-top: 40px; font-size: 0.9rem; color: var(--text-muted);">
 
493
  ${item.photo ? `<img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/equipment/${item.photo}" alt="${item.name}">` : ''}
494
  <h3>${item.name}</h3>
495
  <p><strong>Категория:</strong> ${item.category || 'Не указана'}</p>
496
+ <p class="price">${item.price.toFixed(2)} KGS</p>
497
  <a href="https://api.whatsapp.com/send?phone={{ whatsapp_phone }}&text=Здравствуйте, интересует оборудование: ${encodeURIComponent(item.name)}" target="_blank" class="btn" style="padding: 12px 28px; font-size: 1rem;">Запросить</a>
498
  `;
499
  } else if (currentType === 'project') {
 
563
  e.target.classList.add('active');
564
  const filter = e.target.dataset.filter;
565
  document.querySelectorAll('.equipment-card').forEach(card => {
566
+ card.style.display = (filter === 'all' || card.dataset.category === filter) ? 'flex' : 'none';
567
  });
568
  });
569
  }
 
580
  <meta charset="UTF-8">
581
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
582
  <title>Админ-панель - Раина</title>
583
+ <link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;600;700&family=Poppins:wght@300;400;600&display=swap" rel="stylesheet">
584
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
585
  <style>
586
+ :root {
587
+ --admin-bg: #f4f7f9;
588
+ --admin-card-bg: #ffffff;
589
+ --primary-color: #6a0dad;
590
+ --secondary-color: #a955ff;
591
+ --text-color: #333;
592
+ --text-muted: #555;
593
+ --border-color: #e0e0e0;
594
+ --button-bg: #9b59b6;
595
+ --button-hover-bg: #8e44ad;
596
+ --delete-button-bg: #e74c3c;
597
+ --delete-button-hover-bg: #c0392b;
598
+ }
599
+ body { font-family: 'Poppins', sans-serif; background-color: var(--admin-bg); color: var(--text-color); padding: 20px; line-height: 1.6; }
600
+ .container { max-width: 1200px; margin: 0 auto; background-color: var(--admin-card-bg); padding: 25px; border-radius: 12px; box-shadow: 0 5px 20px rgba(0,0,0,0.07); }
601
+ .header { padding-bottom: 20px; margin-bottom: 30px; border-bottom: 1px solid var(--border-color); display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 15px;}
602
+ h1, h2, h3 { font-weight: 600; color: var(--primary-color); margin-bottom: 15px; font-family: 'Montserrat', sans-serif;}
603
+ h1 { font-size: 2rem; color: var(--text-color); }
604
+ h2 { font-size: 1.7rem; margin-top: 35px; display: flex; align-items: center; gap: 10px; border-bottom: 2px solid var(--primary-color); padding-bottom: 10px; }
605
+ .section { margin-bottom: 40px; padding: 25px; background-color: #fdfdfd; border: 1px solid var(--border-color); border-radius: 10px; }
606
  form { margin-bottom: 20px; }
607
+ label { font-weight: 500; margin-top: 12px; display: block; color: var(--text-muted); font-size: 0.95rem;}
608
+ input[type="text"], input[type="number"], textarea, select { width: 100%; padding: 12px 15px; margin-top: 8px; border: 1px solid var(--border-color); border-radius: 8px; font-size: 1rem; box-sizing: border-box; transition: border-color 0.3s ease;}
609
+ input[type="text"]:focus, input[type="number"]:focus, textarea:focus, select:focus { border-color: var(--secondary-color); outline: none; box-shadow: 0 0 0 2px rgba(169, 85, 255, 0.2); }
610
+ input[type="file"] { padding: 10px; cursor: pointer; border: 1px solid var(--border-color); border-radius: 8px;}
611
+ input[type="file"]:focus { border-color: var(--secondary-color); }
612
+ button, .button { padding: 12px 20px; border: none; border-radius: 8px; background-color: var(--button-bg); color: white; font-weight: 600; cursor: pointer; transition: all 0.3s ease; margin-top: 15px; text-decoration: none; font-size: 1rem;}
613
+ button:hover, .button:hover { background-color: var(--button-hover-bg); transform: translateY(-2px); box-shadow: 0 4px 10px rgba(142, 68, 173, 0.3); }
614
+ .delete-button { background-color: var(--delete-button-bg); }
615
+ .delete-button:hover { background-color: var(--delete-button-hover-bg); }
616
  .item-list { display: grid; gap: 20px; }
617
+ .item { background: #fcfcfd; padding: 20px 25px; border-radius: 10px; border: 1px solid var(--border-color); box-shadow: 0 2px 8px rgba(0,0,0,0.04); display: flex; flex-direction: column; gap: 10px;}
618
+ .item p { margin-bottom: 0;}
619
+ .item-actions { margin-top: 15px; display: flex; gap: 15px; flex-wrap: wrap; align-items: center;}
620
+ .item-actions button { padding: 8px 15px; font-size: 0.9rem; margin-top: 0;}
621
+ .edit-form-container { margin-top: 15px; padding: 20px; background: #f9f8ff; border: 1px dashed #e0cfff; border-radius: 8px; display: none; }
622
+ details { background-color: #fcfcfd; border: 1px solid var(--border-color); border-radius: 10px; margin-bottom: 20px; overflow: hidden; }
623
+ details > summary { cursor: pointer; font-weight: 600; color: var(--primary-color); display: block; padding: 15px 20px; position: relative; list-style: none; background-color: #f4f2ff; transition: background-color 0.3s ease;}
624
+ details > summary::after { content: '\\f078'; font-family: 'Font Awesome 6 Free'; font-weight: 900; position: absolute; right: 20px; top: 50%; transform: translateY(-50%); color: var(--text-muted); transition: transform 0.3s ease;}
625
+ details[open] > summary { background-color: #eedeff; }
626
  details[open] > summary::after { transform: translateY(-50%) rotate(180deg); }
627
+ .photo-preview img { max-width: 80px; max-height: 80px; border-radius: 8px; margin: 5px 5px 0 0; object-fit: cover; border: 1px solid var(--border-color);}
628
+ .message { padding: 12px 20px; border-radius: 8px; margin-bottom: 20px; color: #fff; font-weight: 500; border-left: 5px solid; display: flex; align-items: center; gap: 10px;}
629
+ .message.success { background-color: #28a745; border-color: #1e7e34; }
630
+ .message.error { background-color: #dc3545; border-color: #a71d2a; }
631
+ .message.warning { background-color: #ffc107; color: #333; border-color: #d39e00; }
632
+ .sync-buttons { display: flex; gap: 15px; margin-bottom: 20px; }
633
  </style>
634
  </head>
635
  <body>
636
  <div class="container">
637
+ <div class="header">
638
+ <h1><i class="fas fa-tools"></i> Администрирование ОсОО "Раина"</h1>
639
+ <a href="{{ url_for('landing') }}" class="button"><i class="fas fa-home"></i> На сайт</a>
640
+ </div>
641
+
642
+ {% with messages = get_flashed_messages(with_categories=true) %}
643
+ {% if messages %}
644
+ {% for category, message in messages %}
645
+ <div class="message {{ category }}"><i class="fas fa-{{ category == 'success' ? 'check' : (category == 'error' ? 'times' : 'exclamation-triangle') }}"></i> {{ message }}</div>
646
+ {% endfor %}
647
+ {% endif %}
648
+ {% endwith %}
649
 
650
  <div class="section">
651
+ <h2><i class="fas fa-sync-alt"></i> Синхронизация данных</h2>
652
+ <div class="sync-buttons">
653
+ <form method="POST" action="{{ url_for('force_upload') }}" style="margin:0;">
654
+ <button type="submit" class="button"><i class="fas fa-upload"></i> Загрузить на сервер</button>
655
+ </form>
656
+ <form method="POST" action="{{ url_for('force_download') }}" style="margin:0;">
657
+ <button type="submit" class="button"><i class="fas fa-download"></i> Скачать с сервера</button>
658
+ </form>
659
+ </div>
660
  </div>
661
 
662
  <div class="section">
663
+ <h2><i class="fas fa-images"></i> Реализованные проекты</h2>
664
+ <details><summary><i class="fas fa-plus-circle"></i> Добавить новый проект</summary>
665
+ <form method="POST" enctype="multipart/form-data">
666
+ <input type="hidden" name="action" value="add_project">
667
+ <label>Название проекта*:</label><input type="text" name="title" required>
668
+ <label>Краткое описание*:</label><textarea name="description" rows="3" required></textarea>
669
+ <label>Фото проекта* (рекомендуется 16:9):</label><input type="file" name="photo" accept="image/*" required>
670
+ <button type="submit"><i class="fas fa-save"></i> Добавить проект</button>
671
  </form>
672
  </details>
673
  <div class="item-list">
674
  {% for project in projects %}
675
  <div class="item">
676
  <p><strong>{{ project.title }}</strong>: {{ project.description }}</p>
677
+ {% if project.photo %}
678
+ <div class="photo-preview">
679
+ <img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/projects/{{ project.photo }}" alt="Project Photo">
680
+ </div>
681
+ {% endif %}
682
  <div class="item-actions">
683
+ <button onclick="toggleEditForm('edit-project-{{ loop.index0 }}')"><i class="fas fa-edit"></i> Редактировать</button>
684
+ <form method="POST" style="margin:0;">
685
+ <input type="hidden" name="action" value="delete_project">
686
+ <input type="hidden" name="index" value="{{ loop.index0 }}">
687
+ <button type="submit" class="delete-button"><i class="fas fa-trash-alt"></i> Удалить</button>
688
+ </form>
689
  </div>
690
  <div id="edit-project-{{ loop.index0 }}" class="edit-form-container">
691
+ <form method="POST" enctype="multipart/form-data">
692
+ <input type="hidden" name="action" value="edit_project">
693
+ <input type="hidden" name="index" value="{{ loop.index0 }}">
694
+ <label>Название проекта*:</label><input type="text" name="title" value="{{ project.title }}" required>
695
+ <label>Краткое описание*:</label><textarea name="description" rows="3" required>{{ project.description }}</textarea>
696
  <label>Заменить фото:</label><input type="file" name="photo" accept="image/*">
697
+ <button type="submit"><i class="fas fa-save"></i> Сохранить изменения</button>
698
  </form>
699
  </div>
700
  </div>
 
703
  </div>
704
 
705
  <div class="section">
706
+ <h2><i class="fas fa-briefcase"></i> Услуги "под ключ"</h2>
707
+ <details><summary><i class="fas fa-plus-circle"></i> Добавить новую услугу</summary>
708
+ <form method="POST" enctype="multipart/form-data">
709
+ <input type="hidden" name="action" value="add_service">
710
+ <label>Заголовок услуги*:</label><input type="text" name="title" required>
711
+ <label>Иконка (FontAwesome Class)*:</label><input type="text" name="icon" placeholder="fas fa-tools" required>
712
+ <label>Описание услуги*:</label><textarea name="description" rows="3" required></textarea>
713
+ <label>Фото услуги (необязательно):</label><input type="file" name="photo" accept="image/*">
714
+ <button type="submit"><i class="fas fa-save"></i> Добавить услугу</button>
715
  </form>
716
  </details>
717
  <div class="item-list">
718
  {% for service in services %}
719
  <div class="item">
720
+ <p><i class="{{ service.icon }} fa-fw fa-2x" style="color: var(--primary-color); margin-right: 10px;"></i> <strong>{{ service.title }}</strong>: {{ service.description }}</p>
721
+ {% if service.photo %}
722
+ <div class="photo-preview">
723
+ <img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/services/{{ service.photo }}" alt="Service Photo">
724
+ </div>
725
+ {% endif %}
726
  <div class="item-actions">
727
+ <button onclick="toggleEditForm('edit-service-{{ loop.index0 }}')"><i class="fas fa-edit"></i> Редактировать</button>
728
+ <form method="POST" style="margin:0;">
729
+ <input type="hidden" name="action" value="delete_service">
730
+ <input type="hidden" name="index" value="{{ loop.index0 }}">
731
+ <button type="submit" class="delete-button"><i class="fas fa-trash-alt"></i> Удалить</button>
732
+ </form>
733
  </div>
734
  <div id="edit-service-{{ loop.index0 }}" class="edit-form-container">
735
+ <form method="POST" enctype="multipart/form-data">
736
+ <input type="hidden" name="action" value="edit_service">
737
+ <input type="hidden" name="index" value="{{ loop.index0 }}">
738
+ <label>Заголовок услуги*:</label><input type="text" name="title" value="{{ service.title }}" required>
739
  <label>Иконка*:</label><input type="text" name="icon" value="{{ service.icon }}" required>
740
+ <label>Описание услуги*:</label><textarea name="description" rows="3" required>{{ service.description }}</textarea>
741
  <label>Заменить фото:</label><input type="file" name="photo" accept="image/*">
742
+ <button type="submit"><i class="fas fa-save"></i> Сохранить изменения</button>
743
  </form>
744
  </div>
745
  </div>
 
749
 
750
  <div class="section">
751
  <h2><i class="fas fa-box-open"></i> Оборудование</h2>
752
+
753
+ <details><summary><i class="fas fa-plus-circle"></i> Добавить новую категорию</summary>
754
+ <form method="POST">
755
+ <input type="hidden" name="action" value="add_category">
756
+ <label>Название категории:</label><input type="text" name="category_name" required>
757
+ <button type="submit"><i class="fas fa-save"></i> Добавить категорию</button>
758
+ </form>
759
  </details>
760
  <div class="item-list">
761
  {% for category in categories %}
762
+ <div class="item" style="flex-direction: row; justify-content: space-between; align-items: center;">
763
+ <span style="font-weight: 500;">{{ category }}</span>
764
+ <form method="POST" style="margin: 0;">
765
+ <input type="hidden" name="action" value="delete_category">
766
+ <input type="hidden" name="category_name" value="{{ category }}">
767
+ <button type="submit" class="delete-button" style="margin:0;"><i class="fas fa-trash-alt"></i> Удалить</button>
768
+ </form>
769
  </div>
770
  {% endfor %}
771
  </div>
772
 
773
+ <details style="margin-top:30px;"><summary><i class="fas fa-plus-circle"></i> Добавить оборудование</summary>
774
+ <form method="POST" enctype="multipart/form-data">
775
+ <input type="hidden" name="action" value="add_equipment">
776
+ <label>Название оборудования*:</label><input type="text" name="name" required>
777
  <label>Цена (KGS)*:</label><input type="number" name="price" step="0.01" min="0" required>
778
+ <label>Категория:</label><select name="category">
779
+ <option value="Без категории">Без категории</option>
780
+ {% for cat in categories %}
781
+ <option value="{{ cat }}">{{ cat }}</option>
782
+ {% endfor %}
783
+ </select>
784
+ <label>Фото оборудования (необязательно):</label><input type="file" name="photo" accept="image/*">
785
+ <button type="submit"><i class="fas fa-save"></i> Добавить оборудование</button>
786
  </form>
787
  </details>
788
  <div class="item-list">
789
  {% for item in equipment %}
790
  <div class="item">
791
+ <p><strong style="color: var(--primary-color);">{{ item.name }}</strong> ({{ item.category | default('Без категории') }}) - {{ "%.2f"|format(item.price) }} KGS</p>
792
+ {% if item.photo %}
793
+ <div class="photo-preview">
794
+ <img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/equipment/{{ item.photo }}" alt="Equipment Photo">
795
+ </div>
796
+ {% endif %}
797
  <div class="item-actions">
798
+ <button onclick="toggleEditForm('edit-eq-{{ loop.index0 }}')"><i class="fas fa-edit"></i> Редактировать</button>
799
+ <form method="POST" style="margin:0;">
800
+ <input type="hidden" name="action" value="delete_equipment">
801
+ <input type="hidden" name="index" value="{{ loop.index0 }}">
802
+ <button type="submit" class="delete-button"><i class="fas fa-trash-alt"></i> Удалить</button>
803
+ </form>
804
  </div>
805
  <div id="edit-eq-{{ loop.index0 }}" class="edit-form-container">
806
+ <form method="POST" enctype="multipart/form-data">
807
+ <input type="hidden" name="action" value="edit_equipment">
808
+ <input type="hidden" name="index" value="{{ loop.index0 }}">
809
+ <label>Название оборудования*:</label><input type="text" name="name" value="{{ item.name }}" required>
810
  <label>Цена (KGS)*:</label><input type="number" name="price" value="{{ item.price }}" step="0.01" min="0" required>
811
+ <label>Категория:</label><select name="category">
812
+ {% for cat in categories %}
813
+ <option value="{{ cat }}" {% if item.category == cat %}selected{% endif %}>{{ cat }}</option>
814
+ {% endfor %}
815
+ </select>
816
  <label>Заменить фото:</label><input type="file" name="photo" accept="image/*">
817
+ <button type="submit"><i class="fas fa-save"></i> Сохранить изменения</button>
818
  </form>
819
  </div>
820
  </div>
821
  {% endfor %}
822
  </div>
823
  </div>
824
+ </div>
825
+ <script>function toggleEditForm(id) { const element = document.getElementById(id); if (element.style.display === 'block') { element.style.display = 'none'; } else { element.style.display = 'block'; } }</script>
826
+ </body>
827
+ </html>
828
  '''
829
 
830
  @app.route('/')
 
977
  ext = os.path.splitext(photo.filename)[1].lower()
978
  photo_filename = f"{safe_name}_{datetime.now().strftime('%Y%m%d%H%M%S%f')}{ext}"
979
 
 
980
  photo_file_obj = io.BytesIO(photo.read())
981
 
982
  api.upload_file(
 
1009
  @app.route('/force_upload', methods=['POST'])
1010
  def force_upload():
1011
  upload_db_to_hf()
1012
+ flash("Данные успешно загружены на Hugging Face.", 'success')
1013
  return redirect(url_for('admin'))
1014
 
1015
  @app.route('/force_download', methods=['POST'])
1016
  def force_download():
1017
+ if download_db_from_hf():
1018
+ flash("Данные успешно скачаны с Hugging Face.", 'success')
1019
+ else:
1020
+ flash("Не удалось скачать данные с Hugging Face.", 'error')
1021
  return redirect(url_for('admin'))
1022
 
1023
  if __name__ == '__main__':
 
1026
  if HF_TOKEN_WRITE:
1027
  threading.Thread(target=periodic_backup, daemon=True).start()
1028
  port = int(os.environ.get('PORT', 7860))
1029
+ app.run(host='0.0.0.0', port=port)