7469 lines
482 KiB
Plaintext
7469 lines
482 KiB
Plaintext
// app/Language/en/Validation.php
|
||
<?php
|
||
return [];
|
||
|
||
// app/Language/.gitkeep
|
||
|
||
// app/Libraries/Twig/TwigJsonDecodeExtension.php
|
||
<?php
|
||
namespace App\Libraries\Twig;
|
||
use Twig\Extension\AbstractExtension;
|
||
use Twig\TwigFilter;
|
||
class TwigJsonDecodeExtension extends AbstractExtension
|
||
{
|
||
public function getFilters(): array
|
||
{
|
||
return [
|
||
new TwigFilter('json_decode', [$this, 'jsonDecode']),
|
||
];
|
||
}
|
||
public function jsonDecode(string $json, bool $assoc = true, int $depth = 512, int $flags = 0)
|
||
{
|
||
return json_decode($json, $assoc, $depth, $flags);
|
||
}
|
||
}
|
||
|
||
// app/Libraries/Twig/TwigGlobalsExtension.php
|
||
<?php
|
||
namespace App\Libraries\Twig;
|
||
use Twig\Extension\AbstractExtension;
|
||
use Twig\TwigFunction;
|
||
use Twig\TwigGlobal;
|
||
use App\Models\OrganizationModel;
|
||
use Config\Services;
|
||
class TwigGlobalsExtension extends AbstractExtension
|
||
{
|
||
public function getFunctions(): array
|
||
{
|
||
return [
|
||
new TwigFunction('get_session', [$this, 'getSession'], ['is_safe' => ['html']]),
|
||
new TwigFunction('get_current_org', [$this, 'getCurrentOrg'], ['is_safe' => ['html']]),
|
||
new TwigFunction('get_alerts', [$this, 'getAlerts'], ['is_safe' => ['html']]),
|
||
new TwigFunction('render_pager', [$this, 'renderPager'], ['is_safe' => ['html']]),
|
||
];
|
||
}
|
||
public function getSession()
|
||
{
|
||
return session();
|
||
}
|
||
public function getCurrentOrg()
|
||
{
|
||
$session = session();
|
||
$activeOrgId = $session->get('active_org_id');
|
||
if ($activeOrgId) {
|
||
$orgModel = new OrganizationModel();
|
||
return $orgModel->find($activeOrgId);
|
||
}
|
||
return null;
|
||
}
|
||
public function getAlerts(): array
|
||
{
|
||
$session = session();
|
||
$alerts = [];
|
||
$types = ['success', 'error', 'warning', 'info'];
|
||
foreach ($types as $type) {
|
||
if ($msg = $session->getFlashdata($type)) {
|
||
$alerts[] = ['type' => $type, 'message' => $msg];
|
||
}
|
||
}
|
||
if ($validationErrors = $session->getFlashdata('errors')) {
|
||
foreach ($validationErrors as $error) {
|
||
$alerts[] = ['type' => 'error', 'message' => $error];
|
||
}
|
||
}
|
||
return $alerts;
|
||
}
|
||
public function renderPager($pager)
|
||
{
|
||
if (!$pager) {
|
||
return '';
|
||
}
|
||
return $pager->links();
|
||
}
|
||
}
|
||
|
||
// app/Libraries/EmailLibrary.php
|
||
<?php
|
||
namespace App\Libraries;
|
||
use CodeIgniter\Email\Email;
|
||
use Config\Services;
|
||
class EmailLibrary
|
||
{
|
||
/**
|
||
public function sendVerificationEmail(string $email, string $name, string $token): bool
|
||
{
|
||
$emailConfig = config('Email');
|
||
$verificationUrl = base_url('/auth/verify/' . $token);
|
||
$twig = Services::twig();
|
||
$htmlBody = $twig->render('emails/verification', [
|
||
'name' => $name,
|
||
'verification_url' => $verificationUrl,
|
||
'app_name' => $emailConfig->fromName ?? 'Бизнес.Точка',
|
||
]);
|
||
$emailer = Services::email($emailConfig);
|
||
$emailer->setTo($email);
|
||
$emailer->setFrom($emailConfig->fromEmail, $emailConfig->fromName);
|
||
$emailer->setSubject('Подтверждение регистрации');
|
||
$emailer->setMessage($htmlBody);
|
||
try {
|
||
return $emailer->send();
|
||
} catch (\Exception $e) {
|
||
log_message('error', 'Ошибка отправки email: ' . $e->getMessage());
|
||
return false;
|
||
}
|
||
}
|
||
/**
|
||
public function sendWelcomeEmail(string $email, string $name): bool
|
||
{
|
||
$emailConfig = config('Email');
|
||
$twig = Services::twig();
|
||
$htmlBody = $twig->render('emails/welcome', [
|
||
'name' => $name,
|
||
'app_name' => $emailConfig->fromName ?? 'Бизнес.Точка',
|
||
]);
|
||
$emailer = Services::email($emailConfig);
|
||
$emailer->setTo($email);
|
||
$emailer->setFrom($emailConfig->fromEmail, $emailConfig->fromName);
|
||
$emailer->setSubject('Добро пожаловать!');
|
||
$emailer->setMessage($htmlBody);
|
||
try {
|
||
return $emailer->send();
|
||
} catch (\Exception $e) {
|
||
log_message('error', 'Ошибка отправки email: ' . $e->getMessage());
|
||
return false;
|
||
}
|
||
}
|
||
}
|
||
|
||
// app/Libraries/.gitkeep
|
||
|
||
// app/Helpers/.gitkeep
|
||
|
||
// app/index.html
|
||
<!DOCTYPE html>
|
||
<html>
|
||
<head>
|
||
<title>403 Forbidden</title>
|
||
</head>
|
||
<body>
|
||
|
||
<p>Directory access is forbidden.</p>
|
||
|
||
</body>
|
||
</html>
|
||
|
||
// app/Common.php
|
||
<?php
|
||
/**
|
||
|
||
// app/Views/layouts/base.twig
|
||
{# app/Views/layouts/base.html.twig #}
|
||
{% set session_data = get_session() %}
|
||
{% set current_org = get_current_org() %}
|
||
|
||
<!DOCTYPE html>
|
||
<html lang="ru">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>{% block title %}Бизнес.Точка{% endblock %}</title>
|
||
|
||
<!-- Bootstrap CSS -->
|
||
<link href="{{ base_url('assets/css/bootstrap.min.css') }}" rel="stylesheet">
|
||
<!-- FontAwesome -->
|
||
<link href="{{ base_url('assets/css/all.min.css') }}" rel="stylesheet">
|
||
|
||
{% block styles %}{% endblock %}
|
||
</head>
|
||
<body class="bg-light">
|
||
|
||
<div class="d-flex" id="wrapper">
|
||
<!-- SIDEBAR -->
|
||
<div class="bg-white border-end" id="sidebar-wrapper" style="width: 250px;">
|
||
<div class="sidebar-heading bg-primary text-white p-3 d-flex justify-content-between align-items-center">
|
||
<span><i class="fa-solid fa-circle-nodes me-2"></i>Бизнес.Точка</span>
|
||
</div>
|
||
<div class="list-group list-group-flush mt-2">
|
||
<a href="{{ base_url('/') }}" class="list-group-item list-group-item-action list-group-item-light py-3">
|
||
<i class="fa-solid fa-gauge-high me-2"></i> Главная
|
||
</a>
|
||
|
||
<!-- Ссылки на модули (пока неактивны) -->
|
||
<!-- Модуль Клиенты (базовый, доступен всем) -->
|
||
<a href="{{ base_url('/clients') }}" class="list-group-item list-group-item-action list-group-item-light py-3">
|
||
<i class="fa-solid fa-users me-2"></i> Клиенты
|
||
</a>
|
||
|
||
<a href="#" class="list-group-item list-group-item-action list-group-item-light py-3 disabled">
|
||
<i class="fa-solid fa-users me-2"></i> CRM
|
||
</a>
|
||
<a href="#" class="list-group-item list-group-item-action list-group-item-light py-3 disabled">
|
||
<i class="fa-solid fa-calendar me-2"></i> Booking
|
||
</a>
|
||
<a href="#" class="list-group-item list-group-item-action list-group-item-light py-3 disabled">
|
||
<i class="fa-solid fa-file-contract me-2"></i> Proof
|
||
</a>
|
||
<a href="#" class="list-group-item list-group-item-action list-group-item-light py-3 disabled">
|
||
<i class="fa-solid fa-check-square me-2"></i> Tasks
|
||
</a>
|
||
</div>
|
||
</div>
|
||
<!-- /SIDEBAR -->
|
||
|
||
<!-- Page Content -->
|
||
<div id="page-content-wrapper" class="w-100">
|
||
|
||
<!-- TOPBAR -->
|
||
<nav class="navbar navbar-expand-lg navbar-light bg-white border-bottom px-4 shadow-sm">
|
||
<button class="btn btn-light border-0" id="sidebarToggle"><i class="fa-solid fa-bars"></i></button>
|
||
|
||
<div class="ms-auto d-flex align-items-center">
|
||
|
||
<!-- ДРОПДАУН ОРГАНИЗАЦИИ -->
|
||
<div class="dropdown me-4">
|
||
{% if current_org %}
|
||
<button class="btn btn-light dropdown-toggle border d-flex align-items-center" type="button" data-bs-toggle="dropdown">
|
||
<i class="fa-solid fa-building text-primary me-2"></i>
|
||
<span class="fw-bold text-dark">{{ current_org.name }}</span>
|
||
<span class="badge bg-light text-secondary ms-2 text-uppercase fs-6">{{ current_org.type == 'personal' ? 'Личное' : 'Бизнес' }}</span>
|
||
</button>
|
||
{% else %}
|
||
<a href="{{ base_url('/organizations') }}" class="btn btn-outline-danger">
|
||
<i class="fa-solid fa-exclamation-triangle me-1"></i> Выберите организацию
|
||
</a>
|
||
{% endif %}
|
||
|
||
{% if current_org %}
|
||
<ul class="dropdown-menu dropdown-menu-end shadow-sm">
|
||
{# <li>
|
||
<span class="dropdown-header text-muted small">
|
||
<i class="fa-solid fa-hashtag me-1"></i> {{ current_org.id }}
|
||
</span>
|
||
</li>
|
||
<li><hr class="dropdown-divider"></li> #}
|
||
|
||
<!-- Ссылка: Список всех организаций -->
|
||
<li>
|
||
<a class="dropdown-item" href="{{ base_url('/organizations') }}">
|
||
<i class="fa-solid fa-layer-group text-primary me-2"></i> Список организаций
|
||
</a>
|
||
</li>
|
||
|
||
<!-- Ссылка: Создать новую организацию -->
|
||
<li>
|
||
<a class="dropdown-item" href="{{ base_url('/organizations/create') }}">
|
||
<i class="fa-solid fa-plus-circle text-success me-2"></i> Создать организацию
|
||
</a>
|
||
</li>
|
||
|
||
<li><hr class="dropdown-divider"></li>
|
||
|
||
<!-- Ссылка: Настройки этой организации (будущий функционал) -->
|
||
<li>
|
||
<a class="dropdown-item text-muted" href="#">
|
||
<i class="fa-solid fa-gear me-2"></i> Настройки текущей
|
||
</a>
|
||
</li>
|
||
</ul>
|
||
{% endif %}
|
||
</div>
|
||
<!-- /ДРОПДАУН ОРГАНИЗАЦИИ -->
|
||
|
||
<!-- ПРОФИЛЬ ПОЛЬЗОВАТЕЛЯ -->
|
||
<div class="dropdown">
|
||
<a class="nav-link dropdown-toggle d-flex align-items-center" href="#" role="button" data-bs-toggle="dropdown">
|
||
<div class="bg-primary text-white rounded-circle d-flex justify-content-center align-items-center me-2" style="width:32px; height:32px;">
|
||
{{ session_data.name|first|upper }}
|
||
</div>
|
||
<span class="d-none d-md-inline text-dark">{{ session_data.name }}</span>
|
||
</a>
|
||
<ul class="dropdown-menu dropdown-menu-end shadow-sm">
|
||
<li><h6 class="dropdown-header">{{ session_data.email }}</h6></li>
|
||
<li><a class="dropdown-item" href="#"><i class="fa-regular fa-user me-2"></i> Профиль</a></li>
|
||
<li><hr class="dropdown-divider"></li>
|
||
<li><a class="dropdown-item text-danger" href="{{ base_url('/logout') }}"><i class="fa-solid fa-arrow-right-from-bracket me-2"></i> Выйти</a></li>
|
||
</ul>
|
||
</div>
|
||
<!-- /ПРОФИЛЬ ПОЛЬЗОВАТЕЛЯ -->
|
||
|
||
</div>
|
||
</nav>
|
||
<!-- /TOPBAR -->
|
||
|
||
<!-- CONTENT -->
|
||
<div class="container-fluid p-4">
|
||
<!-- Подключаем компонент алертов (Toasts) -->
|
||
{% include 'components/alerts.twig' %}
|
||
|
||
{% block content %}{% endblock %}
|
||
</div>
|
||
<!-- /CONTENT -->
|
||
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Scripts -->
|
||
<script src="{{ base_url('assets/js/bootstrap.bundle.min.js') }}"></script>
|
||
<script>
|
||
// Скрипт тоггла сайдбара
|
||
document.addEventListener("DOMContentLoaded", function () {
|
||
const sidebarToggle = document.body.querySelector('#sidebarToggle');
|
||
if (sidebarToggle) {
|
||
sidebarToggle.addEventListener('click', event => {
|
||
event.preventDefault();
|
||
document.body.classList.toggle('sb-sidenav-toggled');
|
||
});
|
||
}
|
||
});
|
||
</script>
|
||
{% block scripts %}{% endblock %}
|
||
</body>
|
||
</html>
|
||
// app/Views/layouts/public.twig
|
||
<!DOCTYPE html>
|
||
<html lang="ru">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Вход - Бизнес.Точка</title>
|
||
<link href="{{ base_url('assets/css/bootstrap.min.css') }}" rel="stylesheet">
|
||
<link href="{{ base_url('assets/css/all.min.css') }}" rel="stylesheet">
|
||
</head>
|
||
<body class="bg-light d-flex align-items-center justify-content-center" style="height: 100vh;">
|
||
{% include 'components/alerts.twig' %}
|
||
|
||
{% block content %}{% endblock %}
|
||
|
||
|
||
<script src="{{ base_url('assets/js/bootstrap.bundle.min.js') }}"></script>
|
||
{% block scripts %}{% endblock %}
|
||
</body>
|
||
</html>
|
||
// app/Views/pager/bootstrap_full.php
|
||
<?php
|
||
use CodeIgniter\Pager\PagerRenderer;
|
||
/**
|
||
$pager->setSurroundCount(2);
|
||
?>
|
||
<nav aria-label="<?= lang('Pager.pageNavigation') ?>">
|
||
<ul class="pagination justify-content-center">
|
||
<?php if ($pager->hasPrevious()) : ?>
|
||
<li class="page-item">
|
||
<a href="<?= $pager->getPrevious() ?>" class="page-link" aria-label="<?= lang('Pager.previous') ?>">
|
||
<span aria-hidden="true"><?= lang('Pager.previous') ?></span>
|
||
</a>
|
||
</li>
|
||
<?php endif ?>
|
||
<?php foreach ($pager->links() as $link) : ?>
|
||
<li class="page-item <?= $link['active'] ? 'active' : '' ?>">
|
||
<a href="<?= $link['uri'] ?>" class="page-link"><?= $link['title'] ?></a>
|
||
</li>
|
||
<?php endforeach ?>
|
||
<?php if ($pager->hasNext()) : ?>
|
||
<li class="page-item">
|
||
<a href="<?= $pager->getNext() ?>" class="page-link" aria-label="<?= lang('Pager.next') ?>">
|
||
<span aria-hidden="true"><?= lang('Pager.next') ?></span>
|
||
</a>
|
||
</li>
|
||
<?php endif ?>
|
||
</ul>
|
||
</nav>
|
||
|
||
// app/Views/errors/cli/production.php
|
||
<?php
|
||
include __DIR__ . '/error_exception.php';
|
||
|
||
// app/Views/errors/cli/error_404.php
|
||
<?php
|
||
use CodeIgniter\CLI\CLI;
|
||
CLI::error('ERROR: ' . $code);
|
||
CLI::write($message);
|
||
CLI::newLine();
|
||
|
||
// app/Views/errors/cli/error_exception.php
|
||
<?php
|
||
use CodeIgniter\CLI\CLI;
|
||
CLI::write('[' . $exception::class . ']', 'light_gray', 'red');
|
||
CLI::write($message);
|
||
CLI::write('at ' . CLI::color(clean_path($exception->getFile()) . ':' . $exception->getLine(), 'green'));
|
||
CLI::newLine();
|
||
$last = $exception;
|
||
while ($prevException = $last->getPrevious()) {
|
||
$last = $prevException;
|
||
CLI::write(' Caused by:');
|
||
CLI::write(' [' . $prevException::class . ']', 'red');
|
||
CLI::write(' ' . $prevException->getMessage());
|
||
CLI::write(' at ' . CLI::color(clean_path($prevException->getFile()) . ':' . $prevException->getLine(), 'green'));
|
||
CLI::newLine();
|
||
}
|
||
if (defined('SHOW_DEBUG_BACKTRACE') && SHOW_DEBUG_BACKTRACE) {
|
||
$backtraces = $last->getTrace();
|
||
if ($backtraces) {
|
||
CLI::write('Backtrace:', 'green');
|
||
}
|
||
foreach ($backtraces as $i => $error) {
|
||
$padFile = ' ';
|
||
$padClass = ' ';
|
||
$c = str_pad($i + 1, 3, ' ', STR_PAD_LEFT);
|
||
if (isset($error['file'])) {
|
||
$filepath = clean_path($error['file']) . ':' . $error['line'];
|
||
CLI::write($c . $padFile . CLI::color($filepath, 'yellow'));
|
||
} else {
|
||
CLI::write($c . $padFile . CLI::color('[internal function]', 'yellow'));
|
||
}
|
||
$function = '';
|
||
if (isset($error['class'])) {
|
||
$type = ($error['type'] === '->') ? '()' . $error['type'] : $error['type'];
|
||
$function .= $padClass . $error['class'] . $type . $error['function'];
|
||
} elseif (! isset($error['class']) && isset($error['function'])) {
|
||
$function .= $padClass . $error['function'];
|
||
}
|
||
$args = implode(', ', array_map(static fn ($value): string => match (true) {
|
||
is_object($value) => 'Object(' . $value::class . ')',
|
||
is_array($value) => $value !== [] ? '[...]' : '[]',
|
||
$value === null => 'null',
|
||
default => var_export($value, true),
|
||
}, array_values($error['args'] ?? [])));
|
||
$function .= '(' . $args . ')';
|
||
CLI::write($function);
|
||
CLI::newLine();
|
||
}
|
||
}
|
||
|
||
// app/Views/errors/html/debug.css
|
||
:root {
|
||
--main-bg-color: #fff;
|
||
--main-text-color: #555;
|
||
--dark-text-color: #222;
|
||
--light-text-color: #c7c7c7;
|
||
--brand-primary-color: #DC4814;
|
||
--light-bg-color: #ededee;
|
||
--dark-bg-color: #404040;
|
||
}
|
||
|
||
body {
|
||
height: 100%;
|
||
background: var(--main-bg-color);
|
||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
|
||
color: var(--main-text-color);
|
||
font-weight: 300;
|
||
margin: 0;
|
||
padding: 0;
|
||
}
|
||
h1 {
|
||
font-weight: lighter;
|
||
font-size: 3rem;
|
||
color: var(--dark-text-color);
|
||
margin: 0;
|
||
}
|
||
h1.headline {
|
||
margin-top: 20%;
|
||
font-size: 5rem;
|
||
}
|
||
.text-center {
|
||
text-align: center;
|
||
}
|
||
p.lead {
|
||
font-size: 1.6rem;
|
||
}
|
||
.container {
|
||
max-width: 75rem;
|
||
margin: 0 auto;
|
||
padding: 1rem;
|
||
}
|
||
.header {
|
||
background: var(--light-bg-color);
|
||
color: var(--dark-text-color);
|
||
margin-top: 2.17rem;
|
||
}
|
||
.header .container {
|
||
padding: 1rem;
|
||
}
|
||
.header h1 {
|
||
font-size: 2.5rem;
|
||
font-weight: 500;
|
||
}
|
||
.header p {
|
||
font-size: 1.2rem;
|
||
margin: 0;
|
||
line-height: 2.5;
|
||
}
|
||
.header a {
|
||
color: var(--brand-primary-color);
|
||
margin-left: 2rem;
|
||
display: none;
|
||
text-decoration: none;
|
||
}
|
||
.header:hover a {
|
||
display: inline;
|
||
}
|
||
|
||
.environment {
|
||
background: var(--brand-primary-color);
|
||
color: var(--main-bg-color);
|
||
text-align: center;
|
||
padding: calc(4px + 0.2083vw);
|
||
width: 100%;
|
||
top: 0;
|
||
position: fixed;
|
||
}
|
||
|
||
.source {
|
||
background: #343434;
|
||
color: var(--light-text-color);
|
||
padding: 0.5em 1em;
|
||
border-radius: 5px;
|
||
font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
|
||
font-size: 0.85rem;
|
||
margin: 0;
|
||
overflow-x: scroll;
|
||
}
|
||
.source span.line {
|
||
line-height: 1.4;
|
||
}
|
||
.source span.line .number {
|
||
color: #666;
|
||
}
|
||
.source .line .highlight {
|
||
display: block;
|
||
background: var(--dark-text-color);
|
||
color: var(--light-text-color);
|
||
}
|
||
.source span.highlight .number {
|
||
color: #fff;
|
||
}
|
||
|
||
.tabs {
|
||
list-style: none;
|
||
list-style-position: inside;
|
||
margin: 0;
|
||
padding: 0;
|
||
margin-bottom: -1px;
|
||
}
|
||
.tabs li {
|
||
display: inline;
|
||
}
|
||
.tabs a:link,
|
||
.tabs a:visited {
|
||
padding: 0 1rem;
|
||
line-height: 2.7;
|
||
text-decoration: none;
|
||
color: var(--dark-text-color);
|
||
background: var(--light-bg-color);
|
||
border: 1px solid rgba(0,0,0,0.15);
|
||
border-bottom: 0;
|
||
border-top-left-radius: 5px;
|
||
border-top-right-radius: 5px;
|
||
display: inline-block;
|
||
}
|
||
.tabs a:hover {
|
||
background: var(--light-bg-color);
|
||
border-color: rgba(0,0,0,0.15);
|
||
}
|
||
.tabs a.active {
|
||
background: var(--main-bg-color);
|
||
color: var(--main-text-color);
|
||
}
|
||
.tab-content {
|
||
background: var(--main-bg-color);
|
||
border: 1px solid rgba(0,0,0,0.15);
|
||
}
|
||
.content {
|
||
padding: 1rem;
|
||
}
|
||
.hide {
|
||
display: none;
|
||
}
|
||
|
||
.alert {
|
||
margin-top: 2rem;
|
||
display: block;
|
||
text-align: center;
|
||
line-height: 3.0;
|
||
background: #d9edf7;
|
||
border: 1px solid #bcdff1;
|
||
border-radius: 5px;
|
||
color: #31708f;
|
||
}
|
||
|
||
table {
|
||
width: 100%;
|
||
overflow: hidden;
|
||
}
|
||
th {
|
||
text-align: left;
|
||
border-bottom: 1px solid #e7e7e7;
|
||
padding-bottom: 0.5rem;
|
||
}
|
||
td {
|
||
padding: 0.2rem 0.5rem 0.2rem 0;
|
||
}
|
||
tr:hover td {
|
||
background: #f1f1f1;
|
||
}
|
||
td pre {
|
||
white-space: pre-wrap;
|
||
}
|
||
|
||
.trace a {
|
||
color: inherit;
|
||
}
|
||
.trace table {
|
||
width: auto;
|
||
}
|
||
.trace tr td:first-child {
|
||
min-width: 5em;
|
||
font-weight: bold;
|
||
}
|
||
.trace td {
|
||
background: var(--light-bg-color);
|
||
padding: 0 1rem;
|
||
}
|
||
.trace td pre {
|
||
margin: 0;
|
||
}
|
||
.args {
|
||
display: none;
|
||
}
|
||
|
||
// app/Views/errors/html/production.php
|
||
<!doctype html>
|
||
<html>
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="robots" content="noindex">
|
||
<title><?= lang('Errors.whoops') ?></title>
|
||
<style>
|
||
<?= preg_replace('#[\r\n\t ]+#', ' ', file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . 'debug.css')) ?>
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="container text-center">
|
||
<h1 class="headline"><?= lang('Errors.whoops') ?></h1>
|
||
<p class="lead"><?= lang('Errors.weHitASnag') ?></p>
|
||
</div>
|
||
</body>
|
||
</html>
|
||
|
||
// app/Views/errors/html/error_400.php
|
||
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<title><?= lang('Errors.badRequest') ?></title>
|
||
<style>
|
||
div.logo {
|
||
height: 200px;
|
||
width: 155px;
|
||
display: inline-block;
|
||
opacity: 0.08;
|
||
position: absolute;
|
||
top: 2rem;
|
||
left: 50%;
|
||
margin-left: -73px;
|
||
}
|
||
body {
|
||
height: 100%;
|
||
background: #fafafa;
|
||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||
color: #777;
|
||
font-weight: 300;
|
||
}
|
||
h1 {
|
||
font-weight: lighter;
|
||
letter-spacing: normal;
|
||
font-size: 3rem;
|
||
margin-top: 0;
|
||
margin-bottom: 0;
|
||
color: #222;
|
||
}
|
||
.wrap {
|
||
max-width: 1024px;
|
||
margin: 5rem auto;
|
||
padding: 2rem;
|
||
background: #fff;
|
||
text-align: center;
|
||
border: 1px solid #efefef;
|
||
border-radius: 0.5rem;
|
||
position: relative;
|
||
}
|
||
pre {
|
||
white-space: normal;
|
||
margin-top: 1.5rem;
|
||
}
|
||
code {
|
||
background: #fafafa;
|
||
border: 1px solid #efefef;
|
||
padding: 0.5rem 1rem;
|
||
border-radius: 5px;
|
||
display: block;
|
||
}
|
||
p {
|
||
margin-top: 1.5rem;
|
||
}
|
||
.footer {
|
||
margin-top: 2rem;
|
||
border-top: 1px solid #efefef;
|
||
padding: 1em 2em 0 2em;
|
||
font-size: 85%;
|
||
color: #999;
|
||
}
|
||
a:active,
|
||
a:link,
|
||
a:visited {
|
||
color: #dd4814;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="wrap">
|
||
<h1>400</h1>
|
||
<p>
|
||
<?php if (ENVIRONMENT !== 'production') : ?>
|
||
<?= nl2br(esc($message)) ?>
|
||
<?php else : ?>
|
||
<?= lang('Errors.sorryBadRequest') ?>
|
||
<?php endif; ?>
|
||
</p>
|
||
</div>
|
||
</body>
|
||
</html>
|
||
|
||
// app/Views/errors/html/debug.js
|
||
var tabLinks = new Array();
|
||
var contentDivs = new Array();
|
||
function init()
|
||
{
|
||
var tabListItems = document.getElementById('tabs').childNodes;
|
||
console.log(tabListItems);
|
||
for (var i = 0; i < tabListItems.length; i ++)
|
||
{
|
||
if (tabListItems[i].nodeName == "LI")
|
||
{
|
||
var tabLink = getFirstChildWithTagName(tabListItems[i], 'A');
|
||
var id = getHash(tabLink.getAttribute('href'));
|
||
tabLinks[id] = tabLink;
|
||
contentDivs[id] = document.getElementById(id);
|
||
}
|
||
}
|
||
var i = 0;
|
||
for (var id in tabLinks)
|
||
{
|
||
tabLinks[id].onclick = showTab;
|
||
tabLinks[id].onfocus = function () {
|
||
this.blur()
|
||
};
|
||
if (i == 0)
|
||
{
|
||
tabLinks[id].className = 'active';
|
||
}
|
||
i ++;
|
||
}
|
||
var i = 0;
|
||
for (var id in contentDivs)
|
||
{
|
||
if (i != 0)
|
||
{
|
||
console.log(contentDivs[id]);
|
||
contentDivs[id].className = 'content hide';
|
||
}
|
||
i ++;
|
||
}
|
||
}
|
||
function showTab()
|
||
{
|
||
var selectedId = getHash(this.getAttribute('href'));
|
||
for (var id in contentDivs)
|
||
{
|
||
if (id == selectedId)
|
||
{
|
||
tabLinks[id].className = 'active';
|
||
contentDivs[id].className = 'content';
|
||
}
|
||
else
|
||
{
|
||
tabLinks[id].className = '';
|
||
contentDivs[id].className = 'content hide';
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
function getFirstChildWithTagName(element, tagName)
|
||
{
|
||
for (var i = 0; i < element.childNodes.length; i ++)
|
||
{
|
||
if (element.childNodes[i].nodeName == tagName)
|
||
{
|
||
return element.childNodes[i];
|
||
}
|
||
}
|
||
}
|
||
function getHash(url)
|
||
{
|
||
var hashPos = url.lastIndexOf('#');
|
||
return url.substring(hashPos + 1);
|
||
}
|
||
function toggle(elem)
|
||
{
|
||
elem = document.getElementById(elem);
|
||
if (elem.style && elem.style['display'])
|
||
{
|
||
var disp = elem.style['display'];
|
||
}
|
||
else if (elem.currentStyle)
|
||
{
|
||
var disp = elem.currentStyle['display'];
|
||
}
|
||
else if (window.getComputedStyle)
|
||
{
|
||
var disp = document.defaultView.getComputedStyle(elem, null).getPropertyValue('display');
|
||
}
|
||
elem.style.display = disp == 'block' ? 'none' : 'block';
|
||
return false;
|
||
}
|
||
|
||
// app/Views/errors/html/error_404.php
|
||
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<title><?= lang('Errors.pageNotFound') ?></title>
|
||
<style>
|
||
div.logo {
|
||
height: 200px;
|
||
width: 155px;
|
||
display: inline-block;
|
||
opacity: 0.08;
|
||
position: absolute;
|
||
top: 2rem;
|
||
left: 50%;
|
||
margin-left: -73px;
|
||
}
|
||
body {
|
||
height: 100%;
|
||
background: #fafafa;
|
||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||
color: #777;
|
||
font-weight: 300;
|
||
}
|
||
h1 {
|
||
font-weight: lighter;
|
||
letter-spacing: normal;
|
||
font-size: 3rem;
|
||
margin-top: 0;
|
||
margin-bottom: 0;
|
||
color: #222;
|
||
}
|
||
.wrap {
|
||
max-width: 1024px;
|
||
margin: 5rem auto;
|
||
padding: 2rem;
|
||
background: #fff;
|
||
text-align: center;
|
||
border: 1px solid #efefef;
|
||
border-radius: 0.5rem;
|
||
position: relative;
|
||
}
|
||
pre {
|
||
white-space: normal;
|
||
margin-top: 1.5rem;
|
||
}
|
||
code {
|
||
background: #fafafa;
|
||
border: 1px solid #efefef;
|
||
padding: 0.5rem 1rem;
|
||
border-radius: 5px;
|
||
display: block;
|
||
}
|
||
p {
|
||
margin-top: 1.5rem;
|
||
}
|
||
.footer {
|
||
margin-top: 2rem;
|
||
border-top: 1px solid #efefef;
|
||
padding: 1em 2em 0 2em;
|
||
font-size: 85%;
|
||
color: #999;
|
||
}
|
||
a:active,
|
||
a:link,
|
||
a:visited {
|
||
color: #dd4814;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="wrap">
|
||
<h1>404</h1>
|
||
<p>
|
||
<?php if (ENVIRONMENT !== 'production') : ?>
|
||
<?= nl2br(esc($message)) ?>
|
||
<?php else : ?>
|
||
<?= lang('Errors.sorryCannotFind') ?>
|
||
<?php endif; ?>
|
||
</p>
|
||
</div>
|
||
</body>
|
||
</html>
|
||
|
||
// app/Views/errors/html/error_exception.php
|
||
<?php
|
||
use CodeIgniter\HTTP\Header;
|
||
use CodeIgniter\CodeIgniter;
|
||
$errorId = uniqid('error', true);
|
||
?>
|
||
<!doctype html>
|
||
<html>
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="robots" content="noindex">
|
||
<title><?= esc($title) ?></title>
|
||
<style>
|
||
<?= preg_replace('#[\r\n\t ]+#', ' ', file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . 'debug.css')) ?>
|
||
</style>
|
||
<script>
|
||
<?= file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . 'debug.js') ?>
|
||
</script>
|
||
</head>
|
||
<body onload="init()">
|
||
<!-- Header -->
|
||
<div class="header">
|
||
<div class="environment">
|
||
Displayed at <?= esc(date('H:i:s')) ?> —
|
||
PHP: <?= esc(PHP_VERSION) ?> —
|
||
CodeIgniter: <?= esc(CodeIgniter::CI_VERSION) ?> --
|
||
Environment: <?= ENVIRONMENT ?>
|
||
</div>
|
||
<div class="container">
|
||
<h1><?= esc($title), esc($exception->getCode() ? ' #' . $exception->getCode() : '') ?></h1>
|
||
<p>
|
||
<?= nl2br(esc($exception->getMessage())) ?>
|
||
<a href="https:
|
||
rel="noreferrer" target="_blank">search →</a>
|
||
</p>
|
||
</div>
|
||
</div>
|
||
<!-- Source -->
|
||
<div class="container">
|
||
<p><b><?= esc(clean_path($file)) ?></b> at line <b><?= esc($line) ?></b></p>
|
||
<?php if (is_file($file)) : ?>
|
||
<div class="source">
|
||
<?= static::highlightFile($file, $line, 15); ?>
|
||
</div>
|
||
<?php endif; ?>
|
||
</div>
|
||
<div class="container">
|
||
<?php
|
||
$last = $exception;
|
||
while ($prevException = $last->getPrevious()) {
|
||
$last = $prevException;
|
||
?>
|
||
<pre>
|
||
Caused by:
|
||
<?= esc($prevException::class), esc($prevException->getCode() ? ' #' . $prevException->getCode() : '') ?>
|
||
<?= nl2br(esc($prevException->getMessage())) ?>
|
||
<a href="https:
|
||
rel="noreferrer" target="_blank">search →</a>
|
||
<?= esc(clean_path($prevException->getFile()) . ':' . $prevException->getLine()) ?>
|
||
</pre>
|
||
<?php
|
||
}
|
||
?>
|
||
</div>
|
||
<?php if (defined('SHOW_DEBUG_BACKTRACE') && SHOW_DEBUG_BACKTRACE) : ?>
|
||
<div class="container">
|
||
<ul class="tabs" id="tabs">
|
||
<li><a href="#backtrace">Backtrace</a></li>
|
||
<li><a href="#server">Server</a></li>
|
||
<li><a href="#request">Request</a></li>
|
||
<li><a href="#response">Response</a></li>
|
||
<li><a href="#files">Files</a></li>
|
||
<li><a href="#memory">Memory</a></li>
|
||
</ul>
|
||
<div class="tab-content">
|
||
<!-- Backtrace -->
|
||
<div class="content" id="backtrace">
|
||
<ol class="trace">
|
||
<?php foreach ($trace as $index => $row) : ?>
|
||
<li>
|
||
<p>
|
||
<!-- Trace info -->
|
||
<?php if (isset($row['file']) && is_file($row['file'])) : ?>
|
||
<?php
|
||
if (isset($row['function']) && in_array($row['function'], ['include', 'include_once', 'require', 'require_once'], true)) {
|
||
echo esc($row['function'] . ' ' . clean_path($row['file']));
|
||
} else {
|
||
echo esc(clean_path($row['file']) . ' : ' . $row['line']);
|
||
}
|
||
?>
|
||
<?php else: ?>
|
||
{PHP internal code}
|
||
<?php endif; ?>
|
||
<!-- Class/Method -->
|
||
<?php if (isset($row['class'])) : ?>
|
||
— <?= esc($row['class'] . $row['type'] . $row['function']) ?>
|
||
<?php if (! empty($row['args'])) : ?>
|
||
<?php $argsId = $errorId . 'args' . $index ?>
|
||
( <a href="#" onclick="return toggle('<?= esc($argsId, 'attr') ?>');">arguments</a> )
|
||
<div class="args" id="<?= esc($argsId, 'attr') ?>">
|
||
<table cellspacing="0">
|
||
<?php
|
||
$params = null;
|
||
if (! str_ends_with($row['function'], '}')) {
|
||
$mirror = isset($row['class']) ? new ReflectionMethod($row['class'], $row['function']) : new ReflectionFunction($row['function']);
|
||
$params = $mirror->getParameters();
|
||
}
|
||
foreach ($row['args'] as $key => $value) : ?>
|
||
<tr>
|
||
<td><code><?= esc(isset($params[$key]) ? '$' . $params[$key]->name : "#{$key}") ?></code></td>
|
||
<td><pre><?= esc(print_r($value, true)) ?></pre></td>
|
||
</tr>
|
||
<?php endforeach ?>
|
||
</table>
|
||
</div>
|
||
<?php else : ?>
|
||
()
|
||
<?php endif; ?>
|
||
<?php endif; ?>
|
||
<?php if (! isset($row['class']) && isset($row['function'])) : ?>
|
||
— <?= esc($row['function']) ?>()
|
||
<?php endif; ?>
|
||
</p>
|
||
<!-- Source? -->
|
||
<?php if (isset($row['file']) && is_file($row['file']) && isset($row['class'])) : ?>
|
||
<div class="source">
|
||
<?= static::highlightFile($row['file'], $row['line']) ?>
|
||
</div>
|
||
<?php endif; ?>
|
||
</li>
|
||
<?php endforeach; ?>
|
||
</ol>
|
||
</div>
|
||
<!-- Server -->
|
||
<div class="content" id="server">
|
||
<?php foreach (['_SERVER', '_SESSION'] as $var) : ?>
|
||
<?php
|
||
if (empty($GLOBALS[$var]) || ! is_array($GLOBALS[$var])) {
|
||
continue;
|
||
} ?>
|
||
<h3>$<?= esc($var) ?></h3>
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>Key</th>
|
||
<th>Value</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<?php foreach ($GLOBALS[$var] as $key => $value) : ?>
|
||
<tr>
|
||
<td><?= esc($key) ?></td>
|
||
<td>
|
||
<?php if (is_string($value)) : ?>
|
||
<?= esc($value) ?>
|
||
<?php else: ?>
|
||
<pre><?= esc(print_r($value, true)) ?></pre>
|
||
<?php endif; ?>
|
||
</td>
|
||
</tr>
|
||
<?php endforeach; ?>
|
||
</tbody>
|
||
</table>
|
||
<?php endforeach ?>
|
||
<!-- Constants -->
|
||
<?php $constants = get_defined_constants(true); ?>
|
||
<?php if (! empty($constants['user'])) : ?>
|
||
<h3>Constants</h3>
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>Key</th>
|
||
<th>Value</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<?php foreach ($constants['user'] as $key => $value) : ?>
|
||
<tr>
|
||
<td><?= esc($key) ?></td>
|
||
<td>
|
||
<?php if (is_string($value)) : ?>
|
||
<?= esc($value) ?>
|
||
<?php else: ?>
|
||
<pre><?= esc(print_r($value, true)) ?></pre>
|
||
<?php endif; ?>
|
||
</td>
|
||
</tr>
|
||
<?php endforeach; ?>
|
||
</tbody>
|
||
</table>
|
||
<?php endif; ?>
|
||
</div>
|
||
<!-- Request -->
|
||
<div class="content" id="request">
|
||
<?php $request = service('request'); ?>
|
||
<table>
|
||
<tbody>
|
||
<tr>
|
||
<td style="width: 10em">Path</td>
|
||
<td><?= esc($request->getUri()) ?></td>
|
||
</tr>
|
||
<tr>
|
||
<td>HTTP Method</td>
|
||
<td><?= esc($request->getMethod()) ?></td>
|
||
</tr>
|
||
<tr>
|
||
<td>IP Address</td>
|
||
<td><?= esc($request->getIPAddress()) ?></td>
|
||
</tr>
|
||
<tr>
|
||
<td style="width: 10em">Is AJAX Request?</td>
|
||
<td><?= $request->isAJAX() ? 'yes' : 'no' ?></td>
|
||
</tr>
|
||
<tr>
|
||
<td>Is CLI Request?</td>
|
||
<td><?= $request->isCLI() ? 'yes' : 'no' ?></td>
|
||
</tr>
|
||
<tr>
|
||
<td>Is Secure Request?</td>
|
||
<td><?= $request->isSecure() ? 'yes' : 'no' ?></td>
|
||
</tr>
|
||
<tr>
|
||
<td>User Agent</td>
|
||
<td><?= esc($request->getUserAgent()->getAgentString()) ?></td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
<?php $empty = true; ?>
|
||
<?php foreach (['_GET', '_POST', '_COOKIE'] as $var) : ?>
|
||
<?php
|
||
if (empty($GLOBALS[$var]) || ! is_array($GLOBALS[$var])) {
|
||
continue;
|
||
} ?>
|
||
<?php $empty = false; ?>
|
||
<h3>$<?= esc($var) ?></h3>
|
||
<table style="width: 100%">
|
||
<thead>
|
||
<tr>
|
||
<th>Key</th>
|
||
<th>Value</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<?php foreach ($GLOBALS[$var] as $key => $value) : ?>
|
||
<tr>
|
||
<td><?= esc($key) ?></td>
|
||
<td>
|
||
<?php if (is_string($value)) : ?>
|
||
<?= esc($value) ?>
|
||
<?php else: ?>
|
||
<pre><?= esc(print_r($value, true)) ?></pre>
|
||
<?php endif; ?>
|
||
</td>
|
||
</tr>
|
||
<?php endforeach; ?>
|
||
</tbody>
|
||
</table>
|
||
<?php endforeach ?>
|
||
<?php if ($empty) : ?>
|
||
<div class="alert">
|
||
No $_GET, $_POST, or $_COOKIE Information to show.
|
||
</div>
|
||
<?php endif; ?>
|
||
<?php $headers = $request->headers(); ?>
|
||
<?php if (! empty($headers)) : ?>
|
||
<h3>Headers</h3>
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>Header</th>
|
||
<th>Value</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<?php foreach ($headers as $name => $value) : ?>
|
||
<tr>
|
||
<td><?= esc($name, 'html') ?></td>
|
||
<td>
|
||
<?php
|
||
if ($value instanceof Header) {
|
||
echo esc($value->getValueLine(), 'html');
|
||
} else {
|
||
foreach ($value as $i => $header) {
|
||
echo ' ('. $i+1 . ') ' . esc($header->getValueLine(), 'html');
|
||
}
|
||
}
|
||
?>
|
||
</td>
|
||
</tr>
|
||
<?php endforeach; ?>
|
||
</tbody>
|
||
</table>
|
||
<?php endif; ?>
|
||
</div>
|
||
<!-- Response -->
|
||
<?php
|
||
$response = service('response');
|
||
$response->setStatusCode(http_response_code());
|
||
?>
|
||
<div class="content" id="response">
|
||
<table>
|
||
<tr>
|
||
<td style="width: 15em">Response Status</td>
|
||
<td><?= esc($response->getStatusCode() . ' - ' . $response->getReasonPhrase()) ?></td>
|
||
</tr>
|
||
</table>
|
||
<?php $headers = $response->headers(); ?>
|
||
<?php if (! empty($headers)) : ?>
|
||
<h3>Headers</h3>
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>Header</th>
|
||
<th>Value</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<?php foreach ($headers as $name => $value) : ?>
|
||
<tr>
|
||
<td><?= esc($name, 'html') ?></td>
|
||
<td>
|
||
<?php
|
||
if ($value instanceof Header) {
|
||
echo esc($response->getHeaderLine($name), 'html');
|
||
} else {
|
||
foreach ($value as $i => $header) {
|
||
echo ' ('. $i+1 . ') ' . esc($header->getValueLine(), 'html');
|
||
}
|
||
}
|
||
?>
|
||
</td>
|
||
</tr>
|
||
<?php endforeach; ?>
|
||
</tbody>
|
||
</table>
|
||
<?php endif; ?>
|
||
</div>
|
||
<!-- Files -->
|
||
<div class="content" id="files">
|
||
<?php $files = get_included_files(); ?>
|
||
<ol>
|
||
<?php foreach ($files as $file) :?>
|
||
<li><?= esc(clean_path($file)) ?></li>
|
||
<?php endforeach ?>
|
||
</ol>
|
||
</div>
|
||
<!-- Memory -->
|
||
<div class="content" id="memory">
|
||
<table>
|
||
<tbody>
|
||
<tr>
|
||
<td>Memory Usage</td>
|
||
<td><?= esc(static::describeMemory(memory_get_usage(true))) ?></td>
|
||
</tr>
|
||
<tr>
|
||
<td style="width: 12em">Peak Memory Usage:</td>
|
||
<td><?= esc(static::describeMemory(memory_get_peak_usage(true))) ?></td>
|
||
</tr>
|
||
<tr>
|
||
<td>Memory Limit:</td>
|
||
<td><?= esc(ini_get('memory_limit')) ?></td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div> <!-- /tab-content -->
|
||
</div> <!-- /container -->
|
||
<?php endif; ?>
|
||
</body>
|
||
</html>
|
||
|
||
// app/Views/emails/verification.twig
|
||
<!DOCTYPE html>
|
||
<html lang="ru">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Подтверждение регистрации</title>
|
||
<style>
|
||
body {
|
||
font-family: Arial, sans-serif;
|
||
line-height: 1.6;
|
||
color: #333;
|
||
margin: 0;
|
||
padding: 0;
|
||
background-color: #f5f5f5;
|
||
}
|
||
.container {
|
||
max-width: 600px;
|
||
margin: 0 auto;
|
||
padding: 20px;
|
||
}
|
||
.card {
|
||
background: white;
|
||
border-radius: 8px;
|
||
padding: 30px;
|
||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||
}
|
||
.logo {
|
||
text-align: center;
|
||
margin-bottom: 20px;
|
||
font-size: 24px;
|
||
font-weight: bold;
|
||
color: #0d6efd;
|
||
}
|
||
.btn {
|
||
display: inline-block;
|
||
padding: 12px 24px;
|
||
background-color: #0d6efd;
|
||
color: white;
|
||
text-decoration: none;
|
||
border-radius: 6px;
|
||
font-weight: bold;
|
||
text-align: center;
|
||
}
|
||
.btn:hover {
|
||
background-color: #0b5ed7;
|
||
}
|
||
.footer {
|
||
text-align: center;
|
||
margin-top: 20px;
|
||
font-size: 12px;
|
||
color: #666;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
<div class="card">
|
||
<div class="logo">{{ app_name }}</div>
|
||
|
||
<h2>Добрый день, {{ name }}!</h2>
|
||
|
||
<p>Спасибо за регистрацию в {{ app_name }}.</p>
|
||
|
||
<p>Для завершения регистрации и подтверждения вашего email адреса, пожалуйста, нажмите на кнопку ниже:</p>
|
||
|
||
<p style="text-align: center; margin: 30px 0;">
|
||
<a href="{{ verification_url }}" class="btn">Подтвердить email</a>
|
||
</p>
|
||
|
||
<p>Если кнопка не работает, вы можете скопировать ссылку и вставить её в адресную строку браузера:</p>
|
||
|
||
<p style="word-break: break-all; font-size: 12px; color: #666; background: #f8f9fa; padding: 10px; border-radius: 4px;">
|
||
{{ verification_url }}
|
||
</p>
|
||
|
||
<p>Ссылка действительна в течение 24 часов.</p>
|
||
|
||
<p>Если вы не регистрировались на {{ app_name }}, просто проигнорируйте это письмо.</p>
|
||
</div>
|
||
|
||
<div class="footer">
|
||
<p>© {{ "now"|date("Y") }} {{ app_name }}. Все права защищены.</p>
|
||
</div>
|
||
</div>
|
||
</body>
|
||
</html>
|
||
|
||
// app/Views/emails/welcome.twig
|
||
<!DOCTYPE html>
|
||
<html lang="ru">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Добро пожаловать</title>
|
||
<style>
|
||
body {
|
||
font-family: Arial, sans-serif;
|
||
line-height: 1.6;
|
||
color: #333;
|
||
margin: 0;
|
||
padding: 0;
|
||
background-color: #f5f5f5;
|
||
}
|
||
.container {
|
||
max-width: 600px;
|
||
margin: 0 auto;
|
||
padding: 20px;
|
||
}
|
||
.card {
|
||
background: white;
|
||
border-radius: 8px;
|
||
padding: 30px;
|
||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||
}
|
||
.logo {
|
||
text-align: center;
|
||
margin-bottom: 20px;
|
||
font-size: 24px;
|
||
font-weight: bold;
|
||
color: #198754;
|
||
}
|
||
.btn {
|
||
display: inline-block;
|
||
padding: 12px 24px;
|
||
background-color: #198754;
|
||
color: white;
|
||
text-decoration: none;
|
||
border-radius: 6px;
|
||
font-weight: bold;
|
||
text-align: center;
|
||
}
|
||
.btn:hover {
|
||
background-color: #157347;
|
||
}
|
||
.footer {
|
||
text-align: center;
|
||
margin-top: 20px;
|
||
font-size: 12px;
|
||
color: #666;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
<div class="card">
|
||
<div class="logo">{{ app_name }}</div>
|
||
|
||
<h2>Добро пожаловать, {{ name }}!</h2>
|
||
|
||
<p>Поздравляем! Ваш email успешно подтверждён.</p>
|
||
|
||
<p>Теперь вы можете:</p>
|
||
<ul>
|
||
<li>Создавать и управлять организациями</li>
|
||
<li>Приглашать сотрудников</li>
|
||
<li>Использовать все функции платформы</li>
|
||
</ul>
|
||
|
||
<p style="text-align: center; margin: 30px 0;">
|
||
<a href="{{ base_url('/') }}" class="btn">Перейти в личный кабинет</a>
|
||
</p>
|
||
|
||
<p>Если у вас возникнут вопросы, наша служба поддержки всегда готова помочь.</p>
|
||
|
||
<p>С уважением,<br>Команда {{ app_name }}</p>
|
||
</div>
|
||
|
||
<div class="footer">
|
||
<p>© {{ "now"|date("Y") }} {{ app_name }}. Все права защищены.</p>
|
||
</div>
|
||
</div>
|
||
</body>
|
||
</html>
|
||
|
||
// app/Views/organizations/delete.twig
|
||
{% extends 'layouts/base.twig' %}
|
||
|
||
{% block content %}
|
||
<div class="container" style="max-width: 500px; margin-top: 100px;">
|
||
<div class="card shadow border-danger">
|
||
<div class="card-header bg-danger text-white">
|
||
<h5 class="mb-0"><i class="fa-solid fa-triangle-exclamation me-2"></i>Подтверждение удаления</h5>
|
||
</div>
|
||
<div class="card-body text-center p-4">
|
||
<p class="lead">Вы уверены, что хотите удалить организацию?</p>
|
||
|
||
<div class="alert alert-warning">
|
||
<h5 class="alert-heading">{{ organization.name }}</h5>
|
||
{% if organization.inn %}
|
||
<p class="mb-0">ИНН: {{ organization.inn }}</p>
|
||
{% endif %}
|
||
{% if organization.type == 'personal' %}
|
||
<span class="badge bg-info">Личное пространство</span>
|
||
{% else %}
|
||
<span class="badge bg-secondary">Организация</span>
|
||
{% endif %}
|
||
</div>
|
||
|
||
<div class="alert alert-danger">
|
||
<strong>Внимание!</strong> Это действие нельзя отменить.<br>
|
||
Будут удалены:
|
||
<ul class="text-start mb-0 mt-2">
|
||
<li>Все данные организации</li>
|
||
<li>Все связи с пользователями</li>
|
||
<li>Все записи, связанные с организацией</li>
|
||
</ul>
|
||
</div>
|
||
|
||
{% from 'macros/forms.twig' import form_open, form_close %}
|
||
|
||
{{ form_open(base_url('/organizations/delete/' ~ organization.id)) }}
|
||
<div class="d-grid gap-2">
|
||
<button type="submit" class="btn btn-danger btn-lg">
|
||
<i class="fa-solid fa-trash me-2"></i>Да, удалить безвозвратно
|
||
</button>
|
||
<a href="{{ base_url('/organizations/edit/' ~ organization.id) }}" class="btn btn-outline-secondary">
|
||
Нет, отмена
|
||
</a>
|
||
</div>
|
||
{{ form_close() }}
|
||
|
||
</div>
|
||
</div>
|
||
</div>
|
||
{% endblock %}
|
||
|
||
// app/Views/organizations/edit.twig
|
||
{% extends 'layouts/base.twig' %}
|
||
|
||
{% block content %}
|
||
<div class="container" style="max-width: 800px;">
|
||
<div class="card shadow">
|
||
<div class="card-header bg-primary text-white d-flex justify-content-between align-items-center">
|
||
<h5 class="mb-0">Редактирование организации</h5>
|
||
<a href="{{ base_url('/organizations') }}" class="btn btn-light btn-sm">
|
||
<i class="fa-solid fa-arrow-left"></i> Назад
|
||
</a>
|
||
</div>
|
||
<div class="card-body p-4">
|
||
|
||
{% from 'macros/forms.twig' import form_open, form_close %}
|
||
|
||
{{ form_open(base_url('/organizations/edit/' ~ organization.id)) }}
|
||
|
||
{# Основная информация #}
|
||
<h6 class="text-muted mb-3">Основная информация</h6>
|
||
<div class="mb-3">
|
||
<label for="name" class="form-label">Название организации <span class="text-danger">*</span></label>
|
||
<input type="text" name="name" class="form-control {{ errors.name is defined ? 'is-invalid' : '' }}"
|
||
id="name" required value="{{ old.name|default(organization.name) }}"
|
||
placeholder="Например, ООО Ромашка">
|
||
{% if errors.name is defined %}
|
||
<div class="invalid-feedback">{{ errors.name }}</div>
|
||
{% endif %}
|
||
</div>
|
||
|
||
<hr class="my-4">
|
||
|
||
{# Регистрационные данные #}
|
||
<h6 class="text-muted mb-3">Регистрационные данные</h6>
|
||
<div class="row">
|
||
<div class="col-md-4 mb-3">
|
||
<label for="inn" class="form-label">ИНН</label>
|
||
<input type="text" name="inn" class="form-control" id="inn"
|
||
value="{{ old.inn|default(requisites.inn) }}"
|
||
placeholder="10 или 12 цифр" maxlength="12">
|
||
</div>
|
||
<div class="col-md-4 mb-3">
|
||
<label for="ogrn" class="form-label">ОГРН</label>
|
||
<input type="text" name="ogrn" class="form-control" id="ogrn"
|
||
value="{{ old.ogrn|default(requisites.ogrn) }}"
|
||
placeholder="13 цифр" maxlength="13">
|
||
</div>
|
||
<div class="col-md-4 mb-3">
|
||
<label for="kpp" class="form-label">КПП</label>
|
||
<input type="text" name="kpp" class="form-control" id="kpp"
|
||
value="{{ old.kpp|default(requisites.kpp) }}"
|
||
placeholder="9 цифр" maxlength="9">
|
||
</div>
|
||
</div>
|
||
|
||
<hr class="my-4">
|
||
|
||
{# Адреса #}
|
||
<h6 class="text-muted mb-3">Адреса</h6>
|
||
<div class="mb-3">
|
||
<label for="legal_address" class="form-label">Юридический адрес</label>
|
||
<input type="text" name="legal_address" class="form-control" id="legal_address"
|
||
value="{{ old.legal_address|default(requisites.legal_address) }}"
|
||
placeholder="г. Москва, ул. Примерная, д. 1">
|
||
</div>
|
||
<div class="mb-3">
|
||
<label for="actual_address" class="form-label">Фактический адрес</label>
|
||
<input type="text" name="actual_address" class="form-control" id="actual_address"
|
||
value="{{ old.actual_address|default(requisites.actual_address) }}"
|
||
placeholder="г. Москва, ул. Примерная, д. 1">
|
||
<div class="form-text">Если совпадает с юридическим — оставьте пустым</div>
|
||
</div>
|
||
|
||
<hr class="my-4">
|
||
|
||
{# Контакты #}
|
||
<h6 class="text-muted mb-3">Контакты</h6>
|
||
<div class="row">
|
||
<div class="col-md-4 mb-3">
|
||
<label for="phone" class="form-label">Телефон</label>
|
||
<input type="tel" name="phone" class="form-control" id="phone"
|
||
value="{{ old.phone|default(requisites.phone) }}"
|
||
placeholder="+7 (999) 123-45-67">
|
||
</div>
|
||
<div class="col-md-4 mb-3">
|
||
<label for="email" class="form-label">Email</label>
|
||
<input type="email" name="email" class="form-control" id="email"
|
||
value="{{ old.email|default(requisites.email) }}"
|
||
placeholder="info@company.ru">
|
||
</div>
|
||
<div class="col-md-4 mb-3">
|
||
<label for="website" class="form-label">Веб-сайт</label>
|
||
<input type="url" name="website" class="form-control" id="website"
|
||
value="{{ old.website|default(requisites.website) }}"
|
||
placeholder="https://company.ru">
|
||
</div>
|
||
</div>
|
||
|
||
<hr class="my-4">
|
||
|
||
{# Банковские реквизиты #}
|
||
<h6 class="text-muted mb-3">Банковские реквизиты</h6>
|
||
<div class="mb-3">
|
||
<label for="bank_name" class="form-label">Название банка</label>
|
||
<input type="text" name="bank_name" class="form-control" id="bank_name"
|
||
value="{{ old.bank_name|default(requisites.bank_name) }}"
|
||
placeholder="ПАО Сбербанк г. Москва">
|
||
</div>
|
||
<div class="row">
|
||
<div class="col-md-4 mb-3">
|
||
<label for="bank_bik" class="form-label">БИК</label>
|
||
<input type="text" name="bank_bik" class="form-control" id="bank_bik"
|
||
value="{{ old.bank_bik|default(requisites.bank_bik) }}"
|
||
placeholder="9 цифр" maxlength="9">
|
||
</div>
|
||
<div class="col-md-4 mb-3">
|
||
<label for="checking_account" class="form-label">Расчётный счёт</label>
|
||
<input type="text" name="checking_account" class="form-control" id="checking_account"
|
||
value="{{ old.checking_account|default(requisites.checking_account) }}"
|
||
placeholder="20 цифр" maxlength="20">
|
||
</div>
|
||
<div class="col-md-4 mb-3">
|
||
<label for="correspondent_account" class="form-label">Корреспондентский счёт</label>
|
||
<input type="text" name="correspondent_account" class="form-control" id="correspondent_account"
|
||
value="{{ old.correspondent_account|default(requisites.correspondent_account) }}"
|
||
placeholder="20 цифр" maxlength="20">
|
||
</div>
|
||
</div>
|
||
|
||
<div class="d-grid gap-2 d-md-flex mt-4">
|
||
<button type="submit" class="btn btn-success btn-lg">
|
||
<i class="fa-solid fa-check me-2"></i>Сохранить изменения
|
||
</button>
|
||
<a href="{{ base_url('/organizations') }}" class="btn btn-outline-secondary btn-lg">
|
||
Отмена
|
||
</a>
|
||
<a href="{{ base_url('/organizations/delete/' ~ organization.id) }}"
|
||
class="btn btn-outline-danger btn-lg ms-md-auto">
|
||
<i class="fa-solid fa-trash me-2"></i>Удалить организацию
|
||
</a>
|
||
</div>
|
||
|
||
{{ form_close() }}
|
||
|
||
</div>
|
||
</div>
|
||
</div>
|
||
{% endblock %}
|
||
|
||
// app/Views/organizations/create.twig
|
||
{% extends 'layouts/base.twig' %}
|
||
|
||
{% block content %}
|
||
<div class="container" style="max-width: 800px;">
|
||
<div class="card shadow">
|
||
<div class="card-header bg-primary text-white d-flex justify-content-between align-items-center">
|
||
<h5 class="mb-0">Создание организации</h5>
|
||
<a href="{{ base_url('/organizations') }}" class="btn btn-light btn-sm">
|
||
<i class="fa-solid fa-arrow-left"></i> Назад
|
||
</a>
|
||
</div>
|
||
<div class="card-body p-4">
|
||
|
||
{% from 'macros/forms.twig' import form_open, form_close %}
|
||
|
||
{{ form_open(base_url('/organizations/create')) }}
|
||
|
||
{# Основная информация #}
|
||
<h6 class="text-muted mb-3">Основная информация</h6>
|
||
<div class="mb-3">
|
||
<label for="name" class="form-label">Название организации <span class="text-danger">*</span></label>
|
||
<input type="text" name="name" class="form-control {{ errors.name is defined ? 'is-invalid' : '' }}"
|
||
id="name" required value="{{ old.name|default('') }}"
|
||
placeholder="Например, ООО Ромашка">
|
||
{% if errors.name is defined %}
|
||
<div class="invalid-feedback">{{ errors.name }}</div>
|
||
{% endif %}
|
||
</div>
|
||
|
||
<hr class="my-4">
|
||
|
||
{# Регистрационные данные #}
|
||
<h6 class="text-muted mb-3">Регистрационные данные</h6>
|
||
<div class="row">
|
||
<div class="col-md-4 mb-3">
|
||
<label for="inn" class="form-label">ИНН</label>
|
||
<input type="text" name="inn" class="form-control" id="inn"
|
||
value="{{ old.inn|default('') }}"
|
||
placeholder="10 или 12 цифр" maxlength="12">
|
||
</div>
|
||
<div class="col-md-4 mb-3">
|
||
<label for="ogrn" class="form-label">ОГРН</label>
|
||
<input type="text" name="ogrn" class="form-control" id="ogrn"
|
||
value="{{ old.ogrn|default('') }}"
|
||
placeholder="13 цифр" maxlength="13">
|
||
</div>
|
||
<div class="col-md-4 mb-3">
|
||
<label for="kpp" class="form-label">КПП</label>
|
||
<input type="text" name="kpp" class="form-control" id="kpp"
|
||
value="{{ old.kpp|default('') }}"
|
||
placeholder="9 цифр" maxlength="9">
|
||
</div>
|
||
</div>
|
||
|
||
<hr class="my-4">
|
||
|
||
{# Адреса #}
|
||
<h6 class="text-muted mb-3">Адреса</h6>
|
||
<div class="mb-3">
|
||
<label for="legal_address" class="form-label">Юридический адрес</label>
|
||
<input type="text" name="legal_address" class="form-control" id="legal_address"
|
||
value="{{ old.legal_address|default('') }}"
|
||
placeholder="г. Москва, ул. Примерная, д. 1">
|
||
</div>
|
||
<div class="mb-3">
|
||
<label for="actual_address" class="form-label">Фактический адрес</label>
|
||
<input type="text" name="actual_address" class="form-control" id="actual_address"
|
||
value="{{ old.actual_address|default('') }}"
|
||
placeholder="г. Москва, ул. Примерная, д. 1">
|
||
<div class="form-text">Если совпадает с юридическим — оставьте пустым</div>
|
||
</div>
|
||
|
||
<hr class="my-4">
|
||
|
||
{# Контакты #}
|
||
<h6 class="text-muted mb-3">Контакты</h6>
|
||
<div class="row">
|
||
<div class="col-md-4 mb-3">
|
||
<label for="phone" class="form-label">Телефон</label>
|
||
<input type="tel" name="phone" class="form-control" id="phone"
|
||
value="{{ old.phone|default('') }}"
|
||
placeholder="+7 (999) 123-45-67">
|
||
</div>
|
||
<div class="col-md-4 mb-3">
|
||
<label for="email" class="form-label">Email</label>
|
||
<input type="email" name="email" class="form-control" id="email"
|
||
value="{{ old.email|default('') }}"
|
||
placeholder="info@company.ru">
|
||
</div>
|
||
<div class="col-md-4 mb-3">
|
||
<label for="website" class="form-label">Веб-сайт</label>
|
||
<input type="url" name="website" class="form-control" id="website"
|
||
value="{{ old.website|default('') }}"
|
||
placeholder="https://company.ru">
|
||
</div>
|
||
</div>
|
||
|
||
<hr class="my-4">
|
||
|
||
{# Банковские реквизиты #}
|
||
<h6 class="text-muted mb-3">Банковские реквизиты</h6>
|
||
<div class="mb-3">
|
||
<label for="bank_name" class="form-label">Название банка</label>
|
||
<input type="text" name="bank_name" class="form-control" id="bank_name"
|
||
value="{{ old.bank_name|default('') }}"
|
||
placeholder="ПАО Сбербанк г. Москва">
|
||
</div>
|
||
<div class="row">
|
||
<div class="col-md-4 mb-3">
|
||
<label for="bank_bik" class="form-label">БИК</label>
|
||
<input type="text" name="bank_bik" class="form-control" id="bank_bik"
|
||
value="{{ old.bank_bik|default('') }}"
|
||
placeholder="9 цифр" maxlength="9">
|
||
</div>
|
||
<div class="col-md-4 mb-3">
|
||
<label for="checking_account" class="form-label">Расчётный счёт</label>
|
||
<input type="text" name="checking_account" class="form-control" id="checking_account"
|
||
value="{{ old.checking_account|default('') }}"
|
||
placeholder="20 цифр" maxlength="20">
|
||
</div>
|
||
<div class="col-md-4 mb-3">
|
||
<label for="correspondent_account" class="form-label">Корреспондентский счёт</label>
|
||
<input type="text" name="correspondent_account" class="form-control" id="correspondent_account"
|
||
value="{{ old.correspondent_account|default('') }}"
|
||
placeholder="20 цифр" maxlength="20">
|
||
</div>
|
||
</div>
|
||
|
||
<div class="d-grid gap-2 d-md-flex mt-4">
|
||
<button type="submit" class="btn btn-primary btn-lg">
|
||
<i class="fa-solid fa-check me-2"></i>Создать организацию
|
||
</button>
|
||
<a href="{{ base_url('/organizations') }}" class="btn btn-outline-secondary btn-lg">
|
||
Отмена
|
||
</a>
|
||
</div>
|
||
|
||
{{ form_close() }}
|
||
|
||
</div>
|
||
</div>
|
||
</div>
|
||
{% endblock %}
|
||
|
||
// app/Views/organizations/index.twig
|
||
{% extends 'layouts/base.twig' %}
|
||
|
||
{% block content %}
|
||
<div class="container" style="max-width: 900px;">
|
||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||
<h4 class="mb-0">Мои организации</h4>
|
||
<a href="{{ base_url('/organizations/create') }}" class="btn btn-primary">
|
||
<i class="fa-solid fa-plus me-2"></i>Создать организацию
|
||
</a>
|
||
</div>
|
||
|
||
{% if organizations is empty %}
|
||
<div class="card shadow-sm">
|
||
<div class="card-body text-center py-5">
|
||
<i class="fa-solid fa-building display-1 text-muted mb-3"></i>
|
||
<h5 class="text-muted">Организаций пока нет</h5>
|
||
<p class="text-muted">Создайте свою первую организацию для начала работы</p>
|
||
<a href="{{ base_url('/organizations/create') }}" class="btn btn-primary">
|
||
<i class="fa-solid fa-plus me-2"></i>Создать организацию
|
||
</a>
|
||
</div>
|
||
</div>
|
||
{% else %}
|
||
<div class="row">
|
||
{% for org in organizations %}
|
||
{# Декодируем JSON реквизиты #}
|
||
{% set req = org.requisites ? org.requisites|json_decode : {} %}
|
||
<div class="col-md-6 mb-3">
|
||
<div class="card shadow-sm h-100 {{ org.id == session.get('active_org_id') ? 'border-primary' : '' }}">
|
||
<div class="card-body">
|
||
<div class="d-flex justify-content-between align-items-start mb-3">
|
||
<div>
|
||
<h5 class="card-title mb-1">
|
||
{% if org.type == 'personal' %}
|
||
<i class="fa-solid fa-user text-info me-2"></i>
|
||
{% else %}
|
||
<i class="fa-solid fa-building text-primary me-2"></i>
|
||
{% endif %}
|
||
{{ org.name }}
|
||
</h5>
|
||
<small class="text-muted">
|
||
{% if org.type == 'personal' %}
|
||
Личное пространство
|
||
{% else %}
|
||
{% if req.inn %}ИНН: {{ req.inn }}{% endif %}
|
||
{% endif %}
|
||
</small>
|
||
</div>
|
||
{% if org.id == session.get('active_org_id') %}
|
||
<span class="badge bg-primary">Активна</span>
|
||
{% endif %}
|
||
</div>
|
||
|
||
{# Краткая информация #}
|
||
{% if req.phone or req.email %}
|
||
<div class="mb-3">
|
||
{% if req.phone %}
|
||
<small class="d-block text-muted">
|
||
<i class="fa-solid fa-phone me-1"></i>{{ req.phone }}
|
||
</small>
|
||
{% endif %}
|
||
{% if req.email %}
|
||
<small class="d-block text-muted">
|
||
<i class="fa-solid fa-envelope me-1"></i>{{ req.email }}
|
||
</small>
|
||
{% endif %}
|
||
</div>
|
||
{% endif %}
|
||
|
||
<div class="d-flex flex-wrap gap-2">
|
||
{# Кнопка выбора/переключения #}
|
||
{% if org.id != session.get('active_org_id') %}
|
||
<a href="{{ base_url('/organizations/switch/' ~ org.id) }}"
|
||
class="btn btn-outline-primary btn-sm">
|
||
<i class="fa-solid fa-check me-1"></i>Выбрать
|
||
</a>
|
||
{% endif %}
|
||
|
||
{# Кнопка редактирования #}
|
||
<a href="{{ base_url('/organizations/edit/' ~ org.id) }}"
|
||
class="btn btn-outline-secondary btn-sm">
|
||
<i class="fa-solid fa-pen me-1"></i>Ред.
|
||
</a>
|
||
|
||
{# Кнопка удаления (только для бизнес-организаций) #}
|
||
{% if org.type == 'business' %}
|
||
<a href="{{ base_url('/organizations/delete/' ~ org.id) }}"
|
||
class="btn btn-outline-danger btn-sm">
|
||
<i class="fa-solid fa-trash me-1"></i>Удалить
|
||
</a>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
<div class="card-footer bg-transparent text-muted small">
|
||
Создана: {{ org.created_at|date('d.m.Y') }}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
{% endif %}
|
||
|
||
{# Статистика #}
|
||
<div class="mt-4 text-center text-muted small">
|
||
Всего организаций: {{ count }}
|
||
</div>
|
||
</div>
|
||
{% endblock %}
|
||
|
||
// app/Views/dashboard/index.twig
|
||
{# app/Views/dashboard/index.twig #}
|
||
{% extends 'layouts/base.twig' %}
|
||
|
||
{% block content %}
|
||
<div class="container-fluid">
|
||
|
||
<div class="row mb-4">
|
||
<div class="col-12">
|
||
<h2>{% if current_org %}Добро пожаловать в {{ current_org.name }}!{% else %}Добро пожаловать!{% endif %}</h2>
|
||
<p class="text-muted">Ваш личный кабинет для управления бизнесом.</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="row">
|
||
<!-- Карточки модулей (плейсхолдеры) -->
|
||
<div class="col-md-6 col-lg-3 mb-4">
|
||
<div class="card h-100 text-center p-3">
|
||
<div class="card-body">
|
||
<i class="fa-solid fa-users fa-3x text-primary mb-3"></i>
|
||
<h5 class="card-title">CRM</h5>
|
||
<p class="card-text text-muted">Управление клиентами</p>
|
||
<a href="#" class="btn btn-outline-primary btn-sm">Скоро</a>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="col-md-6 col-lg-3 mb-4">
|
||
<div class="card h-100 text-center p-3">
|
||
<div class="card-body">
|
||
<i class="fa-solid fa-calendar-check fa-3x text-success mb-3"></i>
|
||
<h5 class="card-title">Booking</h5>
|
||
<p class="card-text text-muted">Запись на приём</p>
|
||
<a href="#" class="btn btn-outline-success btn-sm">Скоро</a>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="col-md-6 col-lg-3 mb-4">
|
||
<div class="card h-100 text-center p-3">
|
||
<div class="card-body">
|
||
<i class="fa-solid fa-file-signature fa-3x text-warning mb-3"></i>
|
||
<h5 class="card-title">Proof</h5>
|
||
<p class="card-text text-muted">Согласование файлов</p>
|
||
<a href="#" class="btn btn-outline-warning btn-sm">Скоро</a>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="col-md-6 col-lg-3 mb-4">
|
||
<div class="card h-100 text-center p-3">
|
||
<div class="card-body">
|
||
<i class="fa-solid fa-list-check fa-3x text-danger mb-3"></i>
|
||
<h5 class="card-title">Tasks</h5>
|
||
<p class="card-text text-muted">Задачи и проекты</p>
|
||
<a href="#" class="btn btn-outline-danger btn-sm">Скоро</a>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
{% endblock %}
|
||
// app/Views/welcome_message.php
|
||
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<title>Welcome to CodeIgniter 4!</title>
|
||
<meta name="description" content="The small framework with powerful features">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<link rel="shortcut icon" type="image/png" href="/favicon.ico">
|
||
<!-- STYLES -->
|
||
<style {csp-style-nonce}>
|
||
transition: background-color 300ms ease, color 300ms ease;
|
||
}
|
||
background-color: rgba(221, 72, 20, .2);
|
||
outline: none;
|
||
}
|
||
html, body {
|
||
color: rgba(33, 37, 41, 1);
|
||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
|
||
font-size: 16px;
|
||
margin: 0;
|
||
padding: 0;
|
||
-webkit-font-smoothing: antialiased;
|
||
-moz-osx-font-smoothing: grayscale;
|
||
text-rendering: optimizeLegibility;
|
||
}
|
||
header {
|
||
background-color: rgba(247, 248, 249, 1);
|
||
padding: .4rem 0 0;
|
||
}
|
||
.menu {
|
||
padding: .4rem 2rem;
|
||
}
|
||
header ul {
|
||
border-bottom: 1px solid rgba(242, 242, 242, 1);
|
||
list-style-type: none;
|
||
margin: 0;
|
||
overflow: hidden;
|
||
padding: 0;
|
||
text-align: right;
|
||
}
|
||
header li {
|
||
display: inline-block;
|
||
}
|
||
header li a {
|
||
border-radius: 5px;
|
||
color: rgba(0, 0, 0, .5);
|
||
display: block;
|
||
height: 44px;
|
||
text-decoration: none;
|
||
}
|
||
header li.menu-item a {
|
||
border-radius: 5px;
|
||
margin: 5px 0;
|
||
height: 38px;
|
||
line-height: 36px;
|
||
padding: .4rem .65rem;
|
||
text-align: center;
|
||
}
|
||
header li.menu-item a:hover,
|
||
header li.menu-item a:focus {
|
||
background-color: rgba(221, 72, 20, .2);
|
||
color: rgba(221, 72, 20, 1);
|
||
}
|
||
header .logo {
|
||
float: left;
|
||
height: 44px;
|
||
padding: .4rem .5rem;
|
||
}
|
||
header .menu-toggle {
|
||
display: none;
|
||
float: right;
|
||
font-size: 2rem;
|
||
font-weight: bold;
|
||
}
|
||
header .menu-toggle button {
|
||
background-color: rgba(221, 72, 20, .6);
|
||
border: none;
|
||
border-radius: 3px;
|
||
color: rgba(255, 255, 255, 1);
|
||
cursor: pointer;
|
||
font: inherit;
|
||
font-size: 1.3rem;
|
||
height: 36px;
|
||
padding: 0;
|
||
margin: 11px 0;
|
||
overflow: visible;
|
||
width: 40px;
|
||
}
|
||
header .menu-toggle button:hover,
|
||
header .menu-toggle button:focus {
|
||
background-color: rgba(221, 72, 20, .8);
|
||
color: rgba(255, 255, 255, .8);
|
||
}
|
||
header .heroe {
|
||
margin: 0 auto;
|
||
max-width: 1100px;
|
||
padding: 1rem 1.75rem 1.75rem 1.75rem;
|
||
}
|
||
header .heroe h1 {
|
||
font-size: 2.5rem;
|
||
font-weight: 500;
|
||
}
|
||
header .heroe h2 {
|
||
font-size: 1.5rem;
|
||
font-weight: 300;
|
||
}
|
||
section {
|
||
margin: 0 auto;
|
||
max-width: 1100px;
|
||
padding: 2.5rem 1.75rem 3.5rem 1.75rem;
|
||
}
|
||
section h1 {
|
||
margin-bottom: 2.5rem;
|
||
}
|
||
section h2 {
|
||
font-size: 120%;
|
||
line-height: 2.5rem;
|
||
padding-top: 1.5rem;
|
||
}
|
||
section pre {
|
||
background-color: rgba(247, 248, 249, 1);
|
||
border: 1px solid rgba(242, 242, 242, 1);
|
||
display: block;
|
||
font-size: .9rem;
|
||
margin: 2rem 0;
|
||
padding: 1rem 1.5rem;
|
||
white-space: pre-wrap;
|
||
word-break: break-all;
|
||
}
|
||
section code {
|
||
display: block;
|
||
}
|
||
section a {
|
||
color: rgba(221, 72, 20, 1);
|
||
}
|
||
section svg {
|
||
margin-bottom: -5px;
|
||
margin-right: 5px;
|
||
width: 25px;
|
||
}
|
||
.further {
|
||
background-color: rgba(247, 248, 249, 1);
|
||
border-bottom: 1px solid rgba(242, 242, 242, 1);
|
||
border-top: 1px solid rgba(242, 242, 242, 1);
|
||
}
|
||
.further h2:first-of-type {
|
||
padding-top: 0;
|
||
}
|
||
.svg-stroke {
|
||
fill: none;
|
||
stroke: #000;
|
||
stroke-width: 32px;
|
||
}
|
||
footer {
|
||
background-color: rgba(221, 72, 20, .8);
|
||
text-align: center;
|
||
}
|
||
footer .environment {
|
||
color: rgba(255, 255, 255, 1);
|
||
padding: 2rem 1.75rem;
|
||
}
|
||
footer .copyrights {
|
||
background-color: rgba(62, 62, 62, 1);
|
||
color: rgba(200, 200, 200, 1);
|
||
padding: .25rem 1.75rem;
|
||
}
|
||
@media (max-width: 629px) {
|
||
header ul {
|
||
padding: 0;
|
||
}
|
||
header .menu-toggle {
|
||
padding: 0 1rem;
|
||
}
|
||
header .menu-item {
|
||
background-color: rgba(244, 245, 246, 1);
|
||
border-top: 1px solid rgba(242, 242, 242, 1);
|
||
margin: 0 15px;
|
||
width: calc(100% - 30px);
|
||
}
|
||
header .menu-toggle {
|
||
display: block;
|
||
}
|
||
header .hidden {
|
||
display: none;
|
||
}
|
||
header li.menu-item a {
|
||
background-color: rgba(221, 72, 20, .1);
|
||
}
|
||
header li.menu-item a:hover,
|
||
header li.menu-item a:focus {
|
||
background-color: rgba(221, 72, 20, .7);
|
||
color: rgba(255, 255, 255, .8);
|
||
}
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<!-- HEADER: MENU + HEROE SECTION -->
|
||
<header>
|
||
<div class="menu">
|
||
<ul>
|
||
<li class="logo">
|
||
<a href="https:
|
||
<svg role="img" aria-label="Visit CodeIgniter.com official website!" xmlns="http:
|
||
</a>
|
||
</li>
|
||
<li class="menu-toggle">
|
||
<button id="menuToggle">☰</button>
|
||
</li>
|
||
<li class="menu-item hidden"><a href="#">Home</a></li>
|
||
<li class="menu-item hidden"><a href="https:
|
||
</li>
|
||
<li class="menu-item hidden"><a href="https:
|
||
<li class="menu-item hidden"><a
|
||
href="https:
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
<div class="heroe">
|
||
<h1>Welcome to CodeIgniter <?= CodeIgniter\CodeIgniter::CI_VERSION ?></h1>
|
||
<h2>The small framework with powerful features</h2>
|
||
</div>
|
||
</header>
|
||
<!-- CONTENT -->
|
||
<section>
|
||
<h1>About this page</h1>
|
||
<p>The page you are looking at is being generated dynamically by CodeIgniter.</p>
|
||
<p>If you would like to edit this page you will find it located at:</p>
|
||
<pre><code>app/Views/welcome_message.php</code></pre>
|
||
<p>The corresponding controller for this page can be found at:</p>
|
||
<pre><code>app/Controllers/Home.php</code></pre>
|
||
</section>
|
||
<div class="further">
|
||
<section>
|
||
<h1>Go further</h1>
|
||
<h2>
|
||
<svg xmlns='http:
|
||
Learn
|
||
</h2>
|
||
<p>The User Guide contains an introduction, tutorial, a number of "how to"
|
||
guides, and then reference documentation for the components that make up
|
||
the framework. Check the <a href="https:
|
||
target="_blank">User Guide</a> !</p>
|
||
<h2>
|
||
<svg xmlns='http:
|
||
Discuss
|
||
</h2>
|
||
<p>CodeIgniter is a community-developed open source project, with several
|
||
venues for the community members to gather and exchange ideas. View all
|
||
the threads on <a href="https:
|
||
target="_blank">CodeIgniter's forum</a>, or <a href="https:
|
||
target="_blank">chat on Slack</a> !</p>
|
||
<h2>
|
||
<svg xmlns='http:
|
||
Contribute
|
||
</h2>
|
||
<p>CodeIgniter is a community driven project and accepts contributions
|
||
of code and documentation from the community. Why not
|
||
<a href="https:
|
||
join us</a> ?</p>
|
||
</section>
|
||
</div>
|
||
<!-- FOOTER: DEBUG INFO + COPYRIGHTS -->
|
||
<footer>
|
||
<div class="environment">
|
||
<p>Page rendered in {elapsed_time} seconds using {memory_usage} MB of memory.</p>
|
||
<p>Environment: <?= ENVIRONMENT ?></p>
|
||
</div>
|
||
<div class="copyrights">
|
||
<p>© <?= date('Y') ?> CodeIgniter Foundation. CodeIgniter is open source project released under the MIT
|
||
open source licence.</p>
|
||
</div>
|
||
</footer>
|
||
<!-- SCRIPTS -->
|
||
<script {csp-script-nonce}>
|
||
document.getElementById("menuToggle").addEventListener('click', toggleMenu);
|
||
function toggleMenu() {
|
||
var menuItems = document.getElementsByClassName('menu-item');
|
||
for (var i = 0; i < menuItems.length; i++) {
|
||
var menuItem = menuItems[i];
|
||
menuItem.classList.toggle("hidden");
|
||
}
|
||
}
|
||
</script>
|
||
<!-- -->
|
||
</body>
|
||
</html>
|
||
|
||
// app/Views/landing/index.twig
|
||
{# app/Views/Landing/index.twig #}
|
||
<!DOCTYPE html>
|
||
<html lang="ru">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<title>Бизнес.Точка - Автоматизация для бизнеса</title>
|
||
<link href="{{ base_url('assets/css/bootstrap.min.css') }}" rel="stylesheet">
|
||
<link href="{{ base_url('assets/css/all.min.css') }}" rel="stylesheet">
|
||
</head>
|
||
<body class="bg-light">
|
||
|
||
<div class="container">
|
||
<nav class="d-flex justify-content-between align-items-center py-3">
|
||
<h3 class="text-primary fw-bold m-0"><i class="fa-solid fa-circle-nodes"></i> Бизнес.Точка</h3>
|
||
<div>
|
||
<a href="{{ base_url('/login') }}" class="btn btn-outline-primary me-2">Войти</a>
|
||
<a href="{{ base_url('/register') }}" class="btn btn-primary">Попробовать бесплатно</a>
|
||
</div>
|
||
</nav>
|
||
|
||
<div class="text-center py-5 my-5">
|
||
<h1 class="display-4 fw-bold mb-3">Всё для вашего бизнеса</h1>
|
||
<p class="lead text-muted mb-4">CRM, Запись клиентов, Задачи и Согласование файлов в одном месте.</p>
|
||
<div class="d-flex justify-content-center gap-3">
|
||
<a href="{{ base_url('/register') }}" class="btn btn-primary btn-lg px-5 shadow">Начать бесплатно</a>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
</body>
|
||
</html>
|
||
// app/Views/auth/register_success.twig
|
||
{% extends 'layouts/public.twig' %}
|
||
|
||
{% block content %}
|
||
<div class="container d-flex justify-content-center align-items-center" style="min-height: 80vh;">
|
||
<div class="card shadow-lg border-0" style="max-width: 500px; width: 100%;">
|
||
<div class="card-body p-5 text-center">
|
||
<div class="mb-4">
|
||
<i class="fa-solid fa-envelope-circle-check display-1 text-primary"></i>
|
||
</div>
|
||
|
||
<h3 class="mb-3">Регистрация успешна!</h3>
|
||
|
||
<p class="text-muted mb-4">
|
||
Мы отправили письмо с ссылкой для подтверждения email на вашу почту.
|
||
</p>
|
||
|
||
<div class="alert alert-info text-start">
|
||
<i class="fa-solid fa-info-circle me-2"></i>
|
||
<strong>Что делать дальше:</strong>
|
||
<ol class="mb-0 mt-2">
|
||
<li>Откройте почтовый ящик</li>
|
||
<li>Найдите письмо от нас</li>
|
||
<li>Нажмите на ссылку для подтверждения</li>
|
||
</ol>
|
||
</div>
|
||
|
||
<div class="mb-4">
|
||
<a href="/auth/resend-verification" class="text-muted small">
|
||
<i class="fa-solid fa-rotate-right me-1"></i>Не получили письмо? Отправить снова
|
||
</a>
|
||
</div>
|
||
|
||
<hr class="my-4">
|
||
|
||
<a href="/" class="btn btn-outline-secondary">
|
||
<i class="fa-solid fa-home me-2"></i>На главную
|
||
</a>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
{% endblock %}
|
||
|
||
// app/Views/auth/resend_verification.twig
|
||
{% extends 'layouts/public.twig' %}
|
||
|
||
{% block content %}
|
||
<div class="container d-flex justify-content-center align-items-center" style="min-height: 80vh;">
|
||
<div class="card shadow-lg border-0" style="max-width: 500px; width: 100%;">
|
||
<div class="card-body p-5">
|
||
<div class="text-center mb-4">
|
||
<i class="fa-solid fa-envelope-open-text display-4 text-primary"></i>
|
||
<h4 class="mt-3">Повторная отправка письма</h4>
|
||
<p class="text-muted">Введите ваш email для получения новой ссылки подтверждения</p>
|
||
</div>
|
||
|
||
{% from 'macros/forms.twig' import form_open, form_close %}
|
||
|
||
{{ form_open(base_url('/auth/resend-verification')) }}
|
||
|
||
<div class="mb-3">
|
||
<label for="email" class="form-label">Email</label>
|
||
<input type="email" name="email" class="form-control {{ errors.email is defined ? 'is-invalid' : '' }}"
|
||
id="email" required value="{{ old.email|default('') }}"
|
||
placeholder="name@example.com">
|
||
{% if errors.email is defined %}
|
||
<div class="invalid-feedback">{{ errors.email }}</div>
|
||
{% endif %}
|
||
</div>
|
||
|
||
<div class="d-grid gap-2">
|
||
<button type="submit" class="btn btn-primary">
|
||
<i class="fa-solid fa-paper-plane me-2"></i>Отправить письмо
|
||
</button>
|
||
<a href="/" class="btn btn-outline-secondary">
|
||
На главную
|
||
</a>
|
||
</div>
|
||
|
||
{{ form_close() }}
|
||
|
||
</div>
|
||
</div>
|
||
</div>
|
||
{% endblock %}
|
||
|
||
// app/Views/auth/register.twig
|
||
{% extends 'layouts/public.twig' %}
|
||
|
||
{% block content %}
|
||
|
||
<div class="card shadow-sm" style="width: 100%; max-width: 400px;">
|
||
<div class="card-body p-4">
|
||
<h3 class="text-center mb-4">Регистрация</h3>
|
||
|
||
{# ИСПОЛЬЗУЕМ МАКРОС form_open. CSRF добавится АВТОМАТИЧЕСКИ #}
|
||
{{ form_open(base_url('/register'), 'class="needs-validation"') }}
|
||
|
||
<div class="mb-3">
|
||
<label for="name" class="form-label">Ваше имя</label>
|
||
<input type="text" name="name" class="form-control" id="name" required value="{{ old.name|default('') }}">
|
||
</div>
|
||
<div class="mb-3">
|
||
<label for="email" class="form-label">Email</label>
|
||
<input type="email" name="email" class="form-control" id="email" required value="{{ old.email|default('') }}">
|
||
</div>
|
||
<div class="mb-3">
|
||
<label for="password" class="form-label">Пароль</label>
|
||
<input type="password" name="password" class="form-control" id="password" required minlength="6">
|
||
</div>
|
||
<button type="submit" class="btn btn-primary w-100">Создать аккаунт</button>
|
||
|
||
{# ЗАКРЫВАЕМ ФОРМУ МАКРОСОМ #}
|
||
{{ form_close() }}
|
||
|
||
<div class="mt-3 text-center">
|
||
<small>Уже есть аккаунт? <a href="{{ base_url('/login') }}">Войти</a></small>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{% endblock %}
|
||
// app/Views/auth/verify_error.twig
|
||
{% extends 'layouts/public.twig' %}
|
||
|
||
{% block content %}
|
||
<div class="container d-flex justify-content-center align-items-center" style="min-height: 80vh;">
|
||
<div class="card shadow-lg border-0" style="max-width: 500px; width: 100%;">
|
||
<div class="card-body p-5 text-center">
|
||
<div class="mb-4">
|
||
<i class="fa-solid fa-triangle-exclamation display-1 text-warning"></i>
|
||
</div>
|
||
|
||
<h3 class="mb-3">Ошибка подтверждения</h3>
|
||
|
||
<div class="alert alert-warning text-start">
|
||
{{ message|default('Недействительная ссылка для подтверждения.') }}
|
||
</div>
|
||
|
||
<div class="mb-4">
|
||
<a href="/auth/resend-verification" class="btn btn-outline-primary">
|
||
<i class="fa-solid fa-envelope me-2"></i>Запросить ссылку повторно
|
||
</a>
|
||
</div>
|
||
|
||
<hr class="my-4">
|
||
|
||
<div class="d-flex justify-content-center gap-3">
|
||
<a href="/" class="btn btn-outline-secondary">
|
||
<i class="fa-solid fa-home me-2"></i>На главную
|
||
</a>
|
||
<a href="/login" class="btn btn-outline-secondary">
|
||
<i class="fa-solid fa-sign-in-alt me-2"></i>Войти
|
||
</a>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
{% endblock %}
|
||
|
||
// app/Views/auth/verify_success.twig
|
||
{% extends 'layouts/public.twig' %}
|
||
|
||
{% block content %}
|
||
<div class="container d-flex justify-content-center align-items-center" style="min-height: 80vh;">
|
||
<div class="card shadow-lg border-0" style="max-width: 500px; width: 100%;">
|
||
<div class="card-body p-5 text-center">
|
||
<div class="mb-4">
|
||
<i class="fa-solid fa-circle-check display-1 text-success"></i>
|
||
</div>
|
||
|
||
<h3 class="mb-3">Email подтверждён!</h3>
|
||
|
||
<p class="text-muted mb-4">
|
||
{% if name %}
|
||
{{ name }}, спасибо за подтверждение.
|
||
{% else %}
|
||
Спасибо за подтверждение.
|
||
{% endif %}
|
||
</p>
|
||
|
||
<p class="text-muted mb-4">
|
||
Теперь вы можете войти в систему и начать работу.
|
||
</p>
|
||
|
||
<div class="d-grid gap-2">
|
||
<a href="/login" class="btn btn-primary btn-lg">
|
||
<i class="fa-solid fa-sign-in-alt me-2"></i>Войти
|
||
</a>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
{% endblock %}
|
||
|
||
// app/Views/auth/login.twig
|
||
{% extends 'layouts/public.twig' %}
|
||
|
||
{% block content %}
|
||
<div class="card shadow-sm" style="width: 100%; max-width: 400px;">
|
||
<div class="card-body p-4">
|
||
<div class="text-center mb-4">
|
||
<i class="fa-solid fa-circle-nodes fa-3x text-primary mb-2"></i>
|
||
<h3>Бизнес.Точка</h3>
|
||
<p class="text-muted">Вход в систему</p>
|
||
</div>
|
||
|
||
{{ form_open(base_url('/login'), 'class="needs-validation"') }}
|
||
|
||
<div class="mb-3">
|
||
<label for="email" class="form-label">Email</label>
|
||
<input type="email" name="email" value="{{ old.email|default('') }}" class="form-control" id="email" required autofocus>
|
||
</div>
|
||
<div class="mb-3">
|
||
<label for="password" class="form-label">Пароль</label>
|
||
<input type="password" name="password" value="{{ old.password|default('') }}" class="form-control" id="password" required>
|
||
</div>
|
||
<button type="submit" class="btn btn-primary w-100">Войти</button>
|
||
|
||
{{ form_close() }}
|
||
|
||
<div class="mt-3 text-center">
|
||
<small>Нет аккаунта? <a href="{{ base_url('/register') }}">Зарегистрироваться</a></small>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{% endblock %}
|
||
// app/Views/components/alerts.twig
|
||
{# app/Views/components/alerts.twig #}
|
||
|
||
{% set alerts = get_alerts() %}
|
||
|
||
{% if alerts is not empty %}
|
||
<div class="toast-container position-fixed top-0 end-0 p-3" style="z-index: 1050">
|
||
{% for alert in alerts %}
|
||
{# Преобразуем наш тип 'error' в класс Bootstrap 'danger' #}
|
||
{% set bs_type = alert.type == 'error' ? 'danger' : alert.type %}
|
||
|
||
<div class="toast align-items-center text-white bg-{{ bs_type }} border-0" role="alert" aria-live="assertive" aria-atomic="true">
|
||
<div class="d-flex">
|
||
<div class="toast-body">
|
||
{{ alert.message }}
|
||
</div>
|
||
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
|
||
</div>
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
|
||
<script>
|
||
// Автоматически показываем все toasts при загрузке страницы
|
||
document.addEventListener("DOMContentLoaded", function() {
|
||
var toastElList = [].slice.call(document.querySelectorAll('.toast'));
|
||
toastElList.forEach(function(toastEl) {
|
||
var toast = new bootstrap.Toast(toastEl);
|
||
toast.show();
|
||
});
|
||
});
|
||
</script>
|
||
{% endif %}
|
||
// app/Views/components/table/pagination.twig
|
||
{#
|
||
pagination.twig - Универсальный компонент пагинации
|
||
Использует встроенный пейджер CodeIgniter 4
|
||
|
||
Параметры:
|
||
- pagination: Объект с данными пагинации (из pager->getDetails())
|
||
- currentPage: Текущая страница
|
||
- pageCount: Всего страниц
|
||
- total: Всего записей
|
||
- perPage: Записей на странице
|
||
- from: Начальная запись
|
||
- to: Конечная запись
|
||
- id: ID таблицы для уникальности элементов
|
||
#}
|
||
{% set currentPage = pagination.currentPage|default(1) %}
|
||
{% set totalPages = pagination.pageCount|default(1) %}
|
||
{% set totalRecords = pagination.total|default(0) %}
|
||
{% set perPage = pagination.perPage|default(10) %}
|
||
{% set from = pagination.from|default((currentPage - 1) * perPage + 1) %}
|
||
{% set to = pagination.to|default(min(currentPage * perPage, totalRecords)) %}
|
||
|
||
{# Информация о записях #}
|
||
{% set infoText = 'Показано ' ~ from ~ '–' ~ to ~ ' из ' ~ totalRecords %}
|
||
|
||
<div class="pagination-wrapper">
|
||
{# Информация о количестве записей #}
|
||
<div class="pagination-info">
|
||
<span>{{ infoText }}</span>
|
||
</div>
|
||
|
||
{# Кнопки навигации - посередине #}
|
||
<nav aria-label="Пагинация">
|
||
<ul class="pagination pagination-sm mb-0">
|
||
{# Предыдущая страница #}
|
||
<li class="page-item {{ currentPage <= 1 ? 'disabled' }}">
|
||
<a class="page-link" href="#" data-nav-page="prev" aria-label="Предыдущая">
|
||
<i class="fa-solid fa-chevron-left"></i>
|
||
</a>
|
||
</li>
|
||
|
||
{# Номера страниц #}
|
||
{% set startPage = max(1, currentPage - 2) %}
|
||
{% set endPage = min(totalPages, currentPage + 2) %}
|
||
|
||
{# Всегда показываем первую страницу если есть #}
|
||
{% if startPage > 1 %}
|
||
<li class="page-item">
|
||
<a class="page-link" href="#" data-page="1">1</a>
|
||
</li>
|
||
{% if startPage > 2 %}
|
||
<li class="page-item disabled">
|
||
<span class="page-link">...</span>
|
||
</li>
|
||
{% endif %}
|
||
{% endif %}
|
||
|
||
{# Страницы вокруг текущей #}
|
||
{% for page in startPage..endPage %}
|
||
<li class="page-item {{ page == currentPage ? 'active' }}">
|
||
<a class="page-link" href="#" data-page="{{ page }}">{{ page }}</a>
|
||
</li>
|
||
{% endfor %}
|
||
|
||
{# Всегда показываем последнюю страницу если есть #}
|
||
{% if endPage < totalPages %}
|
||
{% if endPage < totalPages - 1 %}
|
||
<li class="page-item disabled">
|
||
<span class="page-link">...</span>
|
||
</li>
|
||
{% endif %}
|
||
<li class="page-item">
|
||
<a class="page-link" href="#" data-page="{{ totalPages }}">{{ totalPages }}</a>
|
||
</li>
|
||
{% endif %}
|
||
|
||
{# Следующая страница #}
|
||
<li class="page-item {{ currentPage >= totalPages ? 'disabled' }}">
|
||
<a class="page-link" href="#" data-nav-page="next" aria-label="Следующая">
|
||
<i class="fa-solid fa-chevron-right"></i>
|
||
</a>
|
||
</li>
|
||
</ul>
|
||
</nav>
|
||
|
||
{# Выбор количества записей - справа #}
|
||
<div class="per-page-selector">
|
||
<label for="per-page-select-{{ id|default('table') }}">Записей на странице:</label>
|
||
<select id="per-page-select-{{ id|default('table') }}" class="form-select-sm per-page-select">
|
||
<option value="10" {{ perPage == 10 ? 'selected' }}>10</option>
|
||
<option value="25" {{ perPage == 25 ? 'selected' }}>25</option>
|
||
<option value="50" {{ perPage == 50 ? 'selected' }}>50</option>
|
||
<option value="100" {{ perPage == 100 ? 'selected' }}>100</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
|
||
// app/Views/components/table/table_header.twig
|
||
{#
|
||
table_header.twig - Переиспользуемый шаблон заголовка таблицы
|
||
|
||
Параметры:
|
||
- columns: Ассоциативный массив столбцов ['name' => ['label' => 'Name', 'width' => '40%']]
|
||
- sort: Текущий столбец сортировки
|
||
- order: Направление сортировки (asc/desc)
|
||
- filters: Текущие значения фильтров
|
||
#}
|
||
<thead class="table-light">
|
||
<tr>
|
||
{% for columnKey, column in columns %}
|
||
<th style="width: {{ column.width|default('auto') }};"
|
||
class="{{ column.align|default('') }}"
|
||
data-sort-column="{{ columnKey }}">
|
||
<div class="header-content">
|
||
{# Иконка поиска #}
|
||
<i class="fa-solid fa-search search-trigger text-muted"
|
||
data-search-trigger="{{ columnKey }}"
|
||
title="{{ column.searchTitle|default('Поиск по ' ~ column.label|lower) }}"></i>
|
||
|
||
{# Текст заголовка #}
|
||
<span class="header-text" data-header-text="{{ columnKey }}">
|
||
{{ column.label }}
|
||
</span>
|
||
|
||
{# Иконка сортировки #}
|
||
<i class="fa-solid fa-sort sort-icon {{ sort == columnKey ? 'active' : 'text-muted' }}"
|
||
data-sort="{{ columnKey }}"
|
||
title="{{ sort == columnKey ? (order == 'asc' ? 'Сортировка по возрастанию (нажмите для сортировки по убыванию)' : 'Сортировка по убыванию (нажмите для сортировки по возрастанию)') : 'Сортировка' }}"></i>
|
||
|
||
{# Поле поиска #}
|
||
<input type="text"
|
||
class="form-control form-control-sm header-search-input"
|
||
data-search-input="{{ columnKey }}"
|
||
value="{{ filters[columnKey]|default('') }}"
|
||
placeholder="{{ column.placeholder|default('Поиск...') }}"
|
||
style="display: none;">
|
||
</div>
|
||
</th>
|
||
{% endfor %}
|
||
|
||
{# Колонка действий (опционально) #}
|
||
{% if actions is defined and actions %}
|
||
<th class="text-end {{ actions.class|default('') }}" style="width: {{ actions.width|default('auto') }};">
|
||
{{ actions.label|default('Действия') }}
|
||
</th>
|
||
{% endif %}
|
||
</tr>
|
||
</thead>
|
||
|
||
// app/Views/components/table/README.md
|
||
# DataTable Components
|
||
|
||
Переиспользуемые компоненты для отображения интерактивных таблиц с AJAX-загрузкой, сортировкой и поиском.
|
||
|
||
## Структура компонентов
|
||
|
||
```
|
||
public/
|
||
├── js/
|
||
│ └── modules/
|
||
│ └── DataTable.js # JS-модуль для инициализации таблиц
|
||
└── css/
|
||
└── components/
|
||
└── data-table.css # Стили для интерактивных таблиц
|
||
|
||
app/Views/components/table/
|
||
├── table.twig # Основной компонент таблицы
|
||
├── table_header.twig # Переиспользуемый заголовок
|
||
└── pagination.twig # Компонент пагинации
|
||
```
|
||
|
||
## Быстрый старт
|
||
|
||
### 1. Подключение стилей и скриптов
|
||
|
||
В вашем шаблоне добавьте:
|
||
|
||
```twig
|
||
{% block stylesheets %}
|
||
{{ parent() }}
|
||
<link rel="stylesheet" href="/css/components/data-table.css">
|
||
{% endblock %}
|
||
|
||
{% block scripts %}
|
||
{{ parent() }}
|
||
<script src="/js/modules/DataTable.js"></script>
|
||
<script>
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
new DataTable('your-table-id', {
|
||
url: '/your-module/table',
|
||
perPage: 10
|
||
});
|
||
});
|
||
</script>
|
||
{% endblock %}
|
||
```
|
||
|
||
### 2. Использование компонента таблицы
|
||
|
||
```twig
|
||
{{ include('@components/table/table.twig', {
|
||
id: 'products-table',
|
||
url: '/products/table',
|
||
perPage: 25,
|
||
sort: sort|default(''),
|
||
order: order|default('asc'),
|
||
filters: filters|default({}),
|
||
columns: [
|
||
{ name: 'name', label: 'Название', width: '35%' },
|
||
{ name: 'sku', label: 'Артикул', width: '15%' },
|
||
{ name: 'price', label: 'Цена', width: '15%' },
|
||
{ name: 'stock', label: 'Остаток', width: '15%' }
|
||
],
|
||
actions: { label: 'Действия', width: '20%' },
|
||
emptyMessage: 'Товары не найдены'
|
||
}) }}
|
||
```
|
||
|
||
## Конфигурация столбцов
|
||
|
||
Каждый столбец поддерживает следующие параметры:
|
||
|
||
| Параметр | Тип | Описание |
|
||
|----------|-----|----------|
|
||
| `name` | string | Идентификатор столбца (используется для сортировки и фильтрации) |
|
||
| `label` | string | Отображаемое название столбца |
|
||
| `width` | string | Ширина столбца (например, '35%', '200px') |
|
||
| `placeholder` | string | Текст-подсказка в поле поиска |
|
||
| `searchTitle` | string | Title для иконки поиска |
|
||
| `align` | string | CSS-класс выравнивания |
|
||
|
||
## Конфигурация пагинации
|
||
|
||
Компонент автоматически получает данные из объекта `pager`:
|
||
|
||
```php
|
||
// В контроллере
|
||
$pagination = [
|
||
'currentPage' => $pager->getCurrentPage(),
|
||
'totalPages' => $pager->getPageCount(),
|
||
'totalRecords' => $pager->getTotal(),
|
||
'perPage' => $perPage,
|
||
'from' => (($pager->getCurrentPage() - 1) * $perPage + 1),
|
||
'to' => min($pager->getCurrentPage() * $perPage, $pager->getTotal())
|
||
];
|
||
```
|
||
|
||
## Пример контроллера
|
||
|
||
```php
|
||
public function table()
|
||
{
|
||
$page = (int) ($this->request->getGet('page') ?? 1);
|
||
$perPage = (int) ($this->request->getGet('perPage') ?? 10);
|
||
$sort = $this->request->getGet('sort') ?? '';
|
||
$order = $this->request->getGet('order') ?? 'asc';
|
||
|
||
// Фильтры
|
||
$filters = [
|
||
'name' => $this->request->getGet('filters[name]') ?? '',
|
||
];
|
||
|
||
// Построение запроса
|
||
$builder = $this->model->builder();
|
||
|
||
// Применяем фильтры
|
||
if (!empty($filters['name'])) {
|
||
$builder->like('name', $filters['name']);
|
||
}
|
||
|
||
// Сортировка
|
||
if (!empty($sort)) {
|
||
$builder->orderBy($sort, $order);
|
||
}
|
||
|
||
// Пагинация
|
||
$items = $builder->paginate($perPage, 'default', $page);
|
||
|
||
$data = [
|
||
'items' => $items,
|
||
'pager' => $this->model->pager,
|
||
'perPage' => $perPage,
|
||
'sort' => $sort,
|
||
'order' => $order,
|
||
'filters' => $filters,
|
||
];
|
||
|
||
return $this->renderTwig('path/to/your/_table', $data);
|
||
}
|
||
```
|
||
|
||
## AJAX-ответ
|
||
|
||
Для AJAX-запросов контроллер должен возвращать только `tbody` и `tfoot`:
|
||
|
||
```twig
|
||
{# _table.twig для модуля #}
|
||
{% set isAjax = app.request.headers.get('X-Requested-With') == 'XMLHttpRequest' %}
|
||
|
||
{% if isAjax %}
|
||
{# AJAX: только tbody #}
|
||
<tbody>
|
||
{% for item in items %}
|
||
<tr>
|
||
<td>{{ item.name }}</td>
|
||
<td>{{ item.price }}</td>
|
||
<td class="text-end">
|
||
<a href="...">Редактировать</a>
|
||
</td>
|
||
</tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
|
||
{% if items is not empty and pager %}
|
||
<tfoot>
|
||
<tr>
|
||
<td colspan="3">
|
||
{{ include('@components/table/pagination.twig', {
|
||
pagination: paginationData,
|
||
id: 'your-table-id'
|
||
}) }}
|
||
</td>
|
||
</tr>
|
||
</tfoot>
|
||
{% endif %}
|
||
{% else %}
|
||
{# Обычный запрос: полная таблица #}
|
||
<div class="table-responsive">
|
||
{{ include('@components/table/table.twig', {
|
||
id: 'your-table-id',
|
||
url: '/your-module/table',
|
||
perPage: perPage,
|
||
columns: columns,
|
||
pagination: paginationData,
|
||
actions: { label: 'Действия' },
|
||
emptyMessage: 'Нет данных'
|
||
}) }}
|
||
</div>
|
||
{% endif %}
|
||
```
|
||
|
||
## API DataTable
|
||
|
||
### Опции при инициализации
|
||
|
||
```javascript
|
||
new DataTable('container-id', {
|
||
url: '/api/endpoint', // URL для AJAX-загрузки
|
||
perPage: 10, // Записей на странице по умолчанию
|
||
debounceTime: 300, // Задержка поиска в мс
|
||
preserveSearchOnSort: true // Сохранять видимость поиска при сортировке
|
||
});
|
||
```
|
||
|
||
### Методы
|
||
|
||
```javascript
|
||
const table = new DataTable('my-table', options);
|
||
|
||
// Установка фильтра
|
||
table.setFilter('columnName', 'value');
|
||
|
||
// Установка количества записей
|
||
table.setPerPage(25);
|
||
|
||
// Переход на страницу
|
||
table.goToPage(3);
|
||
```
|
||
|
||
## Доступные CSS-классы
|
||
|
||
| Класс | Описание |
|
||
|-------|----------|
|
||
| `.data-table` | Основной контейнер таблицы |
|
||
| `.header-content` | Контейнер для элементов заголовка |
|
||
| `.header-text` | Текст заголовка столбца |
|
||
| `.search-trigger` | Иконка поиска |
|
||
| `.sort-icon` | Иконка сортировки |
|
||
| `.header-search-input` | Поле ввода поиска |
|
||
| `.sort-icon.active` | Активная сортировка |
|
||
| `.pagination-wrapper` | Обёртка пагинации |
|
||
|
||
## Расширение функциональности
|
||
|
||
### Добавление кастомных действий
|
||
|
||
Для добавления кнопок действий в строки:
|
||
|
||
```twig
|
||
{% for client in clients %}
|
||
<tr>
|
||
<td>{{ client.name }}</td>
|
||
<td>{{ client.email }}</td>
|
||
<td class="actions-cell text-end">
|
||
<a href="{{ editUrl }}" class="btn btn-sm btn-outline-primary">
|
||
<i class="fa-solid fa-pen"></i>
|
||
</a>
|
||
</td>
|
||
</tr>
|
||
{% endfor %}
|
||
```
|
||
|
||
### Кастомные строки
|
||
|
||
Компонент поддерживает произвольное содержимое ячеек через параметр `rows`:
|
||
|
||
```twig
|
||
{% set rows = [] %}
|
||
{% for product in products %}
|
||
{% set rows = rows|merge([{
|
||
cells: [
|
||
{ content: '<strong>' ~ product.name ~ '</strong>', class: '' },
|
||
{ content: product.price ~ ' ₽', class: 'text-end' }
|
||
],
|
||
actions: '<a href="...">Редактировать</a>'
|
||
}]) %}
|
||
{% endfor %}
|
||
|
||
{{ include('@components/table/table.twig', {
|
||
id: 'products-table',
|
||
rows: rows,
|
||
columns: columns,
|
||
...
|
||
}) }}
|
||
```
|
||
|
||
// app/Views/components/table/table.twig
|
||
{#
|
||
table.twig - Универсальный компонент таблицы с AJAX-загрузкой
|
||
|
||
Параметры:
|
||
- id: ID контейнера таблицы (обязательно)
|
||
- url: URL для AJAX-загрузки данных (обязательно)
|
||
- perPage: Количество записей на странице (по умолчанию 10)
|
||
- columns: Ассоциативный массив ['name' => ['label' => 'Name', 'width' => '40%']]
|
||
- sort: Текущий столбец сортировки
|
||
- order: Направление сортировки
|
||
- filters: Текущие значения фильтров
|
||
- items: Массив объектов модели (автоматический рендеринг)
|
||
- rows: Предварительно построенные строки (устаревший формат, для совместимости)
|
||
- pagination: Данные пагинации
|
||
- actions: Настройки колонки действий
|
||
- emptyMessage: Сообщение при отсутствии данных
|
||
- tableClass: Дополнительные классы для таблицы
|
||
- itemsKey: Ключ массива items в данных (по умолчанию 'items')
|
||
#}
|
||
<div id="{{ id }}" class="data-table" data-url="{{ url }}" data-per-page="{{ perPage|default(10) }}">
|
||
<table class="table table-hover mb-0 {{ tableClass|default('') }}">
|
||
{# Заголовок таблицы #}
|
||
{{ include('@components/table/table_header.twig', {
|
||
columns: columns,
|
||
sort: sort|default(''),
|
||
order: order|default('asc'),
|
||
filters: filters|default({}),
|
||
actions: actions|default(false)
|
||
}) }}
|
||
|
||
{# Тело таблицы #}
|
||
<tbody>
|
||
{% if rows is defined and rows|length > 0 %}
|
||
{# Старый формат: предварительно построенные строки #}
|
||
{% for row in rows %}
|
||
<tr>
|
||
{% for cell in row.cells %}
|
||
<td class="{{ cell.class|default('') }}">{{ cell.content|raw }}</td>
|
||
{% endfor %}
|
||
{% if row.actions is defined %}
|
||
<td class="actions-cell text-end">
|
||
{{ row.actions|raw }}
|
||
</td>
|
||
{% endif %}
|
||
</tr>
|
||
{% endfor %}
|
||
{% elseif items is defined and items|length > 0 %}
|
||
{# Новый формат: автоматический рендеринг из объектов модели #}
|
||
{% set columnKeys = columns|keys %}
|
||
{% for item in items %}
|
||
<tr>
|
||
{# Ячейки данных #}
|
||
{% for columnKey in columnKeys %}
|
||
<td>{{ attribute(item, columnKey)|default('—') }}</td>
|
||
{% endfor %}
|
||
|
||
{# Колонка действий #}
|
||
{% if actions is defined and actions %}
|
||
<td class="actions-cell text-end">
|
||
{#Placeholder для действий - переопределяется в дочерних шаблонах#}
|
||
{% if item.actions is defined %}{{ item.actions|raw }}{% endif %}
|
||
</td>
|
||
{% endif %}
|
||
</tr>
|
||
{% endfor %}
|
||
{% else %}
|
||
{# Пустое состояние #}
|
||
<tr>
|
||
<td colspan="{{ columns|length + (actions is defined and actions ? 1 : 0) }}"
|
||
class="text-center py-4 text-muted">
|
||
{{ emptyMessage|default('Нет данных для отображения') }}
|
||
</td>
|
||
</tr>
|
||
{% endif %}
|
||
</tbody>
|
||
|
||
{# Футер с пагинацией - показываем всегда #}
|
||
<tfoot>
|
||
<tr>
|
||
<td colspan="{{ columns|length + 1 }}">
|
||
{{ include('@components/table/pagination.twig', {
|
||
pagination: pagerDetails|default({
|
||
currentPage: 1,
|
||
pageCount: 1,
|
||
total: items|length|default(0),
|
||
perPage: perPage|default(10),
|
||
from: 1,
|
||
to: items|length|default(0)
|
||
}),
|
||
id: id
|
||
}) }}
|
||
</td>
|
||
</tr>
|
||
</tfoot>
|
||
</table>
|
||
</div>
|
||
|
||
// app/Views/macros/forms.twig
|
||
{# app/Views/macros/forms.twig #}
|
||
{% macro form_open(action, attributes = '') %}
|
||
<form action="{{ action }}" method="post" {{ attributes|raw }}>
|
||
{# Выводим глобальную переменную csrf_token #}
|
||
{{ csrf_field()|raw }}
|
||
{% endmacro %}
|
||
|
||
{% macro form_close() %}
|
||
</form>
|
||
{% endmacro %}
|
||
// app/.htaccess
|
||
<IfModule authz_core_module>
|
||
Require all denied
|
||
</IfModule>
|
||
<IfModule !authz_core_module>
|
||
Deny from all
|
||
</IfModule>
|
||
|
||
// app/Filters/OrganizationFilter.php
|
||
<?php
|
||
namespace App\Filters;
|
||
use CodeIgniter\Filters\FilterInterface;
|
||
use CodeIgniter\HTTP\RequestInterface;
|
||
use CodeIgniter\HTTP\ResponseInterface;
|
||
class OrganizationFilter implements FilterInterface
|
||
{
|
||
public function before(RequestInterface $request, $arguments = null)
|
||
{
|
||
$session = session();
|
||
$uri = $request->getUri();
|
||
$currentPath = $uri->getPath();
|
||
if (!$session->get('isLoggedIn')) {
|
||
$publicRoutes = [
|
||
'/',
|
||
'/login',
|
||
'/register',
|
||
'/register/success',
|
||
'/auth/verify',
|
||
'/auth/resend-verification',
|
||
];
|
||
if (!in_array($currentPath, $publicRoutes)) {
|
||
return redirect()->to('/');
|
||
}
|
||
return;
|
||
}
|
||
$publicAuthRoutes = [
|
||
'/login',
|
||
'/register',
|
||
'/logout',
|
||
'/register/success',
|
||
'/auth/verify',
|
||
'/auth/resend-verification',
|
||
'/organizations',
|
||
'/organizations/create',
|
||
'/organizations/switch'
|
||
];
|
||
if (in_array($currentPath, $publicAuthRoutes) || strpos($currentPath, '/organizations/switch') === 0) {
|
||
return;
|
||
}
|
||
if ($currentPath === '/') {
|
||
return;
|
||
}
|
||
if (empty(session()->get('active_org_id'))) {
|
||
return redirect()->to('/organizations');
|
||
}
|
||
}
|
||
public function after(RequestInterface $request, ResponseInterface $response, $arguments = null)
|
||
{
|
||
}
|
||
}
|
||
// app/Filters/.gitkeep
|
||
|
||
// app/Models/UserModel.php
|
||
<?php
|
||
namespace App\Models;
|
||
use CodeIgniter\Model;
|
||
class UserModel extends Model
|
||
{
|
||
protected $table = 'users';
|
||
protected $primaryKey = 'id';
|
||
protected $useAutoIncrement = true;
|
||
protected $returnType = 'array';
|
||
protected $useSoftDeletes = false;
|
||
protected $protectFields = true;
|
||
protected $allowedFields = [
|
||
'email',
|
||
'password',
|
||
'name',
|
||
'phone',
|
||
'avatar',
|
||
'verification_token',
|
||
'email_verified',
|
||
'verified_at'
|
||
];
|
||
protected $useTimestamps = true;
|
||
protected $dateFormat = 'datetime';
|
||
protected $createdField = 'created_at';
|
||
protected $updatedField = 'updated_at';
|
||
protected $beforeInsert = ['hashPassword'];
|
||
protected $beforeUpdate = ['hashPassword'];
|
||
protected function hashPassword(array $data)
|
||
{
|
||
if (isset($data['data']['password'])) {
|
||
$data['data']['password'] = password_hash($data['data']['password'], PASSWORD_DEFAULT);
|
||
}
|
||
return $data;
|
||
}
|
||
}
|
||
// app/Models/OrganizationModel.php
|
||
<?php
|
||
namespace App\Models;
|
||
use CodeIgniter\Model;
|
||
class OrganizationModel extends Model
|
||
{
|
||
protected $table = 'organizations';
|
||
protected $primaryKey = 'id';
|
||
protected $useAutoIncrement = true;
|
||
protected $returnType = 'array';
|
||
protected $useSoftDeletes = true;
|
||
protected $allowedFields = ['owner_id', 'name', 'type', 'logo', 'requisites', 'trial_ends_at', 'settings'];
|
||
protected $useTimestamps = true;
|
||
protected $dateFormat = 'datetime';
|
||
protected $createdField = 'created_at';
|
||
protected $updatedField = 'updated_at';
|
||
protected $deletedField = 'deleted_at';
|
||
public function getUserOrganizations(int $userId)
|
||
{
|
||
return $this->where('owner_id', $userId)->findAll();
|
||
}
|
||
}
|
||
// app/Models/.gitkeep
|
||
|
||
// app/Models/OrganizationUserModel.php
|
||
<?php
|
||
namespace App\Models;
|
||
use CodeIgniter\Model;
|
||
class OrganizationUserModel extends Model
|
||
{
|
||
protected $table = 'organization_users';
|
||
protected $primaryKey = 'id';
|
||
protected $useAutoIncrement = true;
|
||
protected $returnType = 'array';
|
||
protected $allowedFields = ['organization_id', 'user_id', 'role', 'status', 'joined_at'];
|
||
protected $useTimestamps = false;
|
||
}
|
||
// app/Database/Seeds/.gitkeep
|
||
|
||
// app/Database/Migrations/2026-01-07-053357_CreateUsersTable.php
|
||
<?php
|
||
namespace App\Database\Migrations;
|
||
use CodeIgniter\Database\Migration;
|
||
class CreateUsersTable extends Migration
|
||
{
|
||
public function up()
|
||
{
|
||
$this->forge->addField([
|
||
'id' => [
|
||
'type' => 'INT',
|
||
'constraint' => 11,
|
||
'unsigned' => true,
|
||
'auto_increment' => true,
|
||
],
|
||
'email' => [
|
||
'type' => 'VARCHAR',
|
||
'constraint' => 255,
|
||
'unique' => true,
|
||
],
|
||
'password' => [
|
||
'type' => 'VARCHAR',
|
||
'constraint' => 255,
|
||
],
|
||
'name' => [
|
||
'type' => 'VARCHAR',
|
||
'constraint' => 100,
|
||
],
|
||
'phone' => [
|
||
'type' => 'VARCHAR',
|
||
'constraint' => 20,
|
||
'null' => true,
|
||
],
|
||
'avatar' => [
|
||
'type' => 'VARCHAR',
|
||
'constraint' => 255,
|
||
'null' => true,
|
||
],
|
||
'created_at' => [
|
||
'type' => 'DATETIME',
|
||
'null' => true,
|
||
],
|
||
'updated_at' => [
|
||
'type' => 'DATETIME',
|
||
'null' => true,
|
||
],
|
||
]);
|
||
$this->forge->addKey('id', true);
|
||
$this->forge->createTable('users');
|
||
}
|
||
public function down()
|
||
{
|
||
$this->forge->dropTable('users');
|
||
}
|
||
}
|
||
// app/Database/Migrations/2026-01-08-000001_AddEmailVerificationToUsers.php
|
||
<?php
|
||
namespace App\Database\Migrations;
|
||
use CodeIgniter\Database\Migration;
|
||
class AddEmailVerificationToUsers extends Migration
|
||
{
|
||
public function up()
|
||
{
|
||
$fields = [
|
||
'verification_token' => [
|
||
'type' => 'VARCHAR',
|
||
'constraint' => 255,
|
||
'null' => true,
|
||
'comment' => 'Токен для подтверждения email',
|
||
],
|
||
'email_verified' => [
|
||
'type' => 'TINYINT',
|
||
'constraint' => 1,
|
||
'default' => 0,
|
||
'comment' => 'Статус подтверждения email (0 - не подтвержден, 1 - подтвержден)',
|
||
],
|
||
'verified_at' => [
|
||
'type' => 'DATETIME',
|
||
'null' => true,
|
||
'comment' => 'Дата и время подтверждения email',
|
||
],
|
||
];
|
||
$this->forge->addColumn('users', $fields);
|
||
}
|
||
public function down()
|
||
{
|
||
$this->forge->dropColumn('users', ['verification_token', 'email_verified', 'verified_at']);
|
||
}
|
||
}
|
||
|
||
// app/Database/Migrations/2026-01-08-200001_CreateOrganizationsClientsTable.php
|
||
<?php
|
||
namespace App\Database\Migrations;
|
||
use CodeIgniter\Database\Migration;
|
||
class CreateOrganizationsClientsTable extends Migration
|
||
{
|
||
public function up()
|
||
{
|
||
$this->forge->addField([
|
||
'id' => [
|
||
'type' => 'INT',
|
||
'constraint' => 11,
|
||
'unsigned' => true,
|
||
'auto_increment' => true,
|
||
],
|
||
'organization_id' => [
|
||
'type' => 'INT',
|
||
'constraint' => 11,
|
||
'unsigned' => true,
|
||
],
|
||
'name' => [
|
||
'type' => 'VARCHAR',
|
||
'constraint' => 255,
|
||
],
|
||
'email' => [
|
||
'type' => 'VARCHAR',
|
||
'constraint' => 255,
|
||
'null' => true,
|
||
],
|
||
'phone' => [
|
||
'type' => 'VARCHAR',
|
||
'constraint' => 50,
|
||
'null' => true,
|
||
],
|
||
'notes' => [
|
||
'type' => 'TEXT',
|
||
'null' => true,
|
||
],
|
||
'created_at' => [
|
||
'type' => 'DATETIME',
|
||
'null' => true,
|
||
],
|
||
'updated_at' => [
|
||
'type' => 'DATETIME',
|
||
'null' => true,
|
||
],
|
||
'deleted_at' => [
|
||
'type' => 'DATETIME',
|
||
'null' => true,
|
||
],
|
||
]);
|
||
$this->forge->addKey('id', true);
|
||
$this->forge->addKey('organization_id');
|
||
$this->forge->addForeignKey('organization_id', 'organizations', 'id', 'CASCADE', 'CASCADE');
|
||
$this->forge->createTable('organizations_clients');
|
||
}
|
||
public function down()
|
||
{
|
||
$this->forge->dropTable('organizations_clients');
|
||
}
|
||
}
|
||
|
||
// app/Database/Migrations/2026-01-07-053413_CreateOrganizationSubscriptionsTable.php
|
||
<?php
|
||
namespace App\Database\Migrations;
|
||
use CodeIgniter\Database\Migration;
|
||
class CreateOrganizationSubscriptionsTable extends Migration
|
||
{
|
||
public function up()
|
||
{
|
||
$this->forge->addField([
|
||
'id' => [
|
||
'type' => 'INT',
|
||
'constraint' => 11,
|
||
'unsigned' => true,
|
||
'auto_increment' => true,
|
||
],
|
||
'organization_id' => [
|
||
'type' => 'INT',
|
||
'constraint' => 11,
|
||
'unsigned' => true,
|
||
],
|
||
'module_code' => [
|
||
'type' => 'VARCHAR',
|
||
'constraint' => 50,
|
||
],
|
||
'status' => [
|
||
'type' => 'ENUM',
|
||
'constraint' => ['trial', 'active', 'expired', 'cancelled'],
|
||
'default' => 'trial',
|
||
],
|
||
'expires_at' => [
|
||
'type' => 'DATETIME',
|
||
'null' => true,
|
||
],
|
||
'created_at' => [
|
||
'type' => 'DATETIME',
|
||
'null' => true,
|
||
],
|
||
]);
|
||
$this->forge->addKey('id', true);
|
||
$this->forge->addUniqueKey(['organization_id', 'module_code']);
|
||
$this->forge->addForeignKey('organization_id', 'organizations', 'id', 'CASCADE', 'CASCADE');
|
||
$this->forge->createTable('organization_subscriptions');
|
||
}
|
||
public function down()
|
||
{
|
||
$this->forge->dropTable('organization_subscriptions');
|
||
}
|
||
}
|
||
// app/Database/Migrations/2026-01-07-053401_CreateOrganizationsTable.php
|
||
<?php
|
||
namespace App\Database\Migrations;
|
||
use CodeIgniter\Database\Migration;
|
||
class CreateOrganizationsTable extends Migration
|
||
{
|
||
public function up()
|
||
{
|
||
$this->forge->addField([
|
||
'id' => [
|
||
'type' => 'INT',
|
||
'constraint' => 11,
|
||
'unsigned' => true,
|
||
'auto_increment' => true,
|
||
],
|
||
'owner_id' => [
|
||
'type' => 'INT',
|
||
'constraint' => 11,
|
||
'unsigned' => true,
|
||
],
|
||
'name' => [
|
||
'type' => 'VARCHAR',
|
||
'constraint' => 255,
|
||
],
|
||
'type' => [
|
||
'type' => 'ENUM',
|
||
'constraint' => ['business', 'personal'],
|
||
'default' => 'business',
|
||
],
|
||
'logo' => [
|
||
'type' => 'VARCHAR',
|
||
'constraint' => 255,
|
||
'null' => true,
|
||
],
|
||
'requisites' => [
|
||
'type' => 'JSON',
|
||
'null' => true,
|
||
],
|
||
'trial_ends_at' => [
|
||
'type' => 'DATETIME',
|
||
'null' => true,
|
||
],
|
||
'settings' => [
|
||
'type' => 'JSON',
|
||
'null' => true,
|
||
],
|
||
'created_at' => [
|
||
'type' => 'DATETIME',
|
||
'null' => true,
|
||
],
|
||
'updated_at' => [
|
||
'type' => 'DATETIME',
|
||
'null' => true,
|
||
],
|
||
'deleted_at' => [
|
||
'type' => 'DATETIME',
|
||
'null' => true,
|
||
],
|
||
]);
|
||
$this->forge->addKey('id', true);
|
||
$this->forge->addForeignKey('owner_id', 'users', 'id', 'CASCADE', 'CASCADE');
|
||
$this->forge->createTable('organizations');
|
||
}
|
||
public function down()
|
||
{
|
||
$this->forge->dropTable('organizations');
|
||
}
|
||
}
|
||
// app/Database/Migrations/.gitkeep
|
||
|
||
// app/Database/Migrations/2026-01-07-053407_CreateOrganizationUsersTable.php
|
||
<?php
|
||
namespace App\Database\Migrations;
|
||
use CodeIgniter\Database\Migration;
|
||
class CreateOrganizationUsersTable extends Migration
|
||
{
|
||
public function up()
|
||
{
|
||
$this->forge->addField([
|
||
'id' => [
|
||
'type' => 'INT',
|
||
'constraint' => 11,
|
||
'unsigned' => true,
|
||
'auto_increment' => true,
|
||
],
|
||
'organization_id' => [
|
||
'type' => 'INT',
|
||
'constraint' => 11,
|
||
'unsigned' => true,
|
||
],
|
||
'user_id' => [
|
||
'type' => 'INT',
|
||
'constraint' => 11,
|
||
'unsigned' => true,
|
||
],
|
||
'role' => [
|
||
'type' => 'ENUM',
|
||
'constraint' => ['owner', 'admin', 'manager', 'guest'],
|
||
'default' => 'manager',
|
||
],
|
||
'status' => [
|
||
'type' => 'ENUM',
|
||
'constraint' => ['active', 'invited', 'blocked'],
|
||
'default' => 'active',
|
||
],
|
||
'joined_at' => [
|
||
'type' => 'DATETIME',
|
||
'null' => true,
|
||
],
|
||
'created_at' => [
|
||
'type' => 'DATETIME',
|
||
'null' => true,
|
||
],
|
||
]);
|
||
$this->forge->addKey('id', true);
|
||
$this->forge->addUniqueKey(['organization_id', 'user_id']);
|
||
$this->forge->addForeignKey('organization_id', 'organizations', 'id', 'CASCADE', 'CASCADE');
|
||
$this->forge->addForeignKey('user_id', 'users', 'id', 'CASCADE', 'CASCADE');
|
||
$this->forge->createTable('organization_users');
|
||
}
|
||
public function down()
|
||
{
|
||
$this->forge->dropTable('organization_users');
|
||
}
|
||
}
|
||
// app/Config/Generators.php
|
||
<?php
|
||
namespace Config;
|
||
use CodeIgniter\Config\BaseConfig;
|
||
class Generators extends BaseConfig
|
||
{
|
||
/**
|
||
public array $views = [
|
||
'make:cell' => [
|
||
'class' => 'CodeIgniter\Commands\Generators\Views\cell.tpl.php',
|
||
'view' => 'CodeIgniter\Commands\Generators\Views\cell_view.tpl.php',
|
||
],
|
||
'make:command' => 'CodeIgniter\Commands\Generators\Views\command.tpl.php',
|
||
'make:config' => 'CodeIgniter\Commands\Generators\Views\config.tpl.php',
|
||
'make:controller' => 'CodeIgniter\Commands\Generators\Views\controller.tpl.php',
|
||
'make:entity' => 'CodeIgniter\Commands\Generators\Views\entity.tpl.php',
|
||
'make:filter' => 'CodeIgniter\Commands\Generators\Views\filter.tpl.php',
|
||
'make:migration' => 'CodeIgniter\Commands\Generators\Views\migration.tpl.php',
|
||
'make:model' => 'CodeIgniter\Commands\Generators\Views\model.tpl.php',
|
||
'make:seeder' => 'CodeIgniter\Commands\Generators\Views\seeder.tpl.php',
|
||
'make:validation' => 'CodeIgniter\Commands\Generators\Views\validation.tpl.php',
|
||
'session:migration' => 'CodeIgniter\Commands\Generators\Views\migration.tpl.php',
|
||
];
|
||
}
|
||
|
||
// app/Config/Cookie.php
|
||
<?php
|
||
namespace Config;
|
||
use CodeIgniter\Config\BaseConfig;
|
||
use DateTimeInterface;
|
||
class Cookie extends BaseConfig
|
||
{
|
||
/**
|
||
public string $prefix = '';
|
||
/**
|
||
public $expires = 0;
|
||
/**
|
||
public string $path = '/';
|
||
/**
|
||
public string $domain = '';
|
||
/**
|
||
public bool $secure = false;
|
||
/**
|
||
public bool $httponly = true;
|
||
/**
|
||
public string $samesite = 'Lax';
|
||
/**
|
||
public bool $raw = false;
|
||
}
|
||
|
||
// app/Config/Cache.php
|
||
<?php
|
||
namespace Config;
|
||
use CodeIgniter\Cache\CacheInterface;
|
||
use CodeIgniter\Cache\Handlers\DummyHandler;
|
||
use CodeIgniter\Cache\Handlers\FileHandler;
|
||
use CodeIgniter\Cache\Handlers\MemcachedHandler;
|
||
use CodeIgniter\Cache\Handlers\PredisHandler;
|
||
use CodeIgniter\Cache\Handlers\RedisHandler;
|
||
use CodeIgniter\Cache\Handlers\WincacheHandler;
|
||
use CodeIgniter\Config\BaseConfig;
|
||
class Cache extends BaseConfig
|
||
{
|
||
/**
|
||
public string $handler = 'file';
|
||
/**
|
||
public string $backupHandler = 'dummy';
|
||
/**
|
||
public string $prefix = '';
|
||
/**
|
||
public int $ttl = 60;
|
||
/**
|
||
public string $reservedCharacters = '{}()/\@:';
|
||
/**
|
||
public array $file = [
|
||
'storePath' => WRITEPATH . 'cache/',
|
||
'mode' => 0640,
|
||
];
|
||
/**
|
||
public array $memcached = [
|
||
'host' => '127.0.0.1',
|
||
'port' => 11211,
|
||
'weight' => 1,
|
||
'raw' => false,
|
||
];
|
||
/**
|
||
public array $redis = [
|
||
'host' => '127.0.0.1',
|
||
'password' => null,
|
||
'port' => 6379,
|
||
'timeout' => 0,
|
||
'database' => 0,
|
||
];
|
||
/**
|
||
public array $validHandlers = [
|
||
'dummy' => DummyHandler::class,
|
||
'file' => FileHandler::class,
|
||
'memcached' => MemcachedHandler::class,
|
||
'predis' => PredisHandler::class,
|
||
'redis' => RedisHandler::class,
|
||
'wincache' => WincacheHandler::class,
|
||
];
|
||
/**
|
||
public $cacheQueryString = false;
|
||
}
|
||
|
||
// app/Config/Images.php
|
||
<?php
|
||
namespace Config;
|
||
use CodeIgniter\Config\BaseConfig;
|
||
use CodeIgniter\Images\Handlers\GDHandler;
|
||
use CodeIgniter\Images\Handlers\ImageMagickHandler;
|
||
class Images extends BaseConfig
|
||
{
|
||
/**
|
||
public string $defaultHandler = 'gd';
|
||
/**
|
||
public string $libraryPath = '/usr/local/bin/convert';
|
||
/**
|
||
public array $handlers = [
|
||
'gd' => GDHandler::class,
|
||
'imagick' => ImageMagickHandler::class,
|
||
];
|
||
}
|
||
|
||
// app/Config/ContentSecurityPolicy.php
|
||
<?php
|
||
namespace Config;
|
||
use CodeIgniter\Config\BaseConfig;
|
||
/**
|
||
class ContentSecurityPolicy extends BaseConfig
|
||
{
|
||
/**
|
||
public bool $reportOnly = false;
|
||
/**
|
||
public ?string $reportURI = null;
|
||
/**
|
||
public bool $upgradeInsecureRequests = false;
|
||
/**
|
||
public $defaultSrc;
|
||
/**
|
||
public $scriptSrc = 'self';
|
||
/**
|
||
public $styleSrc = 'self';
|
||
/**
|
||
public $imageSrc = 'self';
|
||
/**
|
||
public $baseURI;
|
||
/**
|
||
public $childSrc = 'self';
|
||
/**
|
||
public $connectSrc = 'self';
|
||
/**
|
||
public $fontSrc;
|
||
/**
|
||
public $formAction = 'self';
|
||
/**
|
||
public $frameAncestors;
|
||
/**
|
||
public $frameSrc;
|
||
/**
|
||
public $mediaSrc;
|
||
/**
|
||
public $objectSrc = 'self';
|
||
/**
|
||
public $manifestSrc;
|
||
/**
|
||
public $pluginTypes;
|
||
/**
|
||
public $sandbox;
|
||
/**
|
||
public string $styleNonceTag = '{csp-style-nonce}';
|
||
/**
|
||
public string $scriptNonceTag = '{csp-script-nonce}';
|
||
/**
|
||
public bool $autoNonce = true;
|
||
}
|
||
|
||
// app/Config/View.php
|
||
<?php
|
||
namespace Config;
|
||
use CodeIgniter\Config\View as BaseView;
|
||
use CodeIgniter\View\ViewDecoratorInterface;
|
||
/**
|
||
class View extends BaseView
|
||
{
|
||
/**
|
||
public $saveData = true;
|
||
/**
|
||
public $filters = [];
|
||
/**
|
||
public $plugins = [];
|
||
/**
|
||
public array $decorators = [];
|
||
}
|
||
|
||
// app/Config/CURLRequest.php
|
||
<?php
|
||
namespace Config;
|
||
use CodeIgniter\Config\BaseConfig;
|
||
class CURLRequest extends BaseConfig
|
||
{
|
||
/**
|
||
public bool $shareOptions = false;
|
||
}
|
||
|
||
// app/Config/UserAgents.php
|
||
<?php
|
||
namespace Config;
|
||
use CodeIgniter\Config\BaseConfig;
|
||
/**
|
||
class UserAgents extends BaseConfig
|
||
{
|
||
/**
|
||
public array $platforms = [
|
||
'windows nt 10.0' => 'Windows 10',
|
||
'windows nt 6.3' => 'Windows 8.1',
|
||
'windows nt 6.2' => 'Windows 8',
|
||
'windows nt 6.1' => 'Windows 7',
|
||
'windows nt 6.0' => 'Windows Vista',
|
||
'windows nt 5.2' => 'Windows 2003',
|
||
'windows nt 5.1' => 'Windows XP',
|
||
'windows nt 5.0' => 'Windows 2000',
|
||
'windows nt 4.0' => 'Windows NT 4.0',
|
||
'winnt4.0' => 'Windows NT 4.0',
|
||
'winnt 4.0' => 'Windows NT',
|
||
'winnt' => 'Windows NT',
|
||
'windows 98' => 'Windows 98',
|
||
'win98' => 'Windows 98',
|
||
'windows 95' => 'Windows 95',
|
||
'win95' => 'Windows 95',
|
||
'windows phone' => 'Windows Phone',
|
||
'windows' => 'Unknown Windows OS',
|
||
'android' => 'Android',
|
||
'blackberry' => 'BlackBerry',
|
||
'iphone' => 'iOS',
|
||
'ipad' => 'iOS',
|
||
'ipod' => 'iOS',
|
||
'os x' => 'Mac OS X',
|
||
'ppc mac' => 'Power PC Mac',
|
||
'freebsd' => 'FreeBSD',
|
||
'ppc' => 'Macintosh',
|
||
'linux' => 'Linux',
|
||
'debian' => 'Debian',
|
||
'sunos' => 'Sun Solaris',
|
||
'beos' => 'BeOS',
|
||
'apachebench' => 'ApacheBench',
|
||
'aix' => 'AIX',
|
||
'irix' => 'Irix',
|
||
'osf' => 'DEC OSF',
|
||
'hp-ux' => 'HP-UX',
|
||
'netbsd' => 'NetBSD',
|
||
'bsdi' => 'BSDi',
|
||
'openbsd' => 'OpenBSD',
|
||
'gnu' => 'GNU/Linux',
|
||
'unix' => 'Unknown Unix OS',
|
||
'symbian' => 'Symbian OS',
|
||
];
|
||
/**
|
||
public array $browsers = [
|
||
'OPR' => 'Opera',
|
||
'Flock' => 'Flock',
|
||
'Edge' => 'Spartan',
|
||
'Edg' => 'Edge',
|
||
'Chrome' => 'Chrome',
|
||
'Opera.*?Version' => 'Opera',
|
||
'Opera' => 'Opera',
|
||
'MSIE' => 'Internet Explorer',
|
||
'Internet Explorer' => 'Internet Explorer',
|
||
'Trident.* rv' => 'Internet Explorer',
|
||
'Shiira' => 'Shiira',
|
||
'Firefox' => 'Firefox',
|
||
'Chimera' => 'Chimera',
|
||
'Phoenix' => 'Phoenix',
|
||
'Firebird' => 'Firebird',
|
||
'Camino' => 'Camino',
|
||
'Netscape' => 'Netscape',
|
||
'OmniWeb' => 'OmniWeb',
|
||
'Safari' => 'Safari',
|
||
'Mozilla' => 'Mozilla',
|
||
'Konqueror' => 'Konqueror',
|
||
'icab' => 'iCab',
|
||
'Lynx' => 'Lynx',
|
||
'Links' => 'Links',
|
||
'hotjava' => 'HotJava',
|
||
'amaya' => 'Amaya',
|
||
'IBrowse' => 'IBrowse',
|
||
'Maxthon' => 'Maxthon',
|
||
'Ubuntu' => 'Ubuntu Web Browser',
|
||
'Vivaldi' => 'Vivaldi',
|
||
];
|
||
/**
|
||
public array $mobiles = [
|
||
'mobileexplorer' => 'Mobile Explorer',
|
||
'palmsource' => 'Palm',
|
||
'palmscape' => 'Palmscape',
|
||
'motorola' => 'Motorola',
|
||
'nokia' => 'Nokia',
|
||
'palm' => 'Palm',
|
||
'iphone' => 'Apple iPhone',
|
||
'ipad' => 'iPad',
|
||
'ipod' => 'Apple iPod Touch',
|
||
'sony' => 'Sony Ericsson',
|
||
'ericsson' => 'Sony Ericsson',
|
||
'blackberry' => 'BlackBerry',
|
||
'cocoon' => 'O2 Cocoon',
|
||
'blazer' => 'Treo',
|
||
'lg' => 'LG',
|
||
'amoi' => 'Amoi',
|
||
'xda' => 'XDA',
|
||
'mda' => 'MDA',
|
||
'vario' => 'Vario',
|
||
'htc' => 'HTC',
|
||
'samsung' => 'Samsung',
|
||
'sharp' => 'Sharp',
|
||
'sie-' => 'Siemens',
|
||
'alcatel' => 'Alcatel',
|
||
'benq' => 'BenQ',
|
||
'ipaq' => 'HP iPaq',
|
||
'mot-' => 'Motorola',
|
||
'playstation portable' => 'PlayStation Portable',
|
||
'playstation 3' => 'PlayStation 3',
|
||
'playstation vita' => 'PlayStation Vita',
|
||
'hiptop' => 'Danger Hiptop',
|
||
'nec-' => 'NEC',
|
||
'panasonic' => 'Panasonic',
|
||
'philips' => 'Philips',
|
||
'sagem' => 'Sagem',
|
||
'sanyo' => 'Sanyo',
|
||
'spv' => 'SPV',
|
||
'zte' => 'ZTE',
|
||
'sendo' => 'Sendo',
|
||
'nintendo dsi' => 'Nintendo DSi',
|
||
'nintendo ds' => 'Nintendo DS',
|
||
'nintendo 3ds' => 'Nintendo 3DS',
|
||
'wii' => 'Nintendo Wii',
|
||
'open web' => 'Open Web',
|
||
'openweb' => 'OpenWeb',
|
||
'android' => 'Android',
|
||
'symbian' => 'Symbian',
|
||
'SymbianOS' => 'SymbianOS',
|
||
'elaine' => 'Palm',
|
||
'series60' => 'Symbian S60',
|
||
'windows ce' => 'Windows CE',
|
||
'obigo' => 'Obigo',
|
||
'netfront' => 'Netfront Browser',
|
||
'openwave' => 'Openwave Browser',
|
||
'mobilexplorer' => 'Mobile Explorer',
|
||
'operamini' => 'Opera Mini',
|
||
'opera mini' => 'Opera Mini',
|
||
'opera mobi' => 'Opera Mobile',
|
||
'fennec' => 'Firefox Mobile',
|
||
'digital paths' => 'Digital Paths',
|
||
'avantgo' => 'AvantGo',
|
||
'xiino' => 'Xiino',
|
||
'novarra' => 'Novarra Transcoder',
|
||
'vodafone' => 'Vodafone',
|
||
'docomo' => 'NTT DoCoMo',
|
||
'o2' => 'O2',
|
||
'mobile' => 'Generic Mobile',
|
||
'wireless' => 'Generic Mobile',
|
||
'j2me' => 'Generic Mobile',
|
||
'midp' => 'Generic Mobile',
|
||
'cldc' => 'Generic Mobile',
|
||
'up.link' => 'Generic Mobile',
|
||
'up.browser' => 'Generic Mobile',
|
||
'smartphone' => 'Generic Mobile',
|
||
'cellphone' => 'Generic Mobile',
|
||
];
|
||
/**
|
||
public array $robots = [
|
||
'googlebot' => 'Googlebot',
|
||
'msnbot' => 'MSNBot',
|
||
'baiduspider' => 'Baiduspider',
|
||
'bingbot' => 'Bing',
|
||
'slurp' => 'Inktomi Slurp',
|
||
'yahoo' => 'Yahoo',
|
||
'ask jeeves' => 'Ask Jeeves',
|
||
'fastcrawler' => 'FastCrawler',
|
||
'infoseek' => 'InfoSeek Robot 1.0',
|
||
'lycos' => 'Lycos',
|
||
'yandex' => 'YandexBot',
|
||
'mediapartners-google' => 'MediaPartners Google',
|
||
'CRAZYWEBCRAWLER' => 'Crazy Webcrawler',
|
||
'adsbot-google' => 'AdsBot Google',
|
||
'feedfetcher-google' => 'Feedfetcher Google',
|
||
'curious george' => 'Curious George',
|
||
'ia_archiver' => 'Alexa Crawler',
|
||
'MJ12bot' => 'Majestic-12',
|
||
'Uptimebot' => 'Uptimebot',
|
||
];
|
||
}
|
||
|
||
// app/Config/Constants.php
|
||
<?php
|
||
/*
|
||
| --------------------------------------------------------------------
|
||
| App Namespace
|
||
| --------------------------------------------------------------------
|
||
|
|
||
| This defines the default Namespace that is used throughout
|
||
| CodeIgniter to refer to the Application directory. Change
|
||
| this constant to change the namespace that all application
|
||
| classes should use.
|
||
|
|
||
| NOTE: changing this will require manually modifying the
|
||
| existing namespaces of App\* namespaced-classes.
|
||
defined('APP_NAMESPACE') || define('APP_NAMESPACE', 'App');
|
||
/*
|
||
| --------------------------------------------------------------------------
|
||
| Composer Path
|
||
| --------------------------------------------------------------------------
|
||
|
|
||
| The path that Composer's autoload file is expected to live. By default,
|
||
| the vendor folder is in the Root directory, but you can customize that here.
|
||
defined('COMPOSER_PATH') || define('COMPOSER_PATH', ROOTPATH . 'vendor/autoload.php');
|
||
/*
|
||
|--------------------------------------------------------------------------
|
||
| Timing Constants
|
||
|--------------------------------------------------------------------------
|
||
|
|
||
| Provide simple ways to work with the myriad of PHP functions that
|
||
| require information to be in seconds.
|
||
defined('SECOND') || define('SECOND', 1);
|
||
defined('MINUTE') || define('MINUTE', 60);
|
||
defined('HOUR') || define('HOUR', 3600);
|
||
defined('DAY') || define('DAY', 86400);
|
||
defined('WEEK') || define('WEEK', 604800);
|
||
defined('MONTH') || define('MONTH', 2_592_000);
|
||
defined('YEAR') || define('YEAR', 31_536_000);
|
||
defined('DECADE') || define('DECADE', 315_360_000);
|
||
/*
|
||
| --------------------------------------------------------------------------
|
||
| Exit Status Codes
|
||
| --------------------------------------------------------------------------
|
||
|
|
||
| Used to indicate the conditions under which the script is exit()ing.
|
||
| While there is no universal standard for error codes, there are some
|
||
| broad conventions. Three such conventions are mentioned below, for
|
||
| those who wish to make use of them. The CodeIgniter defaults were
|
||
| chosen for the least overlap with these conventions, while still
|
||
| leaving room for others to be defined in future versions and user
|
||
| applications.
|
||
|
|
||
| The three main conventions used for determining exit status codes
|
||
| are as follows:
|
||
|
|
||
| Standard C/C++ Library (stdlibc):
|
||
| http:
|
||
| (This link also contains other GNU-specific conventions)
|
||
| BSD sysexits.h:
|
||
| http:
|
||
| Bash scripting:
|
||
| http:
|
||
|
|
||
defined('EXIT_SUCCESS') || define('EXIT_SUCCESS', 0);
|
||
defined('EXIT_ERROR') || define('EXIT_ERROR', 1);
|
||
defined('EXIT_CONFIG') || define('EXIT_CONFIG', 3);
|
||
defined('EXIT_UNKNOWN_FILE') || define('EXIT_UNKNOWN_FILE', 4);
|
||
defined('EXIT_UNKNOWN_CLASS') || define('EXIT_UNKNOWN_CLASS', 5);
|
||
defined('EXIT_UNKNOWN_METHOD') || define('EXIT_UNKNOWN_METHOD', 6);
|
||
defined('EXIT_USER_INPUT') || define('EXIT_USER_INPUT', 7);
|
||
defined('EXIT_DATABASE') || define('EXIT_DATABASE', 8);
|
||
defined('EXIT__AUTO_MIN') || define('EXIT__AUTO_MIN', 9);
|
||
defined('EXIT__AUTO_MAX') || define('EXIT__AUTO_MAX', 125);
|
||
|
||
// app/Config/Autoload.php
|
||
<?php
|
||
namespace Config;
|
||
use CodeIgniter\Config\AutoloadConfig;
|
||
/**
|
||
class Autoload extends AutoloadConfig
|
||
{
|
||
/**
|
||
public $psr4 = [
|
||
APP_NAMESPACE => APPPATH,
|
||
'App\Modules' => APPPATH . 'Modules',
|
||
'App\Libraries\Twig' => APPPATH . 'Libraries/Twig',
|
||
];
|
||
/**
|
||
public $classmap = [];
|
||
/**
|
||
public $files = [];
|
||
/**
|
||
public $helpers = [];
|
||
}
|
||
|
||
// app/Config/Exceptions.php
|
||
<?php
|
||
namespace Config;
|
||
use CodeIgniter\Config\BaseConfig;
|
||
use CodeIgniter\Debug\ExceptionHandler;
|
||
use CodeIgniter\Debug\ExceptionHandlerInterface;
|
||
use Psr\Log\LogLevel;
|
||
use Throwable;
|
||
/**
|
||
class Exceptions extends BaseConfig
|
||
{
|
||
/**
|
||
public bool $log = true;
|
||
/**
|
||
public array $ignoreCodes = [404];
|
||
/**
|
||
public string $errorViewPath = APPPATH . 'Views/errors';
|
||
/**
|
||
public array $sensitiveDataInTrace = [];
|
||
/**
|
||
public bool $logDeprecations = true;
|
||
/**
|
||
public string $deprecationLogLevel = LogLevel::WARNING;
|
||
/*
|
||
public function handler(int $statusCode, Throwable $exception): ExceptionHandlerInterface
|
||
{
|
||
return new ExceptionHandler($this);
|
||
}
|
||
}
|
||
|
||
// app/Config/Services.php
|
||
<?php
|
||
namespace Config;
|
||
use CodeIgniter\Config\BaseService;
|
||
/**
|
||
class Services extends BaseService
|
||
{
|
||
/*
|
||
}
|
||
|
||
// app/Config/Session.php
|
||
<?php
|
||
namespace Config;
|
||
use CodeIgniter\Config\BaseConfig;
|
||
use CodeIgniter\Session\Handlers\BaseHandler;
|
||
use CodeIgniter\Session\Handlers\FileHandler;
|
||
class Session extends BaseConfig
|
||
{
|
||
/**
|
||
public string $driver = FileHandler::class;
|
||
/**
|
||
public string $cookieName = 'ci_session';
|
||
/**
|
||
public int $expiration = 7200;
|
||
/**
|
||
public string $savePath = WRITEPATH . 'session';
|
||
/**
|
||
public bool $matchIP = false;
|
||
/**
|
||
public int $timeToUpdate = 300;
|
||
/**
|
||
public bool $regenerateDestroy = false;
|
||
/**
|
||
public ?string $DBGroup = null;
|
||
/**
|
||
public int $lockRetryInterval = 100_000;
|
||
/**
|
||
public int $lockMaxRetries = 300;
|
||
}
|
||
|
||
// app/Config/Format.php
|
||
<?php
|
||
namespace Config;
|
||
use CodeIgniter\Config\BaseConfig;
|
||
use CodeIgniter\Format\JSONFormatter;
|
||
use CodeIgniter\Format\XMLFormatter;
|
||
class Format extends BaseConfig
|
||
{
|
||
/**
|
||
public array $supportedResponseFormats = [
|
||
'application/json',
|
||
'application/xml',
|
||
'text/xml',
|
||
];
|
||
/**
|
||
public array $formatters = [
|
||
'application/json' => JSONFormatter::class,
|
||
'application/xml' => XMLFormatter::class,
|
||
'text/xml' => XMLFormatter::class,
|
||
];
|
||
/**
|
||
public array $formatterOptions = [
|
||
'application/json' => JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES,
|
||
'application/xml' => 0,
|
||
'text/xml' => 0,
|
||
];
|
||
}
|
||
|
||
// app/Config/Cors.php
|
||
<?php
|
||
namespace Config;
|
||
use CodeIgniter\Config\BaseConfig;
|
||
/**
|
||
class Cors extends BaseConfig
|
||
{
|
||
/**
|
||
public array $default = [
|
||
/**
|
||
'allowedOrigins' => [],
|
||
/**
|
||
'allowedOriginsPatterns' => [],
|
||
/**
|
||
'supportsCredentials' => false,
|
||
/**
|
||
'allowedHeaders' => [],
|
||
/**
|
||
'exposedHeaders' => [],
|
||
/**
|
||
'allowedMethods' => [],
|
||
/**
|
||
'maxAge' => 7200,
|
||
];
|
||
}
|
||
|
||
// app/Config/Honeypot.php
|
||
<?php
|
||
namespace Config;
|
||
use CodeIgniter\Config\BaseConfig;
|
||
class Honeypot extends BaseConfig
|
||
{
|
||
/**
|
||
public bool $hidden = true;
|
||
/**
|
||
public string $label = 'Fill This Field';
|
||
/**
|
||
public string $name = 'honeypot';
|
||
/**
|
||
public string $template = '<label>{label}</label><input type="text" name="{name}" value="">';
|
||
/**
|
||
public string $container = '<div style="display:none">{template}</div>';
|
||
/**
|
||
public string $containerId = 'hpc';
|
||
}
|
||
|
||
// app/Config/Mimes.php
|
||
<?php
|
||
namespace Config;
|
||
/**
|
||
class Mimes
|
||
{
|
||
/**
|
||
public static array $mimes = [
|
||
'hqx' => [
|
||
'application/mac-binhex40',
|
||
'application/mac-binhex',
|
||
'application/x-binhex40',
|
||
'application/x-mac-binhex40',
|
||
],
|
||
'cpt' => 'application/mac-compactpro',
|
||
'csv' => [
|
||
'text/csv',
|
||
'text/x-comma-separated-values',
|
||
'text/comma-separated-values',
|
||
'application/vnd.ms-excel',
|
||
'application/x-csv',
|
||
'text/x-csv',
|
||
'application/csv',
|
||
'application/excel',
|
||
'application/vnd.msexcel',
|
||
'text/plain',
|
||
],
|
||
'bin' => [
|
||
'application/macbinary',
|
||
'application/mac-binary',
|
||
'application/octet-stream',
|
||
'application/x-binary',
|
||
'application/x-macbinary',
|
||
],
|
||
'dms' => 'application/octet-stream',
|
||
'lha' => 'application/octet-stream',
|
||
'lzh' => 'application/octet-stream',
|
||
'exe' => [
|
||
'application/octet-stream',
|
||
'application/vnd.microsoft.portable-executable',
|
||
'application/x-dosexec',
|
||
'application/x-msdownload',
|
||
],
|
||
'class' => 'application/octet-stream',
|
||
'psd' => [
|
||
'application/x-photoshop',
|
||
'image/vnd.adobe.photoshop',
|
||
],
|
||
'so' => 'application/octet-stream',
|
||
'sea' => 'application/octet-stream',
|
||
'dll' => 'application/octet-stream',
|
||
'oda' => 'application/oda',
|
||
'pdf' => [
|
||
'application/pdf',
|
||
'application/force-download',
|
||
'application/x-download',
|
||
],
|
||
'ai' => [
|
||
'application/pdf',
|
||
'application/postscript',
|
||
],
|
||
'eps' => 'application/postscript',
|
||
'ps' => 'application/postscript',
|
||
'smi' => 'application/smil',
|
||
'smil' => 'application/smil',
|
||
'mif' => 'application/vnd.mif',
|
||
'xls' => [
|
||
'application/vnd.ms-excel',
|
||
'application/msexcel',
|
||
'application/x-msexcel',
|
||
'application/x-ms-excel',
|
||
'application/x-excel',
|
||
'application/x-dos_ms_excel',
|
||
'application/xls',
|
||
'application/x-xls',
|
||
'application/excel',
|
||
'application/download',
|
||
'application/vnd.ms-office',
|
||
'application/msword',
|
||
],
|
||
'ppt' => [
|
||
'application/vnd.ms-powerpoint',
|
||
'application/powerpoint',
|
||
'application/vnd.ms-office',
|
||
'application/msword',
|
||
],
|
||
'pptx' => [
|
||
'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
||
],
|
||
'wbxml' => 'application/wbxml',
|
||
'wmlc' => 'application/wmlc',
|
||
'dcr' => 'application/x-director',
|
||
'dir' => 'application/x-director',
|
||
'dxr' => 'application/x-director',
|
||
'dvi' => 'application/x-dvi',
|
||
'gtar' => 'application/x-gtar',
|
||
'gz' => 'application/x-gzip',
|
||
'gzip' => 'application/x-gzip',
|
||
'php' => [
|
||
'application/x-php',
|
||
'application/x-httpd-php',
|
||
'application/php',
|
||
'text/php',
|
||
'text/x-php',
|
||
'application/x-httpd-php-source',
|
||
],
|
||
'php4' => 'application/x-httpd-php',
|
||
'php3' => 'application/x-httpd-php',
|
||
'phtml' => 'application/x-httpd-php',
|
||
'phps' => 'application/x-httpd-php-source',
|
||
'js' => [
|
||
'application/x-javascript',
|
||
'text/plain',
|
||
],
|
||
'swf' => 'application/x-shockwave-flash',
|
||
'sit' => 'application/x-stuffit',
|
||
'tar' => 'application/x-tar',
|
||
'tgz' => [
|
||
'application/x-tar',
|
||
'application/x-gzip-compressed',
|
||
],
|
||
'z' => 'application/x-compress',
|
||
'xhtml' => 'application/xhtml+xml',
|
||
'xht' => 'application/xhtml+xml',
|
||
'zip' => [
|
||
'application/x-zip',
|
||
'application/zip',
|
||
'application/x-zip-compressed',
|
||
'application/s-compressed',
|
||
'multipart/x-zip',
|
||
],
|
||
'rar' => [
|
||
'application/vnd.rar',
|
||
'application/x-rar',
|
||
'application/rar',
|
||
'application/x-rar-compressed',
|
||
],
|
||
'mid' => 'audio/midi',
|
||
'midi' => 'audio/midi',
|
||
'mpga' => 'audio/mpeg',
|
||
'mp2' => 'audio/mpeg',
|
||
'mp3' => [
|
||
'audio/mpeg',
|
||
'audio/mpg',
|
||
'audio/mpeg3',
|
||
'audio/mp3',
|
||
],
|
||
'aif' => [
|
||
'audio/x-aiff',
|
||
'audio/aiff',
|
||
],
|
||
'aiff' => [
|
||
'audio/x-aiff',
|
||
'audio/aiff',
|
||
],
|
||
'aifc' => 'audio/x-aiff',
|
||
'ram' => 'audio/x-pn-realaudio',
|
||
'rm' => 'audio/x-pn-realaudio',
|
||
'rpm' => 'audio/x-pn-realaudio-plugin',
|
||
'ra' => 'audio/x-realaudio',
|
||
'rv' => 'video/vnd.rn-realvideo',
|
||
'wav' => [
|
||
'audio/x-wav',
|
||
'audio/wave',
|
||
'audio/wav',
|
||
],
|
||
'bmp' => [
|
||
'image/bmp',
|
||
'image/x-bmp',
|
||
'image/x-bitmap',
|
||
'image/x-xbitmap',
|
||
'image/x-win-bitmap',
|
||
'image/x-windows-bmp',
|
||
'image/ms-bmp',
|
||
'image/x-ms-bmp',
|
||
'application/bmp',
|
||
'application/x-bmp',
|
||
'application/x-win-bitmap',
|
||
],
|
||
'gif' => 'image/gif',
|
||
'jpg' => [
|
||
'image/jpeg',
|
||
'image/pjpeg',
|
||
],
|
||
'jpeg' => [
|
||
'image/jpeg',
|
||
'image/pjpeg',
|
||
],
|
||
'jpe' => [
|
||
'image/jpeg',
|
||
'image/pjpeg',
|
||
],
|
||
'jp2' => [
|
||
'image/jp2',
|
||
'video/mj2',
|
||
'image/jpx',
|
||
'image/jpm',
|
||
],
|
||
'j2k' => [
|
||
'image/jp2',
|
||
'video/mj2',
|
||
'image/jpx',
|
||
'image/jpm',
|
||
],
|
||
'jpf' => [
|
||
'image/jp2',
|
||
'video/mj2',
|
||
'image/jpx',
|
||
'image/jpm',
|
||
],
|
||
'jpg2' => [
|
||
'image/jp2',
|
||
'video/mj2',
|
||
'image/jpx',
|
||
'image/jpm',
|
||
],
|
||
'jpx' => [
|
||
'image/jp2',
|
||
'video/mj2',
|
||
'image/jpx',
|
||
'image/jpm',
|
||
],
|
||
'jpm' => [
|
||
'image/jp2',
|
||
'video/mj2',
|
||
'image/jpx',
|
||
'image/jpm',
|
||
],
|
||
'mj2' => [
|
||
'image/jp2',
|
||
'video/mj2',
|
||
'image/jpx',
|
||
'image/jpm',
|
||
],
|
||
'mjp2' => [
|
||
'image/jp2',
|
||
'video/mj2',
|
||
'image/jpx',
|
||
'image/jpm',
|
||
],
|
||
'png' => [
|
||
'image/png',
|
||
'image/x-png',
|
||
],
|
||
'webp' => 'image/webp',
|
||
'tif' => 'image/tiff',
|
||
'tiff' => 'image/tiff',
|
||
'css' => [
|
||
'text/css',
|
||
'text/plain',
|
||
],
|
||
'html' => [
|
||
'text/html',
|
||
'text/plain',
|
||
],
|
||
'htm' => [
|
||
'text/html',
|
||
'text/plain',
|
||
],
|
||
'shtml' => [
|
||
'text/html',
|
||
'text/plain',
|
||
],
|
||
'txt' => 'text/plain',
|
||
'text' => 'text/plain',
|
||
'log' => [
|
||
'text/plain',
|
||
'text/x-log',
|
||
],
|
||
'rtx' => 'text/richtext',
|
||
'rtf' => 'text/rtf',
|
||
'xml' => [
|
||
'application/xml',
|
||
'text/xml',
|
||
'text/plain',
|
||
],
|
||
'xsl' => [
|
||
'application/xml',
|
||
'text/xsl',
|
||
'text/xml',
|
||
],
|
||
'mpeg' => 'video/mpeg',
|
||
'mpg' => 'video/mpeg',
|
||
'mpe' => 'video/mpeg',
|
||
'qt' => 'video/quicktime',
|
||
'mov' => 'video/quicktime',
|
||
'avi' => [
|
||
'video/x-msvideo',
|
||
'video/msvideo',
|
||
'video/avi',
|
||
'application/x-troff-msvideo',
|
||
],
|
||
'movie' => 'video/x-sgi-movie',
|
||
'doc' => [
|
||
'application/msword',
|
||
'application/vnd.ms-office',
|
||
],
|
||
'docx' => [
|
||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||
'application/zip',
|
||
'application/msword',
|
||
'application/x-zip',
|
||
],
|
||
'dot' => [
|
||
'application/msword',
|
||
'application/vnd.ms-office',
|
||
],
|
||
'dotx' => [
|
||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||
'application/zip',
|
||
'application/msword',
|
||
],
|
||
'xlsx' => [
|
||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||
'application/zip',
|
||
'application/vnd.ms-excel',
|
||
'application/msword',
|
||
'application/x-zip',
|
||
],
|
||
'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12',
|
||
'xlsm' => 'application/vnd.ms-excel.sheet.macroEnabled.12',
|
||
'word' => [
|
||
'application/msword',
|
||
'application/octet-stream',
|
||
],
|
||
'xl' => 'application/excel',
|
||
'eml' => 'message/rfc822',
|
||
'json' => [
|
||
'application/json',
|
||
'text/json',
|
||
],
|
||
'pem' => [
|
||
'application/x-x509-user-cert',
|
||
'application/x-pem-file',
|
||
'application/octet-stream',
|
||
],
|
||
'p10' => [
|
||
'application/x-pkcs10',
|
||
'application/pkcs10',
|
||
],
|
||
'p12' => 'application/x-pkcs12',
|
||
'p7a' => 'application/x-pkcs7-signature',
|
||
'p7c' => [
|
||
'application/pkcs7-mime',
|
||
'application/x-pkcs7-mime',
|
||
],
|
||
'p7m' => [
|
||
'application/pkcs7-mime',
|
||
'application/x-pkcs7-mime',
|
||
],
|
||
'p7r' => 'application/x-pkcs7-certreqresp',
|
||
'p7s' => 'application/pkcs7-signature',
|
||
'crt' => [
|
||
'application/x-x509-ca-cert',
|
||
'application/x-x509-user-cert',
|
||
'application/pkix-cert',
|
||
],
|
||
'crl' => [
|
||
'application/pkix-crl',
|
||
'application/pkcs-crl',
|
||
],
|
||
'der' => 'application/x-x509-ca-cert',
|
||
'kdb' => 'application/octet-stream',
|
||
'pgp' => 'application/pgp',
|
||
'gpg' => 'application/gpg-keys',
|
||
'sst' => 'application/octet-stream',
|
||
'csr' => 'application/octet-stream',
|
||
'rsa' => 'application/x-pkcs7',
|
||
'cer' => [
|
||
'application/pkix-cert',
|
||
'application/x-x509-ca-cert',
|
||
],
|
||
'3g2' => 'video/3gpp2',
|
||
'3gp' => [
|
||
'video/3gp',
|
||
'video/3gpp',
|
||
],
|
||
'mp4' => 'video/mp4',
|
||
'm4a' => 'audio/x-m4a',
|
||
'f4v' => [
|
||
'video/mp4',
|
||
'video/x-f4v',
|
||
],
|
||
'flv' => 'video/x-flv',
|
||
'webm' => 'video/webm',
|
||
'aac' => 'audio/x-acc',
|
||
'm4u' => 'application/vnd.mpegurl',
|
||
'm3u' => 'text/plain',
|
||
'xspf' => 'application/xspf+xml',
|
||
'vlc' => 'application/videolan',
|
||
'wmv' => [
|
||
'video/x-ms-wmv',
|
||
'video/x-ms-asf',
|
||
],
|
||
'au' => 'audio/x-au',
|
||
'ac3' => 'audio/ac3',
|
||
'flac' => 'audio/x-flac',
|
||
'ogg' => [
|
||
'audio/ogg',
|
||
'video/ogg',
|
||
'application/ogg',
|
||
],
|
||
'kmz' => [
|
||
'application/vnd.google-earth.kmz',
|
||
'application/zip',
|
||
'application/x-zip',
|
||
],
|
||
'kml' => [
|
||
'application/vnd.google-earth.kml+xml',
|
||
'application/xml',
|
||
'text/xml',
|
||
],
|
||
'ics' => 'text/calendar',
|
||
'ical' => 'text/calendar',
|
||
'zsh' => 'text/x-scriptzsh',
|
||
'7zip' => [
|
||
'application/x-compressed',
|
||
'application/x-zip-compressed',
|
||
'application/zip',
|
||
'multipart/x-zip',
|
||
],
|
||
'cdr' => [
|
||
'application/cdr',
|
||
'application/coreldraw',
|
||
'application/x-cdr',
|
||
'application/x-coreldraw',
|
||
'image/cdr',
|
||
'image/x-cdr',
|
||
'zz-application/zz-winassoc-cdr',
|
||
],
|
||
'wma' => [
|
||
'audio/x-ms-wma',
|
||
'video/x-ms-asf',
|
||
],
|
||
'jar' => [
|
||
'application/java-archive',
|
||
'application/x-java-application',
|
||
'application/x-jar',
|
||
'application/x-compressed',
|
||
],
|
||
'svg' => [
|
||
'image/svg+xml',
|
||
'image/svg',
|
||
'application/xml',
|
||
'text/xml',
|
||
],
|
||
'vcf' => 'text/x-vcard',
|
||
'srt' => [
|
||
'text/srt',
|
||
'text/plain',
|
||
],
|
||
'vtt' => [
|
||
'text/vtt',
|
||
'text/plain',
|
||
],
|
||
'ico' => [
|
||
'image/x-icon',
|
||
'image/x-ico',
|
||
'image/vnd.microsoft.icon',
|
||
],
|
||
'stl' => [
|
||
'application/sla',
|
||
'application/vnd.ms-pki.stl',
|
||
'application/x-navistyle',
|
||
'model/stl',
|
||
'application/octet-stream',
|
||
],
|
||
];
|
||
/**
|
||
public static function guessTypeFromExtension(string $extension)
|
||
{
|
||
$extension = trim(strtolower($extension), '. ');
|
||
if (! array_key_exists($extension, static::$mimes)) {
|
||
return null;
|
||
}
|
||
return is_array(static::$mimes[$extension]) ? static::$mimes[$extension][0] : static::$mimes[$extension];
|
||
}
|
||
/**
|
||
public static function guessExtensionFromType(string $type, ?string $proposedExtension = null)
|
||
{
|
||
$type = trim(strtolower($type), '. ');
|
||
$proposedExtension = trim(strtolower($proposedExtension ?? ''));
|
||
if (
|
||
$proposedExtension !== ''
|
||
&& array_key_exists($proposedExtension, static::$mimes)
|
||
&& in_array($type, (array) static::$mimes[$proposedExtension], true)
|
||
) {
|
||
return $proposedExtension;
|
||
}
|
||
foreach (static::$mimes as $ext => $types) {
|
||
if (in_array($type, (array) $types, true)) {
|
||
return $ext;
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
}
|
||
|
||
// app/Config/Migrations.php
|
||
<?php
|
||
namespace Config;
|
||
use CodeIgniter\Config\BaseConfig;
|
||
class Migrations extends BaseConfig
|
||
{
|
||
/**
|
||
public bool $enabled = true;
|
||
/**
|
||
public string $table = 'migrations';
|
||
/**
|
||
public string $timestampFormat = 'Y-m-d-His_';
|
||
}
|
||
|
||
// app/Config/Pager.php
|
||
<?php
|
||
namespace Config;
|
||
use CodeIgniter\Config\BaseConfig;
|
||
class Pager extends BaseConfig
|
||
{
|
||
/**
|
||
public array $templates = [
|
||
'default_full' => 'App\Views\pager\bootstrap_full',
|
||
'default_simple' => 'CodeIgniter\Pager\Views\default_simple',
|
||
'default_head' => 'CodeIgniter\Pager\Views\default_head',
|
||
];
|
||
/**
|
||
public int $perPage = 20;
|
||
}
|
||
|
||
// app/Config/Feature.php
|
||
<?php
|
||
namespace Config;
|
||
use CodeIgniter\Config\BaseConfig;
|
||
/**
|
||
class Feature extends BaseConfig
|
||
{
|
||
/**
|
||
public bool $autoRoutesImproved = true;
|
||
/**
|
||
public bool $oldFilterOrder = false;
|
||
/**
|
||
public bool $limitZeroAsAll = true;
|
||
/**
|
||
public bool $strictLocaleNegotiation = false;
|
||
}
|
||
|
||
// app/Config/Publisher.php
|
||
<?php
|
||
namespace Config;
|
||
use CodeIgniter\Config\Publisher as BasePublisher;
|
||
/**
|
||
class Publisher extends BasePublisher
|
||
{
|
||
/**
|
||
public $restrictions = [
|
||
ROOTPATH => '*',
|
||
FCPATH => '#\.(s?css|js|map|html?|xml|json|webmanifest|ttf|eot|woff2?|gif|jpe?g|tiff?|png|webp|bmp|ico|svg)$#i',
|
||
];
|
||
}
|
||
|
||
// app/Config/ForeignCharacters.php
|
||
<?php
|
||
namespace Config;
|
||
use CodeIgniter\Config\ForeignCharacters as BaseForeignCharacters;
|
||
/**
|
||
class ForeignCharacters extends BaseForeignCharacters
|
||
{
|
||
}
|
||
|
||
// app/Config/Email.php
|
||
<?php
|
||
namespace Config;
|
||
use CodeIgniter\Config\BaseConfig;
|
||
class Email extends BaseConfig
|
||
{
|
||
public string $fromEmail = '';
|
||
public string $fromName = '';
|
||
public string $recipients = '';
|
||
/**
|
||
public string $userAgent = 'CodeIgniter';
|
||
/**
|
||
public string $protocol = 'mail';
|
||
/**
|
||
public string $mailPath = '/usr/sbin/sendmail';
|
||
/**
|
||
public string $SMTPHost = '';
|
||
/**
|
||
public string $SMTPUser = '';
|
||
/**
|
||
public string $SMTPPass = '';
|
||
/**
|
||
public int $SMTPPort = 25;
|
||
/**
|
||
public int $SMTPTimeout = 5;
|
||
/**
|
||
public bool $SMTPKeepAlive = false;
|
||
/**
|
||
public string $SMTPCrypto = 'tls';
|
||
/**
|
||
public bool $wordWrap = true;
|
||
/**
|
||
public int $wrapChars = 76;
|
||
/**
|
||
public string $mailType = 'text';
|
||
/**
|
||
public string $charset = 'UTF-8';
|
||
/**
|
||
public bool $validate = false;
|
||
/**
|
||
public int $priority = 3;
|
||
/**
|
||
public string $CRLF = "\r\n";
|
||
/**
|
||
public string $newline = "\r\n";
|
||
/**
|
||
public bool $BCCBatchMode = false;
|
||
/**
|
||
public int $BCCBatchSize = 200;
|
||
/**
|
||
public bool $DSN = false;
|
||
}
|
||
|
||
// app/Config/Routes.php
|
||
<?php
|
||
use CodeIgniter\Router\RouteCollection;
|
||
/**
|
||
$routes->get('/', 'Home::index');
|
||
$routes->get('login', 'Auth::login');
|
||
$routes->post('login', 'Auth::login');
|
||
$routes->get('register', 'Auth::register');
|
||
$routes->post('register', 'Auth::register');
|
||
$routes->get('register/success', 'Auth::registerSuccess');
|
||
$routes->get('logout', 'Auth::logout');
|
||
$routes->get('auth/verify/(:any)', 'Auth::verify/$1');
|
||
$routes->get('auth/resend-verification', 'Auth::resendVerification');
|
||
$routes->post('auth/resend-verification', 'Auth::resendVerification');
|
||
$routes->group('', ['filter' => 'org'], static function ($routes) {
|
||
$routes->get('organizations', 'Organizations::index');
|
||
$routes->get('organizations/create', 'Organizations::create');
|
||
$routes->post('organizations/create', 'Organizations::create');
|
||
$routes->get('organizations/edit/(:num)', 'Organizations::edit/$1');
|
||
$routes->post('organizations/edit/(:num)', 'Organizations::edit/$1');
|
||
$routes->get('organizations/delete/(:num)', 'Organizations::delete/$1');
|
||
$routes->post('organizations/delete/(:num)', 'Organizations::delete/$1');
|
||
$routes->get('organizations/switch/(:num)', 'Organizations::switch/$1');
|
||
});
|
||
require_once APPPATH . 'Modules/Clients/Config/Routes.php';
|
||
|
||
// app/Config/Database.php
|
||
<?php
|
||
namespace Config;
|
||
use CodeIgniter\Database\Config;
|
||
/**
|
||
class Database extends Config
|
||
{
|
||
/**
|
||
public string $filesPath = APPPATH . 'Database' . DIRECTORY_SEPARATOR;
|
||
/**
|
||
public string $defaultGroup = 'default';
|
||
/**
|
||
public array $default = [
|
||
'DSN' => '',
|
||
'hostname' => 'localhost',
|
||
'username' => '',
|
||
'password' => '',
|
||
'database' => '',
|
||
'DBDriver' => 'MySQLi',
|
||
'DBPrefix' => '',
|
||
'pConnect' => false,
|
||
'DBDebug' => true,
|
||
'charset' => 'utf8mb4',
|
||
'DBCollat' => 'utf8mb4_general_ci',
|
||
'swapPre' => '',
|
||
'encrypt' => false,
|
||
'compress' => false,
|
||
'strictOn' => false,
|
||
'failover' => [],
|
||
'port' => 3306,
|
||
'numberNative' => false,
|
||
'foundRows' => false,
|
||
'dateFormat' => [
|
||
'date' => 'Y-m-d',
|
||
'datetime' => 'Y-m-d H:i:s',
|
||
'time' => 'H:i:s',
|
||
],
|
||
];
|
||
/**
|
||
public array $tests = [
|
||
'DSN' => '',
|
||
'hostname' => '127.0.0.1',
|
||
'username' => '',
|
||
'password' => '',
|
||
'database' => ':memory:',
|
||
'DBDriver' => 'SQLite3',
|
||
'DBPrefix' => 'db_',
|
||
'pConnect' => false,
|
||
'DBDebug' => true,
|
||
'charset' => 'utf8',
|
||
'DBCollat' => '',
|
||
'swapPre' => '',
|
||
'encrypt' => false,
|
||
'compress' => false,
|
||
'strictOn' => false,
|
||
'failover' => [],
|
||
'port' => 3306,
|
||
'foreignKeys' => true,
|
||
'busyTimeout' => 1000,
|
||
'synchronous' => null,
|
||
'dateFormat' => [
|
||
'date' => 'Y-m-d',
|
||
'datetime' => 'Y-m-d H:i:s',
|
||
'time' => 'H:i:s',
|
||
],
|
||
];
|
||
public function __construct()
|
||
{
|
||
parent::__construct();
|
||
if (ENVIRONMENT === 'testing') {
|
||
$this->defaultGroup = 'tests';
|
||
}
|
||
}
|
||
}
|
||
|
||
// app/Config/Paths.php
|
||
<?php
|
||
namespace Config;
|
||
/**
|
||
class Paths
|
||
{
|
||
/**
|
||
public string $systemDirectory = __DIR__ . '/../../vendor/codeigniter4/framework/system';
|
||
/**
|
||
public string $appDirectory = __DIR__ . '/..';
|
||
/**
|
||
public string $writableDirectory = __DIR__ . '/../../writable';
|
||
/**
|
||
public string $testsDirectory = __DIR__ . '/../../tests';
|
||
/**
|
||
public string $viewDirectory = __DIR__ . '/../Views';
|
||
}
|
||
|
||
// app/Config/Kint.php
|
||
<?php
|
||
namespace Config;
|
||
use Kint\Parser\ConstructablePluginInterface;
|
||
use Kint\Renderer\Rich\TabPluginInterface;
|
||
use Kint\Renderer\Rich\ValuePluginInterface;
|
||
/**
|
||
class Kint
|
||
{
|
||
/*
|
||
|--------------------------------------------------------------------------
|
||
| Global Settings
|
||
|--------------------------------------------------------------------------
|
||
/**
|
||
public $plugins;
|
||
public int $maxDepth = 6;
|
||
public bool $displayCalledFrom = true;
|
||
public bool $expanded = false;
|
||
/*
|
||
|--------------------------------------------------------------------------
|
||
| RichRenderer Settings
|
||
|--------------------------------------------------------------------------
|
||
public string $richTheme = 'aante-light.css';
|
||
public bool $richFolder = false;
|
||
/**
|
||
public $richObjectPlugins;
|
||
/**
|
||
public $richTabPlugins;
|
||
/*
|
||
|--------------------------------------------------------------------------
|
||
| CLI Settings
|
||
|--------------------------------------------------------------------------
|
||
public bool $cliColors = true;
|
||
public bool $cliForceUTF8 = false;
|
||
public bool $cliDetectWidth = true;
|
||
public int $cliMinWidth = 40;
|
||
}
|
||
|
||
// app/Config/Routing.php
|
||
<?php
|
||
/**
|
||
namespace Config;
|
||
use CodeIgniter\Config\Routing as BaseRouting;
|
||
/**
|
||
class Routing extends BaseRouting
|
||
{
|
||
/**
|
||
public array $routeFiles = [
|
||
APPPATH . 'Config/Routes.php',
|
||
];
|
||
/**
|
||
public string $defaultNamespace = 'App\Controllers';
|
||
/**
|
||
public string $defaultController = 'Home';
|
||
/**
|
||
public string $defaultMethod = 'index';
|
||
/**
|
||
public bool $translateURIDashes = false;
|
||
/**
|
||
public ?string $override404 = null;
|
||
/**
|
||
public bool $autoRoute = false;
|
||
/**
|
||
public bool $prioritize = false;
|
||
/**
|
||
public bool $multipleSegmentsOneParam = false;
|
||
/**
|
||
public array $moduleRoutes = [];
|
||
/**
|
||
public bool $translateUriToCamelCase = true;
|
||
}
|
||
|
||
// app/Config/Logger.php
|
||
<?php
|
||
namespace Config;
|
||
use CodeIgniter\Config\BaseConfig;
|
||
use CodeIgniter\Log\Handlers\FileHandler;
|
||
use CodeIgniter\Log\Handlers\HandlerInterface;
|
||
class Logger extends BaseConfig
|
||
{
|
||
/**
|
||
public $threshold = (ENVIRONMENT === 'production') ? 4 : 9;
|
||
/**
|
||
public string $dateFormat = 'Y-m-d H:i:s';
|
||
/**
|
||
public array $handlers = [
|
||
/*
|
||
FileHandler::class => [
|
||
'handles' => [
|
||
'critical',
|
||
'alert',
|
||
'emergency',
|
||
'debug',
|
||
'error',
|
||
'info',
|
||
'notice',
|
||
'warning',
|
||
],
|
||
/*
|
||
'fileExtension' => '',
|
||
/*
|
||
'filePermissions' => 0644,
|
||
/*
|
||
'path' => '',
|
||
],
|
||
/*
|
||
/*
|
||
];
|
||
}
|
||
|
||
// app/Config/Events.php
|
||
<?php
|
||
namespace Config;
|
||
use CodeIgniter\Events\Events;
|
||
use CodeIgniter\Exceptions\FrameworkException;
|
||
use CodeIgniter\HotReloader\HotReloader;
|
||
/*
|
||
Events::on('pre_system', static function (): void {
|
||
if (ENVIRONMENT !== 'testing') {
|
||
if (ini_get('zlib.output_compression')) {
|
||
throw FrameworkException::forEnabledZlibOutputCompression();
|
||
}
|
||
while (ob_get_level() > 0) {
|
||
ob_end_flush();
|
||
}
|
||
ob_start(static fn ($buffer) => $buffer);
|
||
}
|
||
/*
|
||
if (CI_DEBUG && ! is_cli()) {
|
||
Events::on('DBQuery', 'CodeIgniter\Debug\Toolbar\Collectors\Database::collect');
|
||
service('toolbar')->respond();
|
||
if (ENVIRONMENT === 'development') {
|
||
service('routes')->get('__hot-reload', static function (): void {
|
||
(new HotReloader())->run();
|
||
});
|
||
}
|
||
}
|
||
});
|
||
|
||
// app/Config/DocTypes.php
|
||
<?php
|
||
namespace Config;
|
||
class DocTypes
|
||
{
|
||
/**
|
||
public array $list = [
|
||
'xhtml11' => '<!DOCTYPE html PUBLIC "-
|
||
'xhtml1-strict' => '<!DOCTYPE html PUBLIC "-
|
||
'xhtml1-trans' => '<!DOCTYPE html PUBLIC "-
|
||
'xhtml1-frame' => '<!DOCTYPE html PUBLIC "-
|
||
'xhtml-basic11' => '<!DOCTYPE html PUBLIC "-
|
||
'html5' => '<!DOCTYPE html>',
|
||
'html4-strict' => '<!DOCTYPE HTML PUBLIC "-
|
||
'html4-trans' => '<!DOCTYPE HTML PUBLIC "-
|
||
'html4-frame' => '<!DOCTYPE HTML PUBLIC "-
|
||
'mathml1' => '<!DOCTYPE math SYSTEM "http:
|
||
'mathml2' => '<!DOCTYPE math PUBLIC "-
|
||
'svg10' => '<!DOCTYPE svg PUBLIC "-
|
||
'svg11' => '<!DOCTYPE svg PUBLIC "-
|
||
'svg11-basic' => '<!DOCTYPE svg PUBLIC "-
|
||
'svg11-tiny' => '<!DOCTYPE svg PUBLIC "-
|
||
'xhtml-math-svg-xh' => '<!DOCTYPE html PUBLIC "-
|
||
'xhtml-math-svg-sh' => '<!DOCTYPE svg:svg PUBLIC "-
|
||
'xhtml-rdfa-1' => '<!DOCTYPE html PUBLIC "-
|
||
'xhtml-rdfa-2' => '<!DOCTYPE html PUBLIC "-
|
||
];
|
||
/**
|
||
public bool $html5 = true;
|
||
}
|
||
|
||
// app/Config/Security.php
|
||
<?php
|
||
namespace Config;
|
||
use CodeIgniter\Config\BaseConfig;
|
||
class Security extends BaseConfig
|
||
{
|
||
/**
|
||
public string $csrfProtection = 'cookie';
|
||
/**
|
||
public bool $tokenRandomize = false;
|
||
/**
|
||
public string $tokenName = 'csrf_bp';
|
||
/**
|
||
public string $headerName = 'X-CSRF-TOKEN';
|
||
/**
|
||
public string $cookieName = 'csrf_cookie_name';
|
||
/**
|
||
public int $expires = 7200;
|
||
/**
|
||
public bool $regenerate = true;
|
||
/**
|
||
public bool $redirect = (ENVIRONMENT === 'production');
|
||
}
|
||
|
||
// app/Config/Encryption.php
|
||
<?php
|
||
namespace Config;
|
||
use CodeIgniter\Config\BaseConfig;
|
||
/**
|
||
class Encryption extends BaseConfig
|
||
{
|
||
/**
|
||
public string $key = '';
|
||
/**
|
||
public string $driver = 'OpenSSL';
|
||
/**
|
||
public int $blockSize = 16;
|
||
/**
|
||
public string $digest = 'SHA512';
|
||
/**
|
||
public bool $rawData = true;
|
||
/**
|
||
public string $encryptKeyInfo = '';
|
||
/**
|
||
public string $authKeyInfo = '';
|
||
/**
|
||
public string $cipher = 'AES-256-CTR';
|
||
}
|
||
|
||
// app/Config/Toolbar.php
|
||
<?php
|
||
namespace Config;
|
||
use CodeIgniter\Config\BaseConfig;
|
||
use CodeIgniter\Debug\Toolbar\Collectors\Database;
|
||
use CodeIgniter\Debug\Toolbar\Collectors\Events;
|
||
use CodeIgniter\Debug\Toolbar\Collectors\Files;
|
||
use CodeIgniter\Debug\Toolbar\Collectors\Logs;
|
||
use CodeIgniter\Debug\Toolbar\Collectors\Routes;
|
||
use CodeIgniter\Debug\Toolbar\Collectors\Timers;
|
||
use CodeIgniter\Debug\Toolbar\Collectors\Views;
|
||
/**
|
||
class Toolbar extends BaseConfig
|
||
{
|
||
/**
|
||
public array $collectors = [
|
||
Timers::class,
|
||
Database::class,
|
||
Logs::class,
|
||
Views::class,
|
||
Files::class,
|
||
Routes::class,
|
||
Events::class,
|
||
];
|
||
/**
|
||
public bool $collectVarData = true;
|
||
/**
|
||
public int $maxHistory = 20;
|
||
/**
|
||
public string $viewsPath = SYSTEMPATH . 'Debug/Toolbar/Views/';
|
||
/**
|
||
public int $maxQueries = 100;
|
||
/**
|
||
public array $watchedDirectories = [
|
||
'app',
|
||
];
|
||
/**
|
||
public array $watchedExtensions = [
|
||
'php', 'css', 'js', 'html', 'svg', 'json', 'env',
|
||
];
|
||
}
|
||
|
||
// app/Config/Optimize.php
|
||
<?php
|
||
namespace Config;
|
||
/**
|
||
class Optimize
|
||
{
|
||
/**
|
||
public bool $configCacheEnabled = false;
|
||
/**
|
||
public bool $locatorCacheEnabled = false;
|
||
}
|
||
|
||
// app/Config/Boot/testing.php
|
||
<?php
|
||
/*
|
||
/*
|
||
|--------------------------------------------------------------------------
|
||
| ERROR DISPLAY
|
||
|--------------------------------------------------------------------------
|
||
| In development, we want to show as many errors as possible to help
|
||
| make sure they don't make it to production. And save us hours of
|
||
| painful debugging.
|
||
error_reporting(E_ALL);
|
||
ini_set('display_errors', '1');
|
||
/*
|
||
|--------------------------------------------------------------------------
|
||
| DEBUG BACKTRACES
|
||
|--------------------------------------------------------------------------
|
||
| If true, this constant will tell the error screens to display debug
|
||
| backtraces along with the other error information. If you would
|
||
| prefer to not see this, set this value to false.
|
||
defined('SHOW_DEBUG_BACKTRACE') || define('SHOW_DEBUG_BACKTRACE', true);
|
||
/*
|
||
|--------------------------------------------------------------------------
|
||
| DEBUG MODE
|
||
|--------------------------------------------------------------------------
|
||
| Debug mode is an experimental flag that can allow changes throughout
|
||
| the system. It's not widely used currently, and may not survive
|
||
| release of the framework.
|
||
defined('CI_DEBUG') || define('CI_DEBUG', true);
|
||
|
||
// app/Config/Boot/production.php
|
||
<?php
|
||
/*
|
||
|--------------------------------------------------------------------------
|
||
| ERROR DISPLAY
|
||
|--------------------------------------------------------------------------
|
||
| Don't show ANY in production environments. Instead, let the system catch
|
||
| it and display a generic error message.
|
||
|
|
||
| If you set 'display_errors' to '1', CI4's detailed error report will show.
|
||
error_reporting(E_ALL & ~E_DEPRECATED);
|
||
ini_set('display_errors', '0');
|
||
/*
|
||
|--------------------------------------------------------------------------
|
||
| DEBUG MODE
|
||
|--------------------------------------------------------------------------
|
||
| Debug mode is an experimental flag that can allow changes throughout
|
||
| the system. It's not widely used currently, and may not survive
|
||
| release of the framework.
|
||
defined('CI_DEBUG') || define('CI_DEBUG', false);
|
||
|
||
// app/Config/Boot/development.php
|
||
<?php
|
||
/*
|
||
|--------------------------------------------------------------------------
|
||
| ERROR DISPLAY
|
||
|--------------------------------------------------------------------------
|
||
| In development, we want to show as many errors as possible to help
|
||
| make sure they don't make it to production. And save us hours of
|
||
| painful debugging.
|
||
|
|
||
| If you set 'display_errors' to '1', CI4's detailed error report will show.
|
||
error_reporting(E_ALL);
|
||
ini_set('display_errors', '1');
|
||
/*
|
||
|--------------------------------------------------------------------------
|
||
| DEBUG BACKTRACES
|
||
|--------------------------------------------------------------------------
|
||
| If true, this constant will tell the error screens to display debug
|
||
| backtraces along with the other error information. If you would
|
||
| prefer to not see this, set this value to false.
|
||
defined('SHOW_DEBUG_BACKTRACE') || define('SHOW_DEBUG_BACKTRACE', true);
|
||
/*
|
||
|--------------------------------------------------------------------------
|
||
| DEBUG MODE
|
||
|--------------------------------------------------------------------------
|
||
| Debug mode is an experimental flag that can allow changes throughout
|
||
| the system. This will control whether Kint is loaded, and a few other
|
||
| items. It can always be used within your own application too.
|
||
defined('CI_DEBUG') || define('CI_DEBUG', true);
|
||
|
||
// app/Config/Filters.php
|
||
<?php
|
||
namespace Config;
|
||
use CodeIgniter\Config\Filters as BaseFilters;
|
||
use CodeIgniter\Filters\Cors;
|
||
use CodeIgniter\Filters\CSRF;
|
||
use CodeIgniter\Filters\DebugToolbar;
|
||
use CodeIgniter\Filters\ForceHTTPS;
|
||
use CodeIgniter\Filters\Honeypot;
|
||
use CodeIgniter\Filters\InvalidChars;
|
||
use CodeIgniter\Filters\PageCache;
|
||
use CodeIgniter\Filters\PerformanceMetrics;
|
||
use CodeIgniter\Filters\SecureHeaders;
|
||
class Filters extends BaseFilters
|
||
{
|
||
/**
|
||
public array $aliases = [
|
||
'csrf' => CSRF::class,
|
||
'toolbar' => DebugToolbar::class,
|
||
'honeypot' => Honeypot::class,
|
||
'invalidchars' => InvalidChars::class,
|
||
'secureheaders' => SecureHeaders::class,
|
||
'cors' => Cors::class,
|
||
'forcehttps' => ForceHTTPS::class,
|
||
'pagecache' => PageCache::class,
|
||
'performance' => PerformanceMetrics::class,
|
||
'org' => \App\Filters\OrganizationFilter::class,
|
||
];
|
||
/**
|
||
public array $required = [
|
||
'before' => [
|
||
'forcehttps',
|
||
'pagecache',
|
||
],
|
||
'after' => [
|
||
'pagecache',
|
||
'performance',
|
||
'toolbar',
|
||
],
|
||
];
|
||
/**
|
||
public array $globals = [
|
||
'before' => [
|
||
'csrf',
|
||
],
|
||
'after' => [
|
||
],
|
||
];
|
||
/**
|
||
public array $methods = [];
|
||
/**
|
||
public array $filters = [];
|
||
}
|
||
|
||
// app/Config/App.php
|
||
<?php
|
||
namespace Config;
|
||
use CodeIgniter\Config\BaseConfig;
|
||
class App extends BaseConfig
|
||
{
|
||
/**
|
||
public string $baseURL = 'http:
|
||
/**
|
||
public array $allowedHostnames = [];
|
||
/**
|
||
public string $indexPage = '';
|
||
/**
|
||
public string $uriProtocol = 'REQUEST_URI';
|
||
/*
|
||
|--------------------------------------------------------------------------
|
||
| Allowed URL Characters
|
||
|--------------------------------------------------------------------------
|
||
|
|
||
| This lets you specify which characters are permitted within your URLs.
|
||
| When someone tries to submit a URL with disallowed characters they will
|
||
| get a warning message.
|
||
|
|
||
| As a security measure you are STRONGLY encouraged to restrict URLs to
|
||
| as few characters as possible.
|
||
|
|
||
| By default, only these are allowed: `a-z 0-9~%.:_-`
|
||
|
|
||
| Set an empty string to allow all characters -- but only if you are insane.
|
||
|
|
||
| The configured value is actually a regular expression character group
|
||
| and it will be used as: '/\A[<permittedURIChars>]+\z/iu'
|
||
|
|
||
| DO NOT CHANGE THIS UNLESS YOU FULLY UNDERSTAND THE REPERCUSSIONS!!
|
||
|
|
||
public string $permittedURIChars = 'a-z 0-9~%.:_\-';
|
||
/**
|
||
public string $defaultLocale = 'en';
|
||
/**
|
||
public bool $negotiateLocale = false;
|
||
/**
|
||
public array $supportedLocales = ['en'];
|
||
/**
|
||
public string $appTimezone = 'UTC';
|
||
/**
|
||
public string $charset = 'UTF-8';
|
||
/**
|
||
public bool $forceGlobalSecureRequests = false;
|
||
/**
|
||
public array $proxyIPs = [];
|
||
/**
|
||
public bool $CSPEnabled = false;
|
||
}
|
||
|
||
// app/Config/Validation.php
|
||
<?php
|
||
namespace Config;
|
||
use CodeIgniter\Config\BaseConfig;
|
||
use CodeIgniter\Validation\StrictRules\CreditCardRules;
|
||
use CodeIgniter\Validation\StrictRules\FileRules;
|
||
use CodeIgniter\Validation\StrictRules\FormatRules;
|
||
use CodeIgniter\Validation\StrictRules\Rules;
|
||
class Validation extends BaseConfig
|
||
{
|
||
/**
|
||
public array $ruleSets = [
|
||
Rules::class,
|
||
FormatRules::class,
|
||
FileRules::class,
|
||
CreditCardRules::class,
|
||
];
|
||
/**
|
||
public array $templates = [
|
||
'list' => 'CodeIgniter\Validation\Views\list',
|
||
'single' => 'CodeIgniter\Validation\Views\single',
|
||
];
|
||
}
|
||
|
||
// app/Config/Modules.php
|
||
<?php
|
||
namespace Config;
|
||
use CodeIgniter\Modules\Modules as BaseModules;
|
||
/**
|
||
class Modules extends BaseModules
|
||
{
|
||
/**
|
||
public $enabled = true;
|
||
/**
|
||
public $discoverInComposer = true;
|
||
/**
|
||
public $composerPackages = [];
|
||
/**
|
||
public $aliases = [
|
||
'events',
|
||
'filters',
|
||
'registrars',
|
||
'routes',
|
||
'services',
|
||
];
|
||
}
|
||
|
||
// app/Config/Twig.php
|
||
<?php
|
||
namespace Config;
|
||
use CodeIgniter\Config\BaseConfig;
|
||
class Twig extends \Daycry\Twig\Config\Twig
|
||
{
|
||
public string $extension = '.twig';
|
||
/**
|
||
public string $cachePath = WRITEPATH . 'cache/twig';
|
||
/**
|
||
public array $functions_safe = ['form_open', 'form_close', 'form_hidden', 'form_error', 'json_decode', 'set_value', 'csrf_field'];
|
||
/**
|
||
public array $functions_asis = ['session', 'current_url', 'base_url', 'site_url'];
|
||
/**
|
||
public array $paths = [
|
||
APPPATH . 'Views',
|
||
[APPPATH . 'Views/components', 'components'],
|
||
[APPPATH . 'Modules/Clients/Views', 'Clients']
|
||
];
|
||
/**
|
||
public array $filters = [];
|
||
/**
|
||
public array $extensions = [
|
||
\App\Libraries\Twig\TwigJsonDecodeExtension::class,
|
||
\App\Libraries\Twig\TwigGlobalsExtension::class,
|
||
];
|
||
/**
|
||
public bool $strictVariables = false;
|
||
/**
|
||
public bool $saveData = true;
|
||
/**
|
||
public bool $leanMode = false;
|
||
/**
|
||
public ?bool $enableDiscoverySnapshot = null;
|
||
/**
|
||
public ?bool $enableWarmupSummary = null;
|
||
/**
|
||
public ?bool $enableInvalidationHistory = null;
|
||
/**
|
||
public ?bool $enableDynamicMetrics = null;
|
||
/**
|
||
public ?bool $enableExtendedDiagnostics = null;
|
||
/**
|
||
public int $cacheTtl = 0;
|
||
/**
|
||
public bool $toolbarMinimal = false;
|
||
public bool $toolbarShowTemplates = true;
|
||
public int $toolbarMaxTemplates = 50;
|
||
public bool $toolbarShowCapabilities = true;
|
||
public bool $toolbarShowPersistence = true;
|
||
}
|
||
|
||
// app/ThirdParty/.gitkeep
|
||
|
||
// app/Modules/Clients/Views/form.twig
|
||
{% extends 'layouts/base.twig' %}
|
||
{% import 'macros/forms.twig' as forms %}
|
||
|
||
{% block content %}
|
||
<div class="row justify-content-center">
|
||
<div class="col-lg-8">
|
||
<div class="card shadow-sm">
|
||
<div class="card-header bg-white py-3">
|
||
<div class="d-flex align-items-center">
|
||
<a href="{{ base_url('/clients') }}" class="btn btn-outline-secondary me-3">
|
||
<i class="fa-solid fa-arrow-left"></i>
|
||
</a>
|
||
<div>
|
||
<h1 class="h4 mb-0">{{ title }}</h1>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card-body">
|
||
{{ forms.form_open(client ? base_url('/clients/update/' ~ client.id) : base_url('/clients/create')) }}
|
||
|
||
<div class="mb-3">
|
||
<label for="name" class="form-label fw-bold">Имя / Название *</label>
|
||
<input type="text" name="name" id="name" class="form-control {{ errors.name ? 'is-invalid' : '' }}"
|
||
value="{{ old.name ?? client.name ?? '' }}" required autofocus>
|
||
{% if errors.name %}
|
||
<div class="invalid-feedback">{{ errors.name }}</div>
|
||
{% endif %}
|
||
<div class="form-text">ФИО клиента или название компании</div>
|
||
</div>
|
||
|
||
<div class="row">
|
||
<div class="col-md-6 mb-3">
|
||
<label for="email" class="form-label">Email</label>
|
||
<input type="email" name="email" id="email" class="form-control {{ errors.email ? 'is-invalid' : '' }}"
|
||
value="{{ old.email ?? client.email ?? '' }}">
|
||
{% if errors.email %}
|
||
<div class="invalid-feedback">{{ errors.email }}</div>
|
||
{% endif %}
|
||
</div>
|
||
|
||
<div class="col-md-6 mb-3">
|
||
<label for="phone" class="form-label">Телефон</label>
|
||
<input type="tel" name="phone" id="phone" class="form-control {{ errors.phone ? 'is-invalid' : '' }}"
|
||
value="{{ old.phone ?? client.phone ?? '' }}">
|
||
{% if errors.phone %}
|
||
<div class="invalid-feedback">{{ errors.phone }}</div>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
|
||
<div class="mb-4">
|
||
<label for="notes" class="form-label">Заметки</label>
|
||
<textarea name="notes" id="notes" rows="4" class="form-control {{ errors.notes ? 'is-invalid' : '' }}"
|
||
placeholder="Дополнительная информация о клиенте...">{{ old.notes ?? client.notes ?? '' }}</textarea>
|
||
{% if errors.notes %}
|
||
<div class="invalid-feedback">{{ errors.notes }}</div>
|
||
{% endif %}
|
||
</div>
|
||
|
||
<div class="d-flex justify-content-end gap-2">
|
||
<a href="{{ base_url('/clients') }}" class="btn btn-secondary">Отмена</a>
|
||
<button type="submit" class="btn btn-primary">
|
||
<i class="fa-solid fa-check me-2"></i>
|
||
{{ client ? 'Сохранить изменения' : 'Добавить клиента' }}
|
||
</button>
|
||
</div>
|
||
{{ forms.form_close() }}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
{% endblock %}
|
||
|
||
// app/Modules/Clients/Views/_table.twig
|
||
{# app/Modules/Clients/Views/_table.twig #}
|
||
|
||
{# Определяем тип запроса: AJAX = только tbody + footer #}
|
||
{% set isAjax = app.request.headers.get('X-Requested-With') == 'XMLHttpRequest' %}
|
||
|
||
{# Настройки пагинации #}
|
||
{% if pager %}
|
||
{% set pagination = {
|
||
currentPage: pager.getCurrentPage()|default(1),
|
||
totalPages: pager.getPageCount()|default(1),
|
||
totalRecords: pager.getTotal()|default(0),
|
||
perPage: perPage|default(10),
|
||
from: ((pager.getCurrentPage() - 1) * perPage + 1)|default(0),
|
||
to: min(pager.getCurrentPage() * perPage, pager.getTotal())|default(0)
|
||
} %}
|
||
{% else %}
|
||
{% set pagination = {
|
||
currentPage: 1,
|
||
totalPages: 1,
|
||
totalRecords: clients|length|default(0),
|
||
perPage: perPage|default(10),
|
||
from: 1,
|
||
to: clients|length|default(0)
|
||
} %}
|
||
{% endif %}
|
||
|
||
{# Проверка на пустое состояние #}
|
||
{% set isEmpty = clients is empty or clients|length == 0 %}
|
||
|
||
{# AJAX запрос - tbody + footer #}
|
||
<tbody>
|
||
{% if isEmpty %}
|
||
<tr>
|
||
<td colspan="4" class="text-center py-5">
|
||
<i class="fa-solid fa-users text-muted mb-3" style="font-size: 3rem;"></i>
|
||
<p class="text-muted mb-3">
|
||
{% if filters.name or filters.email or filters.phone %}
|
||
Клиенты не найдены
|
||
{% else %}
|
||
Клиентов пока нет
|
||
{% endif %}
|
||
</p>
|
||
<a href="{{ base_url('/clients/new') }}" class="btn btn-primary">
|
||
<i class="fa-solid fa-plus me-2"></i>Добавить клиента
|
||
</a>
|
||
</td>
|
||
</tr>
|
||
{% else %}
|
||
{% for client in clients %}
|
||
<tr>
|
||
<td>
|
||
<div class="d-flex align-items-center">
|
||
<div class="bg-primary text-white rounded-circle d-flex justify-content-center align-items-center me-3" style="width:40px;height:40px;">
|
||
{{ client.name|first|upper }}
|
||
</div>
|
||
<div>
|
||
<strong>{{ client.name }}</strong>
|
||
{% if client.notes %}
|
||
<br><small class="text-muted">{{ client.notes|slice(0, 50) }}{{ client.notes|length > 50 ? '...' : '' }}</small>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
</td>
|
||
<td>
|
||
{% if client.email %}
|
||
<a href="mailto:{{ client.email }}">{{ client.email }}</a>
|
||
{% else %}
|
||
<span class="text-muted">—</span>
|
||
{% endif %}
|
||
</td>
|
||
<td>
|
||
{% if client.phone %}
|
||
<a href="tel:{{ client.phone }}">{{ client.phone }}</a>
|
||
{% else %}
|
||
<span class="text-muted">—</span>
|
||
{% endif %}
|
||
</td>
|
||
<td class="text-end">
|
||
<a href="{{ base_url('/clients/edit/' ~ client.id) }}" class="btn btn-sm btn-outline-primary" title="Редактировать">
|
||
<i class="fa-solid fa-pen"></i>
|
||
</a>
|
||
<a href="{{ base_url('/clients/delete/' ~ client.id) }}" class="btn btn-sm btn-outline-danger" title="Удалить" onclick="return confirm('Вы уверены что хотите удалить этого клиента?');">
|
||
<i class="fa-solid fa-trash"></i>
|
||
</a>
|
||
</td>
|
||
</tr>
|
||
{% endfor %}
|
||
{% endif %}
|
||
</tbody>
|
||
{% if not isEmpty and pager %}
|
||
<tfoot>
|
||
<tr>
|
||
<td colspan="4" class="p-0 border-0">
|
||
{{ include('@components/table/pagination.twig', { pagination: pagination, id: 'clients-table' }) }}
|
||
</td>
|
||
</tr>
|
||
</tfoot>
|
||
{% endif %}
|
||
|
||
// app/Modules/Clients/Views/index.twig
|
||
{% extends 'layouts/base.twig' %}
|
||
|
||
{% block content %}
|
||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||
<div>
|
||
<h1 class="h3 mb-0">Клиенты</h1>
|
||
<p class="text-muted mb-0">Управление клиентами вашей организации</p>
|
||
</div>
|
||
<a href="{{ base_url('/clients/new') }}" class="btn btn-primary">
|
||
<i class="fa-solid fa-plus me-2"></i>Добавить клиента
|
||
</a>
|
||
</div>
|
||
|
||
<div class="card shadow-sm">
|
||
<div class="card-header bg-white py-3">
|
||
<div class="d-flex align-items-center justify-content-between">
|
||
<div class="text-muted small">
|
||
Нажмите на <i class="fa-solid fa-search text-muted"></i> для поиска по столбцу
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{# Формируем строки таблицы из клиентов #}
|
||
{% set tableRows = [] %}
|
||
{% if clients is defined and clients|length > 0 %}
|
||
{% for client in clients %}
|
||
{% set tableRows = tableRows|merge([{
|
||
cells: [
|
||
{
|
||
content: '<div class="d-flex align-items-center">
|
||
<div class="bg-primary text-white rounded-circle d-flex justify-content-center align-items-center me-3" style="width:40px;height:40px;">' ~ client.name|first|upper ~ '</div>
|
||
<div><strong>' ~ client.name ~ '</strong>' ~ (client.notes ? '<br><small class="text-muted">' ~ client.notes|slice(0, 50) ~ (client.notes|length > 50 ? '...' : '') ~ '</small>') ~ '</div>
|
||
</div>',
|
||
class: ''
|
||
},
|
||
{
|
||
content: client.email ? '<a href="mailto:' ~ client.email ~ '">' ~ client.email ~ '</a>' : '<span class="text-muted">—</span>',
|
||
class: ''
|
||
},
|
||
{
|
||
content: client.phone ? '<a href="tel:' ~ client.phone ~ '">' ~ client.phone ~ '</a>' : '<span class="text-muted">—</span>',
|
||
class: ''
|
||
}
|
||
],
|
||
actions: '<a href="' ~ base_url('/clients/edit/' ~ client.id) ~ '" class="btn btn-sm btn-outline-primary" title="Редактировать"><i class="fa-solid fa-pen"></i></a>
|
||
<a href="' ~ base_url('/clients/delete/' ~ client.id) ~ '" class="btn btn-sm btn-outline-danger" title="Удалить" onclick="return confirm(\'Вы уверены что хотите удалить этого клиента?\');"><i class="fa-solid fa-trash"></i></a>'
|
||
}]) %}
|
||
{% endfor %}
|
||
{% endif %}
|
||
|
||
<div id="clients-table">
|
||
{{ include('@components/table/table.twig', {
|
||
id: 'clients-table',
|
||
url: '/clients/table',
|
||
perPage: perPage|default(10),
|
||
sort: sort|default(''),
|
||
order: order|default('asc'),
|
||
filters: filters|default({}),
|
||
columns: {
|
||
name: { label: 'Имя / Название', width: '40%' },
|
||
email: { label: 'Email', width: '25%' },
|
||
phone: { label: 'Телефон', width: '20%' }
|
||
},
|
||
rows: tableRows,
|
||
pagerDetails: {
|
||
currentPage: pagerDetails.currentPage|default(1),
|
||
pageCount: pagerDetails.pageCount|default(1),
|
||
total: pagerDetails.total|default(0),
|
||
perPage: perPage|default(10),
|
||
from: pagerDetails.from|default(1),
|
||
to: pagerDetails.to|default(clients|length|default(0))
|
||
},
|
||
actions: { label: 'Действия', width: '15%' },
|
||
emptyMessage: 'Клиентов пока нет'
|
||
}) }}
|
||
{# CSRF токен для AJAX запросов #}
|
||
{{ csrf_field()|raw }}
|
||
</div>
|
||
</div>
|
||
{% endblock %}
|
||
|
||
{% block stylesheets %}
|
||
{{ parent() }}
|
||
<link rel="stylesheet" href="/assets/css/modules/data-table.css">
|
||
{% endblock %}
|
||
|
||
{% block scripts %}
|
||
{{ parent() }}
|
||
<script src="/assets/js/modules/DataTable.js"></script>
|
||
<script>
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
document.querySelectorAll('.data-table').forEach(function(container) {
|
||
const id = container.id;
|
||
const url = container.dataset.url;
|
||
const perPage = parseInt(container.dataset.perPage) || 10;
|
||
|
||
if (window.dataTables && window.dataTables[id]) {
|
||
return;
|
||
}
|
||
|
||
const table = new DataTable(id, {
|
||
url: url,
|
||
perPage: perPage
|
||
});
|
||
|
||
window.dataTables = window.dataTables || {};
|
||
window.dataTables[id] = table;
|
||
});
|
||
});
|
||
</script>
|
||
{% endblock %}
|
||
|
||
// app/Modules/Clients/Models/ClientModel.php
|
||
<?php
|
||
namespace App\Modules\Clients\Models;
|
||
use CodeIgniter\Model;
|
||
class ClientModel extends Model
|
||
{
|
||
protected $table = 'organizations_clients';
|
||
protected $primaryKey = 'id';
|
||
protected $useAutoIncrement = true;
|
||
protected $returnType = 'array';
|
||
protected $useSoftDeletes = true;
|
||
protected $allowedFields = ['organization_id', 'name', 'email', 'phone', 'notes'];
|
||
protected $useTimestamps = true;
|
||
protected $createdField = 'created_at';
|
||
protected $updatedField = 'updated_at';
|
||
protected $deletedField = 'deleted_at';
|
||
/**
|
||
public function forOrganization(int $organizationId)
|
||
{
|
||
return $this->where('organization_id', $organizationId);
|
||
}
|
||
/**
|
||
public function search(int $organizationId, string $query = '')
|
||
{
|
||
$builder = $this->forOrganization($organizationId);
|
||
if (!empty($query)) {
|
||
$builder->groupStart()
|
||
->like('name', $query)
|
||
->orLike('email', $query)
|
||
->orLike('phone', $query)
|
||
->groupEnd();
|
||
}
|
||
return $builder;
|
||
}
|
||
}
|
||
|
||
// app/Modules/Clients/Config/Routes.php
|
||
<?php
|
||
$routes->group('clients', ['filter' => 'org', 'namespace' => 'App\Modules\Clients\Controllers'], static function ($routes) {
|
||
$routes->get('/', 'Clients::index');
|
||
$routes->get('table', 'Clients::table');
|
||
$routes->get('new', 'Clients::new');
|
||
$routes->post('create', 'Clients::create');
|
||
$routes->get('edit/(:num)', 'Clients::edit/$1');
|
||
$routes->post('update/(:num)', 'Clients::update/$1');
|
||
$routes->get('delete/(:num)', 'Clients::delete/$1');
|
||
});
|
||
|
||
// app/Modules/Clients/Controllers/Clients.php
|
||
<?php
|
||
namespace App\Modules\Clients\Controllers;
|
||
use App\Controllers\BaseController;
|
||
use App\Modules\Clients\Models\ClientModel;
|
||
class Clients extends BaseController
|
||
{
|
||
protected $clientModel;
|
||
public function __construct()
|
||
{
|
||
$this->clientModel = new ClientModel();
|
||
}
|
||
public function index()
|
||
{
|
||
$config = $this->getTableConfig();
|
||
$data = $this->prepareTableData($config);
|
||
$data['title'] = 'Клиенты';
|
||
return $this->renderTwig($config['viewPath'], $data);
|
||
}
|
||
/**
|
||
protected function getTableConfig(): array
|
||
{
|
||
$organizationId = session()->get('active_org_id');
|
||
return [
|
||
'model' => $this->clientModel,
|
||
'columns' => [
|
||
'name' => ['label' => 'Имя / Название', 'width' => '40%'],
|
||
'email' => ['label' => 'Email', 'width' => '25%'],
|
||
'phone' => ['label' => 'Телефон', 'width' => '20%'],
|
||
],
|
||
'searchable' => ['name', 'email', 'phone'],
|
||
'sortable' => ['name', 'email', 'phone', 'created_at'],
|
||
'defaultSort' => 'name',
|
||
'order' => 'asc',
|
||
'viewPath' => '@Clients/index',
|
||
'partialPath' => '@Clients/_table',
|
||
'itemsKey' => 'clients',
|
||
'scope' => function ($builder) use ($organizationId) {
|
||
$builder->where('organization_id', $organizationId);
|
||
},
|
||
];
|
||
}
|
||
public function table()
|
||
{
|
||
return parent::table();
|
||
}
|
||
public function new()
|
||
{
|
||
$data = [
|
||
'title' => 'Добавить клиента',
|
||
'client' => null,
|
||
];
|
||
return $this->renderTwig('@Clients/form', $data);
|
||
}
|
||
public function create()
|
||
{
|
||
$organizationId = session()->get('active_org_id');
|
||
$rules = [
|
||
'name' => 'required|min_length[2]|max_length[255]',
|
||
'email' => 'permit_empty|valid_email',
|
||
'phone' => 'permit_empty|max_length[50]',
|
||
];
|
||
if (!$this->validate($rules)) {
|
||
return redirect()->back()->withInput()->with('errors', $this->validator->getErrors());
|
||
}
|
||
$this->clientModel->insert([
|
||
'organization_id' => $organizationId,
|
||
'name' => $this->request->getPost('name'),
|
||
'email' => $this->request->getPost('email') ?? null,
|
||
'phone' => $this->request->getPost('phone') ?? null,
|
||
'notes' => $this->request->getPost('notes') ?? null,
|
||
]);
|
||
if ($this->clientModel->errors()) {
|
||
return redirect()->back()->withInput()->with('error', 'Ошибка при создании клиента');
|
||
}
|
||
session()->setFlashdata('success', 'Клиент успешно добавлен');
|
||
return redirect()->to('/clients');
|
||
}
|
||
public function edit($id)
|
||
{
|
||
$organizationId = session()->get('active_org_id');
|
||
$client = $this->clientModel
|
||
->where('id', $id)
|
||
->where('organization_id', $organizationId)
|
||
->first();
|
||
if (!$client) {
|
||
throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound('Клиент не найден');
|
||
}
|
||
$data = [
|
||
'title' => 'Редактировать клиента',
|
||
'client' => $client,
|
||
];
|
||
return $this->renderTwig('@Clients/form', $data);
|
||
}
|
||
public function update($id)
|
||
{
|
||
$organizationId = session()->get('active_org_id');
|
||
$client = $this->clientModel
|
||
->where('id', $id)
|
||
->where('organization_id', $organizationId)
|
||
->first();
|
||
if (!$client) {
|
||
throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound('Клиент не найден');
|
||
}
|
||
$rules = [
|
||
'name' => 'required|min_length[2]|max_length[255]',
|
||
'email' => 'permit_empty|valid_email',
|
||
'phone' => 'permit_empty|max_length[50]',
|
||
];
|
||
if (!$this->validate($rules)) {
|
||
return redirect()->back()->withInput()->with('errors', $this->validator->getErrors());
|
||
}
|
||
$this->clientModel->update($id, [
|
||
'name' => $this->request->getPost('name'),
|
||
'email' => $this->request->getPost('email') ?? null,
|
||
'phone' => $this->request->getPost('phone') ?? null,
|
||
'notes' => $this->request->getPost('notes') ?? null,
|
||
]);
|
||
if ($this->clientModel->errors()) {
|
||
return redirect()->back()->withInput()->with('error', 'Ошибка при обновлении клиента');
|
||
}
|
||
session()->setFlashdata('success', 'Клиент успешно обновлён');
|
||
return redirect()->to('/clients');
|
||
}
|
||
public function delete($id)
|
||
{
|
||
$organizationId = session()->get('active_org_id');
|
||
$client = $this->clientModel
|
||
->where('id', $id)
|
||
->where('organization_id', $organizationId)
|
||
->first();
|
||
if (!$client) {
|
||
throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound('Клиент не найден');
|
||
}
|
||
$this->clientModel->delete($id);
|
||
session()->setFlashdata('success', 'Клиент удалён');
|
||
return redirect()->to('/clients');
|
||
}
|
||
}
|
||
|
||
// app/Controllers/Auth.php
|
||
<?php
|
||
namespace App\Controllers;
|
||
use App\Models\UserModel;
|
||
use App\Models\OrganizationModel;
|
||
use App\Models\OrganizationUserModel;
|
||
use App\Libraries\EmailLibrary;
|
||
class Auth extends BaseController
|
||
{
|
||
protected $emailLibrary;
|
||
public function __construct()
|
||
{
|
||
$this->emailLibrary = new EmailLibrary();
|
||
}
|
||
public function register()
|
||
{
|
||
if ($this->request->getMethod() === 'POST') {
|
||
log_message('debug', 'POST запрос получен: ' . print_r($this->request->getPost(), true));
|
||
$rules = [
|
||
'name' => 'required|min_length[3]',
|
||
'email' => 'required|valid_email|is_unique[users.email]',
|
||
'password' => 'required|min_length[6]',
|
||
];
|
||
if (!$this->validate($rules)) {
|
||
return redirect()->back()->with('error', 'Ошибка регистрации');
|
||
}
|
||
$userModel = new UserModel();
|
||
$orgModel = new OrganizationModel();
|
||
$orgUserModel = new OrganizationUserModel();
|
||
$verificationToken = bin2hex(random_bytes(32));
|
||
$userData = [
|
||
'name' => $this->request->getPost('name'),
|
||
'email' => $this->request->getPost('email'),
|
||
'password' => $this->request->getPost('password'),
|
||
'verification_token' => $verificationToken,
|
||
'email_verified' => 0,
|
||
];
|
||
log_message('debug', 'Registration userData: ' . print_r($userData, true));
|
||
$userId = $userModel->insert($userData);
|
||
log_message('debug', 'Insert result, userId: ' . $userId);
|
||
$orgData = [
|
||
'owner_id' => $userId,
|
||
'name' => 'Личное пространство',
|
||
'type' => 'personal',
|
||
];
|
||
$orgId = $orgModel->insert($orgData);
|
||
$orgUserModel->insert([
|
||
'organization_id' => $orgId,
|
||
'user_id' => $userId,
|
||
'role' => 'owner',
|
||
'status' => 'active',
|
||
'joined_at' => date('Y-m-d H:i:s'),
|
||
]);
|
||
$this->emailLibrary->sendVerificationEmail(
|
||
$userData['email'],
|
||
$userData['name'],
|
||
$verificationToken
|
||
);
|
||
session()->setFlashdata('success', 'Регистрация успешна! Пожалуйста, проверьте вашу почту и подтвердите email.');
|
||
return redirect()->to('/register/success');
|
||
}
|
||
return $this->renderTwig('auth/register');
|
||
}
|
||
/**
|
||
public function registerSuccess()
|
||
{
|
||
return $this->renderTwig('auth/register_success');
|
||
}
|
||
/**
|
||
public function verify($token)
|
||
{
|
||
log_message('debug', 'Verify called with token: ' . $token);
|
||
if (empty($token)) {
|
||
return $this->renderTwig('auth/verify_error', [
|
||
'message' => 'Отсутствует токен подтверждения.'
|
||
]);
|
||
}
|
||
$userModel = new UserModel();
|
||
$user = $userModel->where('verification_token', $token)->first();
|
||
log_message('debug', 'User found: ' . ($user ? 'yes' : 'no'));
|
||
if ($user) {
|
||
log_message('debug', 'User email_verified: ' . $user['email_verified']);
|
||
}
|
||
if (!$user) {
|
||
return $this->renderTwig('auth/verify_error', [
|
||
'message' => 'Недействительная ссылка для подтверждения. Возможно, ссылка уже была использована или истек срок её действия.'
|
||
]);
|
||
}
|
||
if ($user['email_verified']) {
|
||
return $this->renderTwig('auth/verify_error', [
|
||
'message' => 'Email уже подтверждён. Вы можете войти в систему.'
|
||
]);
|
||
}
|
||
$updateData = [
|
||
'email_verified' => 1,
|
||
'verified_at' => date('Y-m-d H:i:s'),
|
||
'verification_token' => null,
|
||
];
|
||
$result = $userModel->update($user['id'], $updateData);
|
||
log_message('debug', 'Update result: ' . ($result ? 'success' : 'failed'));
|
||
log_message('debug', 'Update data: ' . print_r($updateData, true));
|
||
if (!$result) {
|
||
log_message('error', 'Update errors: ' . print_r($userModel->errors(), true));
|
||
}
|
||
$this->emailLibrary->sendWelcomeEmail($user['email'], $user['name']);
|
||
return $this->renderTwig('auth/verify_success', [
|
||
'name' => $user['name']
|
||
]);
|
||
}
|
||
/**
|
||
public function resendVerification()
|
||
{
|
||
if ($this->request->getMethod() === 'POST') {
|
||
$email = $this->request->getPost('email');
|
||
if (empty($email)) {
|
||
return redirect()->back()->with('error', 'Введите email');
|
||
}
|
||
$userModel = new UserModel();
|
||
$user = $userModel->where('email', $email)->first();
|
||
if (!$user) {
|
||
return redirect()->back()->with('error', 'Пользователь с таким email не найден');
|
||
}
|
||
if ($user['email_verified']) {
|
||
return redirect()->to('/login')->with('info', 'Email уже подтверждён. Вы можете войти.');
|
||
}
|
||
$newToken = bin2hex(random_bytes(32));
|
||
$userModel->update($user['id'], [
|
||
'verification_token' => $newToken
|
||
]);
|
||
$this->emailLibrary->sendVerificationEmail(
|
||
$user['email'],
|
||
$user['name'],
|
||
$newToken
|
||
);
|
||
return redirect()->back()->with('success', 'Письмо для подтверждения отправлено повторно. Проверьте почту.');
|
||
}
|
||
return $this->renderTwig('auth/resend_verification');
|
||
}
|
||
public function login()
|
||
{
|
||
if ($this->request->getMethod() === 'POST') {
|
||
$userModel = new \App\Models\UserModel();
|
||
$orgUserModel = new \App\Models\OrganizationUserModel();
|
||
$email = $this->request->getPost('email');
|
||
$password = $this->request->getPost('password');
|
||
$user = $userModel->where('email', $email)->first();
|
||
if ($user && password_verify($password, $user['password'])) {
|
||
if (!$user['email_verified']) {
|
||
session()->setFlashdata('warning', 'Email не подтверждён. Проверьте почту или <a href="/auth/resend-verification">запросите письмо повторно</a>.');
|
||
return redirect()->to('/login');
|
||
}
|
||
$userOrgs = $orgUserModel->where('user_id', $user['id'])->findAll();
|
||
if (empty($userOrgs)) {
|
||
session()->setFlashdata('error', 'Ваш аккаунт не привязан ни к одной организации. Обратитесь к поддержке.');
|
||
return redirect()->to('/login');
|
||
}
|
||
$sessionData = [
|
||
'user_id' => $user['id'],
|
||
'email' => $user['email'],
|
||
'name' => $user['name'],
|
||
'isLoggedIn' => true
|
||
];
|
||
if (count($userOrgs) === 1) {
|
||
$sessionData['active_org_id'] = $userOrgs[0]['organization_id'];
|
||
session()->set($sessionData);
|
||
return redirect()->to('/');
|
||
}
|
||
session()->remove('active_org_id');
|
||
session()->set($sessionData);
|
||
session()->setFlashdata('info', 'Выберите пространство для работы');
|
||
return redirect()->to('/organizations');
|
||
} else {
|
||
return redirect()->back()->with('error', 'Неверный логин или пароль');
|
||
}
|
||
}
|
||
return $this->renderTwig('auth/login');
|
||
}
|
||
public function logout()
|
||
{
|
||
session()->destroy();
|
||
session()->remove('active_org_id');
|
||
return redirect()->to('/');
|
||
}
|
||
}
|
||
|
||
// app/Controllers/BaseController.php
|
||
<?php
|
||
namespace App\Controllers;
|
||
use CodeIgniter\Controller;
|
||
use CodeIgniter\HTTP\RequestInterface;
|
||
use CodeIgniter\HTTP\ResponseInterface;
|
||
use Psr\Log\LoggerInterface;
|
||
use App\Models\OrganizationModel;
|
||
/**
|
||
abstract class BaseController extends Controller
|
||
{
|
||
/**
|
||
protected $session;
|
||
/**
|
||
public function initController(RequestInterface $request, ResponseInterface $response, LoggerInterface $logger)
|
||
{
|
||
parent::initController($request, $response, $logger);
|
||
$this->session = service('session');
|
||
}
|
||
public function renderTwig($template, $data = [])
|
||
{
|
||
helper('csrf');
|
||
$twig = \Config\Services::twig();
|
||
$oldInput = $this->session->get('_ci_old_input') ?? [];
|
||
$data['old'] = $data['old'] ?? $oldInput;
|
||
ob_start();
|
||
$twig->display($template, $data);
|
||
$content = ob_get_clean();
|
||
return $content;
|
||
}
|
||
/**
|
||
protected function getTableConfig(): array
|
||
{
|
||
return [
|
||
'model' => null,
|
||
'columns' => [],
|
||
'searchable' => [],
|
||
'sortable' => [],
|
||
'defaultSort' => 'id',
|
||
'order' => 'asc',
|
||
'viewPath' => '',
|
||
'partialPath' => '',
|
||
'itemsKey' => 'items',
|
||
'scope' => null,
|
||
];
|
||
}
|
||
/**
|
||
protected function isAjax(): bool
|
||
{
|
||
$header = $this->request->header('X-Requested-With');
|
||
$value = $header ? $header->getValue() : '';
|
||
return strtolower($value) === 'xmlhttprequest';
|
||
}
|
||
/**
|
||
protected function prepareTableData(?array $config = null): array
|
||
{
|
||
$config = array_merge($this->getTableConfig(), $config ?? []);
|
||
$page = (int) ($this->request->getGet('page') ?? 1);
|
||
$perPage = (int) ($this->request->getGet('perPage') ?? 10);
|
||
$sort = $this->request->getGet('sort') ?? $config['defaultSort'];
|
||
$order = $this->request->getGet('order') ?? $config['order'];
|
||
$filters = [];
|
||
foreach ($this->request->getGet() as $key => $value) {
|
||
if (str_starts_with($key, 'filters[') && str_ends_with($key, ']')) {
|
||
$field = substr($key, 9, -1);
|
||
$filters[$field] = $value;
|
||
}
|
||
}
|
||
$model = $config['model'];
|
||
$builder = $model->builder();
|
||
if (isset($config['scope']) && is_callable($config['scope'])) {
|
||
$config['scope']($builder);
|
||
}
|
||
foreach ($filters as $field => $value) {
|
||
if ($value && in_array($field, $config['searchable'])) {
|
||
$builder->like($field, $value);
|
||
}
|
||
}
|
||
if ($sort && in_array($sort, $config['sortable'])) {
|
||
$builder->orderBy($sort, $order);
|
||
}
|
||
$total = $builder->countAll();
|
||
$builder->select('*');
|
||
$items = $builder->limit($perPage, ($page - 1) * $perPage)->get()->getResult();
|
||
$from = ($page - 1) * $perPage + 1;
|
||
$to = min($page * $perPage, $total);
|
||
$pagerData = [
|
||
'currentPage' => $page,
|
||
'pageCount' => $total > 0 ? (int) ceil($total / $perPage) : 1,
|
||
'total' => $total,
|
||
'perPage' => $perPage,
|
||
'from' => $from,
|
||
'to' => $to,
|
||
];
|
||
$pagerStub = new class($pagerData) {
|
||
private $data;
|
||
public function __construct(array $data) { $this->data = $data; }
|
||
public function getCurrentPage(): int { return $this->data['currentPage'] ?? 1; }
|
||
public function getPageCount(): int { return $this->data['pageCount'] ?? 1; }
|
||
public function getTotal(): int { return $this->data['total'] ?? 0; }
|
||
public function getDetails(): array { return $this->data; }
|
||
};
|
||
$data = [
|
||
$config['itemsKey'] => $items,
|
||
'pager' => $pagerStub,
|
||
'pagerDetails' => $pagerData,
|
||
'perPage' => $perPage,
|
||
'sort' => $sort,
|
||
'order' => $order,
|
||
'filters' => $filters,
|
||
'columns' => $config['columns'],
|
||
];
|
||
return $data;
|
||
}
|
||
/**
|
||
public function table()
|
||
{
|
||
$config = $this->getTableConfig();
|
||
$data = $this->prepareTableData($config);
|
||
if (!$this->isAjax()) {
|
||
return redirect()->to('/');
|
||
}
|
||
return $this->renderTwig($config['partialPath'], $data);
|
||
}
|
||
}
|
||
// app/Controllers/Landing.php
|
||
<?php
|
||
namespace App\Controllers;
|
||
class Landing extends BaseController
|
||
{
|
||
public function index()
|
||
{
|
||
if (session()->get('isLoggedIn')) {
|
||
return redirect()->to('/organizations');
|
||
}
|
||
return $this->renderTwig('landing/index');
|
||
}
|
||
}
|
||
// app/Controllers/Organizations.php
|
||
<?php
|
||
namespace App\Controllers;
|
||
use App\Models\OrganizationModel;
|
||
use App\Models\OrganizationUserModel;
|
||
class Organizations extends BaseController
|
||
{
|
||
public function index()
|
||
{
|
||
$orgModel = new OrganizationModel();
|
||
$orgUserModel = new OrganizationUserModel();
|
||
$userId = session()->get('user_id');
|
||
$userOrgLinks = $orgUserModel->where('user_id', $userId)->findAll();
|
||
$orgIds = array_column($userOrgLinks, 'organization_id');
|
||
$organizations = [];
|
||
if (!empty($orgIds)) {
|
||
$organizations = $orgModel->whereIn('id', $orgIds)->findAll();
|
||
}
|
||
return $this->renderTwig('organizations/index', [
|
||
'organizations' => $organizations,
|
||
'count' => count($organizations)
|
||
]);
|
||
}
|
||
public function create()
|
||
{
|
||
if ($this->request->getMethod() === 'POST') {
|
||
$orgModel = new OrganizationModel();
|
||
$orgUserModel = new OrganizationUserModel();
|
||
$rules = [
|
||
'name' => 'required|min_length[2]',
|
||
];
|
||
if (!$this->validate($rules)) {
|
||
return redirect()->back()->withInput()->with('errors', $this->validator->getErrors());
|
||
}
|
||
$requisites = [
|
||
'inn' => trim($this->request->getPost('inn') ?? ''),
|
||
'ogrn' => trim($this->request->getPost('ogrn') ?? ''),
|
||
'kpp' => trim($this->request->getPost('kpp') ?? ''),
|
||
'legal_address' => trim($this->request->getPost('legal_address') ?? ''),
|
||
'actual_address' => trim($this->request->getPost('actual_address') ?? ''),
|
||
'phone' => trim($this->request->getPost('phone') ?? ''),
|
||
'email' => trim($this->request->getPost('email') ?? ''),
|
||
'website' => trim($this->request->getPost('website') ?? ''),
|
||
'bank_name' => trim($this->request->getPost('bank_name') ?? ''),
|
||
'bank_bik' => trim($this->request->getPost('bank_bik') ?? ''),
|
||
'checking_account' => trim($this->request->getPost('checking_account') ?? ''),
|
||
'correspondent_account' => trim($this->request->getPost('correspondent_account') ?? ''),
|
||
];
|
||
$orgId = $orgModel->insert([
|
||
'owner_id' => session()->get('user_id'),
|
||
'name' => $this->request->getPost('name'),
|
||
'type' => 'business',
|
||
'requisites' => json_encode($requisites),
|
||
'settings' => json_encode([]),
|
||
]);
|
||
$orgUserModel->insert([
|
||
'organization_id' => $orgId,
|
||
'user_id' => session()->get('user_id'),
|
||
'role' => 'owner',
|
||
'status' => 'active',
|
||
'joined_at' => date('Y-m-d H:i:s'),
|
||
]);
|
||
session()->set('active_org_id', $orgId);
|
||
session()->setFlashdata('success', 'Организация успешно создана!');
|
||
return redirect()->to('/');
|
||
}
|
||
return $this->renderTwig('organizations/create');
|
||
}
|
||
/**
|
||
public function edit($orgId)
|
||
{
|
||
$orgModel = new OrganizationModel();
|
||
$orgUserModel = new OrganizationUserModel();
|
||
$userId = session()->get('user_id');
|
||
$membership = $orgUserModel->where('organization_id', $orgId)
|
||
->where('user_id', $userId)
|
||
->first();
|
||
if (!$membership) {
|
||
session()->setFlashdata('error', 'Доступ запрещен');
|
||
return redirect()->to('/organizations');
|
||
}
|
||
$organization = $orgModel->find($orgId);
|
||
if (!$organization) {
|
||
session()->setFlashdata('error', 'Организация не найдена');
|
||
return redirect()->to('/organizations');
|
||
}
|
||
$requisites = json_decode($organization['requisites'] ?? '{}', true);
|
||
if ($this->request->getMethod() === 'POST') {
|
||
$rules = [
|
||
'name' => 'required|min_length[2]',
|
||
];
|
||
if (!$this->validate($rules)) {
|
||
return redirect()->back()->withInput()->with('errors', $this->validator->getErrors());
|
||
}
|
||
$newRequisites = [
|
||
'inn' => trim($this->request->getPost('inn') ?? ''),
|
||
'ogrn' => trim($this->request->getPost('ogrn') ?? ''),
|
||
'kpp' => trim($this->request->getPost('kpp') ?? ''),
|
||
'legal_address' => trim($this->request->getPost('legal_address') ?? ''),
|
||
'actual_address' => trim($this->request->getPost('actual_address') ?? ''),
|
||
'phone' => trim($this->request->getPost('phone') ?? ''),
|
||
'email' => trim($this->request->getPost('email') ?? ''),
|
||
'website' => trim($this->request->getPost('website') ?? ''),
|
||
'bank_name' => trim($this->request->getPost('bank_name') ?? ''),
|
||
'bank_bik' => trim($this->request->getPost('bank_bik') ?? ''),
|
||
'checking_account' => trim($this->request->getPost('checking_account') ?? ''),
|
||
'correspondent_account' => trim($this->request->getPost('correspondent_account') ?? ''),
|
||
];
|
||
$orgModel->update($orgId, [
|
||
'name' => $this->request->getPost('name'),
|
||
'requisites' => json_encode($newRequisites),
|
||
]);
|
||
session()->setFlashdata('success', 'Организация успешно обновлена!');
|
||
return redirect()->to('/organizations');
|
||
}
|
||
return $this->renderTwig('organizations/edit', [
|
||
'organization' => $organization,
|
||
'requisites' => $requisites
|
||
]);
|
||
}
|
||
/**
|
||
public function delete($orgId)
|
||
{
|
||
$orgModel = new OrganizationModel();
|
||
$orgUserModel = new OrganizationUserModel();
|
||
$userId = session()->get('user_id');
|
||
$membership = $orgUserModel->where('organization_id', $orgId)
|
||
->where('user_id', $userId)
|
||
->first();
|
||
if (!$membership) {
|
||
session()->setFlashdata('error', 'Доступ запрещен');
|
||
return redirect()->to('/organizations');
|
||
}
|
||
if ($membership['role'] !== 'owner') {
|
||
session()->setFlashdata('error', 'Только владелец может удалить организацию');
|
||
return redirect()->to('/organizations');
|
||
}
|
||
$organization = $orgModel->find($orgId);
|
||
if (!$organization) {
|
||
session()->setFlashdata('error', 'Организация не найдена');
|
||
return redirect()->to('/organizations');
|
||
}
|
||
if ($this->request->getMethod() === 'POST') {
|
||
$orgUserModel->where('organization_id', $orgId)->delete();
|
||
$orgModel->delete($orgId);
|
||
if (session()->get('active_org_id') == $orgId) {
|
||
session()->remove('active_org_id');
|
||
}
|
||
session()->setFlashdata('success', 'Организация "' . $organization['name'] . '" удалена');
|
||
return redirect()->to('/organizations');
|
||
}
|
||
return $this->renderTwig('organizations/delete', [
|
||
'organization' => $organization
|
||
]);
|
||
}
|
||
public function switch($orgId)
|
||
{
|
||
$userId = session()->get('user_id');
|
||
$orgUserModel = new OrganizationUserModel();
|
||
$membership = $orgUserModel->where('organization_id', $orgId)
|
||
->where('user_id', $userId)
|
||
->first();
|
||
if ($membership) {
|
||
session()->set('active_org_id', $orgId);
|
||
session()->setFlashdata('success', 'Организация изменена');
|
||
return redirect()->to('/');
|
||
} else {
|
||
session()->setFlashdata('error', 'Доступ запрещен');
|
||
return redirect()->to('/organizations');
|
||
}
|
||
}
|
||
}
|
||
|
||
// app/Controllers/Home.php
|
||
<?php
|
||
namespace App\Controllers;
|
||
use App\Models\OrganizationModel;
|
||
use App\Models\OrganizationUserModel;
|
||
class Home extends BaseController
|
||
{
|
||
public function index()
|
||
{
|
||
if (!session()->get('isLoggedIn')) {
|
||
return $this->renderTwig('landing/index');
|
||
}
|
||
$orgId = session()->get('active_org_id');
|
||
if (empty($orgId)){
|
||
session()->remove('active_org_id');
|
||
return redirect()->to('/organizations');
|
||
}
|
||
$data = [
|
||
'title' => 'Рабочий стол',
|
||
];
|
||
return $this->renderTwig('dashboard/index', $data);
|
||
}
|
||
}
|
||
// .gitignore
|
||
#-------------------------
|
||
# Operating Specific Junk Files
|
||
#-------------------------
|
||
|
||
# OS X
|
||
.DS_Store
|
||
.AppleDouble
|
||
.LSOverride
|
||
|
||
# OS X Thumbnails
|
||
._*
|
||
|
||
# Windows image file caches
|
||
Thumbs.db
|
||
ehthumbs.db
|
||
Desktop.ini
|
||
|
||
# Recycle Bin used on file shares
|
||
$RECYCLE.BIN/
|
||
|
||
# Windows Installer files
|
||
*.cab
|
||
*.msi
|
||
*.msm
|
||
*.msp
|
||
|
||
# Windows shortcuts
|
||
*.lnk
|
||
|
||
# Linux
|
||
*~
|
||
|
||
# KDE directory preferences
|
||
.directory
|
||
|
||
# Linux trash folder which might appear on any partition or disk
|
||
.Trash-*
|
||
|
||
#-------------------------
|
||
# Environment Files
|
||
#-------------------------
|
||
# These should never be under version control,
|
||
# as it poses a security risk.
|
||
.env
|
||
.vagrant
|
||
Vagrantfile
|
||
|
||
#-------------------------
|
||
# Temporary Files
|
||
#-------------------------
|
||
writable/cache/*
|
||
!writable/cache/index.html
|
||
|
||
writable/logs/*
|
||
!writable/logs/index.html
|
||
|
||
writable/session/*
|
||
!writable/session/index.html
|
||
|
||
writable/uploads/*
|
||
!writable/uploads/index.html
|
||
|
||
writable/debugbar/*
|
||
!writable/debugbar/index.html
|
||
|
||
php_errors.log
|
||
|
||
#-------------------------
|
||
# User Guide Temp Files
|
||
#-------------------------
|
||
user_guide_src/build/*
|
||
user_guide_src/cilexer/build/*
|
||
user_guide_src/cilexer/dist/*
|
||
user_guide_src/cilexer/pycilexer.egg-info/*
|
||
|
||
#-------------------------
|
||
# Test Files
|
||
#-------------------------
|
||
tests/coverage*
|
||
|
||
# Don't save phpunit under version control.
|
||
phpunit
|
||
|
||
#-------------------------
|
||
# Composer
|
||
#-------------------------
|
||
vendor/
|
||
|
||
#-------------------------
|
||
# IDE / Development Files
|
||
#-------------------------
|
||
|
||
# Modules Testing
|
||
_modules/*
|
||
|
||
# phpenv local config
|
||
.php-version
|
||
|
||
# Jetbrains editors (PHPStorm, etc)
|
||
.idea/
|
||
*.iml
|
||
|
||
# NetBeans
|
||
/nbproject/
|
||
/build/
|
||
/nbbuild/
|
||
/dist/
|
||
/nbdist/
|
||
/nbactions.xml
|
||
/nb-configuration.xml
|
||
/.nb-gradle/
|
||
|
||
# Sublime Text
|
||
*.tmlanguage.cache
|
||
*.tmPreferences.cache
|
||
*.stTheme.cache
|
||
*.sublime-workspace
|
||
*.sublime-project
|
||
.phpintel
|
||
/api/
|
||
|
||
# Visual Studio Code
|
||
.vscode/
|
||
|
||
/results/
|
||
/phpunit*.xml
|
||
|
||
// public/assets/css/modules/data-table.css
|
||
/**
|
||
* DataTable - Универсальные стили для интерактивных таблиц
|
||
*
|
||
* Подключение: <link rel="stylesheet" href="/css/components/data-table.css">
|
||
*/
|
||
|
||
/* Основной контейнер таблицы */
|
||
.data-table {
|
||
width: 100%;
|
||
}
|
||
|
||
/* Интерактивные заголовки */
|
||
.data-table thead th {
|
||
vertical-align: middle;
|
||
white-space: nowrap;
|
||
padding: 0.5rem 0.75rem;
|
||
}
|
||
|
||
.data-table .header-content {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
width: 100%;
|
||
min-width: 0; /* Предотвращает переполнение */
|
||
}
|
||
|
||
/* Текст заголовка */
|
||
.data-table .header-text {
|
||
cursor: pointer;
|
||
flex: 1 1 auto; /* grow, shrink, auto basis */
|
||
transition: color 0.2s ease;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
min-width: 0; /* Важно для flex */
|
||
}
|
||
|
||
.data-table .header-text:hover {
|
||
color: var(--bs-primary, #0d6efd);
|
||
}
|
||
|
||
/* Иконка поиска */
|
||
.data-table .search-trigger {
|
||
cursor: pointer;
|
||
opacity: 0.5;
|
||
transition: opacity 0.2s ease, color 0.2s ease;
|
||
flex-shrink: 0;
|
||
font-size: 0.875rem;
|
||
}
|
||
|
||
.data-table .search-trigger:hover {
|
||
opacity: 1;
|
||
color: var(--bs-primary, #0d6efd);
|
||
}
|
||
|
||
/* Иконки сортировки */
|
||
.data-table .sort-icon {
|
||
cursor: pointer;
|
||
transition: color 0.2s ease;
|
||
flex-shrink: 0;
|
||
font-size: 0.875rem;
|
||
}
|
||
|
||
.data-table .sort-icon:hover {
|
||
color: var(--bs-primary, #0d6efd) !important;
|
||
}
|
||
|
||
.data-table .sort-icon.active {
|
||
color: var(--bs-primary, #0d6efd) !important;
|
||
}
|
||
|
||
/* Поле поиска в заголовке */
|
||
.data-table .header-search-input {
|
||
display: none;
|
||
flex: 1 1 auto; /* grow, shrink, auto basis - занимает доступное пространство */
|
||
min-width: 0; /* Важно для flex-элементов - позволяет сжиматься */
|
||
max-width: 100%; /* Не выходить за пределы контейнера */
|
||
width: auto;
|
||
font-size: 0.875rem;
|
||
padding: 0.25rem 0.5rem;
|
||
}
|
||
|
||
.data-table .header-search-input:focus {
|
||
outline: none;
|
||
box-shadow: 0 0 0 0.2rem rgba(var(--bs-primary-rgb, 13, 110, 253), 0.25);
|
||
}
|
||
|
||
.data-table .header-search-input.visible {
|
||
display: flex; /* Используем flex вместо block для лучшего выравнивания */
|
||
}
|
||
|
||
/* Анимация появления */
|
||
@keyframes data-table-fade-in {
|
||
from {
|
||
opacity: 0;
|
||
transform: translateY(-2px);
|
||
}
|
||
to {
|
||
opacity: 1;
|
||
transform: translateY(0);
|
||
}
|
||
}
|
||
|
||
.data-table .header-search-input {
|
||
animation: data-table-fade-in 0.15s ease-in-out;
|
||
}
|
||
|
||
/* Индикатор сортировки - восходящая */
|
||
.data-table .sort-asc {
|
||
color: var(--bs-primary, #0d6efd) !important;
|
||
}
|
||
|
||
/* Индикатор сортировки - нисходящая */
|
||
.data-table .sort-desc {
|
||
color: var(--bs-primary, #0d6efd) !important;
|
||
}
|
||
|
||
/* Стили для загрузки */
|
||
.data-table .loading {
|
||
position: relative;
|
||
pointer-events: none;
|
||
}
|
||
|
||
.data-table .loading::after {
|
||
content: '';
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background: rgba(255, 255, 255, 0.7);
|
||
z-index: 1;
|
||
}
|
||
|
||
/* Пустое состояние */
|
||
.data-table .empty-state {
|
||
text-align: center;
|
||
padding: 3rem 1rem;
|
||
color: var(--bs-secondary-color, #6c757d);
|
||
}
|
||
|
||
.data-table .empty-state i {
|
||
font-size: 3rem;
|
||
opacity: 0.3;
|
||
margin-bottom: 1rem;
|
||
}
|
||
|
||
.data-table .empty-state h5 {
|
||
margin-bottom: 0.5rem;
|
||
}
|
||
|
||
.data-table .empty-state p {
|
||
margin-bottom: 0;
|
||
opacity: 0.8;
|
||
}
|
||
|
||
/* Адаптивность для мобильных */
|
||
@media (max-width: 768px) {
|
||
.data-table .header-content {
|
||
gap: 4px;
|
||
}
|
||
|
||
.data-table .header-text {
|
||
font-size: 0.875rem;
|
||
}
|
||
|
||
.data-table .search-trigger,
|
||
.data-table .sort-icon {
|
||
font-size: 0.875rem;
|
||
}
|
||
}
|
||
|
||
/* Стили пагинации */
|
||
.data-table .pagination-wrapper {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: flex-start;
|
||
flex-wrap: nowrap;
|
||
gap: 1rem;
|
||
padding: 0.75rem 1rem;
|
||
border-top: 1px solid var(--bs-border-color, #dee2e6);
|
||
width: 100%;
|
||
}
|
||
|
||
.data-table .pagination-info {
|
||
font-size: 0.875rem;
|
||
color: var(--bs-secondary-color, #6c757d);
|
||
flex-shrink: 0;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
/* Пагинация - посередине, занимает доступное пространство */
|
||
.data-table .pagination-wrapper > nav.pagination {
|
||
flex: 0 1 auto;
|
||
margin-left: auto;
|
||
margin-right: auto;
|
||
}
|
||
|
||
/* Селектор - справа, не растягивается */
|
||
.data-table .per-page-selector {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
flex-shrink: 0;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.data-table .page-link {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
min-width: 2rem;
|
||
height: 2rem;
|
||
padding: 0 0.5rem;
|
||
border-radius: 0.375rem;
|
||
font-size: 0.875rem;
|
||
text-decoration: none;
|
||
transition: all 0.2s ease;
|
||
}
|
||
|
||
.data-table .page-link:hover:not(.disabled):not(.active) {
|
||
background-color: var(--bs-primary-bg-subtle, #e9ecef);
|
||
}
|
||
|
||
.data-table .page-link.active {
|
||
background-color: var(--bs-primary, #0d6efd);
|
||
color: #fff;
|
||
border-color: var(--bs-primary, #0d6efd);
|
||
}
|
||
|
||
.data-table .page-link.disabled {
|
||
color: var(--bs-secondary-color, #6c757d);
|
||
pointer-events: none;
|
||
opacity: 0.5;
|
||
}
|
||
|
||
/* Количество записей на странице */
|
||
.data-table .per-page-selector {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.data-table .per-page-selector label {
|
||
font-size: 0.875rem;
|
||
color: var(--bs-secondary-color, #6c757d);
|
||
margin-bottom: 0;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
/* Компактный селектор - принудительно ограничиваем ширину */
|
||
.data-table .per-page-selector .per-page-select,
|
||
.data-table .per-page-selector select {
|
||
appearance: none;
|
||
-webkit-appearance: none;
|
||
-moz-appearance: none;
|
||
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e");
|
||
background-repeat: no-repeat;
|
||
background-position: right 0.5rem center;
|
||
background-size: 16px 12px;
|
||
padding-right: 2rem !important;
|
||
width: 80px !important;
|
||
max-width: 80px !important;
|
||
min-width: 80px !important;
|
||
flex: 0 0 80px !important;
|
||
font-size: 0.875rem !important;
|
||
padding: 0.25rem 0.5rem !important;
|
||
padding-right: 1.5rem !important;
|
||
margin: 0 !important;
|
||
border: 1px solid #dee2e6;
|
||
border-radius: 0.25rem;
|
||
background-color: #fff;
|
||
cursor: pointer;
|
||
}
|
||
|
||
/* Контейнер селектора не должен растягиваться */
|
||
.data-table .per-page-selector {
|
||
width: auto !important;
|
||
max-width: none !important;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
/* Стили для ячеек с действиями */
|
||
.data-table .actions-cell {
|
||
white-space: nowrap;
|
||
text-align: end;
|
||
}
|
||
|
||
.data-table .action-btn {
|
||
padding: 0.25rem 0.5rem;
|
||
font-size: 0.875rem;
|
||
border-radius: 0.25rem;
|
||
margin-left: 0.25rem;
|
||
}
|
||
|
||
/* Hover эффекты для строк */
|
||
.data-table tbody tr {
|
||
transition: background-color 0.15s ease;
|
||
}
|
||
|
||
.data-table tbody tr:hover {
|
||
background-color: var(--bs-table-hover-bg, rgba(0, 0, 0, 0.02));
|
||
}
|
||
|
||
/* Статус активной сортировки в заголовке */
|
||
.data-table th.sorted-asc .sort-icon::before {
|
||
content: '\f160';
|
||
font-weight: 900;
|
||
}
|
||
|
||
.data-table th.sorted-desc .sort-icon::before {
|
||
content: '\f161';
|
||
font-weight: 900;
|
||
}
|
||
|
||
// public/assets/css/bootstrap.min.css
|
||
@charset "UTF-8";/*!
|
||
* Bootstrap v5.3.8 (https://getbootstrap.com/)
|
||
* Copyright 2011-2025 The Bootstrap Authors
|
||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||
*/:root,[data-bs-theme=light]{--bs-blue:#0d6efd;--bs-indigo:#6610f2;--bs-purple:#6f42c1;--bs-pink:#d63384;--bs-red:#dc3545;--bs-orange:#fd7e14;--bs-yellow:#ffc107;--bs-green:#198754;--bs-teal:#20c997;--bs-cyan:#0dcaf0;--bs-black:#000;--bs-white:#fff;--bs-gray:#6c757d;--bs-gray-dark:#343a40;--bs-gray-100:#f8f9fa;--bs-gray-200:#e9ecef;--bs-gray-300:#dee2e6;--bs-gray-400:#ced4da;--bs-gray-500:#adb5bd;--bs-gray-600:#6c757d;--bs-gray-700:#495057;--bs-gray-800:#343a40;--bs-gray-900:#212529;--bs-primary:#0d6efd;--bs-secondary:#6c757d;--bs-success:#198754;--bs-info:#0dcaf0;--bs-warning:#ffc107;--bs-danger:#dc3545;--bs-light:#f8f9fa;--bs-dark:#212529;--bs-primary-rgb:13,110,253;--bs-secondary-rgb:108,117,125;--bs-success-rgb:25,135,84;--bs-info-rgb:13,202,240;--bs-warning-rgb:255,193,7;--bs-danger-rgb:220,53,69;--bs-light-rgb:248,249,250;--bs-dark-rgb:33,37,41;--bs-primary-text-emphasis:#052c65;--bs-secondary-text-emphasis:#2b2f32;--bs-success-text-emphasis:#0a3622;--bs-info-text-emphasis:#055160;--bs-warning-text-emphasis:#664d03;--bs-danger-text-emphasis:#58151c;--bs-light-text-emphasis:#495057;--bs-dark-text-emphasis:#495057;--bs-primary-bg-subtle:#cfe2ff;--bs-secondary-bg-subtle:#e2e3e5;--bs-success-bg-subtle:#d1e7dd;--bs-info-bg-subtle:#cff4fc;--bs-warning-bg-subtle:#fff3cd;--bs-danger-bg-subtle:#f8d7da;--bs-light-bg-subtle:#fcfcfd;--bs-dark-bg-subtle:#ced4da;--bs-primary-border-subtle:#9ec5fe;--bs-secondary-border-subtle:#c4c8cb;--bs-success-border-subtle:#a3cfbb;--bs-info-border-subtle:#9eeaf9;--bs-warning-border-subtle:#ffe69c;--bs-danger-border-subtle:#f1aeb5;--bs-light-border-subtle:#e9ecef;--bs-dark-border-subtle:#adb5bd;--bs-white-rgb:255,255,255;--bs-black-rgb:0,0,0;--bs-font-sans-serif:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue","Noto Sans","Liberation Sans",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--bs-font-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--bs-gradient:linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));--bs-body-font-family:var(--bs-font-sans-serif);--bs-body-font-size:1rem;--bs-body-font-weight:400;--bs-body-line-height:1.5;--bs-body-color:#212529;--bs-body-color-rgb:33,37,41;--bs-body-bg:#fff;--bs-body-bg-rgb:255,255,255;--bs-emphasis-color:#000;--bs-emphasis-color-rgb:0,0,0;--bs-secondary-color:rgba(33, 37, 41, 0.75);--bs-secondary-color-rgb:33,37,41;--bs-secondary-bg:#e9ecef;--bs-secondary-bg-rgb:233,236,239;--bs-tertiary-color:rgba(33, 37, 41, 0.5);--bs-tertiary-color-rgb:33,37,41;--bs-tertiary-bg:#f8f9fa;--bs-tertiary-bg-rgb:248,249,250;--bs-heading-color:inherit;--bs-link-color:#0d6efd;--bs-link-color-rgb:13,110,253;--bs-link-decoration:underline;--bs-link-hover-color:#0a58ca;--bs-link-hover-color-rgb:10,88,202;--bs-code-color:#d63384;--bs-highlight-color:#212529;--bs-highlight-bg:#fff3cd;--bs-border-width:1px;--bs-border-style:solid;--bs-border-color:#dee2e6;--bs-border-color-translucent:rgba(0, 0, 0, 0.175);--bs-border-radius:0.375rem;--bs-border-radius-sm:0.25rem;--bs-border-radius-lg:0.5rem;--bs-border-radius-xl:1rem;--bs-border-radius-xxl:2rem;--bs-border-radius-2xl:var(--bs-border-radius-xxl);--bs-border-radius-pill:50rem;--bs-box-shadow:0 0.5rem 1rem rgba(0, 0, 0, 0.15);--bs-box-shadow-sm:0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);--bs-box-shadow-lg:0 1rem 3rem rgba(0, 0, 0, 0.175);--bs-box-shadow-inset:inset 0 1px 2px rgba(0, 0, 0, 0.075);--bs-focus-ring-width:0.25rem;--bs-focus-ring-opacity:0.25;--bs-focus-ring-color:rgba(13, 110, 253, 0.25);--bs-form-valid-color:#198754;--bs-form-valid-border-color:#198754;--bs-form-invalid-color:#dc3545;--bs-form-invalid-border-color:#dc3545}[data-bs-theme=dark]{color-scheme:dark;--bs-body-color:#dee2e6;--bs-body-color-rgb:222,226,230;--bs-body-bg:#212529;--bs-body-bg-rgb:33,37,41;--bs-emphasis-color:#fff;--bs-emphasis-color-rgb:255,255,255;--bs-secondary-color:rgba(222, 226, 230, 0.75);--bs-secondary-color-rgb:222,226,230;--bs-secondary-bg:#343a40;--bs-secondary-bg-rgb:52,58,64;--bs-tertiary-color:rgba(222, 226, 230, 0.5);--bs-tertiary-color-rgb:222,226,230;--bs-tertiary-bg:#2b3035;--bs-tertiary-bg-rgb:43,48,53;--bs-primary-text-emphasis:#6ea8fe;--bs-secondary-text-emphasis:#a7acb1;--bs-success-text-emphasis:#75b798;--bs-info-text-emphasis:#6edff6;--bs-warning-text-emphasis:#ffda6a;--bs-danger-text-emphasis:#ea868f;--bs-light-text-emphasis:#f8f9fa;--bs-dark-text-emphasis:#dee2e6;--bs-primary-bg-subtle:#031633;--bs-secondary-bg-subtle:#161719;--bs-success-bg-subtle:#051b11;--bs-info-bg-subtle:#032830;--bs-warning-bg-subtle:#332701;--bs-danger-bg-subtle:#2c0b0e;--bs-light-bg-subtle:#343a40;--bs-dark-bg-subtle:#1a1d20;--bs-primary-border-subtle:#084298;--bs-secondary-border-subtle:#41464b;--bs-success-border-subtle:#0f5132;--bs-info-border-subtle:#087990;--bs-warning-border-subtle:#997404;--bs-danger-border-subtle:#842029;--bs-light-border-subtle:#495057;--bs-dark-border-subtle:#343a40;--bs-heading-color:inherit;--bs-link-color:#6ea8fe;--bs-link-hover-color:#8bb9fe;--bs-link-color-rgb:110,168,254;--bs-link-hover-color-rgb:139,185,254;--bs-code-color:#e685b5;--bs-highlight-color:#dee2e6;--bs-highlight-bg:#664d03;--bs-border-color:#495057;--bs-border-color-translucent:rgba(255, 255, 255, 0.15);--bs-form-valid-color:#75b798;--bs-form-valid-border-color:#75b798;--bs-form-invalid-color:#ea868f;--bs-form-invalid-border-color:#ea868f}*,::after,::before{box-sizing:border-box}@media (prefers-reduced-motion:no-preference){:root{scroll-behavior:smooth}}body{margin:0;font-family:var(--bs-body-font-family);font-size:var(--bs-body-font-size);font-weight:var(--bs-body-font-weight);line-height:var(--bs-body-line-height);color:var(--bs-body-color);text-align:var(--bs-body-text-align);background-color:var(--bs-body-bg);-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}hr{margin:1rem 0;color:inherit;border:0;border-top:var(--bs-border-width) solid;opacity:.25}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem;font-weight:500;line-height:1.2;color:var(--bs-heading-color)}.h1,h1{font-size:calc(1.375rem + 1.5vw)}@media (min-width:1200px){.h1,h1{font-size:2.5rem}}.h2,h2{font-size:calc(1.325rem + .9vw)}@media (min-width:1200px){.h2,h2{font-size:2rem}}.h3,h3{font-size:calc(1.3rem + .6vw)}@media (min-width:1200px){.h3,h3{font-size:1.75rem}}.h4,h4{font-size:calc(1.275rem + .3vw)}@media (min-width:1200px){.h4,h4{font-size:1.5rem}}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[title]{-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul{padding-left:2rem}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}.small,small{font-size:.875em}.mark,mark{padding:.1875em;color:var(--bs-highlight-color);background-color:var(--bs-highlight-bg)}sub,sup{position:relative;font-size:.75em;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:rgba(var(--bs-link-color-rgb),var(--bs-link-opacity,1));text-decoration:underline}a:hover{--bs-link-color-rgb:var(--bs-link-hover-color-rgb)}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:var(--bs-font-monospace);font-size:1em}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:.875em}pre code{font-size:inherit;color:inherit;word-break:normal}code{font-size:.875em;color:var(--bs-code-color);word-wrap:break-word}a>code{color:inherit}kbd{padding:.1875rem .375rem;font-size:.875em;color:var(--bs-body-bg);background-color:var(--bs-body-color);border-radius:.25rem}kbd kbd{padding:0;font-size:1em}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:.5rem;padding-bottom:.5rem;color:var(--bs-secondary-color);text-align:left}th{text-align:inherit;text-align:-webkit-match-parent}tbody,td,tfoot,th,thead,tr{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator{display:none!important}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}::-moz-focus-inner{padding:0;border-style:none}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{float:left;width:100%;padding:0;margin-bottom:.5rem;line-height:inherit;font-size:calc(1.275rem + .3vw)}@media (min-width:1200px){legend{font-size:1.5rem}}legend+*{clear:left}::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-fields-wrapper,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-text,::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button{cursor:pointer;filter:grayscale(1)}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}::file-selector-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{display:list-item;cursor:pointer}progress{vertical-align:baseline}[hidden]{display:none!important}.lead{font-size:1.25rem;font-weight:300}.display-1{font-weight:300;line-height:1.2;font-size:calc(1.625rem + 4.5vw)}@media (min-width:1200px){.display-1{font-size:5rem}}.display-2{font-weight:300;line-height:1.2;font-size:calc(1.575rem + 3.9vw)}@media (min-width:1200px){.display-2{font-size:4.5rem}}.display-3{font-weight:300;line-height:1.2;font-size:calc(1.525rem + 3.3vw)}@media (min-width:1200px){.display-3{font-size:4rem}}.display-4{font-weight:300;line-height:1.2;font-size:calc(1.475rem + 2.7vw)}@media (min-width:1200px){.display-4{font-size:3.5rem}}.display-5{font-weight:300;line-height:1.2;font-size:calc(1.425rem + 2.1vw)}@media (min-width:1200px){.display-5{font-size:3rem}}.display-6{font-weight:300;line-height:1.2;font-size:calc(1.375rem + 1.5vw)}@media (min-width:1200px){.display-6{font-size:2.5rem}}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:.875em;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote>:last-child{margin-bottom:0}.blockquote-footer{margin-top:-1rem;margin-bottom:1rem;font-size:.875em;color:#6c757d}.blockquote-footer::before{content:"— "}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:var(--bs-body-bg);border:var(--bs-border-width) solid var(--bs-border-color);border-radius:var(--bs-border-radius);max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:.875em;color:var(--bs-secondary-color)}.container,.container-fluid,.container-lg,.container-md,.container-sm,.container-xl,.container-xxl{--bs-gutter-x:1.5rem;--bs-gutter-y:0;width:100%;padding-right:calc(var(--bs-gutter-x) * .5);padding-left:calc(var(--bs-gutter-x) * .5);margin-right:auto;margin-left:auto}@media (min-width:576px){.container,.container-sm{max-width:540px}}@media (min-width:768px){.container,.container-md,.container-sm{max-width:720px}}@media (min-width:992px){.container,.container-lg,.container-md,.container-sm{max-width:960px}}@media (min-width:1200px){.container,.container-lg,.container-md,.container-sm,.container-xl{max-width:1140px}}@media (min-width:1400px){.container,.container-lg,.container-md,.container-sm,.container-xl,.container-xxl{max-width:1320px}}:root{--bs-breakpoint-xs:0;--bs-breakpoint-sm:576px;--bs-breakpoint-md:768px;--bs-breakpoint-lg:992px;--bs-breakpoint-xl:1200px;--bs-breakpoint-xxl:1400px}.row{--bs-gutter-x:1.5rem;--bs-gutter-y:0;display:flex;flex-wrap:wrap;margin-top:calc(-1 * var(--bs-gutter-y));margin-right:calc(-.5 * var(--bs-gutter-x));margin-left:calc(-.5 * var(--bs-gutter-x))}.row>*{flex-shrink:0;width:100%;max-width:100%;padding-right:calc(var(--bs-gutter-x) * .5);padding-left:calc(var(--bs-gutter-x) * .5);margin-top:var(--bs-gutter-y)}.col{flex:1 0 0}.row-cols-auto>*{flex:0 0 auto;width:auto}.row-cols-1>*{flex:0 0 auto;width:100%}.row-cols-2>*{flex:0 0 auto;width:50%}.row-cols-3>*{flex:0 0 auto;width:33.33333333%}.row-cols-4>*{flex:0 0 auto;width:25%}.row-cols-5>*{flex:0 0 auto;width:20%}.row-cols-6>*{flex:0 0 auto;width:16.66666667%}.col-auto{flex:0 0 auto;width:auto}.col-1{flex:0 0 auto;width:8.33333333%}.col-2{flex:0 0 auto;width:16.66666667%}.col-3{flex:0 0 auto;width:25%}.col-4{flex:0 0 auto;width:33.33333333%}.col-5{flex:0 0 auto;width:41.66666667%}.col-6{flex:0 0 auto;width:50%}.col-7{flex:0 0 auto;width:58.33333333%}.col-8{flex:0 0 auto;width:66.66666667%}.col-9{flex:0 0 auto;width:75%}.col-10{flex:0 0 auto;width:83.33333333%}.col-11{flex:0 0 auto;width:91.66666667%}.col-12{flex:0 0 auto;width:100%}.offset-1{margin-left:8.33333333%}.offset-2{margin-left:16.66666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.33333333%}.offset-5{margin-left:41.66666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.33333333%}.offset-8{margin-left:66.66666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.33333333%}.offset-11{margin-left:91.66666667%}.g-0,.gx-0{--bs-gutter-x:0}.g-0,.gy-0{--bs-gutter-y:0}.g-1,.gx-1{--bs-gutter-x:0.25rem}.g-1,.gy-1{--bs-gutter-y:0.25rem}.g-2,.gx-2{--bs-gutter-x:0.5rem}.g-2,.gy-2{--bs-gutter-y:0.5rem}.g-3,.gx-3{--bs-gutter-x:1rem}.g-3,.gy-3{--bs-gutter-y:1rem}.g-4,.gx-4{--bs-gutter-x:1.5rem}.g-4,.gy-4{--bs-gutter-y:1.5rem}.g-5,.gx-5{--bs-gutter-x:3rem}.g-5,.gy-5{--bs-gutter-y:3rem}@media (min-width:576px){.col-sm{flex:1 0 0}.row-cols-sm-auto>*{flex:0 0 auto;width:auto}.row-cols-sm-1>*{flex:0 0 auto;width:100%}.row-cols-sm-2>*{flex:0 0 auto;width:50%}.row-cols-sm-3>*{flex:0 0 auto;width:33.33333333%}.row-cols-sm-4>*{flex:0 0 auto;width:25%}.row-cols-sm-5>*{flex:0 0 auto;width:20%}.row-cols-sm-6>*{flex:0 0 auto;width:16.66666667%}.col-sm-auto{flex:0 0 auto;width:auto}.col-sm-1{flex:0 0 auto;width:8.33333333%}.col-sm-2{flex:0 0 auto;width:16.66666667%}.col-sm-3{flex:0 0 auto;width:25%}.col-sm-4{flex:0 0 auto;width:33.33333333%}.col-sm-5{flex:0 0 auto;width:41.66666667%}.col-sm-6{flex:0 0 auto;width:50%}.col-sm-7{flex:0 0 auto;width:58.33333333%}.col-sm-8{flex:0 0 auto;width:66.66666667%}.col-sm-9{flex:0 0 auto;width:75%}.col-sm-10{flex:0 0 auto;width:83.33333333%}.col-sm-11{flex:0 0 auto;width:91.66666667%}.col-sm-12{flex:0 0 auto;width:100%}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.33333333%}.offset-sm-2{margin-left:16.66666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.33333333%}.offset-sm-5{margin-left:41.66666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.33333333%}.offset-sm-8{margin-left:66.66666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.33333333%}.offset-sm-11{margin-left:91.66666667%}.g-sm-0,.gx-sm-0{--bs-gutter-x:0}.g-sm-0,.gy-sm-0{--bs-gutter-y:0}.g-sm-1,.gx-sm-1{--bs-gutter-x:0.25rem}.g-sm-1,.gy-sm-1{--bs-gutter-y:0.25rem}.g-sm-2,.gx-sm-2{--bs-gutter-x:0.5rem}.g-sm-2,.gy-sm-2{--bs-gutter-y:0.5rem}.g-sm-3,.gx-sm-3{--bs-gutter-x:1rem}.g-sm-3,.gy-sm-3{--bs-gutter-y:1rem}.g-sm-4,.gx-sm-4{--bs-gutter-x:1.5rem}.g-sm-4,.gy-sm-4{--bs-gutter-y:1.5rem}.g-sm-5,.gx-sm-5{--bs-gutter-x:3rem}.g-sm-5,.gy-sm-5{--bs-gutter-y:3rem}}@media (min-width:768px){.col-md{flex:1 0 0}.row-cols-md-auto>*{flex:0 0 auto;width:auto}.row-cols-md-1>*{flex:0 0 auto;width:100%}.row-cols-md-2>*{flex:0 0 auto;width:50%}.row-cols-md-3>*{flex:0 0 auto;width:33.33333333%}.row-cols-md-4>*{flex:0 0 auto;width:25%}.row-cols-md-5>*{flex:0 0 auto;width:20%}.row-cols-md-6>*{flex:0 0 auto;width:16.66666667%}.col-md-auto{flex:0 0 auto;width:auto}.col-md-1{flex:0 0 auto;width:8.33333333%}.col-md-2{flex:0 0 auto;width:16.66666667%}.col-md-3{flex:0 0 auto;width:25%}.col-md-4{flex:0 0 auto;width:33.33333333%}.col-md-5{flex:0 0 auto;width:41.66666667%}.col-md-6{flex:0 0 auto;width:50%}.col-md-7{flex:0 0 auto;width:58.33333333%}.col-md-8{flex:0 0 auto;width:66.66666667%}.col-md-9{flex:0 0 auto;width:75%}.col-md-10{flex:0 0 auto;width:83.33333333%}.col-md-11{flex:0 0 auto;width:91.66666667%}.col-md-12{flex:0 0 auto;width:100%}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.33333333%}.offset-md-2{margin-left:16.66666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.33333333%}.offset-md-5{margin-left:41.66666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.33333333%}.offset-md-8{margin-left:66.66666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.33333333%}.offset-md-11{margin-left:91.66666667%}.g-md-0,.gx-md-0{--bs-gutter-x:0}.g-md-0,.gy-md-0{--bs-gutter-y:0}.g-md-1,.gx-md-1{--bs-gutter-x:0.25rem}.g-md-1,.gy-md-1{--bs-gutter-y:0.25rem}.g-md-2,.gx-md-2{--bs-gutter-x:0.5rem}.g-md-2,.gy-md-2{--bs-gutter-y:0.5rem}.g-md-3,.gx-md-3{--bs-gutter-x:1rem}.g-md-3,.gy-md-3{--bs-gutter-y:1rem}.g-md-4,.gx-md-4{--bs-gutter-x:1.5rem}.g-md-4,.gy-md-4{--bs-gutter-y:1.5rem}.g-md-5,.gx-md-5{--bs-gutter-x:3rem}.g-md-5,.gy-md-5{--bs-gutter-y:3rem}}@media (min-width:992px){.col-lg{flex:1 0 0}.row-cols-lg-auto>*{flex:0 0 auto;width:auto}.row-cols-lg-1>*{flex:0 0 auto;width:100%}.row-cols-lg-2>*{flex:0 0 auto;width:50%}.row-cols-lg-3>*{flex:0 0 auto;width:33.33333333%}.row-cols-lg-4>*{flex:0 0 auto;width:25%}.row-cols-lg-5>*{flex:0 0 auto;width:20%}.row-cols-lg-6>*{flex:0 0 auto;width:16.66666667%}.col-lg-auto{flex:0 0 auto;width:auto}.col-lg-1{flex:0 0 auto;width:8.33333333%}.col-lg-2{flex:0 0 auto;width:16.66666667%}.col-lg-3{flex:0 0 auto;width:25%}.col-lg-4{flex:0 0 auto;width:33.33333333%}.col-lg-5{flex:0 0 auto;width:41.66666667%}.col-lg-6{flex:0 0 auto;width:50%}.col-lg-7{flex:0 0 auto;width:58.33333333%}.col-lg-8{flex:0 0 auto;width:66.66666667%}.col-lg-9{flex:0 0 auto;width:75%}.col-lg-10{flex:0 0 auto;width:83.33333333%}.col-lg-11{flex:0 0 auto;width:91.66666667%}.col-lg-12{flex:0 0 auto;width:100%}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.33333333%}.offset-lg-2{margin-left:16.66666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.33333333%}.offset-lg-5{margin-left:41.66666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.33333333%}.offset-lg-8{margin-left:66.66666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.33333333%}.offset-lg-11{margin-left:91.66666667%}.g-lg-0,.gx-lg-0{--bs-gutter-x:0}.g-lg-0,.gy-lg-0{--bs-gutter-y:0}.g-lg-1,.gx-lg-1{--bs-gutter-x:0.25rem}.g-lg-1,.gy-lg-1{--bs-gutter-y:0.25rem}.g-lg-2,.gx-lg-2{--bs-gutter-x:0.5rem}.g-lg-2,.gy-lg-2{--bs-gutter-y:0.5rem}.g-lg-3,.gx-lg-3{--bs-gutter-x:1rem}.g-lg-3,.gy-lg-3{--bs-gutter-y:1rem}.g-lg-4,.gx-lg-4{--bs-gutter-x:1.5rem}.g-lg-4,.gy-lg-4{--bs-gutter-y:1.5rem}.g-lg-5,.gx-lg-5{--bs-gutter-x:3rem}.g-lg-5,.gy-lg-5{--bs-gutter-y:3rem}}@media (min-width:1200px){.col-xl{flex:1 0 0}.row-cols-xl-auto>*{flex:0 0 auto;width:auto}.row-cols-xl-1>*{flex:0 0 auto;width:100%}.row-cols-xl-2>*{flex:0 0 auto;width:50%}.row-cols-xl-3>*{flex:0 0 auto;width:33.33333333%}.row-cols-xl-4>*{flex:0 0 auto;width:25%}.row-cols-xl-5>*{flex:0 0 auto;width:20%}.row-cols-xl-6>*{flex:0 0 auto;width:16.66666667%}.col-xl-auto{flex:0 0 auto;width:auto}.col-xl-1{flex:0 0 auto;width:8.33333333%}.col-xl-2{flex:0 0 auto;width:16.66666667%}.col-xl-3{flex:0 0 auto;width:25%}.col-xl-4{flex:0 0 auto;width:33.33333333%}.col-xl-5{flex:0 0 auto;width:41.66666667%}.col-xl-6{flex:0 0 auto;width:50%}.col-xl-7{flex:0 0 auto;width:58.33333333%}.col-xl-8{flex:0 0 auto;width:66.66666667%}.col-xl-9{flex:0 0 auto;width:75%}.col-xl-10{flex:0 0 auto;width:83.33333333%}.col-xl-11{flex:0 0 auto;width:91.66666667%}.col-xl-12{flex:0 0 auto;width:100%}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.33333333%}.offset-xl-2{margin-left:16.66666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.33333333%}.offset-xl-5{margin-left:41.66666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.33333333%}.offset-xl-8{margin-left:66.66666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.33333333%}.offset-xl-11{margin-left:91.66666667%}.g-xl-0,.gx-xl-0{--bs-gutter-x:0}.g-xl-0,.gy-xl-0{--bs-gutter-y:0}.g-xl-1,.gx-xl-1{--bs-gutter-x:0.25rem}.g-xl-1,.gy-xl-1{--bs-gutter-y:0.25rem}.g-xl-2,.gx-xl-2{--bs-gutter-x:0.5rem}.g-xl-2,.gy-xl-2{--bs-gutter-y:0.5rem}.g-xl-3,.gx-xl-3{--bs-gutter-x:1rem}.g-xl-3,.gy-xl-3{--bs-gutter-y:1rem}.g-xl-4,.gx-xl-4{--bs-gutter-x:1.5rem}.g-xl-4,.gy-xl-4{--bs-gutter-y:1.5rem}.g-xl-5,.gx-xl-5{--bs-gutter-x:3rem}.g-xl-5,.gy-xl-5{--bs-gutter-y:3rem}}@media (min-width:1400px){.col-xxl{flex:1 0 0}.row-cols-xxl-auto>*{flex:0 0 auto;width:auto}.row-cols-xxl-1>*{flex:0 0 auto;width:100%}.row-cols-xxl-2>*{flex:0 0 auto;width:50%}.row-cols-xxl-3>*{flex:0 0 auto;width:33.33333333%}.row-cols-xxl-4>*{flex:0 0 auto;width:25%}.row-cols-xxl-5>*{flex:0 0 auto;width:20%}.row-cols-xxl-6>*{flex:0 0 auto;width:16.66666667%}.col-xxl-auto{flex:0 0 auto;width:auto}.col-xxl-1{flex:0 0 auto;width:8.33333333%}.col-xxl-2{flex:0 0 auto;width:16.66666667%}.col-xxl-3{flex:0 0 auto;width:25%}.col-xxl-4{flex:0 0 auto;width:33.33333333%}.col-xxl-5{flex:0 0 auto;width:41.66666667%}.col-xxl-6{flex:0 0 auto;width:50%}.col-xxl-7{flex:0 0 auto;width:58.33333333%}.col-xxl-8{flex:0 0 auto;width:66.66666667%}.col-xxl-9{flex:0 0 auto;width:75%}.col-xxl-10{flex:0 0 auto;width:83.33333333%}.col-xxl-11{flex:0 0 auto;width:91.66666667%}.col-xxl-12{flex:0 0 auto;width:100%}.offset-xxl-0{margin-left:0}.offset-xxl-1{margin-left:8.33333333%}.offset-xxl-2{margin-left:16.66666667%}.offset-xxl-3{margin-left:25%}.offset-xxl-4{margin-left:33.33333333%}.offset-xxl-5{margin-left:41.66666667%}.offset-xxl-6{margin-left:50%}.offset-xxl-7{margin-left:58.33333333%}.offset-xxl-8{margin-left:66.66666667%}.offset-xxl-9{margin-left:75%}.offset-xxl-10{margin-left:83.33333333%}.offset-xxl-11{margin-left:91.66666667%}.g-xxl-0,.gx-xxl-0{--bs-gutter-x:0}.g-xxl-0,.gy-xxl-0{--bs-gutter-y:0}.g-xxl-1,.gx-xxl-1{--bs-gutter-x:0.25rem}.g-xxl-1,.gy-xxl-1{--bs-gutter-y:0.25rem}.g-xxl-2,.gx-xxl-2{--bs-gutter-x:0.5rem}.g-xxl-2,.gy-xxl-2{--bs-gutter-y:0.5rem}.g-xxl-3,.gx-xxl-3{--bs-gutter-x:1rem}.g-xxl-3,.gy-xxl-3{--bs-gutter-y:1rem}.g-xxl-4,.gx-xxl-4{--bs-gutter-x:1.5rem}.g-xxl-4,.gy-xxl-4{--bs-gutter-y:1.5rem}.g-xxl-5,.gx-xxl-5{--bs-gutter-x:3rem}.g-xxl-5,.gy-xxl-5{--bs-gutter-y:3rem}}.table{--bs-table-color-type:initial;--bs-table-bg-type:initial;--bs-table-color-state:initial;--bs-table-bg-state:initial;--bs-table-color:var(--bs-emphasis-color);--bs-table-bg:var(--bs-body-bg);--bs-table-border-color:var(--bs-border-color);--bs-table-accent-bg:transparent;--bs-table-striped-color:var(--bs-emphasis-color);--bs-table-striped-bg:rgba(var(--bs-emphasis-color-rgb), 0.05);--bs-table-active-color:var(--bs-emphasis-color);--bs-table-active-bg:rgba(var(--bs-emphasis-color-rgb), 0.1);--bs-table-hover-color:var(--bs-emphasis-color);--bs-table-hover-bg:rgba(var(--bs-emphasis-color-rgb), 0.075);width:100%;margin-bottom:1rem;vertical-align:top;border-color:var(--bs-table-border-color)}.table>:not(caption)>*>*{padding:.5rem .5rem;color:var(--bs-table-color-state,var(--bs-table-color-type,var(--bs-table-color)));background-color:var(--bs-table-bg);border-bottom-width:var(--bs-border-width);box-shadow:inset 0 0 0 9999px var(--bs-table-bg-state,var(--bs-table-bg-type,var(--bs-table-accent-bg)))}.table>tbody{vertical-align:inherit}.table>thead{vertical-align:bottom}.table-group-divider{border-top:calc(var(--bs-border-width) * 2) solid currentcolor}.caption-top{caption-side:top}.table-sm>:not(caption)>*>*{padding:.25rem .25rem}.table-bordered>:not(caption)>*{border-width:var(--bs-border-width) 0}.table-bordered>:not(caption)>*>*{border-width:0 var(--bs-border-width)}.table-borderless>:not(caption)>*>*{border-bottom-width:0}.table-borderless>:not(:first-child){border-top-width:0}.table-striped>tbody>tr:nth-of-type(odd)>*{--bs-table-color-type:var(--bs-table-striped-color);--bs-table-bg-type:var(--bs-table-striped-bg)}.table-striped-columns>:not(caption)>tr>:nth-child(2n){--bs-table-color-type:var(--bs-table-striped-color);--bs-table-bg-type:var(--bs-table-striped-bg)}.table-active{--bs-table-color-state:var(--bs-table-active-color);--bs-table-bg-state:var(--bs-table-active-bg)}.table-hover>tbody>tr:hover>*{--bs-table-color-state:var(--bs-table-hover-color);--bs-table-bg-state:var(--bs-table-hover-bg)}.table-primary{--bs-table-color:#000;--bs-table-bg:#cfe2ff;--bs-table-border-color:#a6b5cc;--bs-table-striped-bg:#c5d7f2;--bs-table-striped-color:#000;--bs-table-active-bg:#bacbe6;--bs-table-active-color:#000;--bs-table-hover-bg:#bfd1ec;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-secondary{--bs-table-color:#000;--bs-table-bg:#e2e3e5;--bs-table-border-color:#b5b6b7;--bs-table-striped-bg:#d7d8da;--bs-table-striped-color:#000;--bs-table-active-bg:#cbccce;--bs-table-active-color:#000;--bs-table-hover-bg:#d1d2d4;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-success{--bs-table-color:#000;--bs-table-bg:#d1e7dd;--bs-table-border-color:#a7b9b1;--bs-table-striped-bg:#c7dbd2;--bs-table-striped-color:#000;--bs-table-active-bg:#bcd0c7;--bs-table-active-color:#000;--bs-table-hover-bg:#c1d6cc;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-info{--bs-table-color:#000;--bs-table-bg:#cff4fc;--bs-table-border-color:#a6c3ca;--bs-table-striped-bg:#c5e8ef;--bs-table-striped-color:#000;--bs-table-active-bg:#badce3;--bs-table-active-color:#000;--bs-table-hover-bg:#bfe2e9;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-warning{--bs-table-color:#000;--bs-table-bg:#fff3cd;--bs-table-border-color:#ccc2a4;--bs-table-striped-bg:#f2e7c3;--bs-table-striped-color:#000;--bs-table-active-bg:#e6dbb9;--bs-table-active-color:#000;--bs-table-hover-bg:#ece1be;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-danger{--bs-table-color:#000;--bs-table-bg:#f8d7da;--bs-table-border-color:#c6acae;--bs-table-striped-bg:#eccccf;--bs-table-striped-color:#000;--bs-table-active-bg:#dfc2c4;--bs-table-active-color:#000;--bs-table-hover-bg:#e5c7ca;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-light{--bs-table-color:#000;--bs-table-bg:#f8f9fa;--bs-table-border-color:#c6c7c8;--bs-table-striped-bg:#ecedee;--bs-table-striped-color:#000;--bs-table-active-bg:#dfe0e1;--bs-table-active-color:#000;--bs-table-hover-bg:#e5e6e7;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-dark{--bs-table-color:#fff;--bs-table-bg:#212529;--bs-table-border-color:#4d5154;--bs-table-striped-bg:#2c3034;--bs-table-striped-color:#fff;--bs-table-active-bg:#373b3e;--bs-table-active-color:#fff;--bs-table-hover-bg:#323539;--bs-table-hover-color:#fff;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-responsive{overflow-x:auto;-webkit-overflow-scrolling:touch}@media (max-width:575.98px){.table-responsive-sm{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:767.98px){.table-responsive-md{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:991.98px){.table-responsive-lg{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:1199.98px){.table-responsive-xl{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:1399.98px){.table-responsive-xxl{overflow-x:auto;-webkit-overflow-scrolling:touch}}.form-label{margin-bottom:.5rem}.col-form-label{padding-top:calc(.375rem + var(--bs-border-width));padding-bottom:calc(.375rem + var(--bs-border-width));margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(.5rem + var(--bs-border-width));padding-bottom:calc(.5rem + var(--bs-border-width));font-size:1.25rem}.col-form-label-sm{padding-top:calc(.25rem + var(--bs-border-width));padding-bottom:calc(.25rem + var(--bs-border-width));font-size:.875rem}.form-text{margin-top:.25rem;font-size:.875em;color:var(--bs-secondary-color)}.form-control{display:block;width:100%;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:var(--bs-body-color);-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:var(--bs-body-bg);background-clip:padding-box;border:var(--bs-border-width) solid var(--bs-border-color);border-radius:var(--bs-border-radius);transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control{transition:none}}.form-control[type=file]{overflow:hidden}.form-control[type=file]:not(:disabled):not([readonly]){cursor:pointer}.form-control:focus{color:var(--bs-body-color);background-color:var(--bs-body-bg);border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-control::-webkit-date-and-time-value{min-width:85px;height:1.5em;margin:0}.form-control::-webkit-datetime-edit{display:block;padding:0}.form-control::placeholder{color:var(--bs-secondary-color);opacity:1}.form-control:disabled{background-color:var(--bs-secondary-bg);opacity:1}.form-control::-webkit-file-upload-button{padding:.375rem .75rem;margin:-.375rem -.75rem;-webkit-margin-end:.75rem;margin-inline-end:.75rem;color:var(--bs-body-color);background-color:var(--bs-tertiary-bg);pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:var(--bs-border-width);border-radius:0;-webkit-transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}.form-control::file-selector-button{padding:.375rem .75rem;margin:-.375rem -.75rem;-webkit-margin-end:.75rem;margin-inline-end:.75rem;color:var(--bs-body-color);background-color:var(--bs-tertiary-bg);pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:var(--bs-border-width);border-radius:0;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control::-webkit-file-upload-button{-webkit-transition:none;transition:none}.form-control::file-selector-button{transition:none}}.form-control:hover:not(:disabled):not([readonly])::-webkit-file-upload-button{background-color:var(--bs-secondary-bg)}.form-control:hover:not(:disabled):not([readonly])::file-selector-button{background-color:var(--bs-secondary-bg)}.form-control-plaintext{display:block;width:100%;padding:.375rem 0;margin-bottom:0;line-height:1.5;color:var(--bs-body-color);background-color:transparent;border:solid transparent;border-width:var(--bs-border-width) 0}.form-control-plaintext:focus{outline:0}.form-control-plaintext.form-control-lg,.form-control-plaintext.form-control-sm{padding-right:0;padding-left:0}.form-control-sm{min-height:calc(1.5em + .5rem + calc(var(--bs-border-width) * 2));padding:.25rem .5rem;font-size:.875rem;border-radius:var(--bs-border-radius-sm)}.form-control-sm::-webkit-file-upload-button{padding:.25rem .5rem;margin:-.25rem -.5rem;-webkit-margin-end:.5rem;margin-inline-end:.5rem}.form-control-sm::file-selector-button{padding:.25rem .5rem;margin:-.25rem -.5rem;-webkit-margin-end:.5rem;margin-inline-end:.5rem}.form-control-lg{min-height:calc(1.5em + 1rem + calc(var(--bs-border-width) * 2));padding:.5rem 1rem;font-size:1.25rem;border-radius:var(--bs-border-radius-lg)}.form-control-lg::-webkit-file-upload-button{padding:.5rem 1rem;margin:-.5rem -1rem;-webkit-margin-end:1rem;margin-inline-end:1rem}.form-control-lg::file-selector-button{padding:.5rem 1rem;margin:-.5rem -1rem;-webkit-margin-end:1rem;margin-inline-end:1rem}textarea.form-control{min-height:calc(1.5em + .75rem + calc(var(--bs-border-width) * 2))}textarea.form-control-sm{min-height:calc(1.5em + .5rem + calc(var(--bs-border-width) * 2))}textarea.form-control-lg{min-height:calc(1.5em + 1rem + calc(var(--bs-border-width) * 2))}.form-control-color{width:3rem;height:calc(1.5em + .75rem + calc(var(--bs-border-width) * 2));padding:.375rem}.form-control-color:not(:disabled):not([readonly]){cursor:pointer}.form-control-color::-moz-color-swatch{border:0!important;border-radius:var(--bs-border-radius)}.form-control-color::-webkit-color-swatch{border:0!important;border-radius:var(--bs-border-radius)}.form-control-color.form-control-sm{height:calc(1.5em + .5rem + calc(var(--bs-border-width) * 2))}.form-control-color.form-control-lg{height:calc(1.5em + 1rem + calc(var(--bs-border-width) * 2))}.form-select{--bs-form-select-bg-img:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");display:block;width:100%;padding:.375rem 2.25rem .375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:var(--bs-body-color);-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:var(--bs-body-bg);background-image:var(--bs-form-select-bg-img),var(--bs-form-select-bg-icon,none);background-repeat:no-repeat;background-position:right .75rem center;background-size:16px 12px;border:var(--bs-border-width) solid var(--bs-border-color);border-radius:var(--bs-border-radius);transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-select{transition:none}}.form-select:focus{border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-select[multiple],.form-select[size]:not([size="1"]){padding-right:.75rem;background-image:none}.form-select:disabled{background-color:var(--bs-secondary-bg)}.form-select:-moz-focusring{color:transparent;text-shadow:0 0 0 var(--bs-body-color)}.form-select-sm{padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:.875rem;border-radius:var(--bs-border-radius-sm)}.form-select-lg{padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem;border-radius:var(--bs-border-radius-lg)}[data-bs-theme=dark] .form-select{--bs-form-select-bg-img:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23dee2e6' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e")}.form-check{display:block;min-height:1.5rem;padding-left:1.5em;margin-bottom:.125rem}.form-check .form-check-input{float:left;margin-left:-1.5em}.form-check-reverse{padding-right:1.5em;padding-left:0;text-align:right}.form-check-reverse .form-check-input{float:right;margin-right:-1.5em;margin-left:0}.form-check-input{--bs-form-check-bg:var(--bs-body-bg);flex-shrink:0;width:1em;height:1em;margin-top:.25em;vertical-align:top;-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:var(--bs-form-check-bg);background-image:var(--bs-form-check-bg-image);background-repeat:no-repeat;background-position:center;background-size:contain;border:var(--bs-border-width) solid var(--bs-border-color);-webkit-print-color-adjust:exact;color-adjust:exact;print-color-adjust:exact}.form-check-input[type=checkbox]{border-radius:.25em}.form-check-input[type=radio]{border-radius:50%}.form-check-input:active{filter:brightness(90%)}.form-check-input:focus{border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-check-input:checked{background-color:#0d6efd;border-color:#0d6efd}.form-check-input:checked[type=checkbox]{--bs-form-check-bg-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='m6 10 3 3 6-6'/%3e%3c/svg%3e")}.form-check-input:checked[type=radio]{--bs-form-check-bg-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%23fff'/%3e%3c/svg%3e")}.form-check-input[type=checkbox]:indeterminate{background-color:#0d6efd;border-color:#0d6efd;--bs-form-check-bg-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e")}.form-check-input:disabled{pointer-events:none;filter:none;opacity:.5}.form-check-input:disabled~.form-check-label,.form-check-input[disabled]~.form-check-label{cursor:default;opacity:.5}.form-switch{padding-left:2.5em}.form-switch .form-check-input{--bs-form-switch-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%280, 0, 0, 0.25%29'/%3e%3c/svg%3e");width:2em;margin-left:-2.5em;background-image:var(--bs-form-switch-bg);background-position:left center;border-radius:2em;transition:background-position .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-switch .form-check-input{transition:none}}.form-switch .form-check-input:focus{--bs-form-switch-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%2386b7fe'/%3e%3c/svg%3e")}.form-switch .form-check-input:checked{background-position:right center;--bs-form-switch-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e")}.form-switch.form-check-reverse{padding-right:2.5em;padding-left:0}.form-switch.form-check-reverse .form-check-input{margin-right:-2.5em;margin-left:0}.form-check-inline{display:inline-block;margin-right:1rem}.btn-check{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.btn-check:disabled+.btn,.btn-check[disabled]+.btn{pointer-events:none;filter:none;opacity:.65}[data-bs-theme=dark] .form-switch .form-check-input:not(:checked):not(:focus){--bs-form-switch-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%28255, 255, 255, 0.25%29'/%3e%3c/svg%3e")}.form-range{width:100%;height:1.5rem;padding:0;-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:transparent}.form-range:focus{outline:0}.form-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(13,110,253,.25)}.form-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(13,110,253,.25)}.form-range::-moz-focus-outer{border:0}.form-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-.25rem;-webkit-appearance:none;appearance:none;background-color:#0d6efd;border:0;border-radius:1rem;-webkit-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-range::-webkit-slider-thumb{-webkit-transition:none;transition:none}}.form-range::-webkit-slider-thumb:active{background-color:#b6d4fe}.form-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:var(--bs-secondary-bg);border-color:transparent;border-radius:1rem}.form-range::-moz-range-thumb{width:1rem;height:1rem;-moz-appearance:none;appearance:none;background-color:#0d6efd;border:0;border-radius:1rem;-moz-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-range::-moz-range-thumb{-moz-transition:none;transition:none}}.form-range::-moz-range-thumb:active{background-color:#b6d4fe}.form-range::-moz-range-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:var(--bs-secondary-bg);border-color:transparent;border-radius:1rem}.form-range:disabled{pointer-events:none}.form-range:disabled::-webkit-slider-thumb{background-color:var(--bs-secondary-color)}.form-range:disabled::-moz-range-thumb{background-color:var(--bs-secondary-color)}.form-floating{position:relative}.form-floating>.form-control,.form-floating>.form-control-plaintext,.form-floating>.form-select{height:calc(3.5rem + calc(var(--bs-border-width) * 2));min-height:calc(3.5rem + calc(var(--bs-border-width) * 2));line-height:1.25}.form-floating>label{position:absolute;top:0;left:0;z-index:2;max-width:100%;height:100%;padding:1rem .75rem;overflow:hidden;color:rgba(var(--bs-body-color-rgb),.65);text-align:start;text-overflow:ellipsis;white-space:nowrap;pointer-events:none;border:var(--bs-border-width) solid transparent;transform-origin:0 0;transition:opacity .1s ease-in-out,transform .1s ease-in-out}@media (prefers-reduced-motion:reduce){.form-floating>label{transition:none}}.form-floating>.form-control,.form-floating>.form-control-plaintext{padding:1rem .75rem}.form-floating>.form-control-plaintext::placeholder,.form-floating>.form-control::placeholder{color:transparent}.form-floating>.form-control-plaintext:focus,.form-floating>.form-control-plaintext:not(:placeholder-shown),.form-floating>.form-control:focus,.form-floating>.form-control:not(:placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control-plaintext:-webkit-autofill,.form-floating>.form-control:-webkit-autofill{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-select{padding-top:1.625rem;padding-bottom:.625rem;padding-left:.75rem}.form-floating>.form-control-plaintext~label,.form-floating>.form-control:focus~label,.form-floating>.form-control:not(:placeholder-shown)~label,.form-floating>.form-select~label{transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.form-floating>.form-control:-webkit-autofill~label{transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.form-floating>textarea:focus~label::after,.form-floating>textarea:not(:placeholder-shown)~label::after{position:absolute;inset:1rem 0.375rem;z-index:-1;height:1.5em;content:"";background-color:var(--bs-body-bg);border-radius:var(--bs-border-radius)}.form-floating>textarea:disabled~label::after{background-color:var(--bs-secondary-bg)}.form-floating>.form-control-plaintext~label{border-width:var(--bs-border-width) 0}.form-floating>.form-control:disabled~label,.form-floating>:disabled~label{color:#6c757d}.input-group{position:relative;display:flex;flex-wrap:wrap;align-items:stretch;width:100%}.input-group>.form-control,.input-group>.form-floating,.input-group>.form-select{position:relative;flex:1 1 auto;width:1%;min-width:0}.input-group>.form-control:focus,.input-group>.form-floating:focus-within,.input-group>.form-select:focus{z-index:5}.input-group .btn{position:relative;z-index:2}.input-group .btn:focus{z-index:5}.input-group-text{display:flex;align-items:center;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:var(--bs-body-color);text-align:center;white-space:nowrap;background-color:var(--bs-tertiary-bg);border:var(--bs-border-width) solid var(--bs-border-color);border-radius:var(--bs-border-radius)}.input-group-lg>.btn,.input-group-lg>.form-control,.input-group-lg>.form-select,.input-group-lg>.input-group-text{padding:.5rem 1rem;font-size:1.25rem;border-radius:var(--bs-border-radius-lg)}.input-group-sm>.btn,.input-group-sm>.form-control,.input-group-sm>.form-select,.input-group-sm>.input-group-text{padding:.25rem .5rem;font-size:.875rem;border-radius:var(--bs-border-radius-sm)}.input-group-lg>.form-select,.input-group-sm>.form-select{padding-right:3rem}.input-group:not(.has-validation)>.dropdown-toggle:nth-last-child(n+3),.input-group:not(.has-validation)>.form-floating:not(:last-child)>.form-control,.input-group:not(.has-validation)>.form-floating:not(:last-child)>.form-select,.input-group:not(.has-validation)>:not(:last-child):not(.dropdown-toggle):not(.dropdown-menu):not(.form-floating){border-top-right-radius:0;border-bottom-right-radius:0}.input-group.has-validation>.dropdown-toggle:nth-last-child(n+4),.input-group.has-validation>.form-floating:nth-last-child(n+3)>.form-control,.input-group.has-validation>.form-floating:nth-last-child(n+3)>.form-select,.input-group.has-validation>:nth-last-child(n+3):not(.dropdown-toggle):not(.dropdown-menu):not(.form-floating){border-top-right-radius:0;border-bottom-right-radius:0}.input-group>:not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback){margin-left:calc(-1 * var(--bs-border-width));border-top-left-radius:0;border-bottom-left-radius:0}.input-group>.form-floating:not(:first-child)>.form-control,.input-group>.form-floating:not(:first-child)>.form-select{border-top-left-radius:0;border-bottom-left-radius:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:.875em;color:var(--bs-form-valid-color)}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;color:#fff;background-color:var(--bs-success);border-radius:var(--bs-border-radius)}.is-valid~.valid-feedback,.is-valid~.valid-tooltip,.was-validated :valid~.valid-feedback,.was-validated :valid~.valid-tooltip{display:block}.form-control.is-valid,.was-validated .form-control:valid{border-color:var(--bs-form-valid-border-color);padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-valid:focus,.was-validated .form-control:valid:focus{border-color:var(--bs-form-valid-border-color);box-shadow:0 0 0 .25rem rgba(var(--bs-success-rgb),.25)}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.form-select.is-valid,.was-validated .form-select:valid{border-color:var(--bs-form-valid-border-color)}.form-select.is-valid:not([multiple]):not([size]),.form-select.is-valid:not([multiple])[size="1"],.was-validated .form-select:valid:not([multiple]):not([size]),.was-validated .form-select:valid:not([multiple])[size="1"]{--bs-form-select-bg-icon:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1'/%3e%3c/svg%3e");padding-right:4.125rem;background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(.75em + .375rem) calc(.75em + .375rem)}.form-select.is-valid:focus,.was-validated .form-select:valid:focus{border-color:var(--bs-form-valid-border-color);box-shadow:0 0 0 .25rem rgba(var(--bs-success-rgb),.25)}.form-control-color.is-valid,.was-validated .form-control-color:valid{width:calc(3rem + calc(1.5em + .75rem))}.form-check-input.is-valid,.was-validated .form-check-input:valid{border-color:var(--bs-form-valid-border-color)}.form-check-input.is-valid:checked,.was-validated .form-check-input:valid:checked{background-color:var(--bs-form-valid-color)}.form-check-input.is-valid:focus,.was-validated .form-check-input:valid:focus{box-shadow:0 0 0 .25rem rgba(var(--bs-success-rgb),.25)}.form-check-input.is-valid~.form-check-label,.was-validated .form-check-input:valid~.form-check-label{color:var(--bs-form-valid-color)}.form-check-inline .form-check-input~.valid-feedback{margin-left:.5em}.input-group>.form-control:not(:focus).is-valid,.input-group>.form-floating:not(:focus-within).is-valid,.input-group>.form-select:not(:focus).is-valid,.was-validated .input-group>.form-control:not(:focus):valid,.was-validated .input-group>.form-floating:not(:focus-within):valid,.was-validated .input-group>.form-select:not(:focus):valid{z-index:3}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:.875em;color:var(--bs-form-invalid-color)}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;color:#fff;background-color:var(--bs-danger);border-radius:var(--bs-border-radius)}.is-invalid~.invalid-feedback,.is-invalid~.invalid-tooltip,.was-validated :invalid~.invalid-feedback,.was-validated :invalid~.invalid-tooltip{display:block}.form-control.is-invalid,.was-validated .form-control:invalid{border-color:var(--bs-form-invalid-border-color);padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-invalid:focus,.was-validated .form-control:invalid:focus{border-color:var(--bs-form-invalid-border-color);box-shadow:0 0 0 .25rem rgba(var(--bs-danger-rgb),.25)}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.form-select.is-invalid,.was-validated .form-select:invalid{border-color:var(--bs-form-invalid-border-color)}.form-select.is-invalid:not([multiple]):not([size]),.form-select.is-invalid:not([multiple])[size="1"],.was-validated .form-select:invalid:not([multiple]):not([size]),.was-validated .form-select:invalid:not([multiple])[size="1"]{--bs-form-select-bg-icon:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");padding-right:4.125rem;background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(.75em + .375rem) calc(.75em + .375rem)}.form-select.is-invalid:focus,.was-validated .form-select:invalid:focus{border-color:var(--bs-form-invalid-border-color);box-shadow:0 0 0 .25rem rgba(var(--bs-danger-rgb),.25)}.form-control-color.is-invalid,.was-validated .form-control-color:invalid{width:calc(3rem + calc(1.5em + .75rem))}.form-check-input.is-invalid,.was-validated .form-check-input:invalid{border-color:var(--bs-form-invalid-border-color)}.form-check-input.is-invalid:checked,.was-validated .form-check-input:invalid:checked{background-color:var(--bs-form-invalid-color)}.form-check-input.is-invalid:focus,.was-validated .form-check-input:invalid:focus{box-shadow:0 0 0 .25rem rgba(var(--bs-danger-rgb),.25)}.form-check-input.is-invalid~.form-check-label,.was-validated .form-check-input:invalid~.form-check-label{color:var(--bs-form-invalid-color)}.form-check-inline .form-check-input~.invalid-feedback{margin-left:.5em}.input-group>.form-control:not(:focus).is-invalid,.input-group>.form-floating:not(:focus-within).is-invalid,.input-group>.form-select:not(:focus).is-invalid,.was-validated .input-group>.form-control:not(:focus):invalid,.was-validated .input-group>.form-floating:not(:focus-within):invalid,.was-validated .input-group>.form-select:not(:focus):invalid{z-index:4}.btn{--bs-btn-padding-x:0.75rem;--bs-btn-padding-y:0.375rem;--bs-btn-font-family: ;--bs-btn-font-size:1rem;--bs-btn-font-weight:400;--bs-btn-line-height:1.5;--bs-btn-color:var(--bs-body-color);--bs-btn-bg:transparent;--bs-btn-border-width:var(--bs-border-width);--bs-btn-border-color:transparent;--bs-btn-border-radius:var(--bs-border-radius);--bs-btn-hover-border-color:transparent;--bs-btn-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.15),0 1px 1px rgba(0, 0, 0, 0.075);--bs-btn-disabled-opacity:0.65;--bs-btn-focus-box-shadow:0 0 0 0.25rem rgba(var(--bs-btn-focus-shadow-rgb), .5);display:inline-block;padding:var(--bs-btn-padding-y) var(--bs-btn-padding-x);font-family:var(--bs-btn-font-family);font-size:var(--bs-btn-font-size);font-weight:var(--bs-btn-font-weight);line-height:var(--bs-btn-line-height);color:var(--bs-btn-color);text-align:center;text-decoration:none;vertical-align:middle;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none;border:var(--bs-btn-border-width) solid var(--bs-btn-border-color);border-radius:var(--bs-btn-border-radius);background-color:var(--bs-btn-bg);transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.btn{transition:none}}.btn:hover{color:var(--bs-btn-hover-color);background-color:var(--bs-btn-hover-bg);border-color:var(--bs-btn-hover-border-color)}.btn-check+.btn:hover{color:var(--bs-btn-color);background-color:var(--bs-btn-bg);border-color:var(--bs-btn-border-color)}.btn:focus-visible{color:var(--bs-btn-hover-color);background-color:var(--bs-btn-hover-bg);border-color:var(--bs-btn-hover-border-color);outline:0;box-shadow:var(--bs-btn-focus-box-shadow)}.btn-check:focus-visible+.btn{border-color:var(--bs-btn-hover-border-color);outline:0;box-shadow:var(--bs-btn-focus-box-shadow)}.btn-check:checked+.btn,.btn.active,.btn.show,.btn:first-child:active,:not(.btn-check)+.btn:active{color:var(--bs-btn-active-color);background-color:var(--bs-btn-active-bg);border-color:var(--bs-btn-active-border-color)}.btn-check:checked+.btn:focus-visible,.btn.active:focus-visible,.btn.show:focus-visible,.btn:first-child:active:focus-visible,:not(.btn-check)+.btn:active:focus-visible{box-shadow:var(--bs-btn-focus-box-shadow)}.btn-check:checked:focus-visible+.btn{box-shadow:var(--bs-btn-focus-box-shadow)}.btn.disabled,.btn:disabled,fieldset:disabled .btn{color:var(--bs-btn-disabled-color);pointer-events:none;background-color:var(--bs-btn-disabled-bg);border-color:var(--bs-btn-disabled-border-color);opacity:var(--bs-btn-disabled-opacity)}.btn-primary{--bs-btn-color:#fff;--bs-btn-bg:#0d6efd;--bs-btn-border-color:#0d6efd;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#0b5ed7;--bs-btn-hover-border-color:#0a58ca;--bs-btn-focus-shadow-rgb:49,132,253;--bs-btn-active-color:#fff;--bs-btn-active-bg:#0a58ca;--bs-btn-active-border-color:#0a53be;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#0d6efd;--bs-btn-disabled-border-color:#0d6efd}.btn-secondary{--bs-btn-color:#fff;--bs-btn-bg:#6c757d;--bs-btn-border-color:#6c757d;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#5c636a;--bs-btn-hover-border-color:#565e64;--bs-btn-focus-shadow-rgb:130,138,145;--bs-btn-active-color:#fff;--bs-btn-active-bg:#565e64;--bs-btn-active-border-color:#51585e;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#6c757d;--bs-btn-disabled-border-color:#6c757d}.btn-success{--bs-btn-color:#fff;--bs-btn-bg:#198754;--bs-btn-border-color:#198754;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#157347;--bs-btn-hover-border-color:#146c43;--bs-btn-focus-shadow-rgb:60,153,110;--bs-btn-active-color:#fff;--bs-btn-active-bg:#146c43;--bs-btn-active-border-color:#13653f;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#198754;--bs-btn-disabled-border-color:#198754}.btn-info{--bs-btn-color:#000;--bs-btn-bg:#0dcaf0;--bs-btn-border-color:#0dcaf0;--bs-btn-hover-color:#000;--bs-btn-hover-bg:#31d2f2;--bs-btn-hover-border-color:#25cff2;--bs-btn-focus-shadow-rgb:11,172,204;--bs-btn-active-color:#000;--bs-btn-active-bg:#3dd5f3;--bs-btn-active-border-color:#25cff2;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#000;--bs-btn-disabled-bg:#0dcaf0;--bs-btn-disabled-border-color:#0dcaf0}.btn-warning{--bs-btn-color:#000;--bs-btn-bg:#ffc107;--bs-btn-border-color:#ffc107;--bs-btn-hover-color:#000;--bs-btn-hover-bg:#ffca2c;--bs-btn-hover-border-color:#ffc720;--bs-btn-focus-shadow-rgb:217,164,6;--bs-btn-active-color:#000;--bs-btn-active-bg:#ffcd39;--bs-btn-active-border-color:#ffc720;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#000;--bs-btn-disabled-bg:#ffc107;--bs-btn-disabled-border-color:#ffc107}.btn-danger{--bs-btn-color:#fff;--bs-btn-bg:#dc3545;--bs-btn-border-color:#dc3545;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#bb2d3b;--bs-btn-hover-border-color:#b02a37;--bs-btn-focus-shadow-rgb:225,83,97;--bs-btn-active-color:#fff;--bs-btn-active-bg:#b02a37;--bs-btn-active-border-color:#a52834;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#dc3545;--bs-btn-disabled-border-color:#dc3545}.btn-light{--bs-btn-color:#000;--bs-btn-bg:#f8f9fa;--bs-btn-border-color:#f8f9fa;--bs-btn-hover-color:#000;--bs-btn-hover-bg:#d3d4d5;--bs-btn-hover-border-color:#c6c7c8;--bs-btn-focus-shadow-rgb:211,212,213;--bs-btn-active-color:#000;--bs-btn-active-bg:#c6c7c8;--bs-btn-active-border-color:#babbbc;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#000;--bs-btn-disabled-bg:#f8f9fa;--bs-btn-disabled-border-color:#f8f9fa}.btn-dark{--bs-btn-color:#fff;--bs-btn-bg:#212529;--bs-btn-border-color:#212529;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#424649;--bs-btn-hover-border-color:#373b3e;--bs-btn-focus-shadow-rgb:66,70,73;--bs-btn-active-color:#fff;--bs-btn-active-bg:#4d5154;--bs-btn-active-border-color:#373b3e;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#212529;--bs-btn-disabled-border-color:#212529}.btn-outline-primary{--bs-btn-color:#0d6efd;--bs-btn-border-color:#0d6efd;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#0d6efd;--bs-btn-hover-border-color:#0d6efd;--bs-btn-focus-shadow-rgb:13,110,253;--bs-btn-active-color:#fff;--bs-btn-active-bg:#0d6efd;--bs-btn-active-border-color:#0d6efd;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#0d6efd;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#0d6efd;--bs-gradient:none}.btn-outline-secondary{--bs-btn-color:#6c757d;--bs-btn-border-color:#6c757d;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#6c757d;--bs-btn-hover-border-color:#6c757d;--bs-btn-focus-shadow-rgb:108,117,125;--bs-btn-active-color:#fff;--bs-btn-active-bg:#6c757d;--bs-btn-active-border-color:#6c757d;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#6c757d;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#6c757d;--bs-gradient:none}.btn-outline-success{--bs-btn-color:#198754;--bs-btn-border-color:#198754;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#198754;--bs-btn-hover-border-color:#198754;--bs-btn-focus-shadow-rgb:25,135,84;--bs-btn-active-color:#fff;--bs-btn-active-bg:#198754;--bs-btn-active-border-color:#198754;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#198754;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#198754;--bs-gradient:none}.btn-outline-info{--bs-btn-color:#0dcaf0;--bs-btn-border-color:#0dcaf0;--bs-btn-hover-color:#000;--bs-btn-hover-bg:#0dcaf0;--bs-btn-hover-border-color:#0dcaf0;--bs-btn-focus-shadow-rgb:13,202,240;--bs-btn-active-color:#000;--bs-btn-active-bg:#0dcaf0;--bs-btn-active-border-color:#0dcaf0;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#0dcaf0;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#0dcaf0;--bs-gradient:none}.btn-outline-warning{--bs-btn-color:#ffc107;--bs-btn-border-color:#ffc107;--bs-btn-hover-color:#000;--bs-btn-hover-bg:#ffc107;--bs-btn-hover-border-color:#ffc107;--bs-btn-focus-shadow-rgb:255,193,7;--bs-btn-active-color:#000;--bs-btn-active-bg:#ffc107;--bs-btn-active-border-color:#ffc107;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#ffc107;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#ffc107;--bs-gradient:none}.btn-outline-danger{--bs-btn-color:#dc3545;--bs-btn-border-color:#dc3545;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#dc3545;--bs-btn-hover-border-color:#dc3545;--bs-btn-focus-shadow-rgb:220,53,69;--bs-btn-active-color:#fff;--bs-btn-active-bg:#dc3545;--bs-btn-active-border-color:#dc3545;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#dc3545;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#dc3545;--bs-gradient:none}.btn-outline-light{--bs-btn-color:#f8f9fa;--bs-btn-border-color:#f8f9fa;--bs-btn-hover-color:#000;--bs-btn-hover-bg:#f8f9fa;--bs-btn-hover-border-color:#f8f9fa;--bs-btn-focus-shadow-rgb:248,249,250;--bs-btn-active-color:#000;--bs-btn-active-bg:#f8f9fa;--bs-btn-active-border-color:#f8f9fa;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#f8f9fa;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#f8f9fa;--bs-gradient:none}.btn-outline-dark{--bs-btn-color:#212529;--bs-btn-border-color:#212529;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#212529;--bs-btn-hover-border-color:#212529;--bs-btn-focus-shadow-rgb:33,37,41;--bs-btn-active-color:#fff;--bs-btn-active-bg:#212529;--bs-btn-active-border-color:#212529;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#212529;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#212529;--bs-gradient:none}.btn-link{--bs-btn-font-weight:400;--bs-btn-color:var(--bs-link-color);--bs-btn-bg:transparent;--bs-btn-border-color:transparent;--bs-btn-hover-color:var(--bs-link-hover-color);--bs-btn-hover-border-color:transparent;--bs-btn-active-color:var(--bs-link-hover-color);--bs-btn-active-border-color:transparent;--bs-btn-disabled-color:#6c757d;--bs-btn-disabled-border-color:transparent;--bs-btn-box-shadow:0 0 0 #000;--bs-btn-focus-shadow-rgb:49,132,253;text-decoration:underline}.btn-link:focus-visible{color:var(--bs-btn-color)}.btn-link:hover{color:var(--bs-btn-hover-color)}.btn-group-lg>.btn,.btn-lg{--bs-btn-padding-y:0.5rem;--bs-btn-padding-x:1rem;--bs-btn-font-size:1.25rem;--bs-btn-border-radius:var(--bs-border-radius-lg)}.btn-group-sm>.btn,.btn-sm{--bs-btn-padding-y:0.25rem;--bs-btn-padding-x:0.5rem;--bs-btn-font-size:0.875rem;--bs-btn-border-radius:var(--bs-border-radius-sm)}.fade{transition:opacity .15s linear}@media (prefers-reduced-motion:reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{height:0;overflow:hidden;transition:height .35s ease}@media (prefers-reduced-motion:reduce){.collapsing{transition:none}}.collapsing.collapse-horizontal{width:0;height:auto;transition:width .35s ease}@media (prefers-reduced-motion:reduce){.collapsing.collapse-horizontal{transition:none}}.dropdown,.dropdown-center,.dropend,.dropstart,.dropup,.dropup-center{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-bottom:0;border-left:.3em solid transparent}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{--bs-dropdown-zindex:1000;--bs-dropdown-min-width:10rem;--bs-dropdown-padding-x:0;--bs-dropdown-padding-y:0.5rem;--bs-dropdown-spacer:0.125rem;--bs-dropdown-font-size:1rem;--bs-dropdown-color:var(--bs-body-color);--bs-dropdown-bg:var(--bs-body-bg);--bs-dropdown-border-color:var(--bs-border-color-translucent);--bs-dropdown-border-radius:var(--bs-border-radius);--bs-dropdown-border-width:var(--bs-border-width);--bs-dropdown-inner-border-radius:calc(var(--bs-border-radius) - var(--bs-border-width));--bs-dropdown-divider-bg:var(--bs-border-color-translucent);--bs-dropdown-divider-margin-y:0.5rem;--bs-dropdown-box-shadow:var(--bs-box-shadow);--bs-dropdown-link-color:var(--bs-body-color);--bs-dropdown-link-hover-color:var(--bs-body-color);--bs-dropdown-link-hover-bg:var(--bs-tertiary-bg);--bs-dropdown-link-active-color:#fff;--bs-dropdown-link-active-bg:#0d6efd;--bs-dropdown-link-disabled-color:var(--bs-tertiary-color);--bs-dropdown-item-padding-x:1rem;--bs-dropdown-item-padding-y:0.25rem;--bs-dropdown-header-color:#6c757d;--bs-dropdown-header-padding-x:1rem;--bs-dropdown-header-padding-y:0.5rem;position:absolute;z-index:var(--bs-dropdown-zindex);display:none;min-width:var(--bs-dropdown-min-width);padding:var(--bs-dropdown-padding-y) var(--bs-dropdown-padding-x);margin:0;font-size:var(--bs-dropdown-font-size);color:var(--bs-dropdown-color);text-align:left;list-style:none;background-color:var(--bs-dropdown-bg);background-clip:padding-box;border:var(--bs-dropdown-border-width) solid var(--bs-dropdown-border-color);border-radius:var(--bs-dropdown-border-radius)}.dropdown-menu[data-bs-popper]{top:100%;left:0;margin-top:var(--bs-dropdown-spacer)}.dropdown-menu-start{--bs-position:start}.dropdown-menu-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-end{--bs-position:end}.dropdown-menu-end[data-bs-popper]{right:0;left:auto}@media (min-width:576px){.dropdown-menu-sm-start{--bs-position:start}.dropdown-menu-sm-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-sm-end{--bs-position:end}.dropdown-menu-sm-end[data-bs-popper]{right:0;left:auto}}@media (min-width:768px){.dropdown-menu-md-start{--bs-position:start}.dropdown-menu-md-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-md-end{--bs-position:end}.dropdown-menu-md-end[data-bs-popper]{right:0;left:auto}}@media (min-width:992px){.dropdown-menu-lg-start{--bs-position:start}.dropdown-menu-lg-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-lg-end{--bs-position:end}.dropdown-menu-lg-end[data-bs-popper]{right:0;left:auto}}@media (min-width:1200px){.dropdown-menu-xl-start{--bs-position:start}.dropdown-menu-xl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xl-end{--bs-position:end}.dropdown-menu-xl-end[data-bs-popper]{right:0;left:auto}}@media (min-width:1400px){.dropdown-menu-xxl-start{--bs-position:start}.dropdown-menu-xxl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xxl-end{--bs-position:end}.dropdown-menu-xxl-end[data-bs-popper]{right:0;left:auto}}.dropup .dropdown-menu[data-bs-popper]{top:auto;bottom:100%;margin-top:0;margin-bottom:var(--bs-dropdown-spacer)}.dropup .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid transparent;border-bottom:.3em solid;border-left:.3em solid transparent}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-menu[data-bs-popper]{top:0;right:auto;left:100%;margin-top:0;margin-left:var(--bs-dropdown-spacer)}.dropend .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:0;border-bottom:.3em solid transparent;border-left:.3em solid}.dropend .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-toggle::after{vertical-align:0}.dropstart .dropdown-menu[data-bs-popper]{top:0;right:100%;left:auto;margin-top:0;margin-right:var(--bs-dropdown-spacer)}.dropstart .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:""}.dropstart .dropdown-toggle::after{display:none}.dropstart .dropdown-toggle::before{display:inline-block;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:.3em solid;border-bottom:.3em solid transparent}.dropstart .dropdown-toggle:empty::after{margin-left:0}.dropstart .dropdown-toggle::before{vertical-align:0}.dropdown-divider{height:0;margin:var(--bs-dropdown-divider-margin-y) 0;overflow:hidden;border-top:1px solid var(--bs-dropdown-divider-bg);opacity:1}.dropdown-item{display:block;width:100%;padding:var(--bs-dropdown-item-padding-y) var(--bs-dropdown-item-padding-x);clear:both;font-weight:400;color:var(--bs-dropdown-link-color);text-align:inherit;text-decoration:none;white-space:nowrap;background-color:transparent;border:0;border-radius:var(--bs-dropdown-item-border-radius,0)}.dropdown-item:focus,.dropdown-item:hover{color:var(--bs-dropdown-link-hover-color);background-color:var(--bs-dropdown-link-hover-bg)}.dropdown-item.active,.dropdown-item:active{color:var(--bs-dropdown-link-active-color);text-decoration:none;background-color:var(--bs-dropdown-link-active-bg)}.dropdown-item.disabled,.dropdown-item:disabled{color:var(--bs-dropdown-link-disabled-color);pointer-events:none;background-color:transparent}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:var(--bs-dropdown-header-padding-y) var(--bs-dropdown-header-padding-x);margin-bottom:0;font-size:.875rem;color:var(--bs-dropdown-header-color);white-space:nowrap}.dropdown-item-text{display:block;padding:var(--bs-dropdown-item-padding-y) var(--bs-dropdown-item-padding-x);color:var(--bs-dropdown-link-color)}.dropdown-menu-dark{--bs-dropdown-color:#dee2e6;--bs-dropdown-bg:#343a40;--bs-dropdown-border-color:var(--bs-border-color-translucent);--bs-dropdown-box-shadow: ;--bs-dropdown-link-color:#dee2e6;--bs-dropdown-link-hover-color:#fff;--bs-dropdown-divider-bg:var(--bs-border-color-translucent);--bs-dropdown-link-hover-bg:rgba(255, 255, 255, 0.15);--bs-dropdown-link-active-color:#fff;--bs-dropdown-link-active-bg:#0d6efd;--bs-dropdown-link-disabled-color:#adb5bd;--bs-dropdown-header-color:#adb5bd}.btn-group,.btn-group-vertical{position:relative;display:inline-flex;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;flex:1 1 auto}.btn-group-vertical>.btn-check:checked+.btn,.btn-group-vertical>.btn-check:focus+.btn,.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn-check:checked+.btn,.btn-group>.btn-check:focus+.btn,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:1}.btn-toolbar{display:flex;flex-wrap:wrap;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group{border-radius:var(--bs-border-radius)}.btn-group>.btn-group:not(:first-child),.btn-group>:not(.btn-check:first-child)+.btn{margin-left:calc(-1 * var(--bs-border-width))}.btn-group>.btn-group:not(:last-child)>.btn,.btn-group>.btn.dropdown-toggle-split:first-child,.btn-group>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:not(:first-child)>.btn,.btn-group>.btn:nth-child(n+3),.btn-group>:not(.btn-check)+.btn{border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after,.dropend .dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after{margin-left:0}.dropstart .dropdown-toggle-split::before{margin-right:0}.btn-group-sm>.btn+.dropdown-toggle-split,.btn-sm+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-group-lg>.btn+.dropdown-toggle-split,.btn-lg+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{flex-direction:column;align-items:flex-start;justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn-group:not(:first-child),.btn-group-vertical>.btn:not(:first-child){margin-top:calc(-1 * var(--bs-border-width))}.btn-group-vertical>.btn-group:not(:last-child)>.btn,.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child)>.btn,.btn-group-vertical>.btn:nth-child(n+3),.btn-group-vertical>:not(.btn-check)+.btn{border-top-left-radius:0;border-top-right-radius:0}.nav{--bs-nav-link-padding-x:1rem;--bs-nav-link-padding-y:0.5rem;--bs-nav-link-font-weight: ;--bs-nav-link-color:var(--bs-link-color);--bs-nav-link-hover-color:var(--bs-link-hover-color);--bs-nav-link-disabled-color:var(--bs-secondary-color);display:flex;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:var(--bs-nav-link-padding-y) var(--bs-nav-link-padding-x);font-size:var(--bs-nav-link-font-size);font-weight:var(--bs-nav-link-font-weight);color:var(--bs-nav-link-color);text-decoration:none;background:0 0;border:0;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out}@media (prefers-reduced-motion:reduce){.nav-link{transition:none}}.nav-link:focus,.nav-link:hover{color:var(--bs-nav-link-hover-color)}.nav-link:focus-visible{outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.nav-link.disabled,.nav-link:disabled{color:var(--bs-nav-link-disabled-color);pointer-events:none;cursor:default}.nav-tabs{--bs-nav-tabs-border-width:var(--bs-border-width);--bs-nav-tabs-border-color:var(--bs-border-color);--bs-nav-tabs-border-radius:var(--bs-border-radius);--bs-nav-tabs-link-hover-border-color:var(--bs-secondary-bg) var(--bs-secondary-bg) var(--bs-border-color);--bs-nav-tabs-link-active-color:var(--bs-emphasis-color);--bs-nav-tabs-link-active-bg:var(--bs-body-bg);--bs-nav-tabs-link-active-border-color:var(--bs-border-color) var(--bs-border-color) var(--bs-body-bg);border-bottom:var(--bs-nav-tabs-border-width) solid var(--bs-nav-tabs-border-color)}.nav-tabs .nav-link{margin-bottom:calc(-1 * var(--bs-nav-tabs-border-width));border:var(--bs-nav-tabs-border-width) solid transparent;border-top-left-radius:var(--bs-nav-tabs-border-radius);border-top-right-radius:var(--bs-nav-tabs-border-radius)}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{isolation:isolate;border-color:var(--bs-nav-tabs-link-hover-border-color)}.nav-tabs .nav-item.show .nav-link,.nav-tabs .nav-link.active{color:var(--bs-nav-tabs-link-active-color);background-color:var(--bs-nav-tabs-link-active-bg);border-color:var(--bs-nav-tabs-link-active-border-color)}.nav-tabs .dropdown-menu{margin-top:calc(-1 * var(--bs-nav-tabs-border-width));border-top-left-radius:0;border-top-right-radius:0}.nav-pills{--bs-nav-pills-border-radius:var(--bs-border-radius);--bs-nav-pills-link-active-color:#fff;--bs-nav-pills-link-active-bg:#0d6efd}.nav-pills .nav-link{border-radius:var(--bs-nav-pills-border-radius)}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:var(--bs-nav-pills-link-active-color);background-color:var(--bs-nav-pills-link-active-bg)}.nav-underline{--bs-nav-underline-gap:1rem;--bs-nav-underline-border-width:0.125rem;--bs-nav-underline-link-active-color:var(--bs-emphasis-color);gap:var(--bs-nav-underline-gap)}.nav-underline .nav-link{padding-right:0;padding-left:0;border-bottom:var(--bs-nav-underline-border-width) solid transparent}.nav-underline .nav-link:focus,.nav-underline .nav-link:hover{border-bottom-color:currentcolor}.nav-underline .nav-link.active,.nav-underline .show>.nav-link{font-weight:700;color:var(--bs-nav-underline-link-active-color);border-bottom-color:currentcolor}.nav-fill .nav-item,.nav-fill>.nav-link{flex:1 1 auto;text-align:center}.nav-justified .nav-item,.nav-justified>.nav-link{flex-grow:1;flex-basis:0;text-align:center}.nav-fill .nav-item .nav-link,.nav-justified .nav-item .nav-link{width:100%}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{--bs-navbar-padding-x:0;--bs-navbar-padding-y:0.5rem;--bs-navbar-color:rgba(var(--bs-emphasis-color-rgb), 0.65);--bs-navbar-hover-color:rgba(var(--bs-emphasis-color-rgb), 0.8);--bs-navbar-disabled-color:rgba(var(--bs-emphasis-color-rgb), 0.3);--bs-navbar-active-color:rgba(var(--bs-emphasis-color-rgb), 1);--bs-navbar-brand-padding-y:0.3125rem;--bs-navbar-brand-margin-end:1rem;--bs-navbar-brand-font-size:1.25rem;--bs-navbar-brand-color:rgba(var(--bs-emphasis-color-rgb), 1);--bs-navbar-brand-hover-color:rgba(var(--bs-emphasis-color-rgb), 1);--bs-navbar-nav-link-padding-x:0.5rem;--bs-navbar-toggler-padding-y:0.25rem;--bs-navbar-toggler-padding-x:0.75rem;--bs-navbar-toggler-font-size:1.25rem;--bs-navbar-toggler-icon-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%2833, 37, 41, 0.75%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e");--bs-navbar-toggler-border-color:rgba(var(--bs-emphasis-color-rgb), 0.15);--bs-navbar-toggler-border-radius:var(--bs-border-radius);--bs-navbar-toggler-focus-width:0.25rem;--bs-navbar-toggler-transition:box-shadow 0.15s ease-in-out;position:relative;display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;padding:var(--bs-navbar-padding-y) var(--bs-navbar-padding-x)}.navbar>.container,.navbar>.container-fluid,.navbar>.container-lg,.navbar>.container-md,.navbar>.container-sm,.navbar>.container-xl,.navbar>.container-xxl{display:flex;flex-wrap:inherit;align-items:center;justify-content:space-between}.navbar-brand{padding-top:var(--bs-navbar-brand-padding-y);padding-bottom:var(--bs-navbar-brand-padding-y);margin-right:var(--bs-navbar-brand-margin-end);font-size:var(--bs-navbar-brand-font-size);color:var(--bs-navbar-brand-color);text-decoration:none;white-space:nowrap}.navbar-brand:focus,.navbar-brand:hover{color:var(--bs-navbar-brand-hover-color)}.navbar-nav{--bs-nav-link-padding-x:0;--bs-nav-link-padding-y:0.5rem;--bs-nav-link-font-weight: ;--bs-nav-link-color:var(--bs-navbar-color);--bs-nav-link-hover-color:var(--bs-navbar-hover-color);--bs-nav-link-disabled-color:var(--bs-navbar-disabled-color);display:flex;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link.active,.navbar-nav .nav-link.show{color:var(--bs-navbar-active-color)}.navbar-nav .dropdown-menu{position:static}.navbar-text{padding-top:.5rem;padding-bottom:.5rem;color:var(--bs-navbar-color)}.navbar-text a,.navbar-text a:focus,.navbar-text a:hover{color:var(--bs-navbar-active-color)}.navbar-collapse{flex-grow:1;flex-basis:100%;align-items:center}.navbar-toggler{padding:var(--bs-navbar-toggler-padding-y) var(--bs-navbar-toggler-padding-x);font-size:var(--bs-navbar-toggler-font-size);line-height:1;color:var(--bs-navbar-color);background-color:transparent;border:var(--bs-border-width) solid var(--bs-navbar-toggler-border-color);border-radius:var(--bs-navbar-toggler-border-radius);transition:var(--bs-navbar-toggler-transition)}@media (prefers-reduced-motion:reduce){.navbar-toggler{transition:none}}.navbar-toggler:hover{text-decoration:none}.navbar-toggler:focus{text-decoration:none;outline:0;box-shadow:0 0 0 var(--bs-navbar-toggler-focus-width)}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;background-image:var(--bs-navbar-toggler-icon-bg);background-repeat:no-repeat;background-position:center;background-size:100%}.navbar-nav-scroll{max-height:var(--bs-scroll-height,75vh);overflow-y:auto}@media (min-width:576px){.navbar-expand-sm{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-sm .navbar-nav{flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-sm .navbar-nav-scroll{overflow:visible}.navbar-expand-sm .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}.navbar-expand-sm .offcanvas{position:static;z-index:auto;flex-grow:1;width:auto!important;height:auto!important;visibility:visible!important;background-color:transparent!important;border:0!important;transform:none!important;transition:none}.navbar-expand-sm .offcanvas .offcanvas-header{display:none}.navbar-expand-sm .offcanvas .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:768px){.navbar-expand-md{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-md .navbar-nav{flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-md .navbar-nav-scroll{overflow:visible}.navbar-expand-md .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}.navbar-expand-md .offcanvas{position:static;z-index:auto;flex-grow:1;width:auto!important;height:auto!important;visibility:visible!important;background-color:transparent!important;border:0!important;transform:none!important;transition:none}.navbar-expand-md .offcanvas .offcanvas-header{display:none}.navbar-expand-md .offcanvas .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:992px){.navbar-expand-lg{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-lg .navbar-nav{flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-lg .navbar-nav-scroll{overflow:visible}.navbar-expand-lg .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}.navbar-expand-lg .offcanvas{position:static;z-index:auto;flex-grow:1;width:auto!important;height:auto!important;visibility:visible!important;background-color:transparent!important;border:0!important;transform:none!important;transition:none}.navbar-expand-lg .offcanvas .offcanvas-header{display:none}.navbar-expand-lg .offcanvas .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:1200px){.navbar-expand-xl{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-xl .navbar-nav{flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-xl .navbar-nav-scroll{overflow:visible}.navbar-expand-xl .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}.navbar-expand-xl .offcanvas{position:static;z-index:auto;flex-grow:1;width:auto!important;height:auto!important;visibility:visible!important;background-color:transparent!important;border:0!important;transform:none!important;transition:none}.navbar-expand-xl .offcanvas .offcanvas-header{display:none}.navbar-expand-xl .offcanvas .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:1400px){.navbar-expand-xxl{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-xxl .navbar-nav{flex-direction:row}.navbar-expand-xxl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xxl .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-xxl .navbar-nav-scroll{overflow:visible}.navbar-expand-xxl .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-xxl .navbar-toggler{display:none}.navbar-expand-xxl .offcanvas{position:static;z-index:auto;flex-grow:1;width:auto!important;height:auto!important;visibility:visible!important;background-color:transparent!important;border:0!important;transform:none!important;transition:none}.navbar-expand-xxl .offcanvas .offcanvas-header{display:none}.navbar-expand-xxl .offcanvas .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}.navbar-expand{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand .navbar-nav{flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand .navbar-nav-scroll{overflow:visible}.navbar-expand .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-expand .offcanvas{position:static;z-index:auto;flex-grow:1;width:auto!important;height:auto!important;visibility:visible!important;background-color:transparent!important;border:0!important;transform:none!important;transition:none}.navbar-expand .offcanvas .offcanvas-header{display:none}.navbar-expand .offcanvas .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}.navbar-dark,.navbar[data-bs-theme=dark]{--bs-navbar-color:rgba(255, 255, 255, 0.55);--bs-navbar-hover-color:rgba(255, 255, 255, 0.75);--bs-navbar-disabled-color:rgba(255, 255, 255, 0.25);--bs-navbar-active-color:#fff;--bs-navbar-brand-color:#fff;--bs-navbar-brand-hover-color:#fff;--bs-navbar-toggler-border-color:rgba(255, 255, 255, 0.1);--bs-navbar-toggler-icon-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}[data-bs-theme=dark] .navbar-toggler-icon{--bs-navbar-toggler-icon-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.card{--bs-card-spacer-y:1rem;--bs-card-spacer-x:1rem;--bs-card-title-spacer-y:0.5rem;--bs-card-title-color: ;--bs-card-subtitle-color: ;--bs-card-border-width:var(--bs-border-width);--bs-card-border-color:var(--bs-border-color-translucent);--bs-card-border-radius:var(--bs-border-radius);--bs-card-box-shadow: ;--bs-card-inner-border-radius:calc(var(--bs-border-radius) - (var(--bs-border-width)));--bs-card-cap-padding-y:0.5rem;--bs-card-cap-padding-x:1rem;--bs-card-cap-bg:rgba(var(--bs-body-color-rgb), 0.03);--bs-card-cap-color: ;--bs-card-height: ;--bs-card-color: ;--bs-card-bg:var(--bs-body-bg);--bs-card-img-overlay-padding:1rem;--bs-card-group-margin:0.75rem;position:relative;display:flex;flex-direction:column;min-width:0;height:var(--bs-card-height);color:var(--bs-body-color);word-wrap:break-word;background-color:var(--bs-card-bg);background-clip:border-box;border:var(--bs-card-border-width) solid var(--bs-card-border-color);border-radius:var(--bs-card-border-radius)}.card>hr{margin-right:0;margin-left:0}.card>.list-group{border-top:inherit;border-bottom:inherit}.card>.list-group:first-child{border-top-width:0;border-top-left-radius:var(--bs-card-inner-border-radius);border-top-right-radius:var(--bs-card-inner-border-radius)}.card>.list-group:last-child{border-bottom-width:0;border-bottom-right-radius:var(--bs-card-inner-border-radius);border-bottom-left-radius:var(--bs-card-inner-border-radius)}.card>.card-header+.list-group,.card>.list-group+.card-footer{border-top:0}.card-body{flex:1 1 auto;padding:var(--bs-card-spacer-y) var(--bs-card-spacer-x);color:var(--bs-card-color)}.card-title{margin-bottom:var(--bs-card-title-spacer-y);color:var(--bs-card-title-color)}.card-subtitle{margin-top:calc(-.5 * var(--bs-card-title-spacer-y));margin-bottom:0;color:var(--bs-card-subtitle-color)}.card-text:last-child{margin-bottom:0}.card-link+.card-link{margin-left:var(--bs-card-spacer-x)}.card-header{padding:var(--bs-card-cap-padding-y) var(--bs-card-cap-padding-x);margin-bottom:0;color:var(--bs-card-cap-color);background-color:var(--bs-card-cap-bg);border-bottom:var(--bs-card-border-width) solid var(--bs-card-border-color)}.card-header:first-child{border-radius:var(--bs-card-inner-border-radius) var(--bs-card-inner-border-radius) 0 0}.card-footer{padding:var(--bs-card-cap-padding-y) var(--bs-card-cap-padding-x);color:var(--bs-card-cap-color);background-color:var(--bs-card-cap-bg);border-top:var(--bs-card-border-width) solid var(--bs-card-border-color)}.card-footer:last-child{border-radius:0 0 var(--bs-card-inner-border-radius) var(--bs-card-inner-border-radius)}.card-header-tabs{margin-right:calc(-.5 * var(--bs-card-cap-padding-x));margin-bottom:calc(-1 * var(--bs-card-cap-padding-y));margin-left:calc(-.5 * var(--bs-card-cap-padding-x));border-bottom:0}.card-header-tabs .nav-link.active{background-color:var(--bs-card-bg);border-bottom-color:var(--bs-card-bg)}.card-header-pills{margin-right:calc(-.5 * var(--bs-card-cap-padding-x));margin-left:calc(-.5 * var(--bs-card-cap-padding-x))}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:var(--bs-card-img-overlay-padding);border-radius:var(--bs-card-inner-border-radius)}.card-img,.card-img-bottom,.card-img-top{width:100%}.card-img,.card-img-top{border-top-left-radius:var(--bs-card-inner-border-radius);border-top-right-radius:var(--bs-card-inner-border-radius)}.card-img,.card-img-bottom{border-bottom-right-radius:var(--bs-card-inner-border-radius);border-bottom-left-radius:var(--bs-card-inner-border-radius)}.card-group>.card{margin-bottom:var(--bs-card-group-margin)}@media (min-width:576px){.card-group{display:flex;flex-flow:row wrap}.card-group>.card{flex:1 0 0;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:not(:last-child)>.card-header,.card-group>.card:not(:last-child)>.card-img-top{border-top-right-radius:0}.card-group>.card:not(:last-child)>.card-footer,.card-group>.card:not(:last-child)>.card-img-bottom{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:not(:first-child)>.card-header,.card-group>.card:not(:first-child)>.card-img-top{border-top-left-radius:0}.card-group>.card:not(:first-child)>.card-footer,.card-group>.card:not(:first-child)>.card-img-bottom{border-bottom-left-radius:0}}.accordion{--bs-accordion-color:var(--bs-body-color);--bs-accordion-bg:var(--bs-body-bg);--bs-accordion-transition:color 0.15s ease-in-out,background-color 0.15s ease-in-out,border-color 0.15s ease-in-out,box-shadow 0.15s ease-in-out,border-radius 0.15s ease;--bs-accordion-border-color:var(--bs-border-color);--bs-accordion-border-width:var(--bs-border-width);--bs-accordion-border-radius:var(--bs-border-radius);--bs-accordion-inner-border-radius:calc(var(--bs-border-radius) - (var(--bs-border-width)));--bs-accordion-btn-padding-x:1.25rem;--bs-accordion-btn-padding-y:1rem;--bs-accordion-btn-color:var(--bs-body-color);--bs-accordion-btn-bg:var(--bs-accordion-bg);--bs-accordion-btn-icon:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23212529' stroke-linecap='round' stroke-linejoin='round'%3e%3cpath d='m2 5 6 6 6-6'/%3e%3c/svg%3e");--bs-accordion-btn-icon-width:1.25rem;--bs-accordion-btn-icon-transform:rotate(-180deg);--bs-accordion-btn-icon-transition:transform 0.2s ease-in-out;--bs-accordion-btn-active-icon:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23052c65' stroke-linecap='round' stroke-linejoin='round'%3e%3cpath d='m2 5 6 6 6-6'/%3e%3c/svg%3e");--bs-accordion-btn-focus-box-shadow:0 0 0 0.25rem rgba(13, 110, 253, 0.25);--bs-accordion-body-padding-x:1.25rem;--bs-accordion-body-padding-y:1rem;--bs-accordion-active-color:var(--bs-primary-text-emphasis);--bs-accordion-active-bg:var(--bs-primary-bg-subtle)}.accordion-button{position:relative;display:flex;align-items:center;width:100%;padding:var(--bs-accordion-btn-padding-y) var(--bs-accordion-btn-padding-x);font-size:1rem;color:var(--bs-accordion-btn-color);text-align:left;background-color:var(--bs-accordion-btn-bg);border:0;border-radius:0;overflow-anchor:none;transition:var(--bs-accordion-transition)}@media (prefers-reduced-motion:reduce){.accordion-button{transition:none}}.accordion-button:not(.collapsed){color:var(--bs-accordion-active-color);background-color:var(--bs-accordion-active-bg);box-shadow:inset 0 calc(-1 * var(--bs-accordion-border-width)) 0 var(--bs-accordion-border-color)}.accordion-button:not(.collapsed)::after{background-image:var(--bs-accordion-btn-active-icon);transform:var(--bs-accordion-btn-icon-transform)}.accordion-button::after{flex-shrink:0;width:var(--bs-accordion-btn-icon-width);height:var(--bs-accordion-btn-icon-width);margin-left:auto;content:"";background-image:var(--bs-accordion-btn-icon);background-repeat:no-repeat;background-size:var(--bs-accordion-btn-icon-width);transition:var(--bs-accordion-btn-icon-transition)}@media (prefers-reduced-motion:reduce){.accordion-button::after{transition:none}}.accordion-button:hover{z-index:2}.accordion-button:focus{z-index:3;outline:0;box-shadow:var(--bs-accordion-btn-focus-box-shadow)}.accordion-header{margin-bottom:0}.accordion-item{color:var(--bs-accordion-color);background-color:var(--bs-accordion-bg);border:var(--bs-accordion-border-width) solid var(--bs-accordion-border-color)}.accordion-item:first-of-type{border-top-left-radius:var(--bs-accordion-border-radius);border-top-right-radius:var(--bs-accordion-border-radius)}.accordion-item:first-of-type>.accordion-header .accordion-button{border-top-left-radius:var(--bs-accordion-inner-border-radius);border-top-right-radius:var(--bs-accordion-inner-border-radius)}.accordion-item:not(:first-of-type){border-top:0}.accordion-item:last-of-type{border-bottom-right-radius:var(--bs-accordion-border-radius);border-bottom-left-radius:var(--bs-accordion-border-radius)}.accordion-item:last-of-type>.accordion-header .accordion-button.collapsed{border-bottom-right-radius:var(--bs-accordion-inner-border-radius);border-bottom-left-radius:var(--bs-accordion-inner-border-radius)}.accordion-item:last-of-type>.accordion-collapse{border-bottom-right-radius:var(--bs-accordion-border-radius);border-bottom-left-radius:var(--bs-accordion-border-radius)}.accordion-body{padding:var(--bs-accordion-body-padding-y) var(--bs-accordion-body-padding-x)}.accordion-flush>.accordion-item{border-right:0;border-left:0;border-radius:0}.accordion-flush>.accordion-item:first-child{border-top:0}.accordion-flush>.accordion-item:last-child{border-bottom:0}.accordion-flush>.accordion-item>.accordion-collapse,.accordion-flush>.accordion-item>.accordion-header .accordion-button,.accordion-flush>.accordion-item>.accordion-header .accordion-button.collapsed{border-radius:0}[data-bs-theme=dark] .accordion-button::after{--bs-accordion-btn-icon:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%236ea8fe'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708'/%3e%3c/svg%3e");--bs-accordion-btn-active-icon:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%236ea8fe'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708'/%3e%3c/svg%3e")}.breadcrumb{--bs-breadcrumb-padding-x:0;--bs-breadcrumb-padding-y:0;--bs-breadcrumb-margin-bottom:1rem;--bs-breadcrumb-bg: ;--bs-breadcrumb-border-radius: ;--bs-breadcrumb-divider-color:var(--bs-secondary-color);--bs-breadcrumb-item-padding-x:0.5rem;--bs-breadcrumb-item-active-color:var(--bs-secondary-color);display:flex;flex-wrap:wrap;padding:var(--bs-breadcrumb-padding-y) var(--bs-breadcrumb-padding-x);margin-bottom:var(--bs-breadcrumb-margin-bottom);font-size:var(--bs-breadcrumb-font-size);list-style:none;background-color:var(--bs-breadcrumb-bg);border-radius:var(--bs-breadcrumb-border-radius)}.breadcrumb-item+.breadcrumb-item{padding-left:var(--bs-breadcrumb-item-padding-x)}.breadcrumb-item+.breadcrumb-item::before{float:left;padding-right:var(--bs-breadcrumb-item-padding-x);color:var(--bs-breadcrumb-divider-color);content:var(--bs-breadcrumb-divider, "/")}.breadcrumb-item.active{color:var(--bs-breadcrumb-item-active-color)}.pagination{--bs-pagination-padding-x:0.75rem;--bs-pagination-padding-y:0.375rem;--bs-pagination-font-size:1rem;--bs-pagination-color:var(--bs-link-color);--bs-pagination-bg:var(--bs-body-bg);--bs-pagination-border-width:var(--bs-border-width);--bs-pagination-border-color:var(--bs-border-color);--bs-pagination-border-radius:var(--bs-border-radius);--bs-pagination-hover-color:var(--bs-link-hover-color);--bs-pagination-hover-bg:var(--bs-tertiary-bg);--bs-pagination-hover-border-color:var(--bs-border-color);--bs-pagination-focus-color:var(--bs-link-hover-color);--bs-pagination-focus-bg:var(--bs-secondary-bg);--bs-pagination-focus-box-shadow:0 0 0 0.25rem rgba(13, 110, 253, 0.25);--bs-pagination-active-color:#fff;--bs-pagination-active-bg:#0d6efd;--bs-pagination-active-border-color:#0d6efd;--bs-pagination-disabled-color:var(--bs-secondary-color);--bs-pagination-disabled-bg:var(--bs-secondary-bg);--bs-pagination-disabled-border-color:var(--bs-border-color);display:flex;padding-left:0;list-style:none}.page-link{position:relative;display:block;padding:var(--bs-pagination-padding-y) var(--bs-pagination-padding-x);font-size:var(--bs-pagination-font-size);color:var(--bs-pagination-color);text-decoration:none;background-color:var(--bs-pagination-bg);border:var(--bs-pagination-border-width) solid var(--bs-pagination-border-color);transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.page-link{transition:none}}.page-link:hover{z-index:2;color:var(--bs-pagination-hover-color);background-color:var(--bs-pagination-hover-bg);border-color:var(--bs-pagination-hover-border-color)}.page-link:focus{z-index:3;color:var(--bs-pagination-focus-color);background-color:var(--bs-pagination-focus-bg);outline:0;box-shadow:var(--bs-pagination-focus-box-shadow)}.active>.page-link,.page-link.active{z-index:3;color:var(--bs-pagination-active-color);background-color:var(--bs-pagination-active-bg);border-color:var(--bs-pagination-active-border-color)}.disabled>.page-link,.page-link.disabled{color:var(--bs-pagination-disabled-color);pointer-events:none;background-color:var(--bs-pagination-disabled-bg);border-color:var(--bs-pagination-disabled-border-color)}.page-item:not(:first-child) .page-link{margin-left:calc(-1 * var(--bs-border-width))}.page-item:first-child .page-link{border-top-left-radius:var(--bs-pagination-border-radius);border-bottom-left-radius:var(--bs-pagination-border-radius)}.page-item:last-child .page-link{border-top-right-radius:var(--bs-pagination-border-radius);border-bottom-right-radius:var(--bs-pagination-border-radius)}.pagination-lg{--bs-pagination-padding-x:1.5rem;--bs-pagination-padding-y:0.75rem;--bs-pagination-font-size:1.25rem;--bs-pagination-border-radius:var(--bs-border-radius-lg)}.pagination-sm{--bs-pagination-padding-x:0.5rem;--bs-pagination-padding-y:0.25rem;--bs-pagination-font-size:0.875rem;--bs-pagination-border-radius:var(--bs-border-radius-sm)}.badge{--bs-badge-padding-x:0.65em;--bs-badge-padding-y:0.35em;--bs-badge-font-size:0.75em;--bs-badge-font-weight:700;--bs-badge-color:#fff;--bs-badge-border-radius:var(--bs-border-radius);display:inline-block;padding:var(--bs-badge-padding-y) var(--bs-badge-padding-x);font-size:var(--bs-badge-font-size);font-weight:var(--bs-badge-font-weight);line-height:1;color:var(--bs-badge-color);text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:var(--bs-badge-border-radius)}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.alert{--bs-alert-bg:transparent;--bs-alert-padding-x:1rem;--bs-alert-padding-y:1rem;--bs-alert-margin-bottom:1rem;--bs-alert-color:inherit;--bs-alert-border-color:transparent;--bs-alert-border:var(--bs-border-width) solid var(--bs-alert-border-color);--bs-alert-border-radius:var(--bs-border-radius);--bs-alert-link-color:inherit;position:relative;padding:var(--bs-alert-padding-y) var(--bs-alert-padding-x);margin-bottom:var(--bs-alert-margin-bottom);color:var(--bs-alert-color);background-color:var(--bs-alert-bg);border:var(--bs-alert-border);border-radius:var(--bs-alert-border-radius)}.alert-heading{color:inherit}.alert-link{font-weight:700;color:var(--bs-alert-link-color)}.alert-dismissible{padding-right:3rem}.alert-dismissible .btn-close{position:absolute;top:0;right:0;z-index:2;padding:1.25rem 1rem}.alert-primary{--bs-alert-color:var(--bs-primary-text-emphasis);--bs-alert-bg:var(--bs-primary-bg-subtle);--bs-alert-border-color:var(--bs-primary-border-subtle);--bs-alert-link-color:var(--bs-primary-text-emphasis)}.alert-secondary{--bs-alert-color:var(--bs-secondary-text-emphasis);--bs-alert-bg:var(--bs-secondary-bg-subtle);--bs-alert-border-color:var(--bs-secondary-border-subtle);--bs-alert-link-color:var(--bs-secondary-text-emphasis)}.alert-success{--bs-alert-color:var(--bs-success-text-emphasis);--bs-alert-bg:var(--bs-success-bg-subtle);--bs-alert-border-color:var(--bs-success-border-subtle);--bs-alert-link-color:var(--bs-success-text-emphasis)}.alert-info{--bs-alert-color:var(--bs-info-text-emphasis);--bs-alert-bg:var(--bs-info-bg-subtle);--bs-alert-border-color:var(--bs-info-border-subtle);--bs-alert-link-color:var(--bs-info-text-emphasis)}.alert-warning{--bs-alert-color:var(--bs-warning-text-emphasis);--bs-alert-bg:var(--bs-warning-bg-subtle);--bs-alert-border-color:var(--bs-warning-border-subtle);--bs-alert-link-color:var(--bs-warning-text-emphasis)}.alert-danger{--bs-alert-color:var(--bs-danger-text-emphasis);--bs-alert-bg:var(--bs-danger-bg-subtle);--bs-alert-border-color:var(--bs-danger-border-subtle);--bs-alert-link-color:var(--bs-danger-text-emphasis)}.alert-light{--bs-alert-color:var(--bs-light-text-emphasis);--bs-alert-bg:var(--bs-light-bg-subtle);--bs-alert-border-color:var(--bs-light-border-subtle);--bs-alert-link-color:var(--bs-light-text-emphasis)}.alert-dark{--bs-alert-color:var(--bs-dark-text-emphasis);--bs-alert-bg:var(--bs-dark-bg-subtle);--bs-alert-border-color:var(--bs-dark-border-subtle);--bs-alert-link-color:var(--bs-dark-text-emphasis)}@keyframes progress-bar-stripes{0%{background-position-x:var(--bs-progress-height)}}.progress,.progress-stacked{--bs-progress-height:1rem;--bs-progress-font-size:0.75rem;--bs-progress-bg:var(--bs-secondary-bg);--bs-progress-border-radius:var(--bs-border-radius);--bs-progress-box-shadow:var(--bs-box-shadow-inset);--bs-progress-bar-color:#fff;--bs-progress-bar-bg:#0d6efd;--bs-progress-bar-transition:width 0.6s ease;display:flex;height:var(--bs-progress-height);overflow:hidden;font-size:var(--bs-progress-font-size);background-color:var(--bs-progress-bg);border-radius:var(--bs-progress-border-radius)}.progress-bar{display:flex;flex-direction:column;justify-content:center;overflow:hidden;color:var(--bs-progress-bar-color);text-align:center;white-space:nowrap;background-color:var(--bs-progress-bar-bg);transition:var(--bs-progress-bar-transition)}@media (prefers-reduced-motion:reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-size:var(--bs-progress-height) var(--bs-progress-height)}.progress-stacked>.progress{overflow:visible}.progress-stacked>.progress>.progress-bar{width:100%}.progress-bar-animated{animation:1s linear infinite progress-bar-stripes}@media (prefers-reduced-motion:reduce){.progress-bar-animated{animation:none}}.list-group{--bs-list-group-color:var(--bs-body-color);--bs-list-group-bg:var(--bs-body-bg);--bs-list-group-border-color:var(--bs-border-color);--bs-list-group-border-width:var(--bs-border-width);--bs-list-group-border-radius:var(--bs-border-radius);--bs-list-group-item-padding-x:1rem;--bs-list-group-item-padding-y:0.5rem;--bs-list-group-action-color:var(--bs-secondary-color);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-tertiary-bg);--bs-list-group-action-active-color:var(--bs-body-color);--bs-list-group-action-active-bg:var(--bs-secondary-bg);--bs-list-group-disabled-color:var(--bs-secondary-color);--bs-list-group-disabled-bg:var(--bs-body-bg);--bs-list-group-active-color:#fff;--bs-list-group-active-bg:#0d6efd;--bs-list-group-active-border-color:#0d6efd;display:flex;flex-direction:column;padding-left:0;margin-bottom:0;border-radius:var(--bs-list-group-border-radius)}.list-group-numbered{list-style-type:none;counter-reset:section}.list-group-numbered>.list-group-item::before{content:counters(section, ".") ". ";counter-increment:section}.list-group-item{position:relative;display:block;padding:var(--bs-list-group-item-padding-y) var(--bs-list-group-item-padding-x);color:var(--bs-list-group-color);text-decoration:none;background-color:var(--bs-list-group-bg);border:var(--bs-list-group-border-width) solid var(--bs-list-group-border-color)}.list-group-item:first-child{border-top-left-radius:inherit;border-top-right-radius:inherit}.list-group-item:last-child{border-bottom-right-radius:inherit;border-bottom-left-radius:inherit}.list-group-item.disabled,.list-group-item:disabled{color:var(--bs-list-group-disabled-color);pointer-events:none;background-color:var(--bs-list-group-disabled-bg)}.list-group-item.active{z-index:2;color:var(--bs-list-group-active-color);background-color:var(--bs-list-group-active-bg);border-color:var(--bs-list-group-active-border-color)}.list-group-item+.list-group-item{border-top-width:0}.list-group-item+.list-group-item.active{margin-top:calc(-1 * var(--bs-list-group-border-width));border-top-width:var(--bs-list-group-border-width)}.list-group-item-action{width:100%;color:var(--bs-list-group-action-color);text-align:inherit}.list-group-item-action:not(.active):focus,.list-group-item-action:not(.active):hover{z-index:1;color:var(--bs-list-group-action-hover-color);text-decoration:none;background-color:var(--bs-list-group-action-hover-bg)}.list-group-item-action:not(.active):active{color:var(--bs-list-group-action-active-color);background-color:var(--bs-list-group-action-active-bg)}.list-group-horizontal{flex-direction:row}.list-group-horizontal>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal>.list-group-item.active{margin-top:0}.list-group-horizontal>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}@media (min-width:576px){.list-group-horizontal-sm{flex-direction:row}.list-group-horizontal-sm>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-sm>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal-sm>.list-group-item.active{margin-top:0}.list-group-horizontal-sm>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-sm>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}@media (min-width:768px){.list-group-horizontal-md{flex-direction:row}.list-group-horizontal-md>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-md>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal-md>.list-group-item.active{margin-top:0}.list-group-horizontal-md>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-md>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}@media (min-width:992px){.list-group-horizontal-lg{flex-direction:row}.list-group-horizontal-lg>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-lg>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal-lg>.list-group-item.active{margin-top:0}.list-group-horizontal-lg>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-lg>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}@media (min-width:1200px){.list-group-horizontal-xl{flex-direction:row}.list-group-horizontal-xl>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-xl>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal-xl>.list-group-item.active{margin-top:0}.list-group-horizontal-xl>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-xl>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}@media (min-width:1400px){.list-group-horizontal-xxl{flex-direction:row}.list-group-horizontal-xxl>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-xxl>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal-xxl>.list-group-item.active{margin-top:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}.list-group-flush{border-radius:0}.list-group-flush>.list-group-item{border-width:0 0 var(--bs-list-group-border-width)}.list-group-flush>.list-group-item:last-child{border-bottom-width:0}.list-group-item-primary{--bs-list-group-color:var(--bs-primary-text-emphasis);--bs-list-group-bg:var(--bs-primary-bg-subtle);--bs-list-group-border-color:var(--bs-primary-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-primary-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-primary-border-subtle);--bs-list-group-active-color:var(--bs-primary-bg-subtle);--bs-list-group-active-bg:var(--bs-primary-text-emphasis);--bs-list-group-active-border-color:var(--bs-primary-text-emphasis)}.list-group-item-secondary{--bs-list-group-color:var(--bs-secondary-text-emphasis);--bs-list-group-bg:var(--bs-secondary-bg-subtle);--bs-list-group-border-color:var(--bs-secondary-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-secondary-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-secondary-border-subtle);--bs-list-group-active-color:var(--bs-secondary-bg-subtle);--bs-list-group-active-bg:var(--bs-secondary-text-emphasis);--bs-list-group-active-border-color:var(--bs-secondary-text-emphasis)}.list-group-item-success{--bs-list-group-color:var(--bs-success-text-emphasis);--bs-list-group-bg:var(--bs-success-bg-subtle);--bs-list-group-border-color:var(--bs-success-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-success-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-success-border-subtle);--bs-list-group-active-color:var(--bs-success-bg-subtle);--bs-list-group-active-bg:var(--bs-success-text-emphasis);--bs-list-group-active-border-color:var(--bs-success-text-emphasis)}.list-group-item-info{--bs-list-group-color:var(--bs-info-text-emphasis);--bs-list-group-bg:var(--bs-info-bg-subtle);--bs-list-group-border-color:var(--bs-info-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-info-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-info-border-subtle);--bs-list-group-active-color:var(--bs-info-bg-subtle);--bs-list-group-active-bg:var(--bs-info-text-emphasis);--bs-list-group-active-border-color:var(--bs-info-text-emphasis)}.list-group-item-warning{--bs-list-group-color:var(--bs-warning-text-emphasis);--bs-list-group-bg:var(--bs-warning-bg-subtle);--bs-list-group-border-color:var(--bs-warning-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-warning-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-warning-border-subtle);--bs-list-group-active-color:var(--bs-warning-bg-subtle);--bs-list-group-active-bg:var(--bs-warning-text-emphasis);--bs-list-group-active-border-color:var(--bs-warning-text-emphasis)}.list-group-item-danger{--bs-list-group-color:var(--bs-danger-text-emphasis);--bs-list-group-bg:var(--bs-danger-bg-subtle);--bs-list-group-border-color:var(--bs-danger-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-danger-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-danger-border-subtle);--bs-list-group-active-color:var(--bs-danger-bg-subtle);--bs-list-group-active-bg:var(--bs-danger-text-emphasis);--bs-list-group-active-border-color:var(--bs-danger-text-emphasis)}.list-group-item-light{--bs-list-group-color:var(--bs-light-text-emphasis);--bs-list-group-bg:var(--bs-light-bg-subtle);--bs-list-group-border-color:var(--bs-light-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-light-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-light-border-subtle);--bs-list-group-active-color:var(--bs-light-bg-subtle);--bs-list-group-active-bg:var(--bs-light-text-emphasis);--bs-list-group-active-border-color:var(--bs-light-text-emphasis)}.list-group-item-dark{--bs-list-group-color:var(--bs-dark-text-emphasis);--bs-list-group-bg:var(--bs-dark-bg-subtle);--bs-list-group-border-color:var(--bs-dark-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-dark-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-dark-border-subtle);--bs-list-group-active-color:var(--bs-dark-bg-subtle);--bs-list-group-active-bg:var(--bs-dark-text-emphasis);--bs-list-group-active-border-color:var(--bs-dark-text-emphasis)}.btn-close{--bs-btn-close-color:#000;--bs-btn-close-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000'%3e%3cpath d='M.293.293a1 1 0 0 1 1.414 0L8 6.586 14.293.293a1 1 0 1 1 1.414 1.414L9.414 8l6.293 6.293a1 1 0 0 1-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 0 1-1.414-1.414L6.586 8 .293 1.707a1 1 0 0 1 0-1.414'/%3e%3c/svg%3e");--bs-btn-close-opacity:0.5;--bs-btn-close-hover-opacity:0.75;--bs-btn-close-focus-shadow:0 0 0 0.25rem rgba(13, 110, 253, 0.25);--bs-btn-close-focus-opacity:1;--bs-btn-close-disabled-opacity:0.25;box-sizing:content-box;width:1em;height:1em;padding:.25em .25em;color:var(--bs-btn-close-color);background:transparent var(--bs-btn-close-bg) center/1em auto no-repeat;filter:var(--bs-btn-close-filter);border:0;border-radius:.375rem;opacity:var(--bs-btn-close-opacity)}.btn-close:hover{color:var(--bs-btn-close-color);text-decoration:none;opacity:var(--bs-btn-close-hover-opacity)}.btn-close:focus{outline:0;box-shadow:var(--bs-btn-close-focus-shadow);opacity:var(--bs-btn-close-focus-opacity)}.btn-close.disabled,.btn-close:disabled{pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;opacity:var(--bs-btn-close-disabled-opacity)}.btn-close-white{--bs-btn-close-filter:invert(1) grayscale(100%) brightness(200%)}:root,[data-bs-theme=light]{--bs-btn-close-filter: }[data-bs-theme=dark]{--bs-btn-close-filter:invert(1) grayscale(100%) brightness(200%)}.toast{--bs-toast-zindex:1090;--bs-toast-padding-x:0.75rem;--bs-toast-padding-y:0.5rem;--bs-toast-spacing:1.5rem;--bs-toast-max-width:350px;--bs-toast-font-size:0.875rem;--bs-toast-color: ;--bs-toast-bg:rgba(var(--bs-body-bg-rgb), 0.85);--bs-toast-border-width:var(--bs-border-width);--bs-toast-border-color:var(--bs-border-color-translucent);--bs-toast-border-radius:var(--bs-border-radius);--bs-toast-box-shadow:var(--bs-box-shadow);--bs-toast-header-color:var(--bs-secondary-color);--bs-toast-header-bg:rgba(var(--bs-body-bg-rgb), 0.85);--bs-toast-header-border-color:var(--bs-border-color-translucent);width:var(--bs-toast-max-width);max-width:100%;font-size:var(--bs-toast-font-size);color:var(--bs-toast-color);pointer-events:auto;background-color:var(--bs-toast-bg);background-clip:padding-box;border:var(--bs-toast-border-width) solid var(--bs-toast-border-color);box-shadow:var(--bs-toast-box-shadow);border-radius:var(--bs-toast-border-radius)}.toast.showing{opacity:0}.toast:not(.show){display:none}.toast-container{--bs-toast-zindex:1090;position:absolute;z-index:var(--bs-toast-zindex);width:-webkit-max-content;width:-moz-max-content;width:max-content;max-width:100%;pointer-events:none}.toast-container>:not(:last-child){margin-bottom:var(--bs-toast-spacing)}.toast-header{display:flex;align-items:center;padding:var(--bs-toast-padding-y) var(--bs-toast-padding-x);color:var(--bs-toast-header-color);background-color:var(--bs-toast-header-bg);background-clip:padding-box;border-bottom:var(--bs-toast-border-width) solid var(--bs-toast-header-border-color);border-top-left-radius:calc(var(--bs-toast-border-radius) - var(--bs-toast-border-width));border-top-right-radius:calc(var(--bs-toast-border-radius) - var(--bs-toast-border-width))}.toast-header .btn-close{margin-right:calc(-.5 * var(--bs-toast-padding-x));margin-left:var(--bs-toast-padding-x)}.toast-body{padding:var(--bs-toast-padding-x);word-wrap:break-word}.modal{--bs-modal-zindex:1055;--bs-modal-width:500px;--bs-modal-padding:1rem;--bs-modal-margin:0.5rem;--bs-modal-color:var(--bs-body-color);--bs-modal-bg:var(--bs-body-bg);--bs-modal-border-color:var(--bs-border-color-translucent);--bs-modal-border-width:var(--bs-border-width);--bs-modal-border-radius:var(--bs-border-radius-lg);--bs-modal-box-shadow:var(--bs-box-shadow-sm);--bs-modal-inner-border-radius:calc(var(--bs-border-radius-lg) - (var(--bs-border-width)));--bs-modal-header-padding-x:1rem;--bs-modal-header-padding-y:1rem;--bs-modal-header-padding:1rem 1rem;--bs-modal-header-border-color:var(--bs-border-color);--bs-modal-header-border-width:var(--bs-border-width);--bs-modal-title-line-height:1.5;--bs-modal-footer-gap:0.5rem;--bs-modal-footer-bg: ;--bs-modal-footer-border-color:var(--bs-border-color);--bs-modal-footer-border-width:var(--bs-border-width);position:fixed;top:0;left:0;z-index:var(--bs-modal-zindex);display:none;width:100%;height:100%;overflow-x:hidden;overflow-y:auto;outline:0}.modal-dialog{position:relative;width:auto;margin:var(--bs-modal-margin);pointer-events:none}.modal.fade .modal-dialog{transform:translate(0,-50px);transition:transform .3s ease-out}@media (prefers-reduced-motion:reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{transform:none}.modal.modal-static .modal-dialog{transform:scale(1.02)}.modal-dialog-scrollable{height:calc(100% - var(--bs-modal-margin) * 2)}.modal-dialog-scrollable .modal-content{max-height:100%;overflow:hidden}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:flex;align-items:center;min-height:calc(100% - var(--bs-modal-margin) * 2)}.modal-content{position:relative;display:flex;flex-direction:column;width:100%;color:var(--bs-modal-color);pointer-events:auto;background-color:var(--bs-modal-bg);background-clip:padding-box;border:var(--bs-modal-border-width) solid var(--bs-modal-border-color);border-radius:var(--bs-modal-border-radius);outline:0}.modal-backdrop{--bs-backdrop-zindex:1050;--bs-backdrop-bg:#000;--bs-backdrop-opacity:0.5;position:fixed;top:0;left:0;z-index:var(--bs-backdrop-zindex);width:100vw;height:100vh;background-color:var(--bs-backdrop-bg)}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:var(--bs-backdrop-opacity)}.modal-header{display:flex;flex-shrink:0;align-items:center;padding:var(--bs-modal-header-padding);border-bottom:var(--bs-modal-header-border-width) solid var(--bs-modal-header-border-color);border-top-left-radius:var(--bs-modal-inner-border-radius);border-top-right-radius:var(--bs-modal-inner-border-radius)}.modal-header .btn-close{padding:calc(var(--bs-modal-header-padding-y) * .5) calc(var(--bs-modal-header-padding-x) * .5);margin-top:calc(-.5 * var(--bs-modal-header-padding-y));margin-right:calc(-.5 * var(--bs-modal-header-padding-x));margin-bottom:calc(-.5 * var(--bs-modal-header-padding-y));margin-left:auto}.modal-title{margin-bottom:0;line-height:var(--bs-modal-title-line-height)}.modal-body{position:relative;flex:1 1 auto;padding:var(--bs-modal-padding)}.modal-footer{display:flex;flex-shrink:0;flex-wrap:wrap;align-items:center;justify-content:flex-end;padding:calc(var(--bs-modal-padding) - var(--bs-modal-footer-gap) * .5);background-color:var(--bs-modal-footer-bg);border-top:var(--bs-modal-footer-border-width) solid var(--bs-modal-footer-border-color);border-bottom-right-radius:var(--bs-modal-inner-border-radius);border-bottom-left-radius:var(--bs-modal-inner-border-radius)}.modal-footer>*{margin:calc(var(--bs-modal-footer-gap) * .5)}@media (min-width:576px){.modal{--bs-modal-margin:1.75rem;--bs-modal-box-shadow:var(--bs-box-shadow)}.modal-dialog{max-width:var(--bs-modal-width);margin-right:auto;margin-left:auto}.modal-sm{--bs-modal-width:300px}}@media (min-width:992px){.modal-lg,.modal-xl{--bs-modal-width:800px}}@media (min-width:1200px){.modal-xl{--bs-modal-width:1140px}}.modal-fullscreen{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen .modal-footer,.modal-fullscreen .modal-header{border-radius:0}.modal-fullscreen .modal-body{overflow-y:auto}@media (max-width:575.98px){.modal-fullscreen-sm-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-sm-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-sm-down .modal-footer,.modal-fullscreen-sm-down .modal-header{border-radius:0}.modal-fullscreen-sm-down .modal-body{overflow-y:auto}}@media (max-width:767.98px){.modal-fullscreen-md-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-md-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-md-down .modal-footer,.modal-fullscreen-md-down .modal-header{border-radius:0}.modal-fullscreen-md-down .modal-body{overflow-y:auto}}@media (max-width:991.98px){.modal-fullscreen-lg-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-lg-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-lg-down .modal-footer,.modal-fullscreen-lg-down .modal-header{border-radius:0}.modal-fullscreen-lg-down .modal-body{overflow-y:auto}}@media (max-width:1199.98px){.modal-fullscreen-xl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xl-down .modal-footer,.modal-fullscreen-xl-down .modal-header{border-radius:0}.modal-fullscreen-xl-down .modal-body{overflow-y:auto}}@media (max-width:1399.98px){.modal-fullscreen-xxl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xxl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xxl-down .modal-footer,.modal-fullscreen-xxl-down .modal-header{border-radius:0}.modal-fullscreen-xxl-down .modal-body{overflow-y:auto}}.tooltip{--bs-tooltip-zindex:1080;--bs-tooltip-max-width:200px;--bs-tooltip-padding-x:0.5rem;--bs-tooltip-padding-y:0.25rem;--bs-tooltip-margin: ;--bs-tooltip-font-size:0.875rem;--bs-tooltip-color:var(--bs-body-bg);--bs-tooltip-bg:var(--bs-emphasis-color);--bs-tooltip-border-radius:var(--bs-border-radius);--bs-tooltip-opacity:0.9;--bs-tooltip-arrow-width:0.8rem;--bs-tooltip-arrow-height:0.4rem;z-index:var(--bs-tooltip-zindex);display:block;margin:var(--bs-tooltip-margin);font-family:var(--bs-font-sans-serif);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;white-space:normal;word-spacing:normal;line-break:auto;font-size:var(--bs-tooltip-font-size);word-wrap:break-word;opacity:0}.tooltip.show{opacity:var(--bs-tooltip-opacity)}.tooltip .tooltip-arrow{display:block;width:var(--bs-tooltip-arrow-width);height:var(--bs-tooltip-arrow-height)}.tooltip .tooltip-arrow::before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow,.bs-tooltip-top .tooltip-arrow{bottom:calc(-1 * var(--bs-tooltip-arrow-height))}.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow::before,.bs-tooltip-top .tooltip-arrow::before{top:-1px;border-width:var(--bs-tooltip-arrow-height) calc(var(--bs-tooltip-arrow-width) * .5) 0;border-top-color:var(--bs-tooltip-bg)}.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow,.bs-tooltip-end .tooltip-arrow{left:calc(-1 * var(--bs-tooltip-arrow-height));width:var(--bs-tooltip-arrow-height);height:var(--bs-tooltip-arrow-width)}.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow::before,.bs-tooltip-end .tooltip-arrow::before{right:-1px;border-width:calc(var(--bs-tooltip-arrow-width) * .5) var(--bs-tooltip-arrow-height) calc(var(--bs-tooltip-arrow-width) * .5) 0;border-right-color:var(--bs-tooltip-bg)}.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow,.bs-tooltip-bottom .tooltip-arrow{top:calc(-1 * var(--bs-tooltip-arrow-height))}.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow::before,.bs-tooltip-bottom .tooltip-arrow::before{bottom:-1px;border-width:0 calc(var(--bs-tooltip-arrow-width) * .5) var(--bs-tooltip-arrow-height);border-bottom-color:var(--bs-tooltip-bg)}.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow,.bs-tooltip-start .tooltip-arrow{right:calc(-1 * var(--bs-tooltip-arrow-height));width:var(--bs-tooltip-arrow-height);height:var(--bs-tooltip-arrow-width)}.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow::before,.bs-tooltip-start .tooltip-arrow::before{left:-1px;border-width:calc(var(--bs-tooltip-arrow-width) * .5) 0 calc(var(--bs-tooltip-arrow-width) * .5) var(--bs-tooltip-arrow-height);border-left-color:var(--bs-tooltip-bg)}.tooltip-inner{max-width:var(--bs-tooltip-max-width);padding:var(--bs-tooltip-padding-y) var(--bs-tooltip-padding-x);color:var(--bs-tooltip-color);text-align:center;background-color:var(--bs-tooltip-bg);border-radius:var(--bs-tooltip-border-radius)}.popover{--bs-popover-zindex:1070;--bs-popover-max-width:276px;--bs-popover-font-size:0.875rem;--bs-popover-bg:var(--bs-body-bg);--bs-popover-border-width:var(--bs-border-width);--bs-popover-border-color:var(--bs-border-color-translucent);--bs-popover-border-radius:var(--bs-border-radius-lg);--bs-popover-inner-border-radius:calc(var(--bs-border-radius-lg) - var(--bs-border-width));--bs-popover-box-shadow:var(--bs-box-shadow);--bs-popover-header-padding-x:1rem;--bs-popover-header-padding-y:0.5rem;--bs-popover-header-font-size:1rem;--bs-popover-header-color:inherit;--bs-popover-header-bg:var(--bs-secondary-bg);--bs-popover-body-padding-x:1rem;--bs-popover-body-padding-y:1rem;--bs-popover-body-color:var(--bs-body-color);--bs-popover-arrow-width:1rem;--bs-popover-arrow-height:0.5rem;--bs-popover-arrow-border:var(--bs-popover-border-color);z-index:var(--bs-popover-zindex);display:block;max-width:var(--bs-popover-max-width);font-family:var(--bs-font-sans-serif);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;white-space:normal;word-spacing:normal;line-break:auto;font-size:var(--bs-popover-font-size);word-wrap:break-word;background-color:var(--bs-popover-bg);background-clip:padding-box;border:var(--bs-popover-border-width) solid var(--bs-popover-border-color);border-radius:var(--bs-popover-border-radius)}.popover .popover-arrow{display:block;width:var(--bs-popover-arrow-width);height:var(--bs-popover-arrow-height)}.popover .popover-arrow::after,.popover .popover-arrow::before{position:absolute;display:block;content:"";border-color:transparent;border-style:solid;border-width:0}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow,.bs-popover-top>.popover-arrow{bottom:calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width))}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::before,.bs-popover-top>.popover-arrow::after,.bs-popover-top>.popover-arrow::before{border-width:var(--bs-popover-arrow-height) calc(var(--bs-popover-arrow-width) * .5) 0}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::before,.bs-popover-top>.popover-arrow::before{bottom:0;border-top-color:var(--bs-popover-arrow-border)}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::after,.bs-popover-top>.popover-arrow::after{bottom:var(--bs-popover-border-width);border-top-color:var(--bs-popover-bg)}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow,.bs-popover-end>.popover-arrow{left:calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width));width:var(--bs-popover-arrow-height);height:var(--bs-popover-arrow-width)}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::before,.bs-popover-end>.popover-arrow::after,.bs-popover-end>.popover-arrow::before{border-width:calc(var(--bs-popover-arrow-width) * .5) var(--bs-popover-arrow-height) calc(var(--bs-popover-arrow-width) * .5) 0}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::before,.bs-popover-end>.popover-arrow::before{left:0;border-right-color:var(--bs-popover-arrow-border)}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::after,.bs-popover-end>.popover-arrow::after{left:var(--bs-popover-border-width);border-right-color:var(--bs-popover-bg)}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow,.bs-popover-bottom>.popover-arrow{top:calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width))}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::before,.bs-popover-bottom>.popover-arrow::after,.bs-popover-bottom>.popover-arrow::before{border-width:0 calc(var(--bs-popover-arrow-width) * .5) var(--bs-popover-arrow-height)}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::before,.bs-popover-bottom>.popover-arrow::before{top:0;border-bottom-color:var(--bs-popover-arrow-border)}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::after,.bs-popover-bottom>.popover-arrow::after{top:var(--bs-popover-border-width);border-bottom-color:var(--bs-popover-bg)}.bs-popover-auto[data-popper-placement^=bottom] .popover-header::before,.bs-popover-bottom .popover-header::before{position:absolute;top:0;left:50%;display:block;width:var(--bs-popover-arrow-width);margin-left:calc(-.5 * var(--bs-popover-arrow-width));content:"";border-bottom:var(--bs-popover-border-width) solid var(--bs-popover-header-bg)}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow,.bs-popover-start>.popover-arrow{right:calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width));width:var(--bs-popover-arrow-height);height:var(--bs-popover-arrow-width)}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::before,.bs-popover-start>.popover-arrow::after,.bs-popover-start>.popover-arrow::before{border-width:calc(var(--bs-popover-arrow-width) * .5) 0 calc(var(--bs-popover-arrow-width) * .5) var(--bs-popover-arrow-height)}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::before,.bs-popover-start>.popover-arrow::before{right:0;border-left-color:var(--bs-popover-arrow-border)}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::after,.bs-popover-start>.popover-arrow::after{right:var(--bs-popover-border-width);border-left-color:var(--bs-popover-bg)}.popover-header{padding:var(--bs-popover-header-padding-y) var(--bs-popover-header-padding-x);margin-bottom:0;font-size:var(--bs-popover-header-font-size);color:var(--bs-popover-header-color);background-color:var(--bs-popover-header-bg);border-bottom:var(--bs-popover-border-width) solid var(--bs-popover-border-color);border-top-left-radius:var(--bs-popover-inner-border-radius);border-top-right-radius:var(--bs-popover-inner-border-radius)}.popover-header:empty{display:none}.popover-body{padding:var(--bs-popover-body-padding-y) var(--bs-popover-body-padding-x);color:var(--bs-popover-body-color)}.carousel{position:relative}.carousel.pointer-event{touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner::after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;-webkit-backface-visibility:hidden;backface-visibility:hidden;transition:transform .6s ease-in-out}@media (prefers-reduced-motion:reduce){.carousel-item{transition:none}}.carousel-item-next,.carousel-item-prev,.carousel-item.active{display:block}.active.carousel-item-end,.carousel-item-next:not(.carousel-item-start){transform:translateX(100%)}.active.carousel-item-start,.carousel-item-prev:not(.carousel-item-end){transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;transform:none}.carousel-fade .carousel-item-next.carousel-item-start,.carousel-fade .carousel-item-prev.carousel-item-end,.carousel-fade .carousel-item.active{z-index:1;opacity:1}.carousel-fade .active.carousel-item-end,.carousel-fade .active.carousel-item-start{z-index:0;opacity:0;transition:opacity 0s .6s}@media (prefers-reduced-motion:reduce){.carousel-fade .active.carousel-item-end,.carousel-fade .active.carousel-item-start{transition:none}}.carousel-control-next,.carousel-control-prev{position:absolute;top:0;bottom:0;z-index:1;display:flex;align-items:center;justify-content:center;width:15%;padding:0;color:#fff;text-align:center;background:0 0;filter:var(--bs-carousel-control-icon-filter);border:0;opacity:.5;transition:opacity .15s ease}@media (prefers-reduced-motion:reduce){.carousel-control-next,.carousel-control-prev{transition:none}}.carousel-control-next:focus,.carousel-control-next:hover,.carousel-control-prev:focus,.carousel-control-prev:hover{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-next-icon,.carousel-control-prev-icon{display:inline-block;width:2rem;height:2rem;background-repeat:no-repeat;background-position:50%;background-size:100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708'/%3e%3c/svg%3e")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:2;display:flex;justify-content:center;padding:0;margin-right:15%;margin-bottom:1rem;margin-left:15%}.carousel-indicators [data-bs-target]{box-sizing:content-box;flex:0 1 auto;width:30px;height:3px;padding:0;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:var(--bs-carousel-indicator-active-bg);background-clip:padding-box;border:0;border-top:10px solid transparent;border-bottom:10px solid transparent;opacity:.5;transition:opacity .6s ease}@media (prefers-reduced-motion:reduce){.carousel-indicators [data-bs-target]{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:1.25rem;left:15%;padding-top:1.25rem;padding-bottom:1.25rem;color:var(--bs-carousel-caption-color);text-align:center}.carousel-dark{--bs-carousel-indicator-active-bg:#000;--bs-carousel-caption-color:#000;--bs-carousel-control-icon-filter:invert(1) grayscale(100)}:root,[data-bs-theme=light]{--bs-carousel-indicator-active-bg:#fff;--bs-carousel-caption-color:#fff;--bs-carousel-control-icon-filter: }[data-bs-theme=dark]{--bs-carousel-indicator-active-bg:#000;--bs-carousel-caption-color:#000;--bs-carousel-control-icon-filter:invert(1) grayscale(100)}.spinner-border,.spinner-grow{display:inline-block;flex-shrink:0;width:var(--bs-spinner-width);height:var(--bs-spinner-height);vertical-align:var(--bs-spinner-vertical-align);border-radius:50%;animation:var(--bs-spinner-animation-speed) linear infinite var(--bs-spinner-animation-name)}@keyframes spinner-border{to{transform:rotate(360deg)}}.spinner-border{--bs-spinner-width:2rem;--bs-spinner-height:2rem;--bs-spinner-vertical-align:-0.125em;--bs-spinner-border-width:0.25em;--bs-spinner-animation-speed:0.75s;--bs-spinner-animation-name:spinner-border;border:var(--bs-spinner-border-width) solid currentcolor;border-right-color:transparent}.spinner-border-sm{--bs-spinner-width:1rem;--bs-spinner-height:1rem;--bs-spinner-border-width:0.2em}@keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}.spinner-grow{--bs-spinner-width:2rem;--bs-spinner-height:2rem;--bs-spinner-vertical-align:-0.125em;--bs-spinner-animation-speed:0.75s;--bs-spinner-animation-name:spinner-grow;background-color:currentcolor;opacity:0}.spinner-grow-sm{--bs-spinner-width:1rem;--bs-spinner-height:1rem}@media (prefers-reduced-motion:reduce){.spinner-border,.spinner-grow{--bs-spinner-animation-speed:1.5s}}.offcanvas,.offcanvas-lg,.offcanvas-md,.offcanvas-sm,.offcanvas-xl,.offcanvas-xxl{--bs-offcanvas-zindex:1045;--bs-offcanvas-width:400px;--bs-offcanvas-height:30vh;--bs-offcanvas-padding-x:1rem;--bs-offcanvas-padding-y:1rem;--bs-offcanvas-color:var(--bs-body-color);--bs-offcanvas-bg:var(--bs-body-bg);--bs-offcanvas-border-width:var(--bs-border-width);--bs-offcanvas-border-color:var(--bs-border-color-translucent);--bs-offcanvas-box-shadow:var(--bs-box-shadow-sm);--bs-offcanvas-transition:transform 0.3s ease-in-out;--bs-offcanvas-title-line-height:1.5}@media (max-width:575.98px){.offcanvas-sm{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}}@media (max-width:575.98px) and (prefers-reduced-motion:reduce){.offcanvas-sm{transition:none}}@media (max-width:575.98px){.offcanvas-sm.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas-sm.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas-sm.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas-sm.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas-sm.show:not(.hiding),.offcanvas-sm.showing{transform:none}.offcanvas-sm.hiding,.offcanvas-sm.show,.offcanvas-sm.showing{visibility:visible}}@media (min-width:576px){.offcanvas-sm{--bs-offcanvas-height:auto;--bs-offcanvas-border-width:0;background-color:transparent!important}.offcanvas-sm .offcanvas-header{display:none}.offcanvas-sm .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible;background-color:transparent!important}}@media (max-width:767.98px){.offcanvas-md{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}}@media (max-width:767.98px) and (prefers-reduced-motion:reduce){.offcanvas-md{transition:none}}@media (max-width:767.98px){.offcanvas-md.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas-md.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas-md.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas-md.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas-md.show:not(.hiding),.offcanvas-md.showing{transform:none}.offcanvas-md.hiding,.offcanvas-md.show,.offcanvas-md.showing{visibility:visible}}@media (min-width:768px){.offcanvas-md{--bs-offcanvas-height:auto;--bs-offcanvas-border-width:0;background-color:transparent!important}.offcanvas-md .offcanvas-header{display:none}.offcanvas-md .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible;background-color:transparent!important}}@media (max-width:991.98px){.offcanvas-lg{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}}@media (max-width:991.98px) and (prefers-reduced-motion:reduce){.offcanvas-lg{transition:none}}@media (max-width:991.98px){.offcanvas-lg.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas-lg.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas-lg.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas-lg.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas-lg.show:not(.hiding),.offcanvas-lg.showing{transform:none}.offcanvas-lg.hiding,.offcanvas-lg.show,.offcanvas-lg.showing{visibility:visible}}@media (min-width:992px){.offcanvas-lg{--bs-offcanvas-height:auto;--bs-offcanvas-border-width:0;background-color:transparent!important}.offcanvas-lg .offcanvas-header{display:none}.offcanvas-lg .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible;background-color:transparent!important}}@media (max-width:1199.98px){.offcanvas-xl{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}}@media (max-width:1199.98px) and (prefers-reduced-motion:reduce){.offcanvas-xl{transition:none}}@media (max-width:1199.98px){.offcanvas-xl.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas-xl.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas-xl.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas-xl.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas-xl.show:not(.hiding),.offcanvas-xl.showing{transform:none}.offcanvas-xl.hiding,.offcanvas-xl.show,.offcanvas-xl.showing{visibility:visible}}@media (min-width:1200px){.offcanvas-xl{--bs-offcanvas-height:auto;--bs-offcanvas-border-width:0;background-color:transparent!important}.offcanvas-xl .offcanvas-header{display:none}.offcanvas-xl .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible;background-color:transparent!important}}@media (max-width:1399.98px){.offcanvas-xxl{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}}@media (max-width:1399.98px) and (prefers-reduced-motion:reduce){.offcanvas-xxl{transition:none}}@media (max-width:1399.98px){.offcanvas-xxl.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas-xxl.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas-xxl.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas-xxl.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas-xxl.show:not(.hiding),.offcanvas-xxl.showing{transform:none}.offcanvas-xxl.hiding,.offcanvas-xxl.show,.offcanvas-xxl.showing{visibility:visible}}@media (min-width:1400px){.offcanvas-xxl{--bs-offcanvas-height:auto;--bs-offcanvas-border-width:0;background-color:transparent!important}.offcanvas-xxl .offcanvas-header{display:none}.offcanvas-xxl .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible;background-color:transparent!important}}.offcanvas{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}@media (prefers-reduced-motion:reduce){.offcanvas{transition:none}}.offcanvas.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas.show:not(.hiding),.offcanvas.showing{transform:none}.offcanvas.hiding,.offcanvas.show,.offcanvas.showing{visibility:visible}.offcanvas-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#000}.offcanvas-backdrop.fade{opacity:0}.offcanvas-backdrop.show{opacity:.5}.offcanvas-header{display:flex;align-items:center;padding:var(--bs-offcanvas-padding-y) var(--bs-offcanvas-padding-x)}.offcanvas-header .btn-close{padding:calc(var(--bs-offcanvas-padding-y) * .5) calc(var(--bs-offcanvas-padding-x) * .5);margin-top:calc(-.5 * var(--bs-offcanvas-padding-y));margin-right:calc(-.5 * var(--bs-offcanvas-padding-x));margin-bottom:calc(-.5 * var(--bs-offcanvas-padding-y));margin-left:auto}.offcanvas-title{margin-bottom:0;line-height:var(--bs-offcanvas-title-line-height)}.offcanvas-body{flex-grow:1;padding:var(--bs-offcanvas-padding-y) var(--bs-offcanvas-padding-x);overflow-y:auto}.placeholder{display:inline-block;min-height:1em;vertical-align:middle;cursor:wait;background-color:currentcolor;opacity:.5}.placeholder.btn::before{display:inline-block;content:""}.placeholder-xs{min-height:.6em}.placeholder-sm{min-height:.8em}.placeholder-lg{min-height:1.2em}.placeholder-glow .placeholder{animation:placeholder-glow 2s ease-in-out infinite}@keyframes placeholder-glow{50%{opacity:.2}}.placeholder-wave{-webkit-mask-image:linear-gradient(130deg,#000 55%,rgba(0,0,0,0.8) 75%,#000 95%);mask-image:linear-gradient(130deg,#000 55%,rgba(0,0,0,0.8) 75%,#000 95%);-webkit-mask-size:200% 100%;mask-size:200% 100%;animation:placeholder-wave 2s linear infinite}@keyframes placeholder-wave{100%{-webkit-mask-position:-200% 0%;mask-position:-200% 0%}}.clearfix::after{display:block;clear:both;content:""}.text-bg-primary{color:#fff!important;background-color:RGBA(var(--bs-primary-rgb),var(--bs-bg-opacity,1))!important}.text-bg-secondary{color:#fff!important;background-color:RGBA(var(--bs-secondary-rgb),var(--bs-bg-opacity,1))!important}.text-bg-success{color:#fff!important;background-color:RGBA(var(--bs-success-rgb),var(--bs-bg-opacity,1))!important}.text-bg-info{color:#000!important;background-color:RGBA(var(--bs-info-rgb),var(--bs-bg-opacity,1))!important}.text-bg-warning{color:#000!important;background-color:RGBA(var(--bs-warning-rgb),var(--bs-bg-opacity,1))!important}.text-bg-danger{color:#fff!important;background-color:RGBA(var(--bs-danger-rgb),var(--bs-bg-opacity,1))!important}.text-bg-light{color:#000!important;background-color:RGBA(var(--bs-light-rgb),var(--bs-bg-opacity,1))!important}.text-bg-dark{color:#fff!important;background-color:RGBA(var(--bs-dark-rgb),var(--bs-bg-opacity,1))!important}.link-primary{color:RGBA(var(--bs-primary-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-primary-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-primary-rgb),var(--bs-link-underline-opacity,1))!important}.link-primary:focus,.link-primary:hover{color:RGBA(10,88,202,var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(10,88,202,var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(10,88,202,var(--bs-link-underline-opacity,1))!important}.link-secondary{color:RGBA(var(--bs-secondary-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-secondary-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-secondary-rgb),var(--bs-link-underline-opacity,1))!important}.link-secondary:focus,.link-secondary:hover{color:RGBA(86,94,100,var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(86,94,100,var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(86,94,100,var(--bs-link-underline-opacity,1))!important}.link-success{color:RGBA(var(--bs-success-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-success-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-success-rgb),var(--bs-link-underline-opacity,1))!important}.link-success:focus,.link-success:hover{color:RGBA(20,108,67,var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(20,108,67,var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(20,108,67,var(--bs-link-underline-opacity,1))!important}.link-info{color:RGBA(var(--bs-info-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-info-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-info-rgb),var(--bs-link-underline-opacity,1))!important}.link-info:focus,.link-info:hover{color:RGBA(61,213,243,var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(61,213,243,var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(61,213,243,var(--bs-link-underline-opacity,1))!important}.link-warning{color:RGBA(var(--bs-warning-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-warning-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-warning-rgb),var(--bs-link-underline-opacity,1))!important}.link-warning:focus,.link-warning:hover{color:RGBA(255,205,57,var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(255,205,57,var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(255,205,57,var(--bs-link-underline-opacity,1))!important}.link-danger{color:RGBA(var(--bs-danger-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-danger-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-danger-rgb),var(--bs-link-underline-opacity,1))!important}.link-danger:focus,.link-danger:hover{color:RGBA(176,42,55,var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(176,42,55,var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(176,42,55,var(--bs-link-underline-opacity,1))!important}.link-light{color:RGBA(var(--bs-light-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-light-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-light-rgb),var(--bs-link-underline-opacity,1))!important}.link-light:focus,.link-light:hover{color:RGBA(249,250,251,var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(249,250,251,var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(249,250,251,var(--bs-link-underline-opacity,1))!important}.link-dark{color:RGBA(var(--bs-dark-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-dark-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-dark-rgb),var(--bs-link-underline-opacity,1))!important}.link-dark:focus,.link-dark:hover{color:RGBA(26,30,33,var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(26,30,33,var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(26,30,33,var(--bs-link-underline-opacity,1))!important}.link-body-emphasis{color:RGBA(var(--bs-emphasis-color-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-emphasis-color-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-emphasis-color-rgb),var(--bs-link-underline-opacity,1))!important}.link-body-emphasis:focus,.link-body-emphasis:hover{color:RGBA(var(--bs-emphasis-color-rgb),var(--bs-link-opacity,.75))!important;-webkit-text-decoration-color:RGBA(var(--bs-emphasis-color-rgb),var(--bs-link-underline-opacity,0.75))!important;text-decoration-color:RGBA(var(--bs-emphasis-color-rgb),var(--bs-link-underline-opacity,0.75))!important}.focus-ring:focus{outline:0;box-shadow:var(--bs-focus-ring-x,0) var(--bs-focus-ring-y,0) var(--bs-focus-ring-blur,0) var(--bs-focus-ring-width) var(--bs-focus-ring-color)}.icon-link{display:inline-flex;gap:.375rem;align-items:center;-webkit-text-decoration-color:rgba(var(--bs-link-color-rgb),var(--bs-link-opacity,0.5));text-decoration-color:rgba(var(--bs-link-color-rgb),var(--bs-link-opacity,0.5));text-underline-offset:0.25em;-webkit-backface-visibility:hidden;backface-visibility:hidden}.icon-link>.bi{flex-shrink:0;width:1em;height:1em;fill:currentcolor;transition:.2s ease-in-out transform}@media (prefers-reduced-motion:reduce){.icon-link>.bi{transition:none}}.icon-link-hover:focus-visible>.bi,.icon-link-hover:hover>.bi{transform:var(--bs-icon-link-transform,translate3d(.25em,0,0))}.ratio{position:relative;width:100%}.ratio::before{display:block;padding-top:var(--bs-aspect-ratio);content:""}.ratio>*{position:absolute;top:0;left:0;width:100%;height:100%}.ratio-1x1{--bs-aspect-ratio:100%}.ratio-4x3{--bs-aspect-ratio:75%}.ratio-16x9{--bs-aspect-ratio:56.25%}.ratio-21x9{--bs-aspect-ratio:42.8571428571%}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}.sticky-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}.sticky-bottom{position:-webkit-sticky;position:sticky;bottom:0;z-index:1020}@media (min-width:576px){.sticky-sm-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}.sticky-sm-bottom{position:-webkit-sticky;position:sticky;bottom:0;z-index:1020}}@media (min-width:768px){.sticky-md-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}.sticky-md-bottom{position:-webkit-sticky;position:sticky;bottom:0;z-index:1020}}@media (min-width:992px){.sticky-lg-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}.sticky-lg-bottom{position:-webkit-sticky;position:sticky;bottom:0;z-index:1020}}@media (min-width:1200px){.sticky-xl-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}.sticky-xl-bottom{position:-webkit-sticky;position:sticky;bottom:0;z-index:1020}}@media (min-width:1400px){.sticky-xxl-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}.sticky-xxl-bottom{position:-webkit-sticky;position:sticky;bottom:0;z-index:1020}}.hstack{display:flex;flex-direction:row;align-items:center;align-self:stretch}.vstack{display:flex;flex:1 1 auto;flex-direction:column;align-self:stretch}.visually-hidden,.visually-hidden-focusable:not(:focus):not(:focus-within){width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important}.visually-hidden-focusable:not(:focus):not(:focus-within):not(caption),.visually-hidden:not(caption){position:absolute!important}.visually-hidden *,.visually-hidden-focusable:not(:focus):not(:focus-within) *{overflow:hidden!important}.stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;content:""}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.vr{display:inline-block;align-self:stretch;width:var(--bs-border-width);min-height:1em;background-color:currentcolor;opacity:.25}.align-baseline{vertical-align:baseline!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.align-text-bottom{vertical-align:text-bottom!important}.align-text-top{vertical-align:text-top!important}.float-start{float:left!important}.float-end{float:right!important}.float-none{float:none!important}.object-fit-contain{-o-object-fit:contain!important;object-fit:contain!important}.object-fit-cover{-o-object-fit:cover!important;object-fit:cover!important}.object-fit-fill{-o-object-fit:fill!important;object-fit:fill!important}.object-fit-scale{-o-object-fit:scale-down!important;object-fit:scale-down!important}.object-fit-none{-o-object-fit:none!important;object-fit:none!important}.opacity-0{opacity:0!important}.opacity-25{opacity:.25!important}.opacity-50{opacity:.5!important}.opacity-75{opacity:.75!important}.opacity-100{opacity:1!important}.overflow-auto{overflow:auto!important}.overflow-hidden{overflow:hidden!important}.overflow-visible{overflow:visible!important}.overflow-scroll{overflow:scroll!important}.overflow-x-auto{overflow-x:auto!important}.overflow-x-hidden{overflow-x:hidden!important}.overflow-x-visible{overflow-x:visible!important}.overflow-x-scroll{overflow-x:scroll!important}.overflow-y-auto{overflow-y:auto!important}.overflow-y-hidden{overflow-y:hidden!important}.overflow-y-visible{overflow-y:visible!important}.overflow-y-scroll{overflow-y:scroll!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-grid{display:grid!important}.d-inline-grid{display:inline-grid!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:flex!important}.d-inline-flex{display:inline-flex!important}.d-none{display:none!important}.shadow{box-shadow:var(--bs-box-shadow)!important}.shadow-sm{box-shadow:var(--bs-box-shadow-sm)!important}.shadow-lg{box-shadow:var(--bs-box-shadow-lg)!important}.shadow-none{box-shadow:none!important}.focus-ring-primary{--bs-focus-ring-color:rgba(var(--bs-primary-rgb), var(--bs-focus-ring-opacity))}.focus-ring-secondary{--bs-focus-ring-color:rgba(var(--bs-secondary-rgb), var(--bs-focus-ring-opacity))}.focus-ring-success{--bs-focus-ring-color:rgba(var(--bs-success-rgb), var(--bs-focus-ring-opacity))}.focus-ring-info{--bs-focus-ring-color:rgba(var(--bs-info-rgb), var(--bs-focus-ring-opacity))}.focus-ring-warning{--bs-focus-ring-color:rgba(var(--bs-warning-rgb), var(--bs-focus-ring-opacity))}.focus-ring-danger{--bs-focus-ring-color:rgba(var(--bs-danger-rgb), var(--bs-focus-ring-opacity))}.focus-ring-light{--bs-focus-ring-color:rgba(var(--bs-light-rgb), var(--bs-focus-ring-opacity))}.focus-ring-dark{--bs-focus-ring-color:rgba(var(--bs-dark-rgb), var(--bs-focus-ring-opacity))}.position-static{position:static!important}.position-relative{position:relative!important}.position-absolute{position:absolute!important}.position-fixed{position:fixed!important}.position-sticky{position:-webkit-sticky!important;position:sticky!important}.top-0{top:0!important}.top-50{top:50%!important}.top-100{top:100%!important}.bottom-0{bottom:0!important}.bottom-50{bottom:50%!important}.bottom-100{bottom:100%!important}.start-0{left:0!important}.start-50{left:50%!important}.start-100{left:100%!important}.end-0{right:0!important}.end-50{right:50%!important}.end-100{right:100%!important}.translate-middle{transform:translate(-50%,-50%)!important}.translate-middle-x{transform:translateX(-50%)!important}.translate-middle-y{transform:translateY(-50%)!important}.border{border:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color)!important}.border-0{border:0!important}.border-top{border-top:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color)!important}.border-top-0{border-top:0!important}.border-end{border-right:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color)!important}.border-end-0{border-right:0!important}.border-bottom{border-bottom:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color)!important}.border-bottom-0{border-bottom:0!important}.border-start{border-left:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color)!important}.border-start-0{border-left:0!important}.border-primary{--bs-border-opacity:1;border-color:rgba(var(--bs-primary-rgb),var(--bs-border-opacity))!important}.border-secondary{--bs-border-opacity:1;border-color:rgba(var(--bs-secondary-rgb),var(--bs-border-opacity))!important}.border-success{--bs-border-opacity:1;border-color:rgba(var(--bs-success-rgb),var(--bs-border-opacity))!important}.border-info{--bs-border-opacity:1;border-color:rgba(var(--bs-info-rgb),var(--bs-border-opacity))!important}.border-warning{--bs-border-opacity:1;border-color:rgba(var(--bs-warning-rgb),var(--bs-border-opacity))!important}.border-danger{--bs-border-opacity:1;border-color:rgba(var(--bs-danger-rgb),var(--bs-border-opacity))!important}.border-light{--bs-border-opacity:1;border-color:rgba(var(--bs-light-rgb),var(--bs-border-opacity))!important}.border-dark{--bs-border-opacity:1;border-color:rgba(var(--bs-dark-rgb),var(--bs-border-opacity))!important}.border-black{--bs-border-opacity:1;border-color:rgba(var(--bs-black-rgb),var(--bs-border-opacity))!important}.border-white{--bs-border-opacity:1;border-color:rgba(var(--bs-white-rgb),var(--bs-border-opacity))!important}.border-primary-subtle{border-color:var(--bs-primary-border-subtle)!important}.border-secondary-subtle{border-color:var(--bs-secondary-border-subtle)!important}.border-success-subtle{border-color:var(--bs-success-border-subtle)!important}.border-info-subtle{border-color:var(--bs-info-border-subtle)!important}.border-warning-subtle{border-color:var(--bs-warning-border-subtle)!important}.border-danger-subtle{border-color:var(--bs-danger-border-subtle)!important}.border-light-subtle{border-color:var(--bs-light-border-subtle)!important}.border-dark-subtle{border-color:var(--bs-dark-border-subtle)!important}.border-1{border-width:1px!important}.border-2{border-width:2px!important}.border-3{border-width:3px!important}.border-4{border-width:4px!important}.border-5{border-width:5px!important}.border-opacity-10{--bs-border-opacity:0.1}.border-opacity-25{--bs-border-opacity:0.25}.border-opacity-50{--bs-border-opacity:0.5}.border-opacity-75{--bs-border-opacity:0.75}.border-opacity-100{--bs-border-opacity:1}.w-25{width:25%!important}.w-50{width:50%!important}.w-75{width:75%!important}.w-100{width:100%!important}.w-auto{width:auto!important}.mw-100{max-width:100%!important}.vw-100{width:100vw!important}.min-vw-100{min-width:100vw!important}.h-25{height:25%!important}.h-50{height:50%!important}.h-75{height:75%!important}.h-100{height:100%!important}.h-auto{height:auto!important}.mh-100{max-height:100%!important}.vh-100{height:100vh!important}.min-vh-100{min-height:100vh!important}.flex-fill{flex:1 1 auto!important}.flex-row{flex-direction:row!important}.flex-column{flex-direction:column!important}.flex-row-reverse{flex-direction:row-reverse!important}.flex-column-reverse{flex-direction:column-reverse!important}.flex-grow-0{flex-grow:0!important}.flex-grow-1{flex-grow:1!important}.flex-shrink-0{flex-shrink:0!important}.flex-shrink-1{flex-shrink:1!important}.flex-wrap{flex-wrap:wrap!important}.flex-nowrap{flex-wrap:nowrap!important}.flex-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-start{justify-content:flex-start!important}.justify-content-end{justify-content:flex-end!important}.justify-content-center{justify-content:center!important}.justify-content-between{justify-content:space-between!important}.justify-content-around{justify-content:space-around!important}.justify-content-evenly{justify-content:space-evenly!important}.align-items-start{align-items:flex-start!important}.align-items-end{align-items:flex-end!important}.align-items-center{align-items:center!important}.align-items-baseline{align-items:baseline!important}.align-items-stretch{align-items:stretch!important}.align-content-start{align-content:flex-start!important}.align-content-end{align-content:flex-end!important}.align-content-center{align-content:center!important}.align-content-between{align-content:space-between!important}.align-content-around{align-content:space-around!important}.align-content-stretch{align-content:stretch!important}.align-self-auto{align-self:auto!important}.align-self-start{align-self:flex-start!important}.align-self-end{align-self:flex-end!important}.align-self-center{align-self:center!important}.align-self-baseline{align-self:baseline!important}.align-self-stretch{align-self:stretch!important}.order-first{order:-1!important}.order-0{order:0!important}.order-1{order:1!important}.order-2{order:2!important}.order-3{order:3!important}.order-4{order:4!important}.order-5{order:5!important}.order-last{order:6!important}.m-0{margin:0!important}.m-1{margin:.25rem!important}.m-2{margin:.5rem!important}.m-3{margin:1rem!important}.m-4{margin:1.5rem!important}.m-5{margin:3rem!important}.m-auto{margin:auto!important}.mx-0{margin-right:0!important;margin-left:0!important}.mx-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-3{margin-right:1rem!important;margin-left:1rem!important}.mx-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-5{margin-right:3rem!important;margin-left:3rem!important}.mx-auto{margin-right:auto!important;margin-left:auto!important}.my-0{margin-top:0!important;margin-bottom:0!important}.my-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-0{margin-top:0!important}.mt-1{margin-top:.25rem!important}.mt-2{margin-top:.5rem!important}.mt-3{margin-top:1rem!important}.mt-4{margin-top:1.5rem!important}.mt-5{margin-top:3rem!important}.mt-auto{margin-top:auto!important}.me-0{margin-right:0!important}.me-1{margin-right:.25rem!important}.me-2{margin-right:.5rem!important}.me-3{margin-right:1rem!important}.me-4{margin-right:1.5rem!important}.me-5{margin-right:3rem!important}.me-auto{margin-right:auto!important}.mb-0{margin-bottom:0!important}.mb-1{margin-bottom:.25rem!important}.mb-2{margin-bottom:.5rem!important}.mb-3{margin-bottom:1rem!important}.mb-4{margin-bottom:1.5rem!important}.mb-5{margin-bottom:3rem!important}.mb-auto{margin-bottom:auto!important}.ms-0{margin-left:0!important}.ms-1{margin-left:.25rem!important}.ms-2{margin-left:.5rem!important}.ms-3{margin-left:1rem!important}.ms-4{margin-left:1.5rem!important}.ms-5{margin-left:3rem!important}.ms-auto{margin-left:auto!important}.p-0{padding:0!important}.p-1{padding:.25rem!important}.p-2{padding:.5rem!important}.p-3{padding:1rem!important}.p-4{padding:1.5rem!important}.p-5{padding:3rem!important}.px-0{padding-right:0!important;padding-left:0!important}.px-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-3{padding-right:1rem!important;padding-left:1rem!important}.px-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-5{padding-right:3rem!important;padding-left:3rem!important}.py-0{padding-top:0!important;padding-bottom:0!important}.py-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-0{padding-top:0!important}.pt-1{padding-top:.25rem!important}.pt-2{padding-top:.5rem!important}.pt-3{padding-top:1rem!important}.pt-4{padding-top:1.5rem!important}.pt-5{padding-top:3rem!important}.pe-0{padding-right:0!important}.pe-1{padding-right:.25rem!important}.pe-2{padding-right:.5rem!important}.pe-3{padding-right:1rem!important}.pe-4{padding-right:1.5rem!important}.pe-5{padding-right:3rem!important}.pb-0{padding-bottom:0!important}.pb-1{padding-bottom:.25rem!important}.pb-2{padding-bottom:.5rem!important}.pb-3{padding-bottom:1rem!important}.pb-4{padding-bottom:1.5rem!important}.pb-5{padding-bottom:3rem!important}.ps-0{padding-left:0!important}.ps-1{padding-left:.25rem!important}.ps-2{padding-left:.5rem!important}.ps-3{padding-left:1rem!important}.ps-4{padding-left:1.5rem!important}.ps-5{padding-left:3rem!important}.gap-0{gap:0!important}.gap-1{gap:.25rem!important}.gap-2{gap:.5rem!important}.gap-3{gap:1rem!important}.gap-4{gap:1.5rem!important}.gap-5{gap:3rem!important}.row-gap-0{row-gap:0!important}.row-gap-1{row-gap:.25rem!important}.row-gap-2{row-gap:.5rem!important}.row-gap-3{row-gap:1rem!important}.row-gap-4{row-gap:1.5rem!important}.row-gap-5{row-gap:3rem!important}.column-gap-0{-moz-column-gap:0!important;column-gap:0!important}.column-gap-1{-moz-column-gap:0.25rem!important;column-gap:.25rem!important}.column-gap-2{-moz-column-gap:0.5rem!important;column-gap:.5rem!important}.column-gap-3{-moz-column-gap:1rem!important;column-gap:1rem!important}.column-gap-4{-moz-column-gap:1.5rem!important;column-gap:1.5rem!important}.column-gap-5{-moz-column-gap:3rem!important;column-gap:3rem!important}.font-monospace{font-family:var(--bs-font-monospace)!important}.fs-1{font-size:calc(1.375rem + 1.5vw)!important}.fs-2{font-size:calc(1.325rem + .9vw)!important}.fs-3{font-size:calc(1.3rem + .6vw)!important}.fs-4{font-size:calc(1.275rem + .3vw)!important}.fs-5{font-size:1.25rem!important}.fs-6{font-size:1rem!important}.fst-italic{font-style:italic!important}.fst-normal{font-style:normal!important}.fw-lighter{font-weight:lighter!important}.fw-light{font-weight:300!important}.fw-normal{font-weight:400!important}.fw-medium{font-weight:500!important}.fw-semibold{font-weight:600!important}.fw-bold{font-weight:700!important}.fw-bolder{font-weight:bolder!important}.lh-1{line-height:1!important}.lh-sm{line-height:1.25!important}.lh-base{line-height:1.5!important}.lh-lg{line-height:2!important}.text-start{text-align:left!important}.text-end{text-align:right!important}.text-center{text-align:center!important}.text-decoration-none{text-decoration:none!important}.text-decoration-underline{text-decoration:underline!important}.text-decoration-line-through{text-decoration:line-through!important}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.text-wrap{white-space:normal!important}.text-nowrap{white-space:nowrap!important}.text-break{word-wrap:break-word!important;word-break:break-word!important}.text-primary{--bs-text-opacity:1;color:rgba(var(--bs-primary-rgb),var(--bs-text-opacity))!important}.text-secondary{--bs-text-opacity:1;color:rgba(var(--bs-secondary-rgb),var(--bs-text-opacity))!important}.text-success{--bs-text-opacity:1;color:rgba(var(--bs-success-rgb),var(--bs-text-opacity))!important}.text-info{--bs-text-opacity:1;color:rgba(var(--bs-info-rgb),var(--bs-text-opacity))!important}.text-warning{--bs-text-opacity:1;color:rgba(var(--bs-warning-rgb),var(--bs-text-opacity))!important}.text-danger{--bs-text-opacity:1;color:rgba(var(--bs-danger-rgb),var(--bs-text-opacity))!important}.text-light{--bs-text-opacity:1;color:rgba(var(--bs-light-rgb),var(--bs-text-opacity))!important}.text-dark{--bs-text-opacity:1;color:rgba(var(--bs-dark-rgb),var(--bs-text-opacity))!important}.text-black{--bs-text-opacity:1;color:rgba(var(--bs-black-rgb),var(--bs-text-opacity))!important}.text-white{--bs-text-opacity:1;color:rgba(var(--bs-white-rgb),var(--bs-text-opacity))!important}.text-body{--bs-text-opacity:1;color:rgba(var(--bs-body-color-rgb),var(--bs-text-opacity))!important}.text-muted{--bs-text-opacity:1;color:var(--bs-secondary-color)!important}.text-black-50{--bs-text-opacity:1;color:rgba(0,0,0,.5)!important}.text-white-50{--bs-text-opacity:1;color:rgba(255,255,255,.5)!important}.text-body-secondary{--bs-text-opacity:1;color:var(--bs-secondary-color)!important}.text-body-tertiary{--bs-text-opacity:1;color:var(--bs-tertiary-color)!important}.text-body-emphasis{--bs-text-opacity:1;color:var(--bs-emphasis-color)!important}.text-reset{--bs-text-opacity:1;color:inherit!important}.text-opacity-25{--bs-text-opacity:0.25}.text-opacity-50{--bs-text-opacity:0.5}.text-opacity-75{--bs-text-opacity:0.75}.text-opacity-100{--bs-text-opacity:1}.text-primary-emphasis{color:var(--bs-primary-text-emphasis)!important}.text-secondary-emphasis{color:var(--bs-secondary-text-emphasis)!important}.text-success-emphasis{color:var(--bs-success-text-emphasis)!important}.text-info-emphasis{color:var(--bs-info-text-emphasis)!important}.text-warning-emphasis{color:var(--bs-warning-text-emphasis)!important}.text-danger-emphasis{color:var(--bs-danger-text-emphasis)!important}.text-light-emphasis{color:var(--bs-light-text-emphasis)!important}.text-dark-emphasis{color:var(--bs-dark-text-emphasis)!important}.link-opacity-10{--bs-link-opacity:0.1}.link-opacity-10-hover:hover{--bs-link-opacity:0.1}.link-opacity-25{--bs-link-opacity:0.25}.link-opacity-25-hover:hover{--bs-link-opacity:0.25}.link-opacity-50{--bs-link-opacity:0.5}.link-opacity-50-hover:hover{--bs-link-opacity:0.5}.link-opacity-75{--bs-link-opacity:0.75}.link-opacity-75-hover:hover{--bs-link-opacity:0.75}.link-opacity-100{--bs-link-opacity:1}.link-opacity-100-hover:hover{--bs-link-opacity:1}.link-offset-1{text-underline-offset:0.125em!important}.link-offset-1-hover:hover{text-underline-offset:0.125em!important}.link-offset-2{text-underline-offset:0.25em!important}.link-offset-2-hover:hover{text-underline-offset:0.25em!important}.link-offset-3{text-underline-offset:0.375em!important}.link-offset-3-hover:hover{text-underline-offset:0.375em!important}.link-underline-primary{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-primary-rgb),var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-primary-rgb),var(--bs-link-underline-opacity))!important}.link-underline-secondary{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-secondary-rgb),var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-secondary-rgb),var(--bs-link-underline-opacity))!important}.link-underline-success{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-success-rgb),var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-success-rgb),var(--bs-link-underline-opacity))!important}.link-underline-info{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-info-rgb),var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-info-rgb),var(--bs-link-underline-opacity))!important}.link-underline-warning{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-warning-rgb),var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-warning-rgb),var(--bs-link-underline-opacity))!important}.link-underline-danger{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-danger-rgb),var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-danger-rgb),var(--bs-link-underline-opacity))!important}.link-underline-light{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-light-rgb),var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-light-rgb),var(--bs-link-underline-opacity))!important}.link-underline-dark{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-dark-rgb),var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-dark-rgb),var(--bs-link-underline-opacity))!important}.link-underline{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-link-color-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:rgba(var(--bs-link-color-rgb),var(--bs-link-underline-opacity,1))!important}.link-underline-opacity-0{--bs-link-underline-opacity:0}.link-underline-opacity-0-hover:hover{--bs-link-underline-opacity:0}.link-underline-opacity-10{--bs-link-underline-opacity:0.1}.link-underline-opacity-10-hover:hover{--bs-link-underline-opacity:0.1}.link-underline-opacity-25{--bs-link-underline-opacity:0.25}.link-underline-opacity-25-hover:hover{--bs-link-underline-opacity:0.25}.link-underline-opacity-50{--bs-link-underline-opacity:0.5}.link-underline-opacity-50-hover:hover{--bs-link-underline-opacity:0.5}.link-underline-opacity-75{--bs-link-underline-opacity:0.75}.link-underline-opacity-75-hover:hover{--bs-link-underline-opacity:0.75}.link-underline-opacity-100{--bs-link-underline-opacity:1}.link-underline-opacity-100-hover:hover{--bs-link-underline-opacity:1}.bg-primary{--bs-bg-opacity:1;background-color:rgba(var(--bs-primary-rgb),var(--bs-bg-opacity))!important}.bg-secondary{--bs-bg-opacity:1;background-color:rgba(var(--bs-secondary-rgb),var(--bs-bg-opacity))!important}.bg-success{--bs-bg-opacity:1;background-color:rgba(var(--bs-success-rgb),var(--bs-bg-opacity))!important}.bg-info{--bs-bg-opacity:1;background-color:rgba(var(--bs-info-rgb),var(--bs-bg-opacity))!important}.bg-warning{--bs-bg-opacity:1;background-color:rgba(var(--bs-warning-rgb),var(--bs-bg-opacity))!important}.bg-danger{--bs-bg-opacity:1;background-color:rgba(var(--bs-danger-rgb),var(--bs-bg-opacity))!important}.bg-light{--bs-bg-opacity:1;background-color:rgba(var(--bs-light-rgb),var(--bs-bg-opacity))!important}.bg-dark{--bs-bg-opacity:1;background-color:rgba(var(--bs-dark-rgb),var(--bs-bg-opacity))!important}.bg-black{--bs-bg-opacity:1;background-color:rgba(var(--bs-black-rgb),var(--bs-bg-opacity))!important}.bg-white{--bs-bg-opacity:1;background-color:rgba(var(--bs-white-rgb),var(--bs-bg-opacity))!important}.bg-body{--bs-bg-opacity:1;background-color:rgba(var(--bs-body-bg-rgb),var(--bs-bg-opacity))!important}.bg-transparent{--bs-bg-opacity:1;background-color:transparent!important}.bg-body-secondary{--bs-bg-opacity:1;background-color:rgba(var(--bs-secondary-bg-rgb),var(--bs-bg-opacity))!important}.bg-body-tertiary{--bs-bg-opacity:1;background-color:rgba(var(--bs-tertiary-bg-rgb),var(--bs-bg-opacity))!important}.bg-opacity-10{--bs-bg-opacity:0.1}.bg-opacity-25{--bs-bg-opacity:0.25}.bg-opacity-50{--bs-bg-opacity:0.5}.bg-opacity-75{--bs-bg-opacity:0.75}.bg-opacity-100{--bs-bg-opacity:1}.bg-primary-subtle{background-color:var(--bs-primary-bg-subtle)!important}.bg-secondary-subtle{background-color:var(--bs-secondary-bg-subtle)!important}.bg-success-subtle{background-color:var(--bs-success-bg-subtle)!important}.bg-info-subtle{background-color:var(--bs-info-bg-subtle)!important}.bg-warning-subtle{background-color:var(--bs-warning-bg-subtle)!important}.bg-danger-subtle{background-color:var(--bs-danger-bg-subtle)!important}.bg-light-subtle{background-color:var(--bs-light-bg-subtle)!important}.bg-dark-subtle{background-color:var(--bs-dark-bg-subtle)!important}.bg-gradient{background-image:var(--bs-gradient)!important}.user-select-all{-webkit-user-select:all!important;-moz-user-select:all!important;user-select:all!important}.user-select-auto{-webkit-user-select:auto!important;-moz-user-select:auto!important;user-select:auto!important}.user-select-none{-webkit-user-select:none!important;-moz-user-select:none!important;user-select:none!important}.pe-none{pointer-events:none!important}.pe-auto{pointer-events:auto!important}.rounded{border-radius:var(--bs-border-radius)!important}.rounded-0{border-radius:0!important}.rounded-1{border-radius:var(--bs-border-radius-sm)!important}.rounded-2{border-radius:var(--bs-border-radius)!important}.rounded-3{border-radius:var(--bs-border-radius-lg)!important}.rounded-4{border-radius:var(--bs-border-radius-xl)!important}.rounded-5{border-radius:var(--bs-border-radius-xxl)!important}.rounded-circle{border-radius:50%!important}.rounded-pill{border-radius:var(--bs-border-radius-pill)!important}.rounded-top{border-top-left-radius:var(--bs-border-radius)!important;border-top-right-radius:var(--bs-border-radius)!important}.rounded-top-0{border-top-left-radius:0!important;border-top-right-radius:0!important}.rounded-top-1{border-top-left-radius:var(--bs-border-radius-sm)!important;border-top-right-radius:var(--bs-border-radius-sm)!important}.rounded-top-2{border-top-left-radius:var(--bs-border-radius)!important;border-top-right-radius:var(--bs-border-radius)!important}.rounded-top-3{border-top-left-radius:var(--bs-border-radius-lg)!important;border-top-right-radius:var(--bs-border-radius-lg)!important}.rounded-top-4{border-top-left-radius:var(--bs-border-radius-xl)!important;border-top-right-radius:var(--bs-border-radius-xl)!important}.rounded-top-5{border-top-left-radius:var(--bs-border-radius-xxl)!important;border-top-right-radius:var(--bs-border-radius-xxl)!important}.rounded-top-circle{border-top-left-radius:50%!important;border-top-right-radius:50%!important}.rounded-top-pill{border-top-left-radius:var(--bs-border-radius-pill)!important;border-top-right-radius:var(--bs-border-radius-pill)!important}.rounded-end{border-top-right-radius:var(--bs-border-radius)!important;border-bottom-right-radius:var(--bs-border-radius)!important}.rounded-end-0{border-top-right-radius:0!important;border-bottom-right-radius:0!important}.rounded-end-1{border-top-right-radius:var(--bs-border-radius-sm)!important;border-bottom-right-radius:var(--bs-border-radius-sm)!important}.rounded-end-2{border-top-right-radius:var(--bs-border-radius)!important;border-bottom-right-radius:var(--bs-border-radius)!important}.rounded-end-3{border-top-right-radius:var(--bs-border-radius-lg)!important;border-bottom-right-radius:var(--bs-border-radius-lg)!important}.rounded-end-4{border-top-right-radius:var(--bs-border-radius-xl)!important;border-bottom-right-radius:var(--bs-border-radius-xl)!important}.rounded-end-5{border-top-right-radius:var(--bs-border-radius-xxl)!important;border-bottom-right-radius:var(--bs-border-radius-xxl)!important}.rounded-end-circle{border-top-right-radius:50%!important;border-bottom-right-radius:50%!important}.rounded-end-pill{border-top-right-radius:var(--bs-border-radius-pill)!important;border-bottom-right-radius:var(--bs-border-radius-pill)!important}.rounded-bottom{border-bottom-right-radius:var(--bs-border-radius)!important;border-bottom-left-radius:var(--bs-border-radius)!important}.rounded-bottom-0{border-bottom-right-radius:0!important;border-bottom-left-radius:0!important}.rounded-bottom-1{border-bottom-right-radius:var(--bs-border-radius-sm)!important;border-bottom-left-radius:var(--bs-border-radius-sm)!important}.rounded-bottom-2{border-bottom-right-radius:var(--bs-border-radius)!important;border-bottom-left-radius:var(--bs-border-radius)!important}.rounded-bottom-3{border-bottom-right-radius:var(--bs-border-radius-lg)!important;border-bottom-left-radius:var(--bs-border-radius-lg)!important}.rounded-bottom-4{border-bottom-right-radius:var(--bs-border-radius-xl)!important;border-bottom-left-radius:var(--bs-border-radius-xl)!important}.rounded-bottom-5{border-bottom-right-radius:var(--bs-border-radius-xxl)!important;border-bottom-left-radius:var(--bs-border-radius-xxl)!important}.rounded-bottom-circle{border-bottom-right-radius:50%!important;border-bottom-left-radius:50%!important}.rounded-bottom-pill{border-bottom-right-radius:var(--bs-border-radius-pill)!important;border-bottom-left-radius:var(--bs-border-radius-pill)!important}.rounded-start{border-bottom-left-radius:var(--bs-border-radius)!important;border-top-left-radius:var(--bs-border-radius)!important}.rounded-start-0{border-bottom-left-radius:0!important;border-top-left-radius:0!important}.rounded-start-1{border-bottom-left-radius:var(--bs-border-radius-sm)!important;border-top-left-radius:var(--bs-border-radius-sm)!important}.rounded-start-2{border-bottom-left-radius:var(--bs-border-radius)!important;border-top-left-radius:var(--bs-border-radius)!important}.rounded-start-3{border-bottom-left-radius:var(--bs-border-radius-lg)!important;border-top-left-radius:var(--bs-border-radius-lg)!important}.rounded-start-4{border-bottom-left-radius:var(--bs-border-radius-xl)!important;border-top-left-radius:var(--bs-border-radius-xl)!important}.rounded-start-5{border-bottom-left-radius:var(--bs-border-radius-xxl)!important;border-top-left-radius:var(--bs-border-radius-xxl)!important}.rounded-start-circle{border-bottom-left-radius:50%!important;border-top-left-radius:50%!important}.rounded-start-pill{border-bottom-left-radius:var(--bs-border-radius-pill)!important;border-top-left-radius:var(--bs-border-radius-pill)!important}.visible{visibility:visible!important}.invisible{visibility:hidden!important}.z-n1{z-index:-1!important}.z-0{z-index:0!important}.z-1{z-index:1!important}.z-2{z-index:2!important}.z-3{z-index:3!important}@media (min-width:576px){.float-sm-start{float:left!important}.float-sm-end{float:right!important}.float-sm-none{float:none!important}.object-fit-sm-contain{-o-object-fit:contain!important;object-fit:contain!important}.object-fit-sm-cover{-o-object-fit:cover!important;object-fit:cover!important}.object-fit-sm-fill{-o-object-fit:fill!important;object-fit:fill!important}.object-fit-sm-scale{-o-object-fit:scale-down!important;object-fit:scale-down!important}.object-fit-sm-none{-o-object-fit:none!important;object-fit:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-grid{display:grid!important}.d-sm-inline-grid{display:inline-grid!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:flex!important}.d-sm-inline-flex{display:inline-flex!important}.d-sm-none{display:none!important}.flex-sm-fill{flex:1 1 auto!important}.flex-sm-row{flex-direction:row!important}.flex-sm-column{flex-direction:column!important}.flex-sm-row-reverse{flex-direction:row-reverse!important}.flex-sm-column-reverse{flex-direction:column-reverse!important}.flex-sm-grow-0{flex-grow:0!important}.flex-sm-grow-1{flex-grow:1!important}.flex-sm-shrink-0{flex-shrink:0!important}.flex-sm-shrink-1{flex-shrink:1!important}.flex-sm-wrap{flex-wrap:wrap!important}.flex-sm-nowrap{flex-wrap:nowrap!important}.flex-sm-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-sm-start{justify-content:flex-start!important}.justify-content-sm-end{justify-content:flex-end!important}.justify-content-sm-center{justify-content:center!important}.justify-content-sm-between{justify-content:space-between!important}.justify-content-sm-around{justify-content:space-around!important}.justify-content-sm-evenly{justify-content:space-evenly!important}.align-items-sm-start{align-items:flex-start!important}.align-items-sm-end{align-items:flex-end!important}.align-items-sm-center{align-items:center!important}.align-items-sm-baseline{align-items:baseline!important}.align-items-sm-stretch{align-items:stretch!important}.align-content-sm-start{align-content:flex-start!important}.align-content-sm-end{align-content:flex-end!important}.align-content-sm-center{align-content:center!important}.align-content-sm-between{align-content:space-between!important}.align-content-sm-around{align-content:space-around!important}.align-content-sm-stretch{align-content:stretch!important}.align-self-sm-auto{align-self:auto!important}.align-self-sm-start{align-self:flex-start!important}.align-self-sm-end{align-self:flex-end!important}.align-self-sm-center{align-self:center!important}.align-self-sm-baseline{align-self:baseline!important}.align-self-sm-stretch{align-self:stretch!important}.order-sm-first{order:-1!important}.order-sm-0{order:0!important}.order-sm-1{order:1!important}.order-sm-2{order:2!important}.order-sm-3{order:3!important}.order-sm-4{order:4!important}.order-sm-5{order:5!important}.order-sm-last{order:6!important}.m-sm-0{margin:0!important}.m-sm-1{margin:.25rem!important}.m-sm-2{margin:.5rem!important}.m-sm-3{margin:1rem!important}.m-sm-4{margin:1.5rem!important}.m-sm-5{margin:3rem!important}.m-sm-auto{margin:auto!important}.mx-sm-0{margin-right:0!important;margin-left:0!important}.mx-sm-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-sm-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-sm-3{margin-right:1rem!important;margin-left:1rem!important}.mx-sm-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-sm-5{margin-right:3rem!important;margin-left:3rem!important}.mx-sm-auto{margin-right:auto!important;margin-left:auto!important}.my-sm-0{margin-top:0!important;margin-bottom:0!important}.my-sm-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-sm-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-sm-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-sm-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-sm-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-sm-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-sm-0{margin-top:0!important}.mt-sm-1{margin-top:.25rem!important}.mt-sm-2{margin-top:.5rem!important}.mt-sm-3{margin-top:1rem!important}.mt-sm-4{margin-top:1.5rem!important}.mt-sm-5{margin-top:3rem!important}.mt-sm-auto{margin-top:auto!important}.me-sm-0{margin-right:0!important}.me-sm-1{margin-right:.25rem!important}.me-sm-2{margin-right:.5rem!important}.me-sm-3{margin-right:1rem!important}.me-sm-4{margin-right:1.5rem!important}.me-sm-5{margin-right:3rem!important}.me-sm-auto{margin-right:auto!important}.mb-sm-0{margin-bottom:0!important}.mb-sm-1{margin-bottom:.25rem!important}.mb-sm-2{margin-bottom:.5rem!important}.mb-sm-3{margin-bottom:1rem!important}.mb-sm-4{margin-bottom:1.5rem!important}.mb-sm-5{margin-bottom:3rem!important}.mb-sm-auto{margin-bottom:auto!important}.ms-sm-0{margin-left:0!important}.ms-sm-1{margin-left:.25rem!important}.ms-sm-2{margin-left:.5rem!important}.ms-sm-3{margin-left:1rem!important}.ms-sm-4{margin-left:1.5rem!important}.ms-sm-5{margin-left:3rem!important}.ms-sm-auto{margin-left:auto!important}.p-sm-0{padding:0!important}.p-sm-1{padding:.25rem!important}.p-sm-2{padding:.5rem!important}.p-sm-3{padding:1rem!important}.p-sm-4{padding:1.5rem!important}.p-sm-5{padding:3rem!important}.px-sm-0{padding-right:0!important;padding-left:0!important}.px-sm-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-sm-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-sm-3{padding-right:1rem!important;padding-left:1rem!important}.px-sm-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-sm-5{padding-right:3rem!important;padding-left:3rem!important}.py-sm-0{padding-top:0!important;padding-bottom:0!important}.py-sm-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-sm-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-sm-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-sm-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-sm-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-sm-0{padding-top:0!important}.pt-sm-1{padding-top:.25rem!important}.pt-sm-2{padding-top:.5rem!important}.pt-sm-3{padding-top:1rem!important}.pt-sm-4{padding-top:1.5rem!important}.pt-sm-5{padding-top:3rem!important}.pe-sm-0{padding-right:0!important}.pe-sm-1{padding-right:.25rem!important}.pe-sm-2{padding-right:.5rem!important}.pe-sm-3{padding-right:1rem!important}.pe-sm-4{padding-right:1.5rem!important}.pe-sm-5{padding-right:3rem!important}.pb-sm-0{padding-bottom:0!important}.pb-sm-1{padding-bottom:.25rem!important}.pb-sm-2{padding-bottom:.5rem!important}.pb-sm-3{padding-bottom:1rem!important}.pb-sm-4{padding-bottom:1.5rem!important}.pb-sm-5{padding-bottom:3rem!important}.ps-sm-0{padding-left:0!important}.ps-sm-1{padding-left:.25rem!important}.ps-sm-2{padding-left:.5rem!important}.ps-sm-3{padding-left:1rem!important}.ps-sm-4{padding-left:1.5rem!important}.ps-sm-5{padding-left:3rem!important}.gap-sm-0{gap:0!important}.gap-sm-1{gap:.25rem!important}.gap-sm-2{gap:.5rem!important}.gap-sm-3{gap:1rem!important}.gap-sm-4{gap:1.5rem!important}.gap-sm-5{gap:3rem!important}.row-gap-sm-0{row-gap:0!important}.row-gap-sm-1{row-gap:.25rem!important}.row-gap-sm-2{row-gap:.5rem!important}.row-gap-sm-3{row-gap:1rem!important}.row-gap-sm-4{row-gap:1.5rem!important}.row-gap-sm-5{row-gap:3rem!important}.column-gap-sm-0{-moz-column-gap:0!important;column-gap:0!important}.column-gap-sm-1{-moz-column-gap:0.25rem!important;column-gap:.25rem!important}.column-gap-sm-2{-moz-column-gap:0.5rem!important;column-gap:.5rem!important}.column-gap-sm-3{-moz-column-gap:1rem!important;column-gap:1rem!important}.column-gap-sm-4{-moz-column-gap:1.5rem!important;column-gap:1.5rem!important}.column-gap-sm-5{-moz-column-gap:3rem!important;column-gap:3rem!important}.text-sm-start{text-align:left!important}.text-sm-end{text-align:right!important}.text-sm-center{text-align:center!important}}@media (min-width:768px){.float-md-start{float:left!important}.float-md-end{float:right!important}.float-md-none{float:none!important}.object-fit-md-contain{-o-object-fit:contain!important;object-fit:contain!important}.object-fit-md-cover{-o-object-fit:cover!important;object-fit:cover!important}.object-fit-md-fill{-o-object-fit:fill!important;object-fit:fill!important}.object-fit-md-scale{-o-object-fit:scale-down!important;object-fit:scale-down!important}.object-fit-md-none{-o-object-fit:none!important;object-fit:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-grid{display:grid!important}.d-md-inline-grid{display:inline-grid!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:flex!important}.d-md-inline-flex{display:inline-flex!important}.d-md-none{display:none!important}.flex-md-fill{flex:1 1 auto!important}.flex-md-row{flex-direction:row!important}.flex-md-column{flex-direction:column!important}.flex-md-row-reverse{flex-direction:row-reverse!important}.flex-md-column-reverse{flex-direction:column-reverse!important}.flex-md-grow-0{flex-grow:0!important}.flex-md-grow-1{flex-grow:1!important}.flex-md-shrink-0{flex-shrink:0!important}.flex-md-shrink-1{flex-shrink:1!important}.flex-md-wrap{flex-wrap:wrap!important}.flex-md-nowrap{flex-wrap:nowrap!important}.flex-md-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-md-start{justify-content:flex-start!important}.justify-content-md-end{justify-content:flex-end!important}.justify-content-md-center{justify-content:center!important}.justify-content-md-between{justify-content:space-between!important}.justify-content-md-around{justify-content:space-around!important}.justify-content-md-evenly{justify-content:space-evenly!important}.align-items-md-start{align-items:flex-start!important}.align-items-md-end{align-items:flex-end!important}.align-items-md-center{align-items:center!important}.align-items-md-baseline{align-items:baseline!important}.align-items-md-stretch{align-items:stretch!important}.align-content-md-start{align-content:flex-start!important}.align-content-md-end{align-content:flex-end!important}.align-content-md-center{align-content:center!important}.align-content-md-between{align-content:space-between!important}.align-content-md-around{align-content:space-around!important}.align-content-md-stretch{align-content:stretch!important}.align-self-md-auto{align-self:auto!important}.align-self-md-start{align-self:flex-start!important}.align-self-md-end{align-self:flex-end!important}.align-self-md-center{align-self:center!important}.align-self-md-baseline{align-self:baseline!important}.align-self-md-stretch{align-self:stretch!important}.order-md-first{order:-1!important}.order-md-0{order:0!important}.order-md-1{order:1!important}.order-md-2{order:2!important}.order-md-3{order:3!important}.order-md-4{order:4!important}.order-md-5{order:5!important}.order-md-last{order:6!important}.m-md-0{margin:0!important}.m-md-1{margin:.25rem!important}.m-md-2{margin:.5rem!important}.m-md-3{margin:1rem!important}.m-md-4{margin:1.5rem!important}.m-md-5{margin:3rem!important}.m-md-auto{margin:auto!important}.mx-md-0{margin-right:0!important;margin-left:0!important}.mx-md-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-md-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-md-3{margin-right:1rem!important;margin-left:1rem!important}.mx-md-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-md-5{margin-right:3rem!important;margin-left:3rem!important}.mx-md-auto{margin-right:auto!important;margin-left:auto!important}.my-md-0{margin-top:0!important;margin-bottom:0!important}.my-md-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-md-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-md-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-md-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-md-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-md-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-md-0{margin-top:0!important}.mt-md-1{margin-top:.25rem!important}.mt-md-2{margin-top:.5rem!important}.mt-md-3{margin-top:1rem!important}.mt-md-4{margin-top:1.5rem!important}.mt-md-5{margin-top:3rem!important}.mt-md-auto{margin-top:auto!important}.me-md-0{margin-right:0!important}.me-md-1{margin-right:.25rem!important}.me-md-2{margin-right:.5rem!important}.me-md-3{margin-right:1rem!important}.me-md-4{margin-right:1.5rem!important}.me-md-5{margin-right:3rem!important}.me-md-auto{margin-right:auto!important}.mb-md-0{margin-bottom:0!important}.mb-md-1{margin-bottom:.25rem!important}.mb-md-2{margin-bottom:.5rem!important}.mb-md-3{margin-bottom:1rem!important}.mb-md-4{margin-bottom:1.5rem!important}.mb-md-5{margin-bottom:3rem!important}.mb-md-auto{margin-bottom:auto!important}.ms-md-0{margin-left:0!important}.ms-md-1{margin-left:.25rem!important}.ms-md-2{margin-left:.5rem!important}.ms-md-3{margin-left:1rem!important}.ms-md-4{margin-left:1.5rem!important}.ms-md-5{margin-left:3rem!important}.ms-md-auto{margin-left:auto!important}.p-md-0{padding:0!important}.p-md-1{padding:.25rem!important}.p-md-2{padding:.5rem!important}.p-md-3{padding:1rem!important}.p-md-4{padding:1.5rem!important}.p-md-5{padding:3rem!important}.px-md-0{padding-right:0!important;padding-left:0!important}.px-md-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-md-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-md-3{padding-right:1rem!important;padding-left:1rem!important}.px-md-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-md-5{padding-right:3rem!important;padding-left:3rem!important}.py-md-0{padding-top:0!important;padding-bottom:0!important}.py-md-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-md-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-md-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-md-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-md-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-md-0{padding-top:0!important}.pt-md-1{padding-top:.25rem!important}.pt-md-2{padding-top:.5rem!important}.pt-md-3{padding-top:1rem!important}.pt-md-4{padding-top:1.5rem!important}.pt-md-5{padding-top:3rem!important}.pe-md-0{padding-right:0!important}.pe-md-1{padding-right:.25rem!important}.pe-md-2{padding-right:.5rem!important}.pe-md-3{padding-right:1rem!important}.pe-md-4{padding-right:1.5rem!important}.pe-md-5{padding-right:3rem!important}.pb-md-0{padding-bottom:0!important}.pb-md-1{padding-bottom:.25rem!important}.pb-md-2{padding-bottom:.5rem!important}.pb-md-3{padding-bottom:1rem!important}.pb-md-4{padding-bottom:1.5rem!important}.pb-md-5{padding-bottom:3rem!important}.ps-md-0{padding-left:0!important}.ps-md-1{padding-left:.25rem!important}.ps-md-2{padding-left:.5rem!important}.ps-md-3{padding-left:1rem!important}.ps-md-4{padding-left:1.5rem!important}.ps-md-5{padding-left:3rem!important}.gap-md-0{gap:0!important}.gap-md-1{gap:.25rem!important}.gap-md-2{gap:.5rem!important}.gap-md-3{gap:1rem!important}.gap-md-4{gap:1.5rem!important}.gap-md-5{gap:3rem!important}.row-gap-md-0{row-gap:0!important}.row-gap-md-1{row-gap:.25rem!important}.row-gap-md-2{row-gap:.5rem!important}.row-gap-md-3{row-gap:1rem!important}.row-gap-md-4{row-gap:1.5rem!important}.row-gap-md-5{row-gap:3rem!important}.column-gap-md-0{-moz-column-gap:0!important;column-gap:0!important}.column-gap-md-1{-moz-column-gap:0.25rem!important;column-gap:.25rem!important}.column-gap-md-2{-moz-column-gap:0.5rem!important;column-gap:.5rem!important}.column-gap-md-3{-moz-column-gap:1rem!important;column-gap:1rem!important}.column-gap-md-4{-moz-column-gap:1.5rem!important;column-gap:1.5rem!important}.column-gap-md-5{-moz-column-gap:3rem!important;column-gap:3rem!important}.text-md-start{text-align:left!important}.text-md-end{text-align:right!important}.text-md-center{text-align:center!important}}@media (min-width:992px){.float-lg-start{float:left!important}.float-lg-end{float:right!important}.float-lg-none{float:none!important}.object-fit-lg-contain{-o-object-fit:contain!important;object-fit:contain!important}.object-fit-lg-cover{-o-object-fit:cover!important;object-fit:cover!important}.object-fit-lg-fill{-o-object-fit:fill!important;object-fit:fill!important}.object-fit-lg-scale{-o-object-fit:scale-down!important;object-fit:scale-down!important}.object-fit-lg-none{-o-object-fit:none!important;object-fit:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-grid{display:grid!important}.d-lg-inline-grid{display:inline-grid!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:flex!important}.d-lg-inline-flex{display:inline-flex!important}.d-lg-none{display:none!important}.flex-lg-fill{flex:1 1 auto!important}.flex-lg-row{flex-direction:row!important}.flex-lg-column{flex-direction:column!important}.flex-lg-row-reverse{flex-direction:row-reverse!important}.flex-lg-column-reverse{flex-direction:column-reverse!important}.flex-lg-grow-0{flex-grow:0!important}.flex-lg-grow-1{flex-grow:1!important}.flex-lg-shrink-0{flex-shrink:0!important}.flex-lg-shrink-1{flex-shrink:1!important}.flex-lg-wrap{flex-wrap:wrap!important}.flex-lg-nowrap{flex-wrap:nowrap!important}.flex-lg-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-lg-start{justify-content:flex-start!important}.justify-content-lg-end{justify-content:flex-end!important}.justify-content-lg-center{justify-content:center!important}.justify-content-lg-between{justify-content:space-between!important}.justify-content-lg-around{justify-content:space-around!important}.justify-content-lg-evenly{justify-content:space-evenly!important}.align-items-lg-start{align-items:flex-start!important}.align-items-lg-end{align-items:flex-end!important}.align-items-lg-center{align-items:center!important}.align-items-lg-baseline{align-items:baseline!important}.align-items-lg-stretch{align-items:stretch!important}.align-content-lg-start{align-content:flex-start!important}.align-content-lg-end{align-content:flex-end!important}.align-content-lg-center{align-content:center!important}.align-content-lg-between{align-content:space-between!important}.align-content-lg-around{align-content:space-around!important}.align-content-lg-stretch{align-content:stretch!important}.align-self-lg-auto{align-self:auto!important}.align-self-lg-start{align-self:flex-start!important}.align-self-lg-end{align-self:flex-end!important}.align-self-lg-center{align-self:center!important}.align-self-lg-baseline{align-self:baseline!important}.align-self-lg-stretch{align-self:stretch!important}.order-lg-first{order:-1!important}.order-lg-0{order:0!important}.order-lg-1{order:1!important}.order-lg-2{order:2!important}.order-lg-3{order:3!important}.order-lg-4{order:4!important}.order-lg-5{order:5!important}.order-lg-last{order:6!important}.m-lg-0{margin:0!important}.m-lg-1{margin:.25rem!important}.m-lg-2{margin:.5rem!important}.m-lg-3{margin:1rem!important}.m-lg-4{margin:1.5rem!important}.m-lg-5{margin:3rem!important}.m-lg-auto{margin:auto!important}.mx-lg-0{margin-right:0!important;margin-left:0!important}.mx-lg-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-lg-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-lg-3{margin-right:1rem!important;margin-left:1rem!important}.mx-lg-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-lg-5{margin-right:3rem!important;margin-left:3rem!important}.mx-lg-auto{margin-right:auto!important;margin-left:auto!important}.my-lg-0{margin-top:0!important;margin-bottom:0!important}.my-lg-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-lg-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-lg-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-lg-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-lg-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-lg-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-lg-0{margin-top:0!important}.mt-lg-1{margin-top:.25rem!important}.mt-lg-2{margin-top:.5rem!important}.mt-lg-3{margin-top:1rem!important}.mt-lg-4{margin-top:1.5rem!important}.mt-lg-5{margin-top:3rem!important}.mt-lg-auto{margin-top:auto!important}.me-lg-0{margin-right:0!important}.me-lg-1{margin-right:.25rem!important}.me-lg-2{margin-right:.5rem!important}.me-lg-3{margin-right:1rem!important}.me-lg-4{margin-right:1.5rem!important}.me-lg-5{margin-right:3rem!important}.me-lg-auto{margin-right:auto!important}.mb-lg-0{margin-bottom:0!important}.mb-lg-1{margin-bottom:.25rem!important}.mb-lg-2{margin-bottom:.5rem!important}.mb-lg-3{margin-bottom:1rem!important}.mb-lg-4{margin-bottom:1.5rem!important}.mb-lg-5{margin-bottom:3rem!important}.mb-lg-auto{margin-bottom:auto!important}.ms-lg-0{margin-left:0!important}.ms-lg-1{margin-left:.25rem!important}.ms-lg-2{margin-left:.5rem!important}.ms-lg-3{margin-left:1rem!important}.ms-lg-4{margin-left:1.5rem!important}.ms-lg-5{margin-left:3rem!important}.ms-lg-auto{margin-left:auto!important}.p-lg-0{padding:0!important}.p-lg-1{padding:.25rem!important}.p-lg-2{padding:.5rem!important}.p-lg-3{padding:1rem!important}.p-lg-4{padding:1.5rem!important}.p-lg-5{padding:3rem!important}.px-lg-0{padding-right:0!important;padding-left:0!important}.px-lg-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-lg-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-lg-3{padding-right:1rem!important;padding-left:1rem!important}.px-lg-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-lg-5{padding-right:3rem!important;padding-left:3rem!important}.py-lg-0{padding-top:0!important;padding-bottom:0!important}.py-lg-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-lg-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-lg-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-lg-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-lg-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-lg-0{padding-top:0!important}.pt-lg-1{padding-top:.25rem!important}.pt-lg-2{padding-top:.5rem!important}.pt-lg-3{padding-top:1rem!important}.pt-lg-4{padding-top:1.5rem!important}.pt-lg-5{padding-top:3rem!important}.pe-lg-0{padding-right:0!important}.pe-lg-1{padding-right:.25rem!important}.pe-lg-2{padding-right:.5rem!important}.pe-lg-3{padding-right:1rem!important}.pe-lg-4{padding-right:1.5rem!important}.pe-lg-5{padding-right:3rem!important}.pb-lg-0{padding-bottom:0!important}.pb-lg-1{padding-bottom:.25rem!important}.pb-lg-2{padding-bottom:.5rem!important}.pb-lg-3{padding-bottom:1rem!important}.pb-lg-4{padding-bottom:1.5rem!important}.pb-lg-5{padding-bottom:3rem!important}.ps-lg-0{padding-left:0!important}.ps-lg-1{padding-left:.25rem!important}.ps-lg-2{padding-left:.5rem!important}.ps-lg-3{padding-left:1rem!important}.ps-lg-4{padding-left:1.5rem!important}.ps-lg-5{padding-left:3rem!important}.gap-lg-0{gap:0!important}.gap-lg-1{gap:.25rem!important}.gap-lg-2{gap:.5rem!important}.gap-lg-3{gap:1rem!important}.gap-lg-4{gap:1.5rem!important}.gap-lg-5{gap:3rem!important}.row-gap-lg-0{row-gap:0!important}.row-gap-lg-1{row-gap:.25rem!important}.row-gap-lg-2{row-gap:.5rem!important}.row-gap-lg-3{row-gap:1rem!important}.row-gap-lg-4{row-gap:1.5rem!important}.row-gap-lg-5{row-gap:3rem!important}.column-gap-lg-0{-moz-column-gap:0!important;column-gap:0!important}.column-gap-lg-1{-moz-column-gap:0.25rem!important;column-gap:.25rem!important}.column-gap-lg-2{-moz-column-gap:0.5rem!important;column-gap:.5rem!important}.column-gap-lg-3{-moz-column-gap:1rem!important;column-gap:1rem!important}.column-gap-lg-4{-moz-column-gap:1.5rem!important;column-gap:1.5rem!important}.column-gap-lg-5{-moz-column-gap:3rem!important;column-gap:3rem!important}.text-lg-start{text-align:left!important}.text-lg-end{text-align:right!important}.text-lg-center{text-align:center!important}}@media (min-width:1200px){.float-xl-start{float:left!important}.float-xl-end{float:right!important}.float-xl-none{float:none!important}.object-fit-xl-contain{-o-object-fit:contain!important;object-fit:contain!important}.object-fit-xl-cover{-o-object-fit:cover!important;object-fit:cover!important}.object-fit-xl-fill{-o-object-fit:fill!important;object-fit:fill!important}.object-fit-xl-scale{-o-object-fit:scale-down!important;object-fit:scale-down!important}.object-fit-xl-none{-o-object-fit:none!important;object-fit:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-grid{display:grid!important}.d-xl-inline-grid{display:inline-grid!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:flex!important}.d-xl-inline-flex{display:inline-flex!important}.d-xl-none{display:none!important}.flex-xl-fill{flex:1 1 auto!important}.flex-xl-row{flex-direction:row!important}.flex-xl-column{flex-direction:column!important}.flex-xl-row-reverse{flex-direction:row-reverse!important}.flex-xl-column-reverse{flex-direction:column-reverse!important}.flex-xl-grow-0{flex-grow:0!important}.flex-xl-grow-1{flex-grow:1!important}.flex-xl-shrink-0{flex-shrink:0!important}.flex-xl-shrink-1{flex-shrink:1!important}.flex-xl-wrap{flex-wrap:wrap!important}.flex-xl-nowrap{flex-wrap:nowrap!important}.flex-xl-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-xl-start{justify-content:flex-start!important}.justify-content-xl-end{justify-content:flex-end!important}.justify-content-xl-center{justify-content:center!important}.justify-content-xl-between{justify-content:space-between!important}.justify-content-xl-around{justify-content:space-around!important}.justify-content-xl-evenly{justify-content:space-evenly!important}.align-items-xl-start{align-items:flex-start!important}.align-items-xl-end{align-items:flex-end!important}.align-items-xl-center{align-items:center!important}.align-items-xl-baseline{align-items:baseline!important}.align-items-xl-stretch{align-items:stretch!important}.align-content-xl-start{align-content:flex-start!important}.align-content-xl-end{align-content:flex-end!important}.align-content-xl-center{align-content:center!important}.align-content-xl-between{align-content:space-between!important}.align-content-xl-around{align-content:space-around!important}.align-content-xl-stretch{align-content:stretch!important}.align-self-xl-auto{align-self:auto!important}.align-self-xl-start{align-self:flex-start!important}.align-self-xl-end{align-self:flex-end!important}.align-self-xl-center{align-self:center!important}.align-self-xl-baseline{align-self:baseline!important}.align-self-xl-stretch{align-self:stretch!important}.order-xl-first{order:-1!important}.order-xl-0{order:0!important}.order-xl-1{order:1!important}.order-xl-2{order:2!important}.order-xl-3{order:3!important}.order-xl-4{order:4!important}.order-xl-5{order:5!important}.order-xl-last{order:6!important}.m-xl-0{margin:0!important}.m-xl-1{margin:.25rem!important}.m-xl-2{margin:.5rem!important}.m-xl-3{margin:1rem!important}.m-xl-4{margin:1.5rem!important}.m-xl-5{margin:3rem!important}.m-xl-auto{margin:auto!important}.mx-xl-0{margin-right:0!important;margin-left:0!important}.mx-xl-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-xl-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-xl-3{margin-right:1rem!important;margin-left:1rem!important}.mx-xl-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-xl-5{margin-right:3rem!important;margin-left:3rem!important}.mx-xl-auto{margin-right:auto!important;margin-left:auto!important}.my-xl-0{margin-top:0!important;margin-bottom:0!important}.my-xl-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-xl-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-xl-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-xl-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-xl-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-xl-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-xl-0{margin-top:0!important}.mt-xl-1{margin-top:.25rem!important}.mt-xl-2{margin-top:.5rem!important}.mt-xl-3{margin-top:1rem!important}.mt-xl-4{margin-top:1.5rem!important}.mt-xl-5{margin-top:3rem!important}.mt-xl-auto{margin-top:auto!important}.me-xl-0{margin-right:0!important}.me-xl-1{margin-right:.25rem!important}.me-xl-2{margin-right:.5rem!important}.me-xl-3{margin-right:1rem!important}.me-xl-4{margin-right:1.5rem!important}.me-xl-5{margin-right:3rem!important}.me-xl-auto{margin-right:auto!important}.mb-xl-0{margin-bottom:0!important}.mb-xl-1{margin-bottom:.25rem!important}.mb-xl-2{margin-bottom:.5rem!important}.mb-xl-3{margin-bottom:1rem!important}.mb-xl-4{margin-bottom:1.5rem!important}.mb-xl-5{margin-bottom:3rem!important}.mb-xl-auto{margin-bottom:auto!important}.ms-xl-0{margin-left:0!important}.ms-xl-1{margin-left:.25rem!important}.ms-xl-2{margin-left:.5rem!important}.ms-xl-3{margin-left:1rem!important}.ms-xl-4{margin-left:1.5rem!important}.ms-xl-5{margin-left:3rem!important}.ms-xl-auto{margin-left:auto!important}.p-xl-0{padding:0!important}.p-xl-1{padding:.25rem!important}.p-xl-2{padding:.5rem!important}.p-xl-3{padding:1rem!important}.p-xl-4{padding:1.5rem!important}.p-xl-5{padding:3rem!important}.px-xl-0{padding-right:0!important;padding-left:0!important}.px-xl-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-xl-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-xl-3{padding-right:1rem!important;padding-left:1rem!important}.px-xl-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-xl-5{padding-right:3rem!important;padding-left:3rem!important}.py-xl-0{padding-top:0!important;padding-bottom:0!important}.py-xl-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-xl-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-xl-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-xl-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-xl-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-xl-0{padding-top:0!important}.pt-xl-1{padding-top:.25rem!important}.pt-xl-2{padding-top:.5rem!important}.pt-xl-3{padding-top:1rem!important}.pt-xl-4{padding-top:1.5rem!important}.pt-xl-5{padding-top:3rem!important}.pe-xl-0{padding-right:0!important}.pe-xl-1{padding-right:.25rem!important}.pe-xl-2{padding-right:.5rem!important}.pe-xl-3{padding-right:1rem!important}.pe-xl-4{padding-right:1.5rem!important}.pe-xl-5{padding-right:3rem!important}.pb-xl-0{padding-bottom:0!important}.pb-xl-1{padding-bottom:.25rem!important}.pb-xl-2{padding-bottom:.5rem!important}.pb-xl-3{padding-bottom:1rem!important}.pb-xl-4{padding-bottom:1.5rem!important}.pb-xl-5{padding-bottom:3rem!important}.ps-xl-0{padding-left:0!important}.ps-xl-1{padding-left:.25rem!important}.ps-xl-2{padding-left:.5rem!important}.ps-xl-3{padding-left:1rem!important}.ps-xl-4{padding-left:1.5rem!important}.ps-xl-5{padding-left:3rem!important}.gap-xl-0{gap:0!important}.gap-xl-1{gap:.25rem!important}.gap-xl-2{gap:.5rem!important}.gap-xl-3{gap:1rem!important}.gap-xl-4{gap:1.5rem!important}.gap-xl-5{gap:3rem!important}.row-gap-xl-0{row-gap:0!important}.row-gap-xl-1{row-gap:.25rem!important}.row-gap-xl-2{row-gap:.5rem!important}.row-gap-xl-3{row-gap:1rem!important}.row-gap-xl-4{row-gap:1.5rem!important}.row-gap-xl-5{row-gap:3rem!important}.column-gap-xl-0{-moz-column-gap:0!important;column-gap:0!important}.column-gap-xl-1{-moz-column-gap:0.25rem!important;column-gap:.25rem!important}.column-gap-xl-2{-moz-column-gap:0.5rem!important;column-gap:.5rem!important}.column-gap-xl-3{-moz-column-gap:1rem!important;column-gap:1rem!important}.column-gap-xl-4{-moz-column-gap:1.5rem!important;column-gap:1.5rem!important}.column-gap-xl-5{-moz-column-gap:3rem!important;column-gap:3rem!important}.text-xl-start{text-align:left!important}.text-xl-end{text-align:right!important}.text-xl-center{text-align:center!important}}@media (min-width:1400px){.float-xxl-start{float:left!important}.float-xxl-end{float:right!important}.float-xxl-none{float:none!important}.object-fit-xxl-contain{-o-object-fit:contain!important;object-fit:contain!important}.object-fit-xxl-cover{-o-object-fit:cover!important;object-fit:cover!important}.object-fit-xxl-fill{-o-object-fit:fill!important;object-fit:fill!important}.object-fit-xxl-scale{-o-object-fit:scale-down!important;object-fit:scale-down!important}.object-fit-xxl-none{-o-object-fit:none!important;object-fit:none!important}.d-xxl-inline{display:inline!important}.d-xxl-inline-block{display:inline-block!important}.d-xxl-block{display:block!important}.d-xxl-grid{display:grid!important}.d-xxl-inline-grid{display:inline-grid!important}.d-xxl-table{display:table!important}.d-xxl-table-row{display:table-row!important}.d-xxl-table-cell{display:table-cell!important}.d-xxl-flex{display:flex!important}.d-xxl-inline-flex{display:inline-flex!important}.d-xxl-none{display:none!important}.flex-xxl-fill{flex:1 1 auto!important}.flex-xxl-row{flex-direction:row!important}.flex-xxl-column{flex-direction:column!important}.flex-xxl-row-reverse{flex-direction:row-reverse!important}.flex-xxl-column-reverse{flex-direction:column-reverse!important}.flex-xxl-grow-0{flex-grow:0!important}.flex-xxl-grow-1{flex-grow:1!important}.flex-xxl-shrink-0{flex-shrink:0!important}.flex-xxl-shrink-1{flex-shrink:1!important}.flex-xxl-wrap{flex-wrap:wrap!important}.flex-xxl-nowrap{flex-wrap:nowrap!important}.flex-xxl-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-xxl-start{justify-content:flex-start!important}.justify-content-xxl-end{justify-content:flex-end!important}.justify-content-xxl-center{justify-content:center!important}.justify-content-xxl-between{justify-content:space-between!important}.justify-content-xxl-around{justify-content:space-around!important}.justify-content-xxl-evenly{justify-content:space-evenly!important}.align-items-xxl-start{align-items:flex-start!important}.align-items-xxl-end{align-items:flex-end!important}.align-items-xxl-center{align-items:center!important}.align-items-xxl-baseline{align-items:baseline!important}.align-items-xxl-stretch{align-items:stretch!important}.align-content-xxl-start{align-content:flex-start!important}.align-content-xxl-end{align-content:flex-end!important}.align-content-xxl-center{align-content:center!important}.align-content-xxl-between{align-content:space-between!important}.align-content-xxl-around{align-content:space-around!important}.align-content-xxl-stretch{align-content:stretch!important}.align-self-xxl-auto{align-self:auto!important}.align-self-xxl-start{align-self:flex-start!important}.align-self-xxl-end{align-self:flex-end!important}.align-self-xxl-center{align-self:center!important}.align-self-xxl-baseline{align-self:baseline!important}.align-self-xxl-stretch{align-self:stretch!important}.order-xxl-first{order:-1!important}.order-xxl-0{order:0!important}.order-xxl-1{order:1!important}.order-xxl-2{order:2!important}.order-xxl-3{order:3!important}.order-xxl-4{order:4!important}.order-xxl-5{order:5!important}.order-xxl-last{order:6!important}.m-xxl-0{margin:0!important}.m-xxl-1{margin:.25rem!important}.m-xxl-2{margin:.5rem!important}.m-xxl-3{margin:1rem!important}.m-xxl-4{margin:1.5rem!important}.m-xxl-5{margin:3rem!important}.m-xxl-auto{margin:auto!important}.mx-xxl-0{margin-right:0!important;margin-left:0!important}.mx-xxl-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-xxl-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-xxl-3{margin-right:1rem!important;margin-left:1rem!important}.mx-xxl-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-xxl-5{margin-right:3rem!important;margin-left:3rem!important}.mx-xxl-auto{margin-right:auto!important;margin-left:auto!important}.my-xxl-0{margin-top:0!important;margin-bottom:0!important}.my-xxl-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-xxl-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-xxl-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-xxl-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-xxl-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-xxl-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-xxl-0{margin-top:0!important}.mt-xxl-1{margin-top:.25rem!important}.mt-xxl-2{margin-top:.5rem!important}.mt-xxl-3{margin-top:1rem!important}.mt-xxl-4{margin-top:1.5rem!important}.mt-xxl-5{margin-top:3rem!important}.mt-xxl-auto{margin-top:auto!important}.me-xxl-0{margin-right:0!important}.me-xxl-1{margin-right:.25rem!important}.me-xxl-2{margin-right:.5rem!important}.me-xxl-3{margin-right:1rem!important}.me-xxl-4{margin-right:1.5rem!important}.me-xxl-5{margin-right:3rem!important}.me-xxl-auto{margin-right:auto!important}.mb-xxl-0{margin-bottom:0!important}.mb-xxl-1{margin-bottom:.25rem!important}.mb-xxl-2{margin-bottom:.5rem!important}.mb-xxl-3{margin-bottom:1rem!important}.mb-xxl-4{margin-bottom:1.5rem!important}.mb-xxl-5{margin-bottom:3rem!important}.mb-xxl-auto{margin-bottom:auto!important}.ms-xxl-0{margin-left:0!important}.ms-xxl-1{margin-left:.25rem!important}.ms-xxl-2{margin-left:.5rem!important}.ms-xxl-3{margin-left:1rem!important}.ms-xxl-4{margin-left:1.5rem!important}.ms-xxl-5{margin-left:3rem!important}.ms-xxl-auto{margin-left:auto!important}.p-xxl-0{padding:0!important}.p-xxl-1{padding:.25rem!important}.p-xxl-2{padding:.5rem!important}.p-xxl-3{padding:1rem!important}.p-xxl-4{padding:1.5rem!important}.p-xxl-5{padding:3rem!important}.px-xxl-0{padding-right:0!important;padding-left:0!important}.px-xxl-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-xxl-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-xxl-3{padding-right:1rem!important;padding-left:1rem!important}.px-xxl-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-xxl-5{padding-right:3rem!important;padding-left:3rem!important}.py-xxl-0{padding-top:0!important;padding-bottom:0!important}.py-xxl-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-xxl-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-xxl-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-xxl-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-xxl-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-xxl-0{padding-top:0!important}.pt-xxl-1{padding-top:.25rem!important}.pt-xxl-2{padding-top:.5rem!important}.pt-xxl-3{padding-top:1rem!important}.pt-xxl-4{padding-top:1.5rem!important}.pt-xxl-5{padding-top:3rem!important}.pe-xxl-0{padding-right:0!important}.pe-xxl-1{padding-right:.25rem!important}.pe-xxl-2{padding-right:.5rem!important}.pe-xxl-3{padding-right:1rem!important}.pe-xxl-4{padding-right:1.5rem!important}.pe-xxl-5{padding-right:3rem!important}.pb-xxl-0{padding-bottom:0!important}.pb-xxl-1{padding-bottom:.25rem!important}.pb-xxl-2{padding-bottom:.5rem!important}.pb-xxl-3{padding-bottom:1rem!important}.pb-xxl-4{padding-bottom:1.5rem!important}.pb-xxl-5{padding-bottom:3rem!important}.ps-xxl-0{padding-left:0!important}.ps-xxl-1{padding-left:.25rem!important}.ps-xxl-2{padding-left:.5rem!important}.ps-xxl-3{padding-left:1rem!important}.ps-xxl-4{padding-left:1.5rem!important}.ps-xxl-5{padding-left:3rem!important}.gap-xxl-0{gap:0!important}.gap-xxl-1{gap:.25rem!important}.gap-xxl-2{gap:.5rem!important}.gap-xxl-3{gap:1rem!important}.gap-xxl-4{gap:1.5rem!important}.gap-xxl-5{gap:3rem!important}.row-gap-xxl-0{row-gap:0!important}.row-gap-xxl-1{row-gap:.25rem!important}.row-gap-xxl-2{row-gap:.5rem!important}.row-gap-xxl-3{row-gap:1rem!important}.row-gap-xxl-4{row-gap:1.5rem!important}.row-gap-xxl-5{row-gap:3rem!important}.column-gap-xxl-0{-moz-column-gap:0!important;column-gap:0!important}.column-gap-xxl-1{-moz-column-gap:0.25rem!important;column-gap:.25rem!important}.column-gap-xxl-2{-moz-column-gap:0.5rem!important;column-gap:.5rem!important}.column-gap-xxl-3{-moz-column-gap:1rem!important;column-gap:1rem!important}.column-gap-xxl-4{-moz-column-gap:1.5rem!important;column-gap:1.5rem!important}.column-gap-xxl-5{-moz-column-gap:3rem!important;column-gap:3rem!important}.text-xxl-start{text-align:left!important}.text-xxl-end{text-align:right!important}.text-xxl-center{text-align:center!important}}@media (min-width:1200px){.fs-1{font-size:2.5rem!important}.fs-2{font-size:2rem!important}.fs-3{font-size:1.75rem!important}.fs-4{font-size:1.5rem!important}}@media print{.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-grid{display:grid!important}.d-print-inline-grid{display:inline-grid!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:flex!important}.d-print-inline-flex{display:inline-flex!important}.d-print-none{display:none!important}}
|
||
/*# sourceMappingURL=bootstrap.min.css.map */
|
||
// public/assets/js/modules/DataTable.js
|
||
/**
|
||
class DataTable {
|
||
/**
|
||
constructor(containerId, options = {}) {
|
||
this.containerId = containerId;
|
||
this.options = {
|
||
url: options.url || '/api/table',
|
||
perPage: options.perPage || 10,
|
||
debounceTime: options.debounceTime || 300,
|
||
preserveSearchOnSort: options.preserveSearchOnSort !== false,
|
||
...options
|
||
};
|
||
this.state = {
|
||
page: 1,
|
||
perPage: this.options.perPage,
|
||
sort: '',
|
||
order: 'asc',
|
||
filters: {}
|
||
};
|
||
this.searchTimeout = null;
|
||
this.initWithDOM();
|
||
}
|
||
/**
|
||
initWithDOM() {
|
||
this.container = document.getElementById(this.containerId);
|
||
if (!this.container) {
|
||
setTimeout(() => {
|
||
this.container = document.getElementById(this.containerId);
|
||
if (this.container) {
|
||
this.init();
|
||
} else {
|
||
}
|
||
}, 0);
|
||
return;
|
||
}
|
||
this.init();
|
||
}
|
||
/**
|
||
init() {
|
||
if (this.container.dataset.datatableInitialized) {
|
||
console.log('DataTable: Already initialized for', this.containerId);
|
||
return;
|
||
}
|
||
this.container.dataset.datatableInitialized = 'true';
|
||
const tbody = this.container.querySelector('tbody');
|
||
const existingRows = tbody ? tbody.querySelectorAll('tr') : [];
|
||
console.log('DataTable init for', this.containerId, ':', existingRows.length, 'rows found');
|
||
if (existingRows.length > 0) {
|
||
const hasDataRow = Array.from(existingRows).some(tr => !tr.classList.contains('loading'));
|
||
if (hasDataRow) {
|
||
console.log('DataTable: Data exists, skipping AJAX load');
|
||
this.bindEvents();
|
||
return;
|
||
}
|
||
}
|
||
console.log('DataTable: No data found, loading via AJAX...');
|
||
this.bindEvents();
|
||
this.loadData();
|
||
}
|
||
/**
|
||
bindEvents() {
|
||
this.container.addEventListener('click', (e) => this.handleClick(e));
|
||
this.container.addEventListener('input', (e) => this.handleInput(e));
|
||
this.container.addEventListener('keydown', (e) => {
|
||
if (e.key === 'Enter' && e.target.matches('[data-search-input]')) {
|
||
e.preventDefault();
|
||
e.target.blur();
|
||
}
|
||
});
|
||
this.container.addEventListener('change', (e) => {
|
||
if (e.target.matches('[id^="per-page-select-"]')) {
|
||
const value = e.target.value;
|
||
this.setPerPage(value);
|
||
}
|
||
});
|
||
}
|
||
/**
|
||
handleClick(e) {
|
||
const sortIcon = e.target.closest('[data-sort]');
|
||
if (sortIcon) {
|
||
e.stopPropagation();
|
||
const column = sortIcon.dataset.sort;
|
||
this.toggleSort(column);
|
||
return;
|
||
}
|
||
const searchTrigger = e.target.closest('[data-search-trigger]');
|
||
if (searchTrigger) {
|
||
e.stopPropagation();
|
||
const th = searchTrigger.closest('th');
|
||
this.openSearch(th);
|
||
return;
|
||
}
|
||
const headerText = e.target.closest('[data-header-text]');
|
||
if (headerText) {
|
||
e.stopPropagation();
|
||
const th = headerText.closest('th');
|
||
this.openSearch(th);
|
||
return;
|
||
}
|
||
const pageLink = e.target.closest('[data-page]');
|
||
if (pageLink && !e.target.closest('.disabled') && !e.target.closest('.active')) {
|
||
e.preventDefault();
|
||
const page = parseInt(pageLink.dataset.page);
|
||
if (page > 0) {
|
||
this.state.page = page;
|
||
this.loadData();
|
||
}
|
||
return;
|
||
}
|
||
const navLink = e.target.closest('[data-nav-page]');
|
||
if (navLink && !e.target.closest('.disabled')) {
|
||
e.preventDefault();
|
||
const direction = navLink.dataset.navPage;
|
||
this.state.page = direction === 'prev'
|
||
? Math.max(1, this.state.page - 1)
|
||
: this.state.page + 1;
|
||
this.loadData();
|
||
return;
|
||
}
|
||
if (!e.target.closest('thead')) {
|
||
this.closeAllSearches();
|
||
}
|
||
}
|
||
/**
|
||
handleInput(e) {
|
||
if (e.target.matches('[data-search-input]')) {
|
||
const column = e.target.dataset.searchInput;
|
||
this.debouncedSearch(column, e.target.value);
|
||
}
|
||
}
|
||
/**
|
||
debouncedSearch(column, value) {
|
||
clearTimeout(this.searchTimeout);
|
||
this.searchTimeout = setTimeout(() => {
|
||
this.state.filters[column] = value;
|
||
this.state.page = 1;
|
||
this.loadData();
|
||
}, this.options.debounceTime);
|
||
}
|
||
/**
|
||
toggleSort(column) {
|
||
if (this.state.sort === column) {
|
||
this.state.order = this.state.order === 'asc' ? 'desc' : 'asc';
|
||
} else {
|
||
this.state.sort = column;
|
||
this.state.order = 'desc';
|
||
}
|
||
this.loadData();
|
||
}
|
||
/**
|
||
openSearch(th) {
|
||
const headerText = th.querySelector('[data-header-text]');
|
||
const searchInput = th.querySelector('[data-search-input]');
|
||
if (headerText && searchInput) {
|
||
headerText.style.display = 'none';
|
||
searchInput.style.display = 'block';
|
||
searchInput.focus();
|
||
}
|
||
}
|
||
/**
|
||
closeAllSearches() {
|
||
const inputs = this.container.querySelectorAll('[data-search-input]');
|
||
inputs.forEach(input => {
|
||
const th = input.closest('th');
|
||
const headerText = th.querySelector('[data-header-text]');
|
||
if (headerText) {
|
||
if (input.value.trim()) {
|
||
input.style.display = 'none';
|
||
headerText.style.display = 'inline';
|
||
} else {
|
||
input.style.display = 'none';
|
||
headerText.style.display = 'inline';
|
||
}
|
||
}
|
||
});
|
||
}
|
||
/**
|
||
getCsrfToken() {
|
||
if (this.container.dataset.csrfToken) {
|
||
return this.container.dataset.csrfToken;
|
||
}
|
||
const csrfInput = this.container.querySelector('input[name*="csrf"]');
|
||
if (csrfInput) {
|
||
return csrfInput.value;
|
||
}
|
||
const globalCsrfInput = document.querySelector('input[name*="csrf"]');
|
||
if (globalCsrfInput) {
|
||
return globalCsrfInput.value;
|
||
}
|
||
const cookies = document.cookie.split(';');
|
||
for (let cookie of cookies) {
|
||
const [name, value] = cookie.trim().split('=');
|
||
if (name.toLowerCase().includes('csrf')) {
|
||
return decodeURIComponent(value);
|
||
}
|
||
}
|
||
console.warn('CSRF token not found');
|
||
return '';
|
||
}
|
||
/**
|
||
getCsrfTokenName() {
|
||
if (this.container.dataset.csrfTokenName) {
|
||
return this.container.dataset.csrfTokenName;
|
||
}
|
||
const csrfInput = this.container.querySelector('input[name*="csrf"]');
|
||
if (csrfInput) {
|
||
return csrfInput.name;
|
||
}
|
||
const globalCsrfInput = document.querySelector('input[name*="csrf"]');
|
||
if (globalCsrfInput) {
|
||
return globalCsrfInput.name;
|
||
}
|
||
return 'csrf_test_name';
|
||
}
|
||
/**
|
||
updateCsrfToken(response) {
|
||
const csrfHeader = response.headers.get('X-CSRF-TOKEN');
|
||
if (csrfHeader) {
|
||
document.querySelectorAll('input[name*="csrf"]').forEach(input => {
|
||
input.value = csrfHeader;
|
||
});
|
||
this.container.dataset.csrfToken = csrfHeader;
|
||
}
|
||
}
|
||
/**
|
||
async loadData() {
|
||
const params = this.buildParams();
|
||
const csrfToken = this.getCsrfToken();
|
||
const csrfTokenName = this.getCsrfTokenName();
|
||
const url = `${this.options.url}?${params}&${csrfTokenName}=${encodeURIComponent(csrfToken)}`;
|
||
console.log('DataTable: Loading from URL:', url);
|
||
console.log('DataTable: CSRF token:', csrfToken, 'name:', csrfTokenName);
|
||
const tableBody = this.container.querySelector('tbody');
|
||
if (tableBody) {
|
||
tableBody.innerHTML = `
|
||
<tr>
|
||
<td colspan="100" class="text-center py-5">
|
||
<div class="spinner-border text-primary" role="status">
|
||
<span class="visually-hidden">Загрузка...</span>
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
`;
|
||
}
|
||
try {
|
||
const response = await fetch(url, {
|
||
headers: {
|
||
'X-Requested-With': 'XMLHttpRequest',
|
||
'X-CSRF-TOKEN': csrfToken
|
||
}
|
||
});
|
||
this.updateCsrfToken(response);
|
||
console.log('DataTable: Response status:', response.status);
|
||
console.log('DataTable: Response URL:', response.url);
|
||
if (!response.ok) {
|
||
throw new Error(`HTTP error! status: ${response.status}`);
|
||
}
|
||
const html = await response.text();
|
||
console.log('DataTable: HTML length:', html.length);
|
||
console.log('DataTable: HTML preview:', html.substring(0, 200).replace(/\s+/g, ' '));
|
||
this.updateTable(html);
|
||
} catch (error) {
|
||
console.error('DataTable: Ошибка загрузки данных:', error);
|
||
this.showError();
|
||
}
|
||
}
|
||
/**
|
||
updateTable(html) {
|
||
console.log('DataTable: HTML received, length:', html.length);
|
||
console.log('DataTable: HTML starts with:', html.substring(0, 50).replace(/\s+/g, ' '));
|
||
const searchInputs = this.container.querySelectorAll('[data-search-input]');
|
||
const searchStates = {};
|
||
searchInputs.forEach(input => {
|
||
searchStates[input.dataset.searchInput] = input.value;
|
||
});
|
||
const tbodyMatch = html.match(/<tbody[^>]*>[\s\S]*?<\/tbody>/i);
|
||
const tfootMatch = html.match(/<tfoot[^>]*>[\s\S]*?<\/tfoot>/i);
|
||
const oldTableBody = this.container.querySelector('tbody');
|
||
const oldTableFooter = this.container.querySelector('tfoot');
|
||
const table = this.container.querySelector('table');
|
||
console.log('DataTable: tbodyMatch:', !!tbodyMatch, 'oldTableBody:', !!oldTableBody);
|
||
console.log('DataTable: tfootMatch:', !!tfootMatch, 'oldTableFooter:', !!oldTableFooter);
|
||
if (tbodyMatch && oldTableBody && table) {
|
||
const tbodyContent = tbodyMatch[0].replace(/<\/?tbody[^>]*>/gi, '');
|
||
oldTableBody.innerHTML = tbodyContent;
|
||
console.log('DataTable: tbody updated, rows:', oldTableBody.querySelectorAll('tr').length);
|
||
if (tfootMatch) {
|
||
const tfootContent = tfootMatch[0].replace(/<\/?tfoot[^>]*>/gi, '');
|
||
if (oldTableFooter) {
|
||
oldTableFooter.innerHTML = tfootContent;
|
||
} else {
|
||
const newTfoot = document.createElement('tfoot');
|
||
newTfoot.innerHTML = tfootContent;
|
||
table.appendChild(newTfoot);
|
||
}
|
||
} else if (oldTableFooter) {
|
||
oldTableFooter.remove();
|
||
}
|
||
} else {
|
||
console.log('DataTable: Full container replacement');
|
||
this.container.innerHTML = html;
|
||
}
|
||
this.restoreSearchStates(searchStates);
|
||
this.updateURL();
|
||
}
|
||
/**
|
||
restoreSearchStates(savedStates = null) {
|
||
const inputs = this.container.querySelectorAll('[data-search-input]');
|
||
inputs.forEach(input => {
|
||
const column = input.dataset.searchInput;
|
||
const value = savedStates && savedStates[column] !== undefined
|
||
? savedStates[column]
|
||
: input.value.trim();
|
||
const th = input.closest('th');
|
||
const headerText = th.querySelector('[data-header-text]');
|
||
if (headerText) {
|
||
if (value) {
|
||
headerText.style.display = 'none';
|
||
input.style.display = 'block';
|
||
input.value = value;
|
||
} else {
|
||
input.style.display = 'none';
|
||
headerText.style.display = 'inline';
|
||
}
|
||
}
|
||
});
|
||
}
|
||
/**
|
||
updateURL() {
|
||
const params = this.buildParams();
|
||
const baseUrl = this.options.url.split('?')[0];
|
||
const url = `${baseUrl}?${params}`;
|
||
window.history.pushState(this.state, '', url);
|
||
}
|
||
/**
|
||
buildParams() {
|
||
const params = new URLSearchParams();
|
||
params.set('page', this.state.page);
|
||
params.set('perPage', this.state.perPage);
|
||
if (this.state.sort) {
|
||
params.set('sort', this.state.sort);
|
||
params.set('order', this.state.order);
|
||
}
|
||
Object.entries(this.state.filters).forEach(([key, value]) => {
|
||
if (value && value.trim()) {
|
||
params.set(`filters[${key}]`, value);
|
||
}
|
||
});
|
||
return params.toString();
|
||
}
|
||
/**
|
||
showError() {
|
||
const tableBody = this.container.querySelector('tbody');
|
||
if (tableBody) {
|
||
tableBody.innerHTML = `
|
||
<tr>
|
||
<td colspan="100" class="alert alert-danger m-3">
|
||
Ошибка загрузки данных. Пожалуйста, обновите страницу.
|
||
</td>
|
||
</tr>
|
||
`;
|
||
}
|
||
}
|
||
/**
|
||
setFilter(column, value) {
|
||
this.state.filters[column] = value;
|
||
this.state.page = 1;
|
||
this.loadData();
|
||
}
|
||
/**
|
||
setPerPage(value) {
|
||
this.state.perPage = parseInt(value);
|
||
this.state.page = 1;
|
||
this.loadData();
|
||
}
|
||
/**
|
||
goToPage(page) {
|
||
this.state.page = Math.max(1, parseInt(page));
|
||
this.loadData();
|
||
}
|
||
}
|
||
window.DataTable = DataTable;
|
||
|
||
// public/.htaccess
|
||
# Disable directory browsing
|
||
Options -Indexes
|
||
|
||
# ----------------------------------------------------------------------
|
||
# Rewrite engine
|
||
# ----------------------------------------------------------------------
|
||
|
||
# Turning on the rewrite engine is necessary for the following rules and features.
|
||
# FollowSymLinks must be enabled for this to work.
|
||
<IfModule mod_rewrite.c>
|
||
Options +FollowSymlinks
|
||
RewriteEngine On
|
||
|
||
# If you installed CodeIgniter in a subfolder, you will need to
|
||
# change the following line to match the subfolder you need.
|
||
# http://httpd.apache.org/docs/current/mod/mod_rewrite.html#rewritebase
|
||
# RewriteBase /
|
||
|
||
# Redirect Trailing Slashes...
|
||
RewriteCond %{REQUEST_FILENAME} !-d
|
||
RewriteCond %{REQUEST_URI} (.+)/$
|
||
RewriteRule ^ %1 [L,R=301]
|
||
|
||
# Rewrite "www.example.com -> example.com"
|
||
RewriteCond %{HTTPS} !=on
|
||
RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]
|
||
RewriteRule ^ http://%1%{REQUEST_URI} [R=301,L]
|
||
|
||
# Checks to see if the user is attempting to access a valid file,
|
||
# such as an image or css document, if this isn't true it sends the
|
||
# request to the front controller, index.php
|
||
RewriteCond %{REQUEST_FILENAME} !-f
|
||
RewriteCond %{REQUEST_FILENAME} !-d
|
||
RewriteRule ^([\s\S]*)$ index.php/$1 [L,NC,QSA]
|
||
|
||
# Ensure Authorization header is passed along
|
||
RewriteCond %{HTTP:Authorization} .
|
||
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
|
||
</IfModule>
|
||
|
||
<IfModule !mod_rewrite.c>
|
||
# If we don't have mod_rewrite installed, all 404's
|
||
# can be sent to index.php, and everything works as normal.
|
||
ErrorDocument 404 index.php
|
||
</IfModule>
|
||
|
||
# Disable server signature start
|
||
ServerSignature Off
|
||
# Disable server signature end
|
||
|
||
// public/index.php
|
||
<?php
|
||
use CodeIgniter\Boot;
|
||
use Config\Paths;
|
||
/*
|
||
$minPhpVersion = '8.1';
|
||
if (version_compare(PHP_VERSION, $minPhpVersion, '<')) {
|
||
$message = sprintf(
|
||
'Your PHP version must be %s or higher to run CodeIgniter. Current version: %s',
|
||
$minPhpVersion,
|
||
PHP_VERSION,
|
||
);
|
||
header('HTTP/1.1 503 Service Unavailable.', true, 503);
|
||
echo $message;
|
||
exit(1);
|
||
}
|
||
/*
|
||
define('FCPATH', __DIR__ . DIRECTORY_SEPARATOR);
|
||
if (getcwd() . DIRECTORY_SEPARATOR !== FCPATH) {
|
||
chdir(FCPATH);
|
||
}
|
||
/*
|
||
require FCPATH . '../app/Config/Paths.php';
|
||
$paths = new Paths();
|
||
require $paths->systemDirectory . '/Boot.php';
|
||
exit(Boot::bootWeb($paths));
|
||
|
||
// .env
|
||
#--------------------------------------------------------------------
|
||
# Example Environment Configuration file
|
||
#
|
||
# This file can be used as a starting point for your own
|
||
# custom .env files, and contains most of the possible settings
|
||
# available in a default install.
|
||
#
|
||
# By default, all of the settings are commented out. If you want
|
||
# to override the setting, you must un-comment it by removing the '#'
|
||
# at the beginning of the line.
|
||
#--------------------------------------------------------------------
|
||
|
||
#--------------------------------------------------------------------
|
||
# ENVIRONMENT
|
||
#--------------------------------------------------------------------
|
||
|
||
CI_ENVIRONMENT = development
|
||
|
||
#--------------------------------------------------------------------
|
||
# APP
|
||
#--------------------------------------------------------------------
|
||
|
||
app.baseURL = 'https://bp.taskms.ru'
|
||
# If you have trouble with `.`, you could also use `_`.
|
||
# app_baseURL = ''
|
||
# app.forceGlobalSecureRequests = false
|
||
# app.CSPEnabled = false
|
||
|
||
#--------------------------------------------------------------------
|
||
# DATABASE
|
||
#--------------------------------------------------------------------
|
||
|
||
database.default.hostname = localhost
|
||
database.default.database = bp_mirv_db
|
||
database.default.username = bp_mirv
|
||
database.default.password = bp_mirv_Moloko22
|
||
database.default.DBDriver = MySQLi
|
||
database.default.DBPrefix =
|
||
database.default.port = 3306
|
||
|
||
#--------------------------------------------------------------------
|
||
# ENCRYPTION
|
||
#--------------------------------------------------------------------
|
||
|
||
encryption.key = sadfonusdofuhsefiouhw9er87yhdf
|
||
|
||
|
||
#--------------------------------------------------------------------
|
||
# SMTP
|
||
#--------------------------------------------------------------------
|
||
|
||
email.protocol = 'smtp'
|
||
email.SMTPHost = 'smtp.yandex.ru'
|
||
email.SMTPCrypto = 'ssl'
|
||
email.SMTPPort = 465
|
||
email.SMTPUser = 'mirvtop@yandex.ru'
|
||
email.SMTPPass = 'azpudcybqsqbbqns'
|
||
email.fromEmail = 'mirvtop@yandex.ru'
|
||
email.fromName = 'Бизнес.Точка'
|
||
|
||
email.mailType = 'html'
|
||
|
||
#--------------------------------------------------------------------
|
||
# SESSION
|
||
#--------------------------------------------------------------------
|
||
|
||
# session.driver = 'CodeIgniter\Session\Handlers\FileHandler'
|
||
# session.savePath = null
|
||
|
||
#--------------------------------------------------------------------
|
||
# LOGGER
|
||
#--------------------------------------------------------------------
|
||
|
||
# logger.threshold = 4
|
||
|