Spaces:
Running
Running
| from flask import Flask, request, jsonify, redirect, url_for, render_template, send_from_directory, send_file, render_template_string | |
| import os | |
| import sqlite3 | |
| from datetime import datetime | |
| import pytz | |
| import io | |
| import base64 | |
| from dotenv import load_dotenv | |
| import requests | |
| import json | |
| import logging | |
| import uuid | |
| from io import BytesIO | |
| import uuid | |
| from tabulate import tabulate | |
| import numpy as np | |
| from scipy.optimize import linprog | |
| from typing import Dict, Any, Union, List | |
| import itertools | |
| import traceback | |
| import logging | |
| from werkzeug.utils import secure_filename | |
| import globs | |
| from api_logic import api | |
| from urllib.parse import urlencode # Добавлен правильный импорт | |
| load_dotenv() | |
| # Инициализация базы данных | |
| def init_db(db_name): | |
| conn = sqlite3.connect(db_name) | |
| cursor = conn.cursor() | |
| # Таблица с системными данными (твоя старая таблица) | |
| cursor.execute(''' | |
| CREATE TABLE IF NOT EXISTS system_data ( | |
| id INTEGER PRIMARY KEY AUTOINCREMENT, | |
| date_time TEXT, | |
| dey TEXT, | |
| wek TEXT, | |
| v_hid TEXT, | |
| v_min TEXT, | |
| ph TEXT, | |
| ec TEXT, | |
| tS TEXT, | |
| tA TEXT, | |
| hDm TEXT, | |
| sVen TEXT, | |
| onA TEXT, | |
| onB TEXT, | |
| onC TEXT, | |
| nPh TEXT, | |
| nEC TEXT, | |
| nTa TEXT, | |
| nLon TEXT, | |
| nLoff TEXT | |
| ) | |
| ''') | |
| # **Новая таблица для пользователей бота** | |
| cursor.execute(''' | |
| CREATE TABLE IF NOT EXISTS bot_users ( | |
| id INTEGER PRIMARY KEY AUTOINCREMENT, -- Уникальный ID | |
| chat_id INTEGER UNIQUE, -- Telegram ID пользователя | |
| created_at TEXT -- Время добавления (ISO формат) | |
| ) | |
| ''') | |
| conn.commit() | |
| conn.close() | |
| # Глобальные переменные | |
| api_key_sys = os.getenv('api_key') # Берём значение API-ключа | |
| btg_key = os.getenv('btg_key') # Берём значение | |
| btg_id = os.getenv('btg_id') # Берём значение | |
| btg_on = os.getenv('btg_on') # Берём значение | |
| globs.dey = 0 | |
| globs.wek = 0 | |
| globs.v_hid = 0 | |
| globs.v_min = 0 | |
| globs.ph = 0 | |
| globs.ec = 0 | |
| globs.tS = 0 | |
| globs.tA = 0 | |
| globs.hDm = 0 | |
| globs.sVen = 0 | |
| globs.onA = 0 | |
| globs.onB = 0 | |
| globs.onC = 0 | |
| globs.ph_eep = 0 | |
| globs.ph_on_eep = 0 | |
| globs.ec_eep = 0 | |
| globs.ec_A_eep = 0 | |
| globs.ec_B_eep = 0 | |
| globs.ec_C_eep = 0 | |
| globs.l_ON_h_eep = 0 | |
| globs.l_ON_m_eep = 0 | |
| globs.l_OFF_h_eep = 0 | |
| globs.l_OFF_m_eep = 0 | |
| globs.t_Voz_eep = 0 | |
| # Создаем экземпляр Flask-приложения | |
| app = Flask(__name__, template_folder="./") | |
| app.config['DEBUG'] = True | |
| UPLOAD_FOLDER = 'uploads' | |
| ALLOWED_EXTENSIONS = {'jpg', 'jpeg', 'png', 'gif'} | |
| # Создаем папку для загрузок если ее нет | |
| os.makedirs(UPLOAD_FOLDER, exist_ok=True) | |
| # Глобальная переменная для хранения последнего изображения в памяти | |
| latest_image = {"data": None, "filename": None} | |
| # Настроим логирование | |
| logging.basicConfig(level=logging.DEBUG) | |
| # Функция сохранения в базу данных системы автоматизации гидропоники | |
| def save_data_to_db(db_name, data): | |
| try: | |
| conn = sqlite3.connect(db_name) | |
| cursor = conn.cursor() | |
| # ✅ Устанавливаем московское время (UTC+3) | |
| moscow_tz = pytz.timezone("Europe/Moscow") | |
| current_time = datetime.now(moscow_tz).strftime('%Y-%m-%d %H:%M:%S') | |
| # Вставляем данные в таблицу | |
| cursor.execute(''' | |
| INSERT INTO system_data ( | |
| date_time, dey, wek, v_hid, v_min, ph, ec, tS, tA, hDm, sVen, onA, onB, onC, nPh, nEC, nTa, nLon, nLoff | |
| ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) | |
| ''', ( | |
| current_time, # ✅ Дата и время по Москве | |
| data['dey'], data['wek'], data['v_hid'], data['v_min'], data['ph'], data['ec'], | |
| data['tS'], data['tA'], data['hDm'], data['sVen'], data['onA'], data['onB'], | |
| data['onC'], data['nPh'], data['nEC'], data['nTa'], data['nLon'], data['nLoff'] | |
| )) | |
| conn.commit() | |
| conn.close() | |
| except Exception as e: | |
| return jsonify({'status': 'error', 'message': str(e)}), 500 | |
| # Маршрут сохранения в базу | |
| def sav_db_api(): | |
| # Инициализируем базу данных | |
| init_db('system_data.db') | |
| # Получаем данные из запроса | |
| data = { | |
| 'dey': request.args.get('dey'), | |
| 'wek': request.args.get('wek'), | |
| 'v_hid': request.args.get('v_hid'), | |
| 'v_min': request.args.get('v_min'), | |
| 'ph': request.args.get('ph'), | |
| 'ec': request.args.get('ec'), | |
| 'tS': request.args.get('tS'), | |
| 'tA': request.args.get('tA'), | |
| 'hDm': request.args.get('hDm'), | |
| 'sVen': request.args.get('sVen'), | |
| 'onA': request.args.get('onA'), | |
| 'onB': request.args.get('onB'), | |
| 'onC': request.args.get('onC'), | |
| 'nPh': request.args.get('nPh'), | |
| 'nEC': request.args.get('nEC'), | |
| 'nTa': request.args.get('nTa'), | |
| 'nLon': request.args.get('nLon'), | |
| 'nLoff': request.args.get('nLoff') | |
| } | |
| # Проверяем, что все необходимые параметры переданы | |
| required_params = ['dey', 'wek', 'v_hid', 'v_min', 'ph', 'ec', 'tS', 'tA', 'hDm', 'sVen', 'onA', 'onB', 'onC', 'nPh', 'nEC', 'nTa', 'nLon', 'nLoff'] | |
| for param in required_params: | |
| if data[param] is None: | |
| return jsonify({'status': 'error', 'message': f'Отсутствует параметр: {param}'}), 400 | |
| # Сохраняем данные в базу | |
| save_data_to_db('system_data.db', data) | |
| # Возвращаем ответ | |
| return jsonify({'status': 'success', 'message': 'Save OK'}) | |
| # Проверка входа на страницы | |
| def check_api_key(): | |
| api_sys_param = request.args.get('api_sys') # Получаем параметр из запроса | |
| if api_sys_param == api_key_sys: | |
| return jsonify({"status": "ok"}), 200 # ✅ Совпадает — отправляем "ok" | |
| else: | |
| return jsonify({"status": "error", "message": "Invalid API key"}), 403 # ❌ Ошибка 403 | |
| # Тестовый запрос с установки | |
| def test_server(): | |
| api_key_param = request.args.get('api_sys') # Получаем параметр из запроса | |
| err_ser = 1 if api_key_param == api_key_sys else 0 # Проверяем совпадение ключей | |
| return jsonify(err_ser=err_ser) | |
| def test_server_str(): | |
| api_key_param = request.args.get('api_sys') | |
| err_ser = "1" if api_key_param == api_key_sys else "0" | |
| return err_ser # Возвращаем строку "1" или "0" | |
| # Тестовый запрос с установки | |
| def btg_teleg(): | |
| api_key_param = request.args.get('api_sys') # Получаем параметр из запроса | |
| return jsonify(btg_key_ser=btg_key,btg_id_ser=btg_id,btg_on_ser=btg_on) | |
| # Маршрут для вывода всех данных из таблицы | |
| def get_all_data(): | |
| try: | |
| conn = sqlite3.connect('system_data.db') | |
| cursor = conn.cursor() | |
| # Выполняем запрос для получения всех данных из таблицы | |
| cursor.execute('SELECT * FROM system_data') | |
| rows = cursor.fetchall() | |
| # Получаем названия столбцов | |
| column_names = [description[0] for description in cursor.description] | |
| # Преобразуем данные в формат JSON | |
| data = [] | |
| for row in rows: | |
| data.append(dict(zip(column_names, row))) | |
| conn.close() | |
| # Возвращаем данные в формате JSON | |
| return jsonify(data) | |
| except Exception as e: | |
| return jsonify({'status': 'error', 'message': str(e)}), 500 | |
| # Удаление базы | |
| def delete_db(): | |
| try: | |
| conn = sqlite3.connect("system_data.db") # Используем вашу БД | |
| cursor = conn.cursor() | |
| # ✅ Удаляем все записи из таблицы | |
| cursor.execute("DELETE FROM system_data") | |
| # ✅ Сбрасываем автоинкрементный счётчик ID (для SQLite) | |
| cursor.execute("DELETE FROM sqlite_sequence WHERE name='system_data'") | |
| conn.commit() | |
| conn.close() | |
| return jsonify({'status': 'ok', 'message': 'База данных успешно очищена'}) | |
| except Exception as e: | |
| return jsonify({'status': 'error', 'message': str(e)}), 500 | |
| def plot_week(): | |
| try: | |
| # Получаем номер недели из параметров запроса | |
| week_number = request.args.get('week', default=1, type=int) | |
| week_number = max(1, min(30, week_number)) # Ограничиваем диапазон 1-30 | |
| # Подключаемся к базе данных | |
| conn = sqlite3.connect('system_data.db') | |
| cursor = conn.cursor() | |
| # Проверяем существование таблицы | |
| cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='system_data'") | |
| table_exists = cursor.fetchone() | |
| if not table_exists: | |
| conn.close() | |
| return render_template('plot_week.html', data=None, week_number=week_number, table_exists=False) | |
| # Запрашиваем данные за выбранную неделю | |
| cursor.execute(''' | |
| SELECT date_time, dey, ph, ec, tS, tA, hDm, sVen, onA, onB, onC, v_hid, v_min | |
| FROM system_data | |
| WHERE wek = ? | |
| ORDER BY date_time | |
| ''', (str(week_number),)) # Приводим week_number к строке, так как wek имеет тип TEXT | |
| rows = cursor.fetchall() | |
| conn.close() | |
| # Если данных нет | |
| if not rows: | |
| return render_template('plot_week.html', data=None, week_number=week_number, table_exists=True) | |
| # Формируем данные для JSON | |
| data = { | |
| 'week': week_number, | |
| 'dates': [row[0] for row in rows], | |
| 'days_of_week': [int(row[1]) if row[1] else 0 for row in rows], # Преобразуем dey в int | |
| 'ph': [float(row[2]) if row[2] else 0.0 for row in rows], # pH | |
| 'ec': [float(row[3]) if row[3] else 0.0 for row in rows], # EC | |
| 'tS': [float(row[4]) if row[4] else 0.0 for row in rows], # Температура раствора | |
| 'tA': [float(row[5]) if row[5] else 0.0 for row in rows], # Температура воздуха | |
| 'hDm': [float(row[6]) if row[6] else 0.0 for row in rows], # Влажность воздуха | |
| 'sVen': [float(row[7]) if row[7] else 0.0 for row in rows], # Обороты вентилятора | |
| 'onA': [float(row[8]) if row[8] else 0.0 for row in rows], # Насос A | |
| 'onB': [float(row[9]) if row[9] else 0.0 for row in rows], # Насос B | |
| 'onC': [float(row[10]) if row[10] else 0.0 for row in rows], # Насос C | |
| 'sus': [f"{row[11]}:{row[12]}" if row[11] and row[12] else "0:0" for row in rows] # Объединяем v_hid и v_min | |
| } | |
| # Отправляем данные в HTML-шаблон | |
| return render_template('plot_week.html', data=data, week_number=week_number, table_exists=True) | |
| except Exception as e: | |
| # Логируем ошибку в консоль для отладки | |
| print(f"Ошибка: {str(e)}") | |
| return render_template('plot_week.html', data=None, week_number=week_number, table_exists=True, message=f"Ошибка: {str(e)}") | |
| def index(): | |
| return flask.render_template('index.html') | |
| def online(): | |
| return render_template('online.html') | |
| def table(): | |
| return render_template('table.html') | |
| def online_api(): | |
| # Устанавливаем московское время (UTC+3) | |
| moscow_tz = pytz.timezone("Europe/Moscow") | |
| current_time = datetime.now(moscow_tz) | |
| # Форматируем дату и время отдельно | |
| date = current_time.strftime('%Y-%m-%d') # Например, "2025-03-23" | |
| time = current_time.strftime('%H:%M:%S') # Например, "14:35:42" | |
| return jsonify( | |
| dey=globs.dey, | |
| wek=globs.wek, | |
| v_hid=globs.v_hid, | |
| v_min=globs.v_min, | |
| ph=globs.ph, | |
| ec=globs.ec, | |
| tS=globs.tS, | |
| tA=globs.tA, | |
| hDm=globs.hDm, | |
| sVen=globs.sVen, | |
| rFul=globs.rFul, | |
| rLi=globs.rLi, | |
| rWat=globs.rWat, | |
| rRas=globs.rRas, | |
| rPH=globs.rPH, | |
| rEC=globs.rEC, | |
| rSl=globs.rSl, | |
| rLe=globs.rLe, | |
| alW=globs.alW, | |
| ec_A_eep=globs.ec_A_eep, | |
| ec_B_eep=globs.ec_B_eep, | |
| ec_C_eep=globs.ec_C_eep, | |
| date=date, # Добавляем дату | |
| time=time # Добавляем время | |
| ) | |
| def settings(): | |
| return render_template('settings.html') | |
| def settings_api(): | |
| return jsonify(ph_eep=globs.ph_eep, | |
| ph_on_eep=globs.ph_on_eep, | |
| ec_eep=globs.ec_eep, | |
| ec_A_eep=globs.ec_A_eep, | |
| ec_B_eep=globs.ec_B_eep, | |
| ec_C_eep=globs.ec_C_eep, | |
| l_ON_h_eep=globs.l_ON_h_eep, | |
| l_ON_m_eep=globs.l_ON_m_eep, | |
| l_OFF_h_eep=globs.l_OFF_h_eep, | |
| l_OFF_m_eep=globs.l_OFF_m_eep, | |
| t_Voz_eep=globs.t_Voz_eep, | |
| set_st=globs.set_status | |
| ) | |
| def set_pH_value(): | |
| ph_value = request.args.get('value') | |
| globs.ph_set = ph_value | |
| globs.eep_set = 1 | |
| return "pH value set successfully" | |
| def ph_on_value(): | |
| ph_on_value = request.args.get('value') | |
| globs.ph_on_set = ph_on_value | |
| globs.eep_set = 2 | |
| return "EC value set successfully" | |
| def set_EC_value(): | |
| ec_value = request.args.get('value') | |
| globs.ec_set = ec_value | |
| globs.eep_set = 3 | |
| return "EC value set successfully" | |
| def ec_A_setValue(): | |
| ec_A_setValue = request.args.get('value') | |
| globs.ec_A_set = ec_A_setValue | |
| globs.eep_set = 4 | |
| return "EC value set successfully" | |
| def ec_B_setValue(): | |
| ec_B_setValue = request.args.get('value') | |
| globs.ec_B_set = ec_B_setValue | |
| globs.eep_set = 5 | |
| return "EC value set successfully" | |
| def ec_C_setValue(): | |
| ec_C_setValue = request.args.get('value') | |
| globs.ec_C_set = ec_C_setValue | |
| globs.eep_set = 6 | |
| return "EC value set successfully" | |
| def l_ON_set(): | |
| globs.l_ON_h_set = request.args.get('l_ON_h_set') | |
| globs.l_ON_m_set = request.args.get('l_ON_m_set') | |
| globs.eep_set = 7 | |
| return "EC value set successfully" | |
| def l_OFF_set(): | |
| globs.l_OFF_h_set = request.args.get('l_OFF_h_set') | |
| globs.l_OFF_m_set = request.args.get('l_OFF_m_set') | |
| globs.eep_set = 8 | |
| return "EC value set successfully" | |
| def t_Voz_eep_set(): | |
| t_Voz_eep_set = request.args.get('value') | |
| globs.t_Voz_set = t_Voz_eep_set | |
| globs.eep_set = 9 | |
| return "EC value set successfully" | |
| def but_start(): | |
| globs.eep_set = 10 | |
| return jsonify(value_set="start") | |
| def but_stop(): | |
| globs.eep_set = 11 | |
| return jsonify(value_set="stop") | |
| def but_res(): | |
| globs.eep_set = 12 | |
| return jsonify(value_set="res") | |
| def but_sliv(): | |
| globs.eep_set = 13 | |
| return jsonify(value_set="sliv") | |
| def handle_api(): | |
| response = api() | |
| return response | |
| def handle_save_db(): | |
| response = save_db() | |
| return response | |
| def set_res(): | |
| globs.eep_set = 0 | |
| return jsonify(value_set="reset") | |
| def allowed_file(filename): | |
| return '.' in filename and \ | |
| filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS | |
| def upload_file(): | |
| if 'file' not in request.files: | |
| return jsonify({"error": "No file part"}), 400 | |
| file = request.files['file'] | |
| if file.filename == '': | |
| return jsonify({"error": "No selected file"}), 400 | |
| if not allowed_file(file.filename): | |
| return jsonify({"error": "Invalid file type"}), 400 | |
| # Генерируем имя для файла с использованием времени | |
| timestamp = datetime.now().strftime('%Y.%m.%d_%H:%M:%S_') | |
| filename = timestamp + file.filename | |
| save_path = os.path.join(UPLOAD_FOLDER, filename) | |
| # Открываем файл для записи и собираем его части | |
| try: | |
| with open(save_path, 'wb') as f: | |
| while chunk := file.read(1024): # Чтение и запись данных частями | |
| f.write(chunk) | |
| return jsonify({ | |
| "message": "File uploaded successfully", | |
| "filename": filename, | |
| "path": save_path | |
| }), 200 | |
| except Exception as e: | |
| return jsonify({"error": str(e)}), 500 | |
| def uploaded_file(filename): | |
| return send_from_directory(UPLOAD_FOLDER, filename) | |
| # 🧠 2. Маршрут: сохраняет файл только в память (BytesIO) | |
| # Маршрут для загрузки файла в память | |
| # Загрузка изображения в память (в виде байтов) | |
| def upload_file_to_memory(): | |
| if 'file' not in request.files: | |
| return jsonify({"error": "No file part"}), 400 | |
| file = request.files['file'] | |
| if file.filename == '': | |
| return jsonify({"error": "No selected file"}), 400 | |
| if not file.filename.lower().endswith(('.jpg', '.jpeg', '.png')): | |
| return jsonify({"error": "Invalid file type"}), 400 | |
| timestamp = datetime.now().strftime('%Y.%m.%d_%H:%M:%S_') | |
| filename = timestamp + file.filename | |
| try: | |
| # Читаем весь файл в байты | |
| file_bytes = file.read() | |
| # Сохраняем в переменную | |
| latest_image["data"] = file_bytes | |
| latest_image["filename"] = filename | |
| return jsonify({ | |
| "message": "Image uploaded successfully", | |
| "filename": filename | |
| }), 200 | |
| except Exception as e: | |
| return jsonify({"error": str(e)}), 500 | |
| # Получение последнего изображения | |
| def get_last_image(): | |
| if not latest_image["data"]: | |
| return jsonify({"error": "No image available"}), 404 | |
| # Возвращаем через новый BytesIO каждый раз | |
| return send_file( | |
| BytesIO(latest_image["data"]), | |
| mimetype='image/jpeg', | |
| download_name=latest_image["filename"] | |
| ) | |
| def view_image(): | |
| return render_template('show_image.html') | |
| def nutri_call(): | |
| return render_template('nutri_call.html') | |
| def spoller(): | |
| return render_template('spoller.html') | |
| from tabulate import tabulate | |
| INPUT_DATA = { | |
| "fertilizerConstants": { | |
| "Кальциевая селитра": {"N (NO3-)": 0.11863, "Ca_nitrate": 0.16972}, | |
| "Хелат кальция": {"Ca_chelate": 0.10}, | |
| "Калий азотнокислый": {"N (NO3-)": 0.13854, "K": 0.36672}, | |
| "Аммоний азотнокислый": {"N (NO3-)": 0.17499, "N (NH4+)": 0.17499}, | |
| "Сульфат магния": {"Mg": 0.10220, "S": 0.13483}, | |
| "Монофосфат калия": {"P": 0.22761, "K": 0.28731}, | |
| "Калий сернокислый": {"K": 0.44874, "S": 0.18401} | |
| }, | |
| "profileSettings": { | |
| "P": 50, "K": 220, "Mg": 24, "S": 60, | |
| "Ca_nitrate_target": 60, | |
| "Ca_chelate_target": 20, | |
| "NO3_RAT": 4, "TOTAL_NITROG": 125, "liters": 100 | |
| } | |
| } | |
| class NutrientCalculator: | |
| def __init__(self, fertilizer_constants, profile_settings, liters, rounding_precision): | |
| self.fertilizers = fertilizer_constants | |
| self.profile = profile_settings | |
| self.volume = liters | |
| self.rounding_precision = rounding_precision | |
| total_parts = self.profile["NO3_RAT"] + 1 | |
| self.target = { | |
| 'P': self.profile["P"], | |
| 'K': self.profile["K"], | |
| 'Mg': self.profile["Mg"], | |
| 'S': self.profile["S"], | |
| 'Ca_nitrate': self.profile["Ca_nitrate_target"], | |
| 'Ca_chelate': self.profile["Ca_chelate_target"], | |
| 'N (NO3-)': self.profile["TOTAL_NITROG"] * (self.profile["NO3_RAT"] / total_parts), | |
| 'N (NH4+)': self.profile["TOTAL_NITROG"] * (1 / total_parts) | |
| } | |
| self.actual = { | |
| 'P': 0.0, 'K': 0.0, 'Mg': 0.0, 'S': 0.0, | |
| 'Ca_nitrate': 0.0, 'Ca_chelate': 0.0, | |
| 'N (NO3-)': 0.0, 'N (NH4+)': 0.0 | |
| } | |
| self.results = {fert: {'граммы': 0.0} for fert in self.fertilizers} | |
| def _apply_fertilizer(self, name, element, target_ppm): | |
| if name not in self.fertilizers: | |
| raise KeyError(f"Удобрение '{name}' не найдено!") | |
| content = self.fertilizers[name].get(element, 0) | |
| if content == 0: | |
| print(f"ПРЕДУПРЕЖДЕНИЕ: Удобрение '{name}' не содержит элемент '{element}'") | |
| return | |
| grams = (target_ppm * self.volume) / (content * 1000) | |
| self.results[name]['граммы'] += round(grams, self.rounding_precision) | |
| for el, val in self.fertilizers[name].items(): | |
| if el in self.actual: | |
| added_ppm = (grams * val * 1000) / self.volume | |
| self.actual[el] += round(added_ppm, self.rounding_precision) | |
| def _balance_k_s(self): | |
| k_needed = self.target["K"] - self.actual["K"] | |
| s_needed = self.target["S"] - self.actual["S"] | |
| if k_needed > 0 and s_needed > 0: | |
| k_fraction = self.fertilizers["Калий сернокислый"].get("K", 0) | |
| s_fraction = self.fertilizers["Калий сернокислый"].get("S", 0) | |
| if k_fraction == 0 or s_fraction == 0: | |
| return | |
| k_from_k2so4 = min(k_needed, s_needed * k_fraction / s_fraction) | |
| self._apply_fertilizer("Калий сернокислый", "K", k_from_k2so4) | |
| remaining_k = self.target["K"] - self.actual["K"] | |
| if remaining_k > 0: | |
| self._apply_fertilizer("Калий азотнокислый", "K", remaining_k) | |
| def _verify_results(self): | |
| deficits = {} | |
| for el in self.target: | |
| diff = self.target[el] - self.actual[el] | |
| if abs(diff) > 0.1: | |
| deficits[el] = round(diff, self.rounding_precision) | |
| return { | |
| 'fertilizers': {k: round(v['граммы'], self.rounding_precision) for k, v in self.results.items() if v['граммы'] > 0}, | |
| 'actual_profile': {k: round(v, self.rounding_precision) for k, v in self.actual.items()}, | |
| 'deficits': deficits | |
| } | |
| def calculate(self): | |
| # 1. Вносим хелатный кальций - строго 20 ppm | |
| self._apply_fertilizer("Хелат кальция", "Ca_chelate", self.target["Ca_chelate"]) | |
| # 2. Вносим нитратный кальций - строго 60 ppm | |
| self._apply_fertilizer("Кальциевая селитра", "Ca_nitrate", self.target["Ca_nitrate"]) | |
| # 3. Остальные элементы | |
| self._apply_fertilizer("Аммоний азотнокислый", "N (NH4+)", self.target["N (NH4+)"]) | |
| self._apply_fertilizer("Монофосфат калия", "P", self.target["P"]) | |
| self._apply_fertilizer("Сульфат магния", "Mg", self.target["Mg"]) | |
| self._balance_k_s() | |
| # 4. Балансируем нитратный азот | |
| no3_needed = self.target["N (NO3-)"] - self.actual["N (NO3-)"] | |
| if no3_needed > 0: | |
| self._apply_fertilizer("Калий азотнокислый", "N (NO3-)", no3_needed) | |
| return self._verify_results() | |
| def generate_report(self, results): | |
| fert_table = [] | |
| for name, data in results['fertilizers'].items(): | |
| fert_table.append([name, f"{data} г"]) | |
| total_ca = self.actual['Ca_nitrate'] + self.actual['Ca_chelate'] | |
| ca_diff = total_ca - (self.target['Ca_nitrate'] + self.target['Ca_chelate']) | |
| element_table = [ | |
| ["P", f"{self.target['P']} ppm", f"{self.actual['P']} ppm"], | |
| ["K", f"{self.target['K']} ppm", f"{self.actual['K']} ppm"], | |
| ["Mg", f"{self.target['Mg']} ppm", f"{self.actual['Mg']} ppm"], | |
| ["S", f"{self.target['S']} ppm", f"{self.actual['S']} ppm"], | |
| ["Ca (нитрат)", f"{self.target['Ca_nitrate']} ppm", f"{self.actual['Ca_nitrate']} ppm"], | |
| ["Ca (хелат)", f"{self.target['Ca_chelate']} ppm", f"{self.actual['Ca_chelate']} ppm"], | |
| ["Ca (общий)", f"80 ppm", f"{total_ca} ppm", f"{ca_diff:+.2f} ppm"], | |
| ["N-NO3", f"{self.target['N (NO3-)']} ppm", f"{self.actual['N (NO3-)']} ppm"], | |
| ["N-NH4", f"{self.target['N (NH4+)']} ppm", f"{self.actual['N (NH4+)']} ppm"] | |
| ] | |
| report = "РЕКОМЕНДУЕМЫЕ УДОБРЕНИЯ:\n" | |
| report += tabulate(fert_table, headers=["Удобрение", "Количество"], tablefmt="grid") | |
| report += "\n\nБАЛАНС ЭЛЕМЕНТОВ:\n" | |
| report += tabulate(element_table, | |
| headers=["Элемент", "Цель", "Факт", "Отклонение"], | |
| tablefmt="grid") | |
| if results['deficits']: | |
| report += "\n\nВНИМАНИЕ: Обнаружены небольшие отклонения:" | |
| for el, diff in results['deficits'].items(): | |
| report += f"\n- {el}: не хватает {abs(diff)} ppm" | |
| return report | |
| # Запуск расчета | |
| calculator = NutrientCalculator( | |
| fertilizer_constants=INPUT_DATA["fertilizerConstants"], | |
| profile_settings=INPUT_DATA["profileSettings"], | |
| liters=INPUT_DATA["profileSettings"]["liters"], | |
| rounding_precision=3 | |
| ) | |
| results = calculator.calculate() | |
| print(calculator.generate_report(results)) | |
| def handle_calculation(): | |
| try: | |
| # 1. Получаем и парсим данные | |
| data = request.get_json() | |
| if not data: | |
| return jsonify({"error": "No JSON data received"}), 400 | |
| # Логируем входные данные для отладки | |
| print("\n=== ВХОДНЫЕ ДАННЫЕ ===") | |
| print(json.dumps(data, indent=2, ensure_ascii=False)) | |
| # 2. Извлекаем основные параметры | |
| fertilizer_constants = data.get("fertilizerConstants", {}) | |
| profile_settings = data.get("profileSettings", {}) | |
| # Проверяем наличие необходимых полей | |
| if not fertilizer_constants or not profile_settings: | |
| return jsonify({"error": "Missing fertilizerConstants or profileSettings"}), 400 | |
| # 3. Извлекаем дополнительные параметры | |
| liters = profile_settings.get("liters", 100) # Объем раствора (литры) | |
| rounding_precision = profile_settings.get("rounding_precision", 3) # Точность округления | |
| # 4. Создаем и запускаем калькулятор | |
| calculator = NutrientCalculator( | |
| fertilizer_constants=fertilizer_constants, | |
| profile_settings=profile_settings, | |
| liters=liters, | |
| rounding_precision=rounding_precision | |
| ) | |
| results = calculator.calculate() | |
| # 5. Формируем дополнительные данные | |
| element_contributions = {} | |
| for fert_name in calculator.fertilizers.keys(): | |
| grams = calculator.results[fert_name]['граммы'] | |
| element_contributions[fert_name] = {} | |
| for element, percent in calculator.fertilizers[fert_name].items(): | |
| added_ppm = (grams * percent * 1000) / calculator.volume | |
| element_contributions[fert_name][element] = round(added_ppm, rounding_precision) | |
| # 6. Формируем полный ответ | |
| response = { | |
| "fertilizers": results['fertilizers'], | |
| "actual_profile": results['actual_profile'], | |
| "deficits": results['deficits'], | |
| "total_ppm": results['total_ppm'], | |
| "element_contributions": element_contributions, | |
| "nitrogen_ratios": { | |
| "NH4_RATIO": 1, | |
| "NO3_RATIO": profile_settings.get("NO3_RAT", 0), | |
| "TOTAL_NITROGEN": profile_settings.get("TOTAL_NITROG", 0) | |
| } | |
| } | |
| return jsonify(response), 200 | |
| except Exception as e: | |
| error_msg = f"Server error: {str(e)}" | |
| print(error_msg) | |
| return jsonify({ | |
| "error": error_msg, | |
| "fertilizers": {}, | |
| "actual_profile": {}, | |
| "deficits": {}, | |
| "element_contributions": {}, | |
| "total_ppm": 0 | |
| }), 500 | |
| # Чат | |
| # Глобальные переменные | |
| messages = [] | |
| def chat_page(): | |
| user_id = str(uuid.uuid4())[:8] | |
| return render_template('chat.html', user_id=user_id) | |
| def send_message(): | |
| data = request.json | |
| user_id = data.get('user_id', 'anon') | |
| encrypted_text = data.get('encrypted_text', '') | |
| if not encrypted_text: | |
| return jsonify({'status': 'error', 'message': 'Empty encrypted message'}) | |
| message = { | |
| 'id': len(messages) + 1, | |
| 'user_id': user_id, | |
| 'encrypted_text': encrypted_text, | |
| 'timestamp': datetime.now().strftime("%H:%M:%S"), | |
| 'username': f"User_{user_id}" | |
| } | |
| messages.append(message) | |
| if len(messages) > 200: | |
| messages.pop(0) | |
| return jsonify({'status': 'success'}) | |
| def get_messages(): | |
| return jsonify({'messages': messages}) | |
| def get_new_messages(): | |
| last_id = request.args.get('last_id', 0, type=int) | |
| new_messages = [m for m in messages if m['id'] > last_id] | |
| return jsonify({'messages': new_messages}) | |
| def clear_chat(): | |
| global messages | |
| messages.clear() | |
| return jsonify({'status': 'success', 'message': 'Chat cleared'}) | |
| # Паттерны в виде JSON | |
| PATTERNS = { | |
| "pattern1": [ | |
| "577416161737624147874371687965675387455166255764767112646", | |
| "35257777811486552662718756762243826291673871341244823811", | |
| "8773555692535217838989632448467218821741268475368315292", | |
| "651811262788738622888695683314939713825385323895246722", | |
| "26992388967612584177565252645433784217824855285761494", | |
| "8692527864473743585322777819976163638616341714347544", | |
| "562779651821127844854955691974779992577975885772398", | |
| "28957626913239638349451261172257992735773474359538", | |
| "1853488614553692274496387289473792918351722785582", | |
| "948737575918962492846926918421172219286894964141", | |
| "43611333519868642131628619363289431215584461555", | |
| "7972466861855516344781571399518474336143817611", | |
| "779613557941167978269638439569322769757298472", | |
| "57674813745274776186692373526354946733928329", | |
| "3442394129792254795362511878989441416321252", | |
| "786534532772479275898762966888485557953377", | |
| "65287985959627293488648263577334113758615", | |
| "2716784555689923737513189835167524134576", | |
| "987463911258925111364498828674376547934", | |
| "86219312374827622491848711542714292737", | |
| "5831343512319484641933682696985622911", | |
| "424477863541433115136951866684284212", | |
| "66825659895576426649656953353613633", | |
| "3517225885134168314622658688974996", | |
| "868947474647575245184824557872496", | |
| "55842222112333769693316913669646", | |
| "1436444323566146663647614936611", | |
| "579188755823751339912475439372", | |
| "37197631415136463913623973319", | |
| "1817494556649119314985371641", | |
| "998244912314121345484818715", | |
| "98168413545533479933399686", | |
| "8975354899186727936639655", | |
| "873889389195499739393621", | |
| "61278328115949713333983", | |
| "7396251926544784666382", | |
| "136876128298263133921", | |
| "49564731128189446323", | |
| "4521214231998481955", | |
| "973335654198339151", | |
| "71668229518263166", | |
| "8735142569189473", | |
| "618656726198421", | |
| "79522498718363", | |
| "7574648689299", | |
| "332113558229", | |
| "65324814142", | |
| "2856395556", | |
| "142935112", | |
| "56238623" | |
| ], | |
| "pattern2": [ | |
| "566464378915756256634131852256166", | |
| "23111716816332872397544947472773", | |
| "5422887597965169537398442229951", | |
| "964176357762676581138386442956", | |
| "61584983548844249242225186252", | |
| "7643482893738664266447695877", | |
| "417731183112531683182465465", | |
| "58514292423784752491612912", | |
| "4465622665163237641773213", | |
| "812284832679551415851534", | |
| "93413325847516556446687", | |
| "3754657432367212181356", | |
| "139123275594933399482", | |
| "43135593154436639431", | |
| "7448153469879393474", | |
| "283968716867333722", | |
| "12365687554166194", | |
| "3592256319573714", | |
| "852472941531185", | |
| "47629245684294", | |
| "2482269253624", | |
| "631486278986", | |
| "94535896885", | |
| "4988486574", | |
| "487335232", | |
| "36168755" | |
| ], | |
| "pattern3": [ | |
| "6874371515144176932462356166320254753", | |
| "562718666658584635618582773952279238", | |
| "28989533324443198279441951357497252", | |
| "1888586656887418197485156483247977", | |
| "977445322576259917234662132562775", | |
| "75289854734875918957138345728953", | |
| "3718849217363519853842279391858", | |
| "189734238199861848236497331944", | |
| "98717652919857933159147164148", | |
| "8688427211843736465152871553", | |
| "557369932937119112667168618", | |
| "13196935231821123834875579", | |
| "4416638754913235227363137", | |
| "857392639414558749199441", | |
| "43132893455914624119485", | |
| "7445183791515186521434", | |
| "289692171666695273577", | |
| "18662388733365791835", | |
| "9538527616692371928", | |
| "582479477362518121", | |
| "41627425198769933", | |
| "5789267618646936", | |
| "368284479511639", | |
| "95113827562793", | |
| "5624219328973", | |
| "286631351871", | |
| "15394486968", | |
| "6834835665", | |
| "527328232", | |
| "79151155" | |
| ] | |
| } | |
| def generate_mandala_html(pattern_array, container_id="mandala-container"): | |
| """Генерирует HTML код мандалы идентично JavaScript версии""" | |
| def get_color_for_digit(digit): | |
| color_map = { | |
| '1': 'yellow', '2': 'blue', '3': 'turquoise', '4': 'pink', | |
| '5': 'orange', '6': 'violet', '7': 'red', '8': 'dark_blue', '9': 'green' | |
| } | |
| return color_map.get(digit, 'white') | |
| html_parts = [f'<div class="hexagon size-16" id="{container_id}">'] | |
| for side in range(1, 7): | |
| html_parts.append(f'<div class="side side-{side}">') | |
| for line in range(16): | |
| if line >= len(pattern_array): | |
| break | |
| pattern_line = pattern_array[line] | |
| rombs_count = 16 - line | |
| html_parts.append(f'<div class="line line-{line}">') | |
| for i in range(min(rombs_count, len(pattern_line))): | |
| digit = pattern_line[i] | |
| color = get_color_for_digit(digit) | |
| html_parts.append( | |
| f'<div class="romb {color}"><span>{digit}</span></div>' | |
| ) | |
| html_parts.append('</div>') | |
| html_parts.append('</div>') | |
| html_parts.append('</div>') | |
| return '\n'.join(html_parts) | |
| # HTML шаблон с кнопками | |
| MANDALA_TEMPLATE = """ | |
| <!DOCTYPE html> | |
| <html> | |
| <head> | |
| <style> | |
| .button-container { | |
| text-align: center; | |
| margin: 20px 0; | |
| } | |
| .pattern-btn { | |
| padding: 10px 20px; | |
| margin: 0 10px; | |
| font-size: 16px; | |
| cursor: pointer; | |
| background-color: #4CAF50; | |
| color: white; | |
| border: none; | |
| border-radius: 5px; | |
| } | |
| .pattern-btn:hover { | |
| background-color: #45a049; | |
| } | |
| .pattern-btn.active { | |
| background-color: #2196F3; | |
| } | |
| #mandala-container { | |
| margin: 20px auto; | |
| } | |
| /* Базовые стили для мандалы */ | |
| .container_top { padding-top: 30px; } | |
| .details_numbers { margin: 15px 0 0px 0; } | |
| .details_numbers br { clear: both; } | |
| .result { margin: 40px 0 40px 0; } | |
| .one_letter { width: 16px; font-size: 16px; display: inline-block; /* float: left; */ } | |
| .bword { font-weight: bold; } | |
| .smenu { margin: 0 auto 20px auto; width: 100%; } | |
| .btn.disabled{ } | |
| .hid { display: none!important; } | |
| .clear { clear: both; } | |
| .graphs { margin: 20px 0 0 0; } | |
| .graph_numbers { /* float: left; */ } | |
| .graph_colors { /* float: left; margin: 0 40px 0 0;*/} | |
| .well-align { margin:auto; width: 490px; } | |
| .method_light { border-left: 1px #000 solid; border-top: 1px #000 solid; margin: auto;} | |
| .graph_numbers .method_light tr td { width: 18px; } | |
| .method_light tr td { border-right: 1px #000 solid; border-bottom: 1px #000 solid; vertical-align: middle; text-align: center; } | |
| .method_light tr td.decart_gorizont { border-bottom: 3px #000 solid!important; } | |
| .method_light tr td.decart_vertical { border-right: 3px #000 solid!important; } | |
| .graph_colors .method_light { border-left: 1px #000 solid; border-top: 1px #000 solid; } | |
| .graph_colors .method_light tr { padding: 0px; margin: 0px; } | |
| .graph_colors .method_light tr td { padding: 0px; margin: 0px; border-right: 1px #000 solid; border-bottom: 1px #000 solid; } | |
| .no-border .method_light, .no-border .method_light tr td { | |
| border: 0px!important; | |
| } | |
| .cell, .cell_noresize { text-align: center; display: block; width: 18px; height: 18px; } | |
| .cell_noresize { border: 1px #ccc solid; } | |
| .white { color: #ffffff; background-color: #ffffff; } | |
| .red { color: #ff0000; background-color: #ff0000; } | |
| .dark_blue { color: #0000ff; background-color: #0000ff; } | |
| .green { color: #336600; background-color: #336600; } | |
| .yellow { color: #ffff00; background-color: #ffff00; } | |
| .blue { color: #0099ff; background-color: #0099ff; } | |
| .turquoise { color: #00ffff; background-color: #00ffff; } | |
| .pink { color: #ff00ff; background-color: #ff00ff; } | |
| .orange { color: #ff9900; background-color: #ff9900; } | |
| .violet { color: #9900ff; background-color: #9900ff; } | |
| @media print { | |
| .white { color: #ffffff!important; background-color: #ffffff!important; } | |
| .red { color: #ff0000!important; background-color: #ff0000!important; } | |
| .dark_blue { color: #0000ff!important; background-color: #0000ff!important; } | |
| .green { color: #336600!important; background-color: #336600!important; } | |
| .yellow { color: #ffff00!important; background-color: #ffff00!important; } | |
| .blue { color: #0099ff!important; background-color: #0099ff!important; } | |
| .turquoise { color: #00ffff!important; background-color: #00ffff!important; } | |
| .pink { color: #ff00ff!important; background-color: #ff00ff!important; } | |
| .orange { color: #ff9900!important; background-color: #ff9900!important; } | |
| .violet { color: #9900ff!important; background-color: #9900ff!important; } | |
| } | |
| .hide, .mob-only { display: none;} | |
| .show { display: block; } | |
| .print { margin: 40px auto; } | |
| .one_color {} | |
| .one_color .cell_noresize { margin-right: 8px; } | |
| .one_color span { float: left; } | |
| .one_color p { clear: both; /* padding: 0 37px; */ } | |
| #word { max-width: calc(100% - 80px); height: 66x; font-size: 20px; } | |
| .navbar-form-own .btn-default { width: 18%; text-transform: uppercase; } | |
| .off { display: none; } | |
| .on { display: block; } | |
| .numerology_details { } | |
| .numerology_details tr td { text-align: center; width: 25px; } | |
| .numerology_details tr td.wc { font-weight: bold; } | |
| .plinks { margin: 10px 0 0 0; padding: 0px; } | |
| .plinks li { | |
| list-style: none; | |
| display: inline-block; | |
| margin: 0 20px 0 0; | |
| padding: 0px; | |
| } | |
| .green_b { background: green!important; } | |
| .red_b { background: red!important; } | |
| .point_number { width: 50px; text-align: center; } | |
| .method_point { margin: auto; } | |
| .graph_numbers .method_point tr td { width: 16px; height: 16px; vertical-align: middle; font-size: 18px; line-height: 16px; } | |
| .method_point tr td { vertical-align: middle; text-align: center; } | |
| .tooltip{ | |
| position:absolute; | |
| z-index:1070; | |
| display:block; | |
| margin:0; | |
| font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji"; | |
| font-style:normal; | |
| font-weight:400; | |
| line-height:1.5; | |
| text-align:left; | |
| text-align:start; | |
| text-decoration:none; | |
| text-shadow:none; | |
| text-transform:none; | |
| letter-spacing:normal; | |
| word-break:normal; | |
| word-spacing:normal; | |
| white-space:normal; | |
| line-break:auto; | |
| font-size:.875rem; | |
| word-wrap:break-word; | |
| opacity:0 | |
| } | |
| .tooltip.show{ | |
| opacity:.9 | |
| } | |
| .tooltip .arrow{ | |
| position:absolute; | |
| display:block; | |
| width:.8rem; | |
| height:.4rem | |
| } | |
| .tooltip .arrow::before{ | |
| position:absolute; | |
| content:""; | |
| border-color:transparent; | |
| border-style:solid | |
| } | |
| .bs-tooltip-auto[x-placement^=top],.bs-tooltip-top{ | |
| padding:.4rem 0 | |
| } | |
| .bs-tooltip-auto[x-placement^=top] .arrow,.bs-tooltip-top .arrow{ | |
| bottom:0 | |
| } | |
| .bs-tooltip-auto[x-placement^=top] .arrow::before,.bs-tooltip-top .arrow::before{ | |
| top:0; | |
| border-width:.4rem .4rem 0; | |
| border-top-color:#000 | |
| } | |
| .bs-tooltip-auto[x-placement^=right],.bs-tooltip-right{ | |
| padding:0 .4rem | |
| } | |
| .bs-tooltip-auto[x-placement^=right] .arrow,.bs-tooltip-right .arrow{ | |
| left:0; | |
| width:.4rem; | |
| height:.8rem | |
| } | |
| .bs-tooltip-auto[x-placement^=right] .arrow::before,.bs-tooltip-right .arrow::before{ | |
| right:0; | |
| border-width:.4rem .4rem .4rem 0; | |
| border-right-color:#000 | |
| } | |
| .bs-tooltip-auto[x-placement^=bottom],.bs-tooltip-bottom{ | |
| padding:.4rem 0 | |
| } | |
| .bs-tooltip-auto[x-placement^=bottom] .arrow,.bs-tooltip-bottom .arrow{ | |
| top:0 | |
| } | |
| .bs-tooltip-auto[x-placement^=bottom] .arrow::before,.bs-tooltip-bottom .arrow::before{ | |
| bottom:0; | |
| border-width:0 .4rem .4rem; | |
| border-bottom-color:#000 | |
| } | |
| .bs-tooltip-auto[x-placement^=left],.bs-tooltip-left{ | |
| padding:0 .4rem | |
| } | |
| .bs-tooltip-auto[x-placement^=left] .arrow,.bs-tooltip-left .arrow{ | |
| right:0; | |
| width:.4rem; | |
| height:.8rem | |
| } | |
| .bs-tooltip-auto[x-placement^=left] .arrow::before,.bs-tooltip-left .arrow::before{ | |
| left:0; | |
| border-width:.4rem 0 .4rem .4rem; | |
| border-left-color:#000 | |
| } | |
| .tooltip-inner{ | |
| max-width:200px; | |
| padding:.25rem .5rem; | |
| color:#fff; | |
| text-align:center; | |
| background-color:#000; | |
| border-radius:.25rem | |
| } | |
| .glyphicon-exclamation-sign{ | |
| margin: 10px 10px 0 10px; | |
| } | |
| .pad0 { padding: 0px!important;} | |
| .pad10 { padding: 20px 5px!important;} | |
| .more-result { margin-top: 10px; text-align: center; } | |
| @media (max-width: 414px){ | |
| .mob-only { display: block; } | |
| .well-align { max-width: 100%; } | |
| .point_number { margin-top: 10px; } | |
| .jumbotron .container { | |
| padding: 0px!important; | |
| } | |
| } | |
| .overflow-auto { overflow: auto; width: 100%; } | |
| .nowrap { white-space: nowrap; } | |
| .w100 { text-align: center; margin: 10px 0 0 10px; } | |
| .tor_value { font-size: 10px; } | |
| .nav-pills{ | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| } | |
| .tab-ids li a { font-weight: 600; } | |
| .navbar-form-own { | |
| } | |
| @media (min-width: 1024px){ | |
| .tab-ids, .tab-pane .smenu { | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| } | |
| h1, .h1 { | |
| font-size: 30px!important; | |
| } | |
| } | |
| .romb { | |
| width: 0; | |
| height: 0; | |
| border: 11px solid transparent; | |
| border-bottom: 19px solid transparent; /* #fff */ | |
| position: relative; | |
| background-color: transparent; | |
| } | |
| .romb:after { | |
| content: ''; | |
| position: absolute; | |
| left: -11px; | |
| top: 19px; | |
| width: 0; | |
| height: 0; | |
| border: 11px solid transparent; | |
| border-top: 19px solid transparent; /*#fff; */ | |
| } | |
| .romb span { | |
| position: absolute; | |
| top: 11px; | |
| left: -3px; | |
| /* color: white; */ | |
| z-index: 2; | |
| } | |
| .hexagon.courts { | |
| height: 700px; | |
| width: 700px; | |
| margin: 90px auto 20px; | |
| } | |
| .courts .hex { | |
| margin-top: 8px; | |
| width: 16px; | |
| height: 10px; | |
| } | |
| .courts .hex:before { | |
| content: " "; | |
| width: 0; height: 0; | |
| border-bottom: 4px solid var(--hex-color); | |
| border-left: 8px solid transparent; | |
| border-right: 8px solid transparent; | |
| position: absolute; | |
| top: -4px; | |
| } | |
| .courts .hex:after { | |
| content: ""; | |
| width: 0; | |
| position: absolute; | |
| bottom: -4px; | |
| border-top: 4px solid var(--hex-color); | |
| border-left: 8px solid transparent; | |
| border-right: 8px solid transparent; | |
| } | |
| .hexagon.courts .line { | |
| margin-top: -4px!important; | |
| } | |
| .hexagon.courts .side { | |
| display: inline-block; | |
| margin: 0px; | |
| padding: 0px; | |
| } | |
| .hexagon.courts .side-1 { | |
| transform: rotate(-30deg); | |
| position: absolute; | |
| left: 0px; | |
| top: 0px; | |
| z-index: 6; | |
| } | |
| .hexagon.courts .side-2 { | |
| transform: rotate(30deg); | |
| position: absolute; | |
| left: 134px; | |
| top: 11px; | |
| z-index: 5; | |
| } | |
| .hexagon.courts .side-3 { | |
| transform: rotate(90deg); | |
| position: absolute; | |
| left: 188px; | |
| top: 133px; | |
| z-index: 4; | |
| } | |
| .hexagon.courts .side-4 { | |
| transform: rotate(150deg); | |
| position: absolute; | |
| left: 118px; | |
| top: 247px; | |
| z-index: 3; | |
| } | |
| .hexagon.courts .side-5 { | |
| transform: rotate(210deg); | |
| position: absolute; | |
| left: -14px; | |
| top: 222px; | |
| z-index: 2; | |
| } | |
| .hexagon.courts .side-6 { | |
| transform: rotate(270deg); | |
| position: absolute; | |
| left: -73px; | |
| top: 114px; | |
| z-index: 1; | |
| } | |
| .hexagon { | |
| position: relative; | |
| /* max-width: 438px;*/ | |
| height: 700px; | |
| width: 700px; | |
| margin: 90px auto 20px; | |
| } | |
| .hexagon .line { | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| margin: auto; | |
| width: 500px; | |
| margin-top: -11px; | |
| } | |
| .hexagon .side { | |
| display: inline-block; | |
| margin: 0px; | |
| padding: 0px; | |
| } | |
| .hexagon .side-1 { | |
| transform: rotate(-30deg); | |
| position: absolute; | |
| left: 0px; | |
| top: 0px; | |
| } | |
| .hexagon .side-2 { | |
| transform: rotate(30deg); | |
| position: absolute; | |
| left: 171px; /* менÑетÑÑ */ | |
| top: 0px; | |
| } | |
| .hexagon .side-3 { | |
| transform: rotate(90deg); | |
| position: absolute; | |
| left: 256px; | |
| top: 148px; | |
| } | |
| .hexagon .side-4 { | |
| transform: rotate(150deg); | |
| position: absolute; | |
| left: 171px; | |
| top: 296px; | |
| } | |
| .hexagon .side-5 { | |
| transform: rotate(210deg); | |
| position: absolute; | |
| left: 0px; | |
| top: 296px; | |
| } | |
| .hexagon .side-6 { | |
| transform: rotate(270deg); | |
| position: absolute; | |
| left: -85px; | |
| top: 148px; | |
| } | |
| .hexagon.size-6 { | |
| position: relative; | |
| height: 400px; | |
| width: 400px; | |
| margin: 90px auto 20px; | |
| } | |
| .hexagon.size-6 .line { | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| margin: auto; | |
| width: 500px; | |
| margin-top: -11px; | |
| } | |
| .hexagon.size-6 .side { | |
| display: inline-block; | |
| margin: 0px; | |
| padding: 0px; | |
| } | |
| .hexagon.size-6 .side-1 { | |
| transform: rotate(-30deg); | |
| position: absolute; | |
| left: 0px; | |
| top: 0px; | |
| } | |
| .hexagon.size-6 .side-2 { | |
| transform: rotate(30deg); | |
| position: absolute; | |
| left: 102px; | |
| top: 11px; | |
| } | |
| .hexagon.size-6 .side-3 { | |
| transform: rotate(90deg); | |
| position: absolute; | |
| left: 114px; | |
| top: 66px; | |
| } | |
| .hexagon.size-6 .side-4 { | |
| transform: rotate(150deg); | |
| position: absolute; | |
| left: 76px; | |
| top: 132px; | |
| } | |
| .hexagon.size-6 .side-5 { | |
| transform: rotate(210deg); | |
| position: absolute; | |
| left: 0px; | |
| top: 132px; | |
| } | |
| .hexagon.size-6 .side-6 { | |
| transform: rotate(270deg); | |
| position: absolute; | |
| left: -38px; | |
| top: 66px; | |
| } | |
| .hexagon.size-8 { | |
| position: relative; | |
| /* max-width: 438px;*/ | |
| height: 400px; | |
| width: 700px; | |
| margin: 90px auto 20px; | |
| } | |
| .hexagon.size-8 .line { | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| margin: auto; | |
| width: 500px; | |
| margin-top: -11px; | |
| } | |
| .hexagon.size-8 .side { | |
| display: inline-block; | |
| margin: 0px; | |
| padding: 0px; | |
| } | |
| .hexagon.size-8 .side-1 { | |
| transform: rotate(-30deg); | |
| position: absolute; | |
| left: 0px; | |
| top: 0px; | |
| } | |
| .hexagon.size-8 .side-2 { | |
| transform: rotate(30deg); | |
| position: absolute; | |
| left: 95px; | |
| top: 0px; | |
| } | |
| .hexagon.size-8 .side-3 { | |
| transform: rotate(90deg); | |
| position: absolute; | |
| left: 143px; | |
| top: 82px; | |
| } | |
| .hexagon.size-8 .side-4 { | |
| transform: rotate(150deg); | |
| position: absolute; | |
| left: 95px; | |
| top: 164px; | |
| } | |
| .hexagon.size-8 .side-5 { | |
| transform: rotate(210deg); | |
| position: absolute; | |
| left: 0px; | |
| top: 164px; | |
| } | |
| .hexagon.size-8 .side-6 { | |
| transform: rotate(270deg); | |
| position: absolute; | |
| left: -48px; | |
| top: 82px; | |
| } | |
| .hexagon.size-12 { | |
| position: relative; | |
| height: 580px; | |
| width: 580px; | |
| margin: 90px auto 20px; | |
| } | |
| .hexagon.size-12 .line { | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| margin: auto; | |
| width: 500px; | |
| margin-top: -11px; | |
| } | |
| .hexagon.size-12 .side { | |
| display: inline-block; | |
| margin: 0px; | |
| padding: 0px; | |
| } | |
| .hexagon.size-12 .side-1 { | |
| transform: rotate(-30deg); | |
| position: absolute; | |
| left: 0px; | |
| top: 0px; | |
| } | |
| .hexagon.size-12 .side-2 { | |
| transform: rotate(30deg); | |
| position: absolute; | |
| left: 133px; | |
| top: 0px; | |
| } | |
| .hexagon.size-12 .side-3 { | |
| transform: rotate(90deg); | |
| position: absolute; | |
| left: 200px; | |
| top: 115px; | |
| } | |
| .hexagon.size-12 .side-4 { | |
| transform: rotate(150deg); | |
| position: absolute; | |
| left: 133px; | |
| top: 230px; | |
| } | |
| .hexagon.size-12 .side-5 { | |
| transform: rotate(210deg); | |
| position: absolute; | |
| left: 0px; | |
| top: 230px; | |
| } | |
| .hexagon.size-12 .side-6 { | |
| transform: rotate(270deg); | |
| position: absolute; | |
| left: -67px; | |
| top: 115px; | |
| } | |
| .hexagon.courts.size-12 { | |
| position: relative; | |
| height: 580px; | |
| width: 580px; | |
| margin: 90px auto 20px; | |
| } | |
| .hexagon.courts.size-12 .line { | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| margin: auto; | |
| width: 500px; | |
| margin-top: -11px; | |
| } | |
| .hexagon.courts.size-12 .side { | |
| display: inline-block; | |
| margin: 0px; | |
| padding: 0px; | |
| } | |
| .hexagon.courts.size-12 .side-1 { | |
| transform: rotate(-30deg); | |
| position: absolute; | |
| left: 0px; | |
| top: 0px; | |
| } | |
| .hexagon.courts.size-12 .side-2 { | |
| transform: rotate(30deg); | |
| position: absolute; | |
| left: 77px; | |
| top: 0px; | |
| } | |
| .hexagon.courts.size-12 .side-3 { | |
| transform: rotate(90deg); | |
| position: absolute; | |
| left: 115px; | |
| top: 68px; | |
| } | |
| .hexagon.courts.size-12 .side-4 { | |
| transform: rotate(150deg); | |
| position: absolute; | |
| left: 76px; | |
| top: 135px; | |
| } | |
| .hexagon.courts.size-12 .side-5 { | |
| transform: rotate(210deg); | |
| position: absolute; | |
| left: -1px; | |
| top: 134px; | |
| } | |
| .hexagon.courts.size-12 .side-6 { | |
| transform: rotate(270deg); | |
| position: absolute; | |
| left: -39px; | |
| top: 67px; | |
| } | |
| .hexagon.courts.size-16 .side-1 { | |
| transform: rotate(-30deg); | |
| position: absolute; | |
| left: 0px; | |
| top: 0px; | |
| } | |
| .hexagon.courts.size-16 .side-2 { | |
| transform: rotate(30deg); | |
| position: absolute; | |
| left: 104px; | |
| top: 0px; | |
| } | |
| .hexagon.courts.size-16 .side-3 { | |
| transform: rotate(90deg); | |
| position: absolute; | |
| left: 158px; | |
| top: 90px; | |
| } | |
| .hexagon.courts.size-16 .side-4 { | |
| transform: rotate(150deg); | |
| position: absolute; | |
| left: 104px; | |
| top: 180px; | |
| } | |
| .hexagon.courts.size-16 .side-5 { | |
| transform: rotate(210deg); | |
| position: absolute; | |
| left: 2px; | |
| top: 180px; | |
| } | |
| .hexagon.courts.size-16 .side-6 { | |
| transform: rotate(270deg); | |
| position: absolute; | |
| left: -52px; | |
| top: 92px; | |
| } | |
| .octagon.size-16 { | |
| width: 700px; | |
| height: 700px; | |
| } | |
| .hexagon.size-24 { | |
| position: relative; | |
| /* max-width: 438px;*/ | |
| height: 860px; | |
| width: 700px; | |
| margin: 130px auto 130px auto; | |
| } | |
| .hexagon.size-24 .line { | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| margin: auto; | |
| width: 500px; | |
| margin-top: -11px; | |
| } | |
| .hexagon.size-24 .side { | |
| display: inline-block; | |
| margin: 0px; | |
| padding: 0px; | |
| } | |
| .hexagon.size-24 .side-1 { | |
| transform: rotate(-30deg); | |
| position: absolute; | |
| left: 0px; | |
| top: 0px; | |
| } | |
| .hexagon.size-24 .side-2 { | |
| transform: rotate(30deg); | |
| position: absolute; | |
| left: 247px; | |
| top: 0px; | |
| } | |
| .hexagon.size-24 .side-3 { | |
| transform: rotate(90deg); | |
| position: absolute; | |
| left: 371px; | |
| top: 214px; | |
| } | |
| .hexagon.size-24 .side-4 { | |
| transform: rotate(150deg); | |
| position: absolute; | |
| left: 247px; | |
| top: 428px; | |
| } | |
| .hexagon.size-24 .side-5 { | |
| transform: rotate(210deg); | |
| position: absolute; | |
| left: 0px; | |
| top: 427px; | |
| } | |
| .hexagon.size-24 .side-6 { | |
| transform: rotate(270deg); | |
| position: absolute; | |
| left: -124px; | |
| top: 213px; | |
| } | |
| .octagon { | |
| position: relative; | |
| /* max-width: 438px;*/ | |
| height: 580px; | |
| width: 580px; | |
| margin: 65px auto 65px; | |
| } | |
| .octagon .side { | |
| display: inline-block; | |
| margin: 0px; | |
| padding: 0px; | |
| } | |
| .octagon .side-1 { | |
| transform: rotate(-22.5deg); | |
| position: absolute; | |
| left: 0px; | |
| top: 0px; | |
| } | |
| .octagon .side-2 { | |
| transform: rotate(22.5deg); | |
| position: absolute; | |
| left: 150px; /* менÑетÑÑ */ | |
| top: 0px; | |
| } | |
| .octagon .side-3 { | |
| transform: rotate(67.5deg); | |
| position: absolute; | |
| left: 256px; | |
| top: 106px; | |
| } | |
| .octagon .side-4 { | |
| transform: rotate(112.5deg); | |
| position: absolute; | |
| left: 256px; | |
| top: 256px; | |
| } | |
| .octagon .side-5 { | |
| transform: rotate(157.5deg); | |
| position: absolute; | |
| left: 150px; | |
| top: 362px; | |
| } | |
| .octagon .side-6 { | |
| transform: rotate(202.5deg); | |
| position: absolute; | |
| left: 0px; | |
| top: 362px; | |
| } | |
| .octagon .side-7 { | |
| transform: rotate(247.5deg); | |
| position: absolute; | |
| left: -106px; | |
| top: 256px; | |
| } | |
| .octagon .side-8 { | |
| transform: rotate(292.5deg); | |
| position: absolute; | |
| left: -106px; | |
| top: 106px; | |
| } | |
| .octagon.size-6 { | |
| position: relative; | |
| /* max-width: 438px;*/ | |
| height: 280px; | |
| width: 580px; | |
| margin: 65px auto 65px; | |
| } | |
| .octagon.size-6 .side { | |
| display: inline-block; | |
| margin: 0px; | |
| padding: 0px; | |
| } | |
| .octagon.size-6 .side-1 { | |
| transform: rotate(-22.5deg); | |
| position: absolute; | |
| left: 0px; | |
| top: 0px; | |
| } | |
| .octagon.size-6 .side-2 { | |
| transform: rotate(22.5deg); | |
| position: absolute; | |
| left: 66px; | |
| top: 0px; | |
| } | |
| .octagon.size-6 .side-3 { | |
| transform: rotate(67.5deg); | |
| position: absolute; | |
| left: 113px; | |
| top: 47px; | |
| } | |
| .octagon.size-6 .side-4 { | |
| transform: rotate(112.5deg); | |
| position: absolute; | |
| left: 112px; | |
| top: 114px; | |
| } | |
| .octagon.size-6 .side-5 { | |
| transform: rotate(157.5deg); | |
| position: absolute; | |
| left: 66px; | |
| top: 161px; | |
| } | |
| .octagon.size-6 .side-6 { | |
| transform: rotate(202.5deg); | |
| position: absolute; | |
| left: 0px; | |
| top: 161px; | |
| } | |
| .octagon.size-6 .side-7 { | |
| transform: rotate(247.5deg); | |
| position: absolute; | |
| left: -47px; | |
| top: 114px; | |
| } | |
| .octagon.size-6 .side-8 { | |
| transform: rotate(292.5deg); | |
| position: absolute; | |
| left: -47px; | |
| top: 47px; | |
| } | |
| .octagon.size-8 { | |
| position: relative; | |
| /* max-width: 438px;*/ | |
| height: 380px; | |
| width: 580px; | |
| margin: 65px auto 65px; | |
| } | |
| .octagon.size-8 .side { | |
| display: inline-block; | |
| margin: 0px; | |
| padding: 0px; | |
| } | |
| .octagon.size-8 .side-1 { | |
| transform: rotate(-22.5deg); | |
| position: absolute; | |
| left: 0px; | |
| top: 0px; | |
| } | |
| .octagon.size-8 .side-2 { | |
| transform: rotate(22.5deg); | |
| position: absolute; | |
| left: 83px; | |
| top: 0px; | |
| } | |
| .octagon.size-8 .side-3 { | |
| transform: rotate(67.5deg); | |
| position: absolute; | |
| left: 142px; | |
| top: 59px; | |
| } | |
| .octagon.size-8 .side-4 { | |
| transform: rotate(112.5deg); | |
| position: absolute; | |
| left: 142px; | |
| top: 142px; | |
| } | |
| .octagon.size-8 .side-5 { | |
| transform: rotate(157.5deg); | |
| position: absolute; | |
| left: 83px; | |
| top: 201px; | |
| } | |
| .octagon.size-8 .side-6 { | |
| transform: rotate(202.5deg); | |
| position: absolute; | |
| left: 0px; | |
| top: 201px; | |
| } | |
| .octagon.size-8 .side-7 { | |
| transform: rotate(247.5deg); | |
| position: absolute; | |
| left: -59px; | |
| top: 142px; | |
| } | |
| .octagon.size-8 .side-8 { | |
| transform: rotate(292.5deg); | |
| position: absolute; | |
| left: -59px; | |
| top: 59px; | |
| } | |
| .octagon.size-12 { | |
| position: relative; | |
| /* max-width: 438px;*/ | |
| height: 600px; | |
| width: 600px; | |
| margin: 65px auto 65px; | |
| } | |
| .octagon.size-12 .side { | |
| display: inline-block; | |
| margin: 0px; | |
| padding: 0px; | |
| } | |
| .octagon.size-12 .side-1 { | |
| transform: rotate(-22.5deg); | |
| position: absolute; | |
| left: 20px; | |
| top: 24px; | |
| } | |
| .octagon.size-12 .side-2 { | |
| transform: rotate(22.5deg); | |
| position: absolute; | |
| left: 136px; | |
| top: 25px; | |
| } | |
| .octagon.size-12 .side-3 { | |
| transform: rotate(67.5deg); | |
| position: absolute; | |
| left: 220px; | |
| top: 106px; | |
| } | |
| .octagon.size-12 .side-4 { | |
| transform: rotate(112.5deg); | |
| position: absolute; | |
| left: 220px; | |
| top: 223px; | |
| } | |
| .octagon.size-12 .side-5 { | |
| transform: rotate(157.5deg); | |
| position: absolute; | |
| left: 136px; | |
| top: 304px; | |
| } | |
| .octagon.size-12 .side-6 { | |
| transform: rotate(202.5deg); | |
| position: absolute; | |
| left: 20px; | |
| top: 305px; | |
| } | |
| .octagon.size-12 .side-7 { | |
| transform: rotate(247.5deg); | |
| position: absolute; | |
| left: -63px; | |
| top: 223px; | |
| } | |
| .octagon.size-12 .side-8 { | |
| transform: rotate(292.5deg); | |
| position: absolute; | |
| left: -63px; | |
| top: 106px; | |
| } | |
| .octagon.size-24 { | |
| position: relative; | |
| /* max-width: 438px;*/ | |
| height: 1050px; | |
| width: 580px; | |
| margin: 65px auto 65px; | |
| } | |
| .octagon.size-24 .side { | |
| display: inline-block; | |
| margin: 0px; | |
| padding: 0px; | |
| } | |
| .octagon.size-24 .side-1 { | |
| transform: rotate(-22.5deg); | |
| position: absolute; | |
| left: -20px; | |
| top: 0px; | |
| } | |
| .octagon.size-24 .side-2 { | |
| transform: rotate(22.5deg); | |
| position: absolute; | |
| left: 196px; | |
| top: 0px; | |
| } | |
| .octagon.size-24 .side-3 { | |
| transform: rotate(67.5deg); | |
| position: absolute; | |
| left: 349px; | |
| top: 153px; | |
| } | |
| .octagon.size-24 .side-4 { | |
| transform: rotate(112.5deg); | |
| position: absolute; | |
| left: 349px; | |
| top: 368px | |
| } | |
| .octagon.size-24 .side-5 { | |
| transform: rotate(157.5deg); | |
| position: absolute; | |
| left: 196px; | |
| top: 521px; | |
| } | |
| .octagon.size-24 .side-6 { | |
| transform: rotate(202.5deg); | |
| position: absolute; | |
| left: -20px; | |
| top: 521px; | |
| } | |
| .octagon.size-24 .side-7 { | |
| transform: rotate(247.5deg); | |
| position: absolute; | |
| left: -172px; | |
| top: 368px; | |
| } | |
| .octagon.size-24 .side-8 { | |
| transform: rotate(292.5deg); | |
| position: absolute; | |
| left: -174px; | |
| top: 152px; | |
| } | |
| .octagon .romb { | |
| width: 0; | |
| height: 0; | |
| border: 9px solid transparent; | |
| border-bottom: 21.8px solid transparent; /* #fff */ | |
| position: relative; | |
| background-color: transparent; | |
| } | |
| .octagon .romb:after { | |
| content: ''; | |
| position: absolute; | |
| left: -9px; | |
| top: 21.8px; | |
| width: 0; | |
| height: 0; | |
| border: 9px solid transparent; | |
| border-top: 21.8px solid transparent; /*#fff; */ | |
| } | |
| .octagon .romb span { | |
| position: absolute; | |
| top: 9px; | |
| left: -5px; | |
| /* color: white; */ | |
| z-index: 2; | |
| } | |
| .octagon .line { | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| margin: auto; | |
| width: 400px; | |
| margin-top: -9px; | |
| } | |
| :root { | |
| --hex-color: transparent; | |
| --hex-border-color: #000; | |
| --dark_blue-color: #0000ff; | |
| --red-color: #ff0000; | |
| } | |
| .romb.white { color: #ffffff; border-bottom-color: #ffffff; } | |
| .romb.white:after { color: #ffffff; border-top-color: #ffffff; } | |
| .romb.red{ color: #ff0000; border-bottom-color: #ff0000; } | |
| .romb.red:after { color: #ff0000; border-top-color: #ff0000; } | |
| .romb.dark_blue { color: #0000ff; border-bottom-color: #0000ff; } | |
| .romb.dark_blue:after { color: #0000ff; border-top-color: #0000ff; } | |
| .romb.green { color: #336600; border-bottom-color: #336600; } | |
| .romb.green:after { color: #336600; border-top-color: #336600; } | |
| .romb.yellow { color: #ffff00; border-bottom-color: #ffff00; } | |
| .romb.yellow:after { color: #ffff00; border-top-color: #ffff00; } | |
| .romb.blue { color: #0099ff; border-bottom-color: #0099ff; } | |
| .romb.blue:after { color: #0099ff; border-top-color: #0099ff; } | |
| .romb.turquoise { color: #00ffff; border-bottom-color: #00ffff; } | |
| .romb.turquoise:after { color: #00ffff; border-top-color: #00ffff; } | |
| .romb.pink { color: #ff00ff; border-bottom-color: #ff00ff; } | |
| .romb.pink:after { color: #ff00ff; border-top-color: #ff00ff; } | |
| .romb.orange { color: #ff9900; border-bottom-color: #ff9900; } | |
| .romb.orange:after { color: #ff9900; border-top-color: #ff9900; } | |
| .romb.violet { color: #9900ff; border-bottom-color: #9900ff; } | |
| .romb.violet:after { color: #9900ff; border-top-color: #9900ff; } | |
| .hex.white { background-color: #ffffff; color:#ffffff; } | |
| .hex.white:before { border-bottom-color: #ffffff; } | |
| .hex.white:after { border-top-color: #ffffff;} | |
| .hex.red { background-color: #ff0000; color:#ff0000; } | |
| .hex.red:before { border-bottom-color: #ff0000; } | |
| .hex.red:after { border-top-color: #ff0000;} | |
| .hex.dark_blue { background-color: #0000ff; color:#0000ff; } | |
| .hex.dark_blue:before { border-bottom-color: #0000ff; } | |
| .hex.dark_blue:after { border-top-color: #0000ff;} | |
| .hex.green { background-color: #336600; color:#336600; } | |
| .hex.green:before { border-bottom-color: #336600; } | |
| .hex.green:after { border-top-color: #336600;} | |
| .hex.yellow { background-color: #ffff00; color:#ffff00; } | |
| .hex.yellow:before { border-bottom-color: #ffff00; } | |
| .hex.yellow:after { border-top-color: #ffff00;} | |
| .hex.blue { background-color: #0099ff; color:#0099ff; } | |
| .hex.blue:before { border-bottom-color: #0099ff; } | |
| .hex.blue:after { border-top-color: #0099ff;} | |
| .hex.turquoise { background-color: #00ffff; color:#00ffff; } | |
| .hex.turquoise:before { border-bottom-color: #00ffff; } | |
| .hex.turquoise:after { border-top-color: #00ffff;} | |
| .hex.pink { background-color: #ff00ff; color:#ff00ff; } | |
| .hex.pink:before { border-bottom-color: #ff00ff; } | |
| .hex.pink:after { border-top-color: #ff00ff;} | |
| .hex.orange { background-color: #ff9900; color:#ff9900; } | |
| .hex.orange:before { border-bottom-color: #ff9900; } | |
| .hex.orange:after { border-top-color: #ff9900;} | |
| .hex.violet { background-color: #9900ff; color:#9900ff; } | |
| .hex.violet:before { border-bottom-color: #9900ff; } | |
| .hex.violet:after { border-top-color: #9900ff;} | |
| .hex { | |
| margin-top: 10px; | |
| width: 18px; | |
| height: 10px; | |
| background-color: var(--hex-color); | |
| position: relative; | |
| color: #fff; | |
| /* transform: rotate(90deg); */ | |
| } | |
| .hex span { | |
| position: absolute; | |
| top: -2px; | |
| left: 7px; | |
| z-index: 2; | |
| font-size: 10px; | |
| /* transform: rotate(-90deg); */ | |
| } | |
| .hex:before { | |
| content: " "; | |
| width: 0; height: 0; | |
| border-bottom: 5px solid var(--hex-color); | |
| border-left: 9px solid transparent; | |
| border-right: 9px solid transparent; | |
| position: absolute; | |
| top: -5px; | |
| } | |
| .hex:after { | |
| content: ""; | |
| width: 0; | |
| position: absolute; | |
| bottom: -5px; | |
| border-top: 5px solid var(--hex-color); | |
| border-left: 9px solid transparent; | |
| border-right: 9px solid transparent; | |
| } | |
| .cards_start { | |
| display: flex; | |
| } | |
| /* | |
| .card { | |
| margin-left: -170px; | |
| } | |
| */ | |
| .owl-carousel { -ms-touch-action: pan-y; touch-action: pan-y; } | |
| .owl-carousel .owl-item img { max-width: 100%; width: auto!important; } | |
| .cards_start.owl-carousel .owl-stage-outer { overflow: inherit; } | |
| .scene { | |
| width: 220px; | |
| height: 321px; | |
| perspective: 600px; | |
| margin-left: -120px; | |
| } | |
| .scene:first-child { margin-left: 0px; } | |
| .card { | |
| width: 100%; | |
| height: 100%; | |
| line-height: 321px; | |
| transition: transform 1s; | |
| transform-style: preserve-3d; | |
| cursor: pointer; | |
| position: relative; | |
| border: 1px solid #CCC; | |
| box-shadow: 0 0 10px rgba(0,0,0,0.8); | |
| text-align: center; | |
| } | |
| .card img { max-width: 221px; } | |
| .scene-active { width: 381px; height: 551px; margin-top: -110px;} | |
| .scene-active .card img { max-width: 381px; } | |
| .card.is-flipped { | |
| transform: rotateY(180deg); | |
| } | |
| .card__face { | |
| position: absolute; | |
| width: 100%; | |
| height: 100%; | |
| backface-visibility: hidden; | |
| } | |
| .card__face--front { | |
| } | |
| .card__face--back { | |
| transform: rotateY(180deg); | |
| } | |
| .rekl { text-align: center; } | |
| .lazy { margin: auto; vertical-align: middle; } | |
| .form-inputs { | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| flex-wrap: nowrap; | |
| } | |
| .send_build { | |
| margin: 15px 0 0 0; | |
| text-transform: uppercase; | |
| width: 100%!important; | |
| } | |
| .send_build_mobile { display: none; | |
| } | |
| .send_build_web { display: block; } | |
| .width-100 { max-width: 100%!important; } | |
| .more-save i img { max-height: 18px; } | |
| @media (max-width: 768px){ | |
| h1 { font-size: 24px!important; } | |
| h2 { font-size: 20px!important; } | |
| h3 { font-size: 16px!important; } | |
| #word { /* width: 100%; */ max-width: calc(100% - 80px); } | |
| .send_build_web { | |
| display: none!important; | |
| } | |
| .w100 { padding: 0; margin: 10px 0 0 0; width: 100%; } | |
| .w100 button:first-child { width: 90%; } | |
| .results { overflow: auto; max-width: 100%; } | |
| .navbar-form-own .btn-default { width: 100%; text-transform: uppercase; } | |
| .container { padding: 15px 0px!important; } | |
| .send_build_mobile { display: block; } | |
| .jumbotron { | |
| padding: 5px 10px!important; | |
| } | |
| .center { text-align: center; } | |
| } | |
| .flex-center { | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| } | |
| .one_num { | |
| width: 16px; | |
| height: 16px; | |
| line-height: 16px; | |
| } | |
| .flex-side { display: inline-block; } | |
| .mt-5 { margin-top: 5px; } | |
| .mt-10 { margin-top: 10px; } | |
| .mt-20 { margin-top: 20px; } | |
| @media (max-width: 420px){ | |
| .m-block { display: block; } | |
| .m-none { display: none!important; } | |
| .btn { padding: 6px 10px!important;} | |
| } | |
| .center-red { background: #f6f6f6; color: red; padding: 5px 10px; margin: auto; width: 430px; max-width: 100%; } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="button-container"> | |
| <button class="pattern-btn active" onclick="switchPattern('pattern1')">Паттерн 1</button> | |
| <button class="pattern-btn" onclick="switchPattern('pattern2')">Паттерн 2</button> | |
| <button class="pattern-btn" onclick="switchPattern('pattern3')">Паттерн 3</button> | |
| </div> | |
| <br><br><br> | |
| <div id="mandala-display"> | |
| {{ mandala_html|safe }} | |
| </div> | |
| <script> | |
| function switchPattern(patternName) { | |
| // Обновляем активную кнопку | |
| document.querySelectorAll('.pattern-btn').forEach(btn => { | |
| btn.classList.remove('active'); | |
| }); | |
| event.target.classList.add('active'); | |
| // Загружаем новый паттерн | |
| fetch('/switch-pattern', { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| }, | |
| body: JSON.stringify({pattern: patternName}) | |
| }) | |
| .then(response => response.json()) | |
| .then(data => { | |
| document.getElementById('mandala-display').innerHTML = data.mandala_html; | |
| }) | |
| .catch(error => console.error('Error:', error)); | |
| } | |
| </script> | |
| </body> | |
| </html> | |
| """ | |
| def show_mandala(): | |
| # Используем паттерн по умолчанию | |
| mandala_html = generate_mandala_html(PATTERNS["pattern1"]) | |
| return render_template_string(MANDALA_TEMPLATE, mandala_html=mandala_html) | |
| def switch_pattern(): | |
| data = request.get_json() | |
| pattern_name = data.get('pattern', 'pattern1') | |
| if pattern_name in PATTERNS: | |
| mandala_html = generate_mandala_html(PATTERNS[pattern_name]) | |
| return jsonify({'mandala_html': mandala_html}) | |
| else: | |
| return jsonify({'error': 'Pattern not found'}), 404 | |
| if __name__ == '__main__': | |
| app.run(host='0.0.0.0', port=int(os.environ.get('PORT', 7860))) | |