Compare commits
2 commits
f7153248c5
...
633b0f0d05
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
633b0f0d05 | ||
|
|
209d2f29e1 |
3 changed files with 444 additions and 108 deletions
|
|
@ -18,6 +18,33 @@ import config
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
router = Router()
|
router = Router()
|
||||||
|
|
||||||
|
|
||||||
|
async def safe_edit_or_reply(event, text: str, reply_markup=None, parse_mode='HTML'):
|
||||||
|
"""
|
||||||
|
Безопасное редактирование сообщения.
|
||||||
|
Если сообщение содержит медиа (фото и т.д.), мы удаляем его и отправляем новое.
|
||||||
|
"""
|
||||||
|
if isinstance(event, CallbackQuery):
|
||||||
|
await event.answer()
|
||||||
|
message = event.message
|
||||||
|
if (hasattr(message, 'photo') and message.photo) or \
|
||||||
|
(hasattr(message, 'video') and message.video) or \
|
||||||
|
(hasattr(message, 'document') and message.document) or \
|
||||||
|
(hasattr(message, 'content_type') and message.content_type != 'text'):
|
||||||
|
try:
|
||||||
|
await message.delete()
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Не удалось удалить сообщение с медиа: {e}")
|
||||||
|
await message.answer(text, reply_markup=reply_markup, parse_mode=parse_mode)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
await message.edit_text(text, reply_markup=reply_markup, parse_mode=parse_mode)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Ошибка edit_text: {e}. Отправляем новое сообщение.")
|
||||||
|
await message.answer(text, reply_markup=reply_markup, parse_mode=parse_mode)
|
||||||
|
elif isinstance(event, Message):
|
||||||
|
await event.answer(text, reply_markup=reply_markup, parse_mode=parse_mode)
|
||||||
|
|
||||||
@router.message(Command('cancel'), StateFilter('*'))
|
@router.message(Command('cancel'), StateFilter('*'))
|
||||||
@router.message(F.text.lower().in_(['отмена', 'отменить']), StateFilter('*'))
|
@router.message(F.text.lower().in_(['отмена', 'отменить']), StateFilter('*'))
|
||||||
async def global_cancel(message: Message, state: FSMContext):
|
async def global_cancel(message: Message, state: FSMContext):
|
||||||
|
|
@ -74,7 +101,8 @@ async def cb_main_menu(callback: CallbackQuery):
|
||||||
ig_stmt = select(InitiativeGroup).where(InitiativeGroup.user_id == user_id, InitiativeGroup.is_active == True)
|
ig_stmt = select(InitiativeGroup).where(InitiativeGroup.user_id == user_id, InitiativeGroup.is_active == True)
|
||||||
is_ig = (await session.execute(ig_stmt)).scalar_one_or_none() is not None
|
is_ig = (await session.execute(ig_stmt)).scalar_one_or_none() is not None
|
||||||
|
|
||||||
await callback.message.edit_text(
|
await safe_edit_or_reply(
|
||||||
|
callback,
|
||||||
"Главное меню:",
|
"Главное меню:",
|
||||||
reply_markup=get_main_menu(verified=user.verified, is_ig=is_ig, is_admin=(user.is_admin or user_id == config.ADMIN_USER_ID), is_superadmin=(user_id == config.ADMIN_USER_ID)),
|
reply_markup=get_main_menu(verified=user.verified, is_ig=is_ig, is_admin=(user.is_admin or user_id == config.ADMIN_USER_ID), is_superadmin=(user_id == config.ADMIN_USER_ID)),
|
||||||
parse_mode='HTML'
|
parse_mode='HTML'
|
||||||
|
|
@ -85,11 +113,7 @@ async def cb_main_menu(callback: CallbackQuery):
|
||||||
async def cb_phones(event):
|
async def cb_phones(event):
|
||||||
text = '📞 <b>ЭКСТРЕННЫЕ ТЕЛЕФОНЫ</b>\n\nВыберите категорию ниже:'
|
text = '📞 <b>ЭКСТРЕННЫЕ ТЕЛЕФОНЫ</b>\n\nВыберите категорию ниже:'
|
||||||
kb = get_phones_menu()
|
kb = get_phones_menu()
|
||||||
if isinstance(event, CallbackQuery):
|
await safe_edit_or_reply(event, text, reply_markup=kb, parse_mode='HTML')
|
||||||
await event.answer()
|
|
||||||
await event.message.edit_text(text, reply_markup=kb, parse_mode='HTML')
|
|
||||||
else:
|
|
||||||
await event.answer(text, reply_markup=kb, parse_mode='HTML')
|
|
||||||
|
|
||||||
@router.message(F.text & ~F.text.startswith('/'))
|
@router.message(F.text & ~F.text.startswith('/'))
|
||||||
async def chat_with_ai(message: Message):
|
async def chat_with_ai(message: Message):
|
||||||
|
|
@ -136,8 +160,18 @@ async def cb_phone_category(callback: CallbackQuery):
|
||||||
stmt = select(Service).where(Service.category == cat_code).order_by(Service.name)
|
stmt = select(Service).where(Service.category == cat_code).order_by(Service.name)
|
||||||
services = list((await session.execute(stmt)).scalars().all())
|
services = list((await session.execute(stmt)).scalars().all())
|
||||||
|
|
||||||
|
user_stmt = select(User).where(User.user_id == callback.from_user.id)
|
||||||
|
user = (await session.execute(user_stmt)).scalar_one_or_none()
|
||||||
|
ig_stmt = select(InitiativeGroup).where(InitiativeGroup.user_id == callback.from_user.id, InitiativeGroup.is_active == True)
|
||||||
|
is_ig = (await session.execute(ig_stmt)).scalar_one_or_none() is not None
|
||||||
|
|
||||||
|
is_admin = user.is_admin or callback.from_user.id == config.ADMIN_USER_ID if user else False
|
||||||
|
is_superadmin = callback.from_user.id == config.ADMIN_USER_ID
|
||||||
|
verified = user.verified if user else False
|
||||||
|
menu_kb = get_main_menu(verified=verified, is_ig=is_ig, is_admin=is_admin, is_superadmin=is_superadmin)
|
||||||
|
|
||||||
if not services:
|
if not services:
|
||||||
return await callback.message.edit_text("👷 Данные для этой категории еще не заполнены.", reply_markup=get_main_menu())
|
return await safe_edit_or_reply(callback, "👷 Данные для этой категории еще не заполнены.", reply_markup=menu_kb)
|
||||||
|
|
||||||
text = f"📞 <b>СПИСОК ТЕЛЕФОНОВ: {cat_code.upper()}</b>\n\n"
|
text = f"📞 <b>СПИСОК ТЕЛЕФОНОВ: {cat_code.upper()}</b>\n\n"
|
||||||
for s in services:
|
for s in services:
|
||||||
|
|
@ -150,11 +184,9 @@ async def cb_phone_category(callback: CallbackQuery):
|
||||||
|
|
||||||
if photo_path and os.path.exists(photo_path):
|
if photo_path and os.path.exists(photo_path):
|
||||||
await callback.message.delete()
|
await callback.message.delete()
|
||||||
await callback.message.answer_photo(FSInputFile(photo_path), caption=text, reply_markup=get_main_menu(), parse_mode='HTML')
|
await callback.message.answer_photo(FSInputFile(photo_path), caption=text, reply_markup=menu_kb, parse_mode='HTML')
|
||||||
else:
|
else:
|
||||||
await callback.message.edit_text(text, reply_markup=get_main_menu(), parse_mode='HTML')
|
await safe_edit_or_reply(callback, text, reply_markup=menu_kb, parse_mode='HTML')
|
||||||
|
|
||||||
await callback.answer()
|
|
||||||
|
|
||||||
@router.message(Command('rules'))
|
@router.message(Command('rules'))
|
||||||
@router.callback_query(F.data == 'rules')
|
@router.callback_query(F.data == 'rules')
|
||||||
|
|
@ -163,11 +195,7 @@ async def cb_rules(event):
|
||||||
kb = InlineKeyboardBuilder()
|
kb = InlineKeyboardBuilder()
|
||||||
kb.row(InlineKeyboardButton(text="🏠 В меню", callback_data="main_menu"))
|
kb.row(InlineKeyboardButton(text="🏠 В меню", callback_data="main_menu"))
|
||||||
|
|
||||||
if isinstance(event, Message):
|
await safe_edit_or_reply(event, text, reply_markup=kb.as_markup(), parse_mode='HTML')
|
||||||
await event.answer(text, reply_markup=kb.as_markup(), parse_mode='HTML')
|
|
||||||
else:
|
|
||||||
await event.answer()
|
|
||||||
await event.message.edit_text(text, reply_markup=kb.as_markup(), parse_mode='HTML')
|
|
||||||
|
|
||||||
@router.message(Command('about'))
|
@router.message(Command('about'))
|
||||||
@router.callback_query(F.data == 'about_house')
|
@router.callback_query(F.data == 'about_house')
|
||||||
|
|
@ -180,11 +208,7 @@ async def cb_about(event):
|
||||||
kb = InlineKeyboardBuilder()
|
kb = InlineKeyboardBuilder()
|
||||||
kb.row(InlineKeyboardButton(text="🏠 В меню", callback_data="main_menu"))
|
kb.row(InlineKeyboardButton(text="🏠 В меню", callback_data="main_menu"))
|
||||||
|
|
||||||
if isinstance(event, Message):
|
await safe_edit_or_reply(event, text, reply_markup=kb.as_markup(), parse_mode='HTML')
|
||||||
await event.answer(text, reply_markup=kb.as_markup(), parse_mode='HTML')
|
|
||||||
else:
|
|
||||||
await event.answer()
|
|
||||||
await event.message.edit_text(text, reply_markup=kb.as_markup(), parse_mode='HTML')
|
|
||||||
|
|
||||||
@router.message(Command('profile'))
|
@router.message(Command('profile'))
|
||||||
@router.callback_query(F.data == 'my_profile')
|
@router.callback_query(F.data == 'my_profile')
|
||||||
|
|
@ -207,11 +231,7 @@ async def cb_profile(event):
|
||||||
|
|
||||||
kb = get_profile_keyboard(user.meter_reminders_enabled if user else True)
|
kb = get_profile_keyboard(user.meter_reminders_enabled if user else True)
|
||||||
|
|
||||||
if isinstance(event, Message):
|
await safe_edit_or_reply(event, text, reply_markup=kb, parse_mode='HTML')
|
||||||
await event.answer(text, reply_markup=kb, parse_mode='HTML')
|
|
||||||
else:
|
|
||||||
await event.answer()
|
|
||||||
await event.message.edit_text(text, reply_markup=kb, parse_mode='HTML')
|
|
||||||
|
|
||||||
@router.callback_query(F.data == "toggle_meter_reminders")
|
@router.callback_query(F.data == "toggle_meter_reminders")
|
||||||
async def cb_toggle_meter_reminders(callback: CallbackQuery):
|
async def cb_toggle_meter_reminders(callback: CallbackQuery):
|
||||||
|
|
@ -244,12 +264,11 @@ async def cb_toggle_meter_reminders(callback: CallbackQuery):
|
||||||
f"Рейтинг: {user.rating} 🏆")
|
f"Рейтинг: {user.rating} 🏆")
|
||||||
|
|
||||||
kb = get_profile_keyboard(new_val)
|
kb = get_profile_keyboard(new_val)
|
||||||
await callback.message.edit_text(text, reply_markup=kb, parse_mode='HTML')
|
await safe_edit_or_reply(callback, text, reply_markup=kb, parse_mode='HTML')
|
||||||
|
|
||||||
@router.callback_query(F.data == "edit_location")
|
@router.callback_query(F.data == "edit_location")
|
||||||
async def edit_location_start(callback: CallbackQuery):
|
async def edit_location_start(callback: CallbackQuery):
|
||||||
await callback.message.edit_text("Введите ваш подъезд и этаж через пробел\n<i>Например: '3 5' (3 подъезд, 5 этаж)</i>", parse_mode='HTML')
|
await safe_edit_or_reply(callback, "Введите ваш подъезд и этаж через пробел\n<i>Например: '3 5' (3 подъезд, 5 этаж)</i>")
|
||||||
await callback.answer()
|
|
||||||
|
|
||||||
@router.message(F.text.regexp(r"^\d+\s+\d+$"))
|
@router.message(F.text.regexp(r"^\d+\s+\d+$"))
|
||||||
async def process_location(message: Message):
|
async def process_location(message: Message):
|
||||||
|
|
@ -282,8 +301,4 @@ async def cb_help(event):
|
||||||
kb = InlineKeyboardBuilder()
|
kb = InlineKeyboardBuilder()
|
||||||
kb.row(InlineKeyboardButton(text="🏠 В меню", callback_data="main_menu"))
|
kb.row(InlineKeyboardButton(text="🏠 В меню", callback_data="main_menu"))
|
||||||
|
|
||||||
if isinstance(event, Message):
|
await safe_edit_or_reply(event, text, reply_markup=kb.as_markup(), parse_mode='HTML')
|
||||||
await event.answer(text, reply_markup=kb.as_markup(), parse_mode='HTML')
|
|
||||||
else:
|
|
||||||
await event.answer()
|
|
||||||
await event.message.edit_text(text, reply_markup=kb.as_markup(), parse_mode='HTML')
|
|
||||||
|
|
|
||||||
|
|
@ -1,74 +1,327 @@
|
||||||
/* MATRIX STYLE FOR LKM37 ADMIN PANEL */
|
/* GLASSMORPHISM DARK UI FOR DOMOVOY WEB PANEL */
|
||||||
|
@import url('https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap');
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--matrix-green: #22c55e;
|
--bg-main: #06060c;
|
||||||
--matrix-green-glow: #10b981;
|
--bg-gradient: radial-gradient(circle at 50% 0%, #150f30 0%, #06060c 80%);
|
||||||
--matrix-black: #050505;
|
--panel-bg: rgba(18, 18, 29, 0.45);
|
||||||
--matrix-gray: #1a1a1a;
|
--panel-bg-hover: rgba(26, 26, 42, 0.6);
|
||||||
--matrix-cyan: #06b6d4;
|
--panel-border: rgba(255, 255, 255, 0.08);
|
||||||
--matrix-red: #ef4444;
|
--panel-border-hover: rgba(255, 255, 255, 0.15);
|
||||||
|
|
||||||
|
--color-primary: #8a2be2;
|
||||||
|
--color-primary-glow: rgba(138, 43, 226, 0.35);
|
||||||
|
--color-success: #00ffcc;
|
||||||
|
--color-success-glow: rgba(0, 255, 204, 0.25);
|
||||||
|
--color-warning: #ffb300;
|
||||||
|
--color-warning-glow: rgba(255, 179, 0, 0.25);
|
||||||
|
--color-danger: #ff2d55;
|
||||||
|
--color-danger-glow: rgba(255, 45, 85, 0.25);
|
||||||
|
--color-info: #00bfff;
|
||||||
|
--color-info-glow: rgba(0, 191, 255, 0.25);
|
||||||
|
|
||||||
|
--color-text-main: #f5f5f7;
|
||||||
|
--color-text-muted: #8e8e93;
|
||||||
|
--color-text-dark: #3a3a3c;
|
||||||
|
|
||||||
|
--font-main: 'Outfit', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||||
|
--font-mono: 'JetBrains Mono', monospace;
|
||||||
|
|
||||||
|
--shadow-neon: 0 8px 32px 0 rgba(0, 0, 0, 0.4), 0 0 16px 0 var(--color-primary-glow);
|
||||||
|
--transition-smooth: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
background-color: var(--matrix-black);
|
background-color: var(--bg-main) !important;
|
||||||
color: var(--matrix-green);
|
background-image: var(--bg-gradient) !important;
|
||||||
font-family: 'Courier New', Courier, monospace;
|
background-attachment: fixed !important;
|
||||||
|
color: var(--color-text-main) !important;
|
||||||
|
font-family: var(--font-main) !important;
|
||||||
|
min-height: 100vh;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Сайдбар */
|
||||||
.sidebar {
|
.sidebar {
|
||||||
background: var(--matrix-black);
|
background: rgba(10, 10, 18, 0.3) !important;
|
||||||
border-right: 1px solid var(--matrix-green);
|
backdrop-filter: blur(20px) !important;
|
||||||
box-shadow: 2px 0 15px rgba(34, 197, 94, 0.2);
|
-webkit-backdrop-filter: blur(20px) !important;
|
||||||
|
border-right: 1px solid var(--panel-border) !important;
|
||||||
|
box-shadow: 4px 0 24px rgba(0, 0, 0, 0.3) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar .nav-link {
|
.sidebar .nav-link {
|
||||||
color: var(--matrix-green);
|
color: var(--color-text-muted) !important;
|
||||||
border-left: 2px solid transparent;
|
border-left: 3px solid transparent !important;
|
||||||
transition: 0.3s;
|
transition: var(--transition-smooth) !important;
|
||||||
text-transform: uppercase;
|
padding: 0.75rem 1.5rem !important;
|
||||||
font-size: 0.9rem;
|
font-weight: 500 !important;
|
||||||
letter-spacing: 1px;
|
text-transform: uppercase !important;
|
||||||
|
font-family: var(--font-main) !important;
|
||||||
|
letter-spacing: 0.5px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar .nav-link:hover, .sidebar .nav-link.active {
|
.sidebar .nav-link:hover {
|
||||||
background-color: rgba(34, 197, 94, 0.1);
|
color: var(--color-text-main) !important;
|
||||||
color: white;
|
background: rgba(255, 255, 255, 0.03) !important;
|
||||||
border-left-color: var(--matrix-green);
|
border-left-color: rgba(255, 255, 255, 0.2) !important;
|
||||||
text-shadow: 0 0 10px var(--matrix-green);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sidebar .nav-link.active {
|
||||||
|
color: var(--color-success) !important;
|
||||||
|
background: rgba(0, 255, 204, 0.05) !important;
|
||||||
|
border-left-color: var(--color-success) !important;
|
||||||
|
text-shadow: 0 0 10px var(--color-success-glow) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar .nav-link i, .sidebar .nav-link bi {
|
||||||
|
margin-right: 0.5rem !important;
|
||||||
|
font-size: 1.1rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Карточки */
|
||||||
.card {
|
.card {
|
||||||
background-color: var(--matrix-gray);
|
background: var(--panel-bg) !important;
|
||||||
border: 1px solid var(--matrix-green);
|
backdrop-filter: blur(12px) !important;
|
||||||
color: var(--matrix-green);
|
-webkit-backdrop-filter: blur(12px) !important;
|
||||||
|
border: 1px solid var(--panel-border) !important;
|
||||||
|
border-radius: 16px !important;
|
||||||
|
transition: var(--transition-smooth) !important;
|
||||||
|
color: var(--color-text-main) !important;
|
||||||
|
overflow: hidden !important;
|
||||||
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card:hover {
|
||||||
|
background: var(--panel-bg-hover) !important;
|
||||||
|
border-color: var(--panel-border-hover) !important;
|
||||||
|
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.4) !important;
|
||||||
|
transform: translateY(-2px) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-header {
|
.card-header {
|
||||||
background-color: rgba(34, 197, 94, 0.1);
|
background: rgba(255, 255, 255, 0.02) !important;
|
||||||
border-bottom: 1px solid var(--matrix-green);
|
border-bottom: 1px solid var(--panel-border) !important;
|
||||||
|
font-weight: 600 !important;
|
||||||
|
text-transform: uppercase !important;
|
||||||
|
letter-spacing: 0.5px !important;
|
||||||
|
padding: 1rem 1.25rem !important;
|
||||||
|
color: var(--color-text-main) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-matrix {
|
.card-body {
|
||||||
background-color: transparent;
|
padding: 1.25rem !important;
|
||||||
border: 1px solid var(--matrix-green);
|
|
||||||
color: var(--matrix-green);
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-matrix:hover {
|
/* Переопределение старых рамок и фонов */
|
||||||
background-color: var(--matrix-green);
|
.border-success, .border-warning, .border-cyan, .border-danger {
|
||||||
color: black;
|
border-color: var(--panel-border) !important;
|
||||||
box-shadow: 0 0 20px var(--matrix-green);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Эффект падающих символов на фоне (легкий) */
|
.card.border-success:hover, .card.border-warning:hover, .card.border-cyan:hover {
|
||||||
.matrix-bg {
|
border-color: var(--panel-border-hover) !important;
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
z-index: -1;
|
|
||||||
opacity: 0.05;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.card.border-danger {
|
||||||
|
border-color: rgba(255, 45, 85, 0.3) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card.border-danger:hover {
|
||||||
|
border-color: var(--color-danger) !important;
|
||||||
|
box-shadow: 0 8px 30px rgba(255, 45, 85, 0.15) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Таблицы */
|
||||||
|
.table {
|
||||||
|
color: var(--color-text-main) !important;
|
||||||
|
background-color: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table > :not(caption) > * > * {
|
||||||
|
background-color: transparent !important;
|
||||||
|
color: var(--color-text-main) !important;
|
||||||
|
border-bottom: 1px solid var(--panel-border) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table > thead > tr > th {
|
||||||
|
color: var(--color-text-muted) !important;
|
||||||
|
font-weight: 600 !important;
|
||||||
|
text-transform: uppercase !important;
|
||||||
|
font-size: 0.8rem !important;
|
||||||
|
letter-spacing: 0.5px !important;
|
||||||
|
border-bottom: 2px solid var(--panel-border) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-hover tbody tr:hover td {
|
||||||
|
background-color: rgba(255, 255, 255, 0.02) !important;
|
||||||
|
color: var(--color-text-main) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Кнопки */
|
||||||
|
.btn {
|
||||||
|
border-radius: 8px !important;
|
||||||
|
font-weight: 500 !important;
|
||||||
|
transition: var(--transition-smooth) !important;
|
||||||
|
text-transform: uppercase !important;
|
||||||
|
letter-spacing: 0.5px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-matrix, .btn-outline-success {
|
||||||
|
background-color: transparent !important;
|
||||||
|
border: 1px solid var(--color-success) !important;
|
||||||
|
color: var(--color-success) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-matrix:hover, .btn-outline-success:hover, .btn-outline-success:active, .btn-outline-success:focus {
|
||||||
|
background-color: var(--color-success) !important;
|
||||||
|
color: #000 !important;
|
||||||
|
border-color: var(--color-success) !important;
|
||||||
|
box-shadow: 0 0 15px var(--color-success-glow) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-outline-primary {
|
||||||
|
border-color: var(--color-primary) !important;
|
||||||
|
color: var(--color-text-main) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-outline-primary:hover {
|
||||||
|
background-color: var(--color-primary) !important;
|
||||||
|
color: var(--color-text-main) !important;
|
||||||
|
box-shadow: 0 0 15px var(--color-primary-glow) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-outline-danger {
|
||||||
|
border-color: var(--color-danger) !important;
|
||||||
|
color: var(--color-danger) !important;
|
||||||
|
background-color: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-outline-danger:hover {
|
||||||
|
background-color: var(--color-danger) !important;
|
||||||
|
color: #fff !important;
|
||||||
|
box-shadow: 0 0 15px var(--color-danger-glow) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-outline-warning {
|
||||||
|
border-color: var(--color-warning) !important;
|
||||||
|
color: var(--color-warning) !important;
|
||||||
|
background-color: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-outline-warning:hover {
|
||||||
|
background-color: var(--color-warning) !important;
|
||||||
|
color: #000 !important;
|
||||||
|
box-shadow: 0 0 15px var(--color-warning-glow) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-outline-info {
|
||||||
|
border-color: var(--color-info) !important;
|
||||||
|
color: var(--color-info) !important;
|
||||||
|
background-color: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-outline-info:hover {
|
||||||
|
background-color: var(--color-info) !important;
|
||||||
|
color: #000 !important;
|
||||||
|
box-shadow: 0 0 15px var(--color-info-glow) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Формы */
|
||||||
|
.form-control, .form-select {
|
||||||
|
background-color: rgba(255, 255, 255, 0.03) !important;
|
||||||
|
border: 1px solid var(--panel-border) !important;
|
||||||
|
color: var(--color-text-main) !important;
|
||||||
|
border-radius: 8px !important;
|
||||||
|
transition: var(--transition-smooth) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control:focus, .form-select:focus {
|
||||||
|
background-color: rgba(255, 255, 255, 0.05) !important;
|
||||||
|
border-color: var(--color-primary) !important;
|
||||||
|
color: var(--color-text-main) !important;
|
||||||
|
box-shadow: 0 0 8px var(--color-primary-glow) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control::placeholder {
|
||||||
|
color: var(--color-text-muted) !important;
|
||||||
|
opacity: 0.6 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-black {
|
||||||
|
background-color: rgba(10, 10, 15, 0.5) !important;
|
||||||
|
border: 1px solid var(--panel-border) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ссылки */
|
||||||
|
a {
|
||||||
|
color: var(--color-success);
|
||||||
|
text-decoration: none;
|
||||||
|
transition: var(--transition-smooth);
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
color: #fff;
|
||||||
|
text-shadow: 0 0 8px var(--color-success-glow);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Баджи */
|
||||||
|
.badge {
|
||||||
|
border-radius: 6px !important;
|
||||||
|
font-weight: 500 !important;
|
||||||
|
padding: 0.4em 0.6em !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-success {
|
||||||
|
background-color: rgba(0, 255, 204, 0.15) !important;
|
||||||
|
color: var(--color-success) !important;
|
||||||
|
border: 1px solid var(--color-success) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-danger {
|
||||||
|
background-color: rgba(255, 45, 85, 0.15) !important;
|
||||||
|
color: var(--color-danger) !important;
|
||||||
|
border: 1px solid var(--color-danger) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-warning {
|
||||||
|
background-color: rgba(255, 179, 0, 0.15) !important;
|
||||||
|
color: var(--color-warning) !important;
|
||||||
|
border: 1px solid var(--color-warning) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-info {
|
||||||
|
background-color: rgba(0, 191, 255, 0.15) !important;
|
||||||
|
color: var(--color-info) !important;
|
||||||
|
border: 1px solid var(--color-info) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Утилиты и кастомный контент */
|
||||||
|
hr {
|
||||||
|
border-color: var(--panel-border) !important;
|
||||||
|
opacity: 0.5 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre, code {
|
||||||
|
font-family: var(--font-mono) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Кастомный Scrollbar */
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
background: rgba(10, 10, 15, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba(255, 255, 255, 0.08);
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,10 +21,10 @@
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<!-- Sidebar (ВОССТАНОВЛЕННОЕ ПОЛНОЕ МЕНЮ) -->
|
<!-- Sidebar (ВОССТАНОВЛЕННОЕ ПОЛНОЕ МЕНЮ) -->
|
||||||
<div class="col-md-2 sidebar p-0 border-end border-success">
|
<div class="col-md-2 sidebar p-0">
|
||||||
<div class="p-3 text-center border-bottom border-success">
|
<div class="p-3 text-center border-bottom" style="border-color: var(--panel-border) !important;">
|
||||||
<h4 class="mb-0" style="color: var(--matrix-green); text-shadow: 0 0 10px var(--matrix-green);">LKM37</h4>
|
<h4 class="mb-0" style="color: var(--color-success); text-shadow: 0 0 15px var(--color-success-glow); font-weight: 700; letter-spacing: 1px;">LKM37</h4>
|
||||||
<small class="text-success opacity-50">CORE SYSTEM v4.0</small>
|
<small class="text-success opacity-50" style="font-family: var(--font-mono); font-size: 0.7rem; letter-spacing: 0.5px;">CORE SYSTEM v4.5</small>
|
||||||
</div>
|
</div>
|
||||||
<nav class="nav flex-column mt-3">
|
<nav class="nav flex-column mt-3">
|
||||||
<a class="nav-link {% block nav_dashboard %}{% endblock %}" href="/">
|
<a class="nav-link {% block nav_dashboard %}{% endblock %}" href="/">
|
||||||
|
|
@ -94,30 +94,98 @@
|
||||||
<script>
|
<script>
|
||||||
const canvas = document.getElementById('matrix-canvas');
|
const canvas = document.getElementById('matrix-canvas');
|
||||||
const ctx = canvas.getContext('2d');
|
const ctx = canvas.getContext('2d');
|
||||||
canvas.width = window.innerWidth;
|
|
||||||
canvas.height = window.innerHeight;
|
|
||||||
const letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
|
||||||
const fontSize = 16;
|
|
||||||
const columns = canvas.width / fontSize;
|
|
||||||
const drops = Array(Math.floor(columns)).fill(1);
|
|
||||||
|
|
||||||
function draw() {
|
let width = canvas.width = window.innerWidth;
|
||||||
ctx.fillStyle = "rgba(0, 0, 0, 0.05)";
|
let height = canvas.height = window.innerHeight;
|
||||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
||||||
ctx.fillStyle = "#22c55e";
|
const particles = [];
|
||||||
ctx.font = fontSize + "px monospace";
|
const properties = {
|
||||||
for (let i = 0; i < drops.length; i++) {
|
bgColor: '#06060c',
|
||||||
const text = letters[Math.floor(Math.random() * letters.length)];
|
particleColor: 'rgba(138, 43, 226, 0.25)',
|
||||||
ctx.fillText(text, i * fontSize, drops[i] * fontSize);
|
lineColor: 'rgba(0, 255, 204, 0.05)',
|
||||||
if (drops[i] * fontSize > canvas.height && Math.random() > 0.975) drops[i] = 0;
|
particleRadius: 2.5,
|
||||||
drops[i]++;
|
particleCount: 70,
|
||||||
|
maxVelocity: 0.4,
|
||||||
|
lineLength: 140
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('resize', function() {
|
||||||
|
width = canvas.width = window.innerWidth;
|
||||||
|
height = canvas.height = window.innerHeight;
|
||||||
|
});
|
||||||
|
|
||||||
|
class Particle {
|
||||||
|
constructor() {
|
||||||
|
this.x = Math.random() * width;
|
||||||
|
this.y = Math.random() * height;
|
||||||
|
this.velocityPosition = {
|
||||||
|
x: (Math.random() - 0.5) * properties.maxVelocity,
|
||||||
|
y: (Math.random() - 0.5) * properties.maxVelocity
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
position() {
|
||||||
|
if (this.x + this.velocityPosition.x > width || this.x + this.velocityPosition.x < 0) {
|
||||||
|
this.velocityPosition.x = -this.velocityPosition.x;
|
||||||
|
}
|
||||||
|
if (this.y + this.velocityPosition.y > height || this.y + this.velocityPosition.y < 0) {
|
||||||
|
this.velocityPosition.y = -this.velocityPosition.y;
|
||||||
|
}
|
||||||
|
this.x += this.velocityPosition.x;
|
||||||
|
this.y += this.velocityPosition.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
reDraw() {
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(this.x, this.y, properties.particleRadius, 0, Math.PI * 2);
|
||||||
|
ctx.closePath();
|
||||||
|
ctx.fillStyle = properties.particleColor;
|
||||||
|
ctx.fill();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setInterval(draw, 33);
|
|
||||||
window.addEventListener('resize', () => {
|
function drawLines() {
|
||||||
canvas.width = window.innerWidth;
|
let x1, y1, x2, y2, length, opacity;
|
||||||
canvas.height = window.innerHeight;
|
for (let i = 0; i < particles.length; i++) {
|
||||||
});
|
for (let j = i + 1; j < particles.length; j++) {
|
||||||
|
x1 = particles[i].x;
|
||||||
|
y1 = particles[i].y;
|
||||||
|
x2 = particles[j].x;
|
||||||
|
y2 = particles[j].y;
|
||||||
|
length = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
|
||||||
|
if (length < properties.lineLength) {
|
||||||
|
opacity = 1 - (length / properties.lineLength);
|
||||||
|
ctx.strokeStyle = `rgba(0, 255, 204, ${opacity * 0.06})`;
|
||||||
|
ctx.lineWidth = '0.5';
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(x1, y1);
|
||||||
|
ctx.lineTo(x2, y2);
|
||||||
|
ctx.closePath();
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
for (let i = 0; i < properties.particleCount; i++) {
|
||||||
|
particles.push(new Particle());
|
||||||
|
}
|
||||||
|
loop();
|
||||||
|
}
|
||||||
|
|
||||||
|
function loop() {
|
||||||
|
ctx.fillStyle = properties.bgColor;
|
||||||
|
ctx.fillRect(0, 0, width, height);
|
||||||
|
for (let i = 0; i < particles.length; i++) {
|
||||||
|
particles[i].position();
|
||||||
|
particles[i].reDraw();
|
||||||
|
}
|
||||||
|
drawLines();
|
||||||
|
requestAnimationFrame(loop);
|
||||||
|
}
|
||||||
|
|
||||||
|
init();
|
||||||
</script>
|
</script>
|
||||||
{% block extra_script %}{% endblock %}
|
{% block extra_script %}{% endblock %}
|
||||||
</body>
|
</body>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue