From a447b17e698b60b2126d5b3965a11aaba45601a5 Mon Sep 17 00:00:00 2001 From: Carlos Arias Date: Mon, 14 Sep 2026 16:15:41 -0500 Subject: [PATCH] Initial commit: SeedProject API framework PHP MVC seed project with router, database layer, auth helpers, LLM provider integrations, Stripe gateway, and console commands. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01VVcDB9cRQZRRmUivMHRQGL --- .env.example | 44 + .gitignore | 22 + .htaccess | 12 + .memory/changelog.md | 16 + .memory/documentation.md | 2392 +++++ .memory/llm.md | 313 + .reference_files/schema.sql | 808 ++ app/Components/Role.php | 158 + app/Components/Template.php | 31 + app/Controllers/AppController.php | 31 + app/Core/Email.php | 82 + app/Gateways/Stripe.php | 152 + app/Helpers/Auth.php | 119 + app/Helpers/DB.php | 366 + app/Helpers/Debug.php | 22 + app/Helpers/Format.php | 84 + app/Helpers/Functions.php | 287 + app/Helpers/GlobalConst.php | 16 + app/Helpers/Hash.php | 67 + app/Helpers/Logger.php | 72 + app/Helpers/Menu.php | 146 + app/Helpers/Numbers.php | 52 + app/Helpers/Pagination.php | 177 + app/Helpers/PluginManager.php | 353 + app/Helpers/Router.php | 31 + app/Helpers/Validate.php | 26 + app/Helpers/helpers.md | 200 + app/Helpers/helpers.php | 8 + app/LLM/Anthropic.php | 799 ++ app/LLM/GeminiService.php | 749 ++ app/LLM/LLMManager.php | 254 + app/LLM/LLMProvider.php | 191 + app/LLM/OpenAIsvc.php | 11 + app/LLM/svcAnthropic.php | 181 + app/LLM/svcDeepSeek.php | 177 + app/LLM/svcGemini.php | 172 + app/LLM/svcMistral.php | 169 + app/LLM/svcOpenAI.php | 175 + commands/GreetCommand.php | 55 + commands/SeedMenuCommand.php | 134 + commands/commands.md | 119 + composer.json | 49 + composer.lock | 1970 ++++ config.php | 41 + console | 17 + core/AltoRouter.php | 253 + core/Bootstrap.php | 248 + core/Controller.php | 29 + core/Database.php | 321 + core/ErrorHandler.php | 76 + core/Model.php | 14 + core/Session.php | 65 + core/View.php | 51 + core/core.md | 128 + index.php | 6 + manifest.json | 21 + public/assets/css/bootstrap.css | 10775 ++++++++++++++++++++ public/assets/imgs/WelcomeSeedProject.jpg | Bin 0 -> 88590 bytes public/assets/js/bootstrap.bundle.min.js | 7 + public/assets/js/custom.js | 0 public/assets/js/index.js | 177 + public/assets/js/jquery.min.js | 2 + public/assets/js/sw.js | 5 + public/controllers/controllers.md | 168 + public/controllers/docs.php | 29 + public/controllers/error.php | 24 + public/controllers/index.php | 28 + public/index.php | 11 + public/models/games_model.php | 17 + public/models/index_model.php | 11 + public/models/login_model.php | 74 + public/views/_error/index.php | 15 + public/views/aitest/index.php | 55 + public/views/aitest/translate.php | 40 + public/views/docs/index.php | 1 + public/views/index/index.php | 52 + public/views/partial/menu.php | 0 public/views/views.md | 170 + public/views/wrapper/site/footer.php | 19 + public/views/wrapper/site/header.php | 52 + sw.js | 19 + system/ErrorHandler.php | 170 + system/errors.json | 362 + 83 files changed, 24845 insertions(+) create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 .htaccess create mode 100644 .memory/changelog.md create mode 100644 .memory/documentation.md create mode 100644 .memory/llm.md create mode 100644 .reference_files/schema.sql create mode 100644 app/Components/Role.php create mode 100644 app/Components/Template.php create mode 100644 app/Controllers/AppController.php create mode 100644 app/Core/Email.php create mode 100644 app/Gateways/Stripe.php create mode 100644 app/Helpers/Auth.php create mode 100644 app/Helpers/DB.php create mode 100644 app/Helpers/Debug.php create mode 100644 app/Helpers/Format.php create mode 100644 app/Helpers/Functions.php create mode 100644 app/Helpers/GlobalConst.php create mode 100644 app/Helpers/Hash.php create mode 100644 app/Helpers/Logger.php create mode 100644 app/Helpers/Menu.php create mode 100644 app/Helpers/Numbers.php create mode 100644 app/Helpers/Pagination.php create mode 100644 app/Helpers/PluginManager.php create mode 100644 app/Helpers/Router.php create mode 100644 app/Helpers/Validate.php create mode 100644 app/Helpers/helpers.md create mode 100644 app/Helpers/helpers.php create mode 100644 app/LLM/Anthropic.php create mode 100644 app/LLM/GeminiService.php create mode 100644 app/LLM/LLMManager.php create mode 100644 app/LLM/LLMProvider.php create mode 100644 app/LLM/OpenAIsvc.php create mode 100644 app/LLM/svcAnthropic.php create mode 100644 app/LLM/svcDeepSeek.php create mode 100644 app/LLM/svcGemini.php create mode 100644 app/LLM/svcMistral.php create mode 100644 app/LLM/svcOpenAI.php create mode 100644 commands/GreetCommand.php create mode 100644 commands/SeedMenuCommand.php create mode 100644 commands/commands.md create mode 100644 composer.json create mode 100644 composer.lock create mode 100644 config.php create mode 100644 console create mode 100644 core/AltoRouter.php create mode 100644 core/Bootstrap.php create mode 100644 core/Controller.php create mode 100644 core/Database.php create mode 100644 core/ErrorHandler.php create mode 100644 core/Model.php create mode 100644 core/Session.php create mode 100644 core/View.php create mode 100644 core/core.md create mode 100644 index.php create mode 100644 manifest.json create mode 100644 public/assets/css/bootstrap.css create mode 100644 public/assets/imgs/WelcomeSeedProject.jpg create mode 100644 public/assets/js/bootstrap.bundle.min.js create mode 100644 public/assets/js/custom.js create mode 100644 public/assets/js/index.js create mode 100644 public/assets/js/jquery.min.js create mode 100644 public/assets/js/sw.js create mode 100644 public/controllers/controllers.md create mode 100644 public/controllers/docs.php create mode 100644 public/controllers/error.php create mode 100644 public/controllers/index.php create mode 100644 public/index.php create mode 100644 public/models/games_model.php create mode 100644 public/models/index_model.php create mode 100644 public/models/login_model.php create mode 100644 public/views/_error/index.php create mode 100644 public/views/aitest/index.php create mode 100644 public/views/aitest/translate.php create mode 100644 public/views/docs/index.php create mode 100644 public/views/index/index.php create mode 100644 public/views/partial/menu.php create mode 100644 public/views/views.md create mode 100644 public/views/wrapper/site/footer.php create mode 100644 public/views/wrapper/site/header.php create mode 100644 sw.js create mode 100644 system/ErrorHandler.php create mode 100644 system/errors.json diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..bacf2bd --- /dev/null +++ b/.env.example @@ -0,0 +1,44 @@ +# Application +URL= +PROJECT_NAME= +DEBUG= +SECUREAPI= +WELCOME= +INDEV= + +# Database — primary +DB_TYPE= +DB_HOST= +DB_USER= +DB_NAME= +DB_PASS= + +# Database — legalwriter +DB2_HOST= +DB2_NAME= +DB2_USER= +DB2_PASS= + +# Email (MailerSend) +EMAILHOST= +EMAILUSER= +EMAILPASSWORD= +FROMEMAIL= +MAILERSEND_API_KEY= + +# Stripe — managed via Admin > Integrations (sp_settings); these are emergency fallbacks only +STRIPE_SECRET= +STRIPE_PUBLIC= + +# Security / Hashing +HASH_PASSWORD_KEY= +HASH_API_KEY= +ENCRYPT_KEY= +CONSUMER_PROFILE_SALT= +AUTHORIZATION= + +# Google OAuth +GOOGLE_CLIENT_ID= +GOOGLE_CLIENT_SECRET= + +GIT_REPO= \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bec2a93 --- /dev/null +++ b/.gitignore @@ -0,0 +1,22 @@ +# Environment / secrets +.env +.env.* +!.env.example + +# Dependencies +/vendor/ + +# Logs & runtime +/system/*.log +*.log + +# ACME / server +/.well-known/acme-challenge/ +.user.ini + +# OS / editor +.DS_Store +Thumbs.db +*.swp +.idea/ +.vscode/ diff --git a/.htaccess b/.htaccess new file mode 100644 index 0000000..42212ca --- /dev/null +++ b/.htaccess @@ -0,0 +1,12 @@ +RewriteEngine On + +#RewriteCond %{HTTPS} !=on +#RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301,NE] +Header always set Content-Security-Policy "upgrade-insecure-requests;" + +RewriteCond %{REQUEST_FILENAME} !-d +RewriteCond %{REQUEST_FILENAME} !-f +RewriteCond %{REQUEST_FILENAME} !-l +RewriteRule ^(.+)$ index.php?url=$1 [QSA,L] + +# enable PHP error logging diff --git a/.memory/changelog.md b/.memory/changelog.md new file mode 100644 index 0000000..ee04f3c --- /dev/null +++ b/.memory/changelog.md @@ -0,0 +1,16 @@ +# SeedProject Changelog + +## 2026-04-27 — Cleanup and permissions + +- **`.claude/settings.local.json`** — added `COMPOSER_ALLOW_SUPERUSER=1 composer install` to allowed commands; reordered hooks block +- **`SeedProjectNaked.zip`** — removed stale zip artifact from repo + +## 2026-04-27 — Fix HTTP 500 on boot + +### PHP 8.3 Compatibility +- **`core/Database.php`** — renamed `query()` to `execQuery()` to resolve fatal signature mismatch with `PDO::query()` in PHP 8.3; updated internal calls in `retrieve()` and `pagination()` +- **`app/Components/Role.php`** — updated `deletePerm()` to call `execQuery()` instead of `query()` + +### Composer / Autoloader +- **`composer.json`** — created from scratch (was missing); added `autoload.classmap` for `core/`, `app/Helpers`, `app/Controllers`, `app/Components`, `app/Gateways`, `system/` so framework classes load via composer +- **`vendor/`** — ran `composer dump-autoload` to regenerate autoload files with proper classmap diff --git a/.memory/documentation.md b/.memory/documentation.md new file mode 100644 index 0000000..24fb858 --- /dev/null +++ b/.memory/documentation.md @@ -0,0 +1,2392 @@ +# Framework Infrastructure and Directory System + +This section provides a comprehensive overview of the framework's architecture, outlining the purpose of its main directories and the overall file structure. + +## Core Concepts + +The framework is organized into several key directories, each with a specific responsibility. This separation of concerns is crucial for maintaining a clean and scalable codebase. + +### `api/` - API Endpoints +_Handles all API requests in a controller-only MVC structure._ + +### `app/` - Application Core & Business Logic +_The heart of the application, replacing traditional models. Contains business logic, database interactions, and reusable components._ + +### `public/` - User Interface +_The document root for the application's UI, containing controllers, views, and static assets._ + +### `commands/` - CLI Tasks +_Houses command-line scripts for automated tasks, cron jobs, and internal server processing._ + +### `core/` - Framework Core +_Contains the foundational framework files that should rarely be modified._ + +## Directory Structure +``` +/www/wwwroot/(project_directory)/ +├── api/ +├── app/ +├── commands/ +├── core/ +├── plugins/ +├── public/ +├── templates/ +└── vendor/ +``` + +### Detailed `app/` Directory Structure +``` +app/ +├── Helpers/ +│ ├── DB.php # Database operations +│ ├── Session.php # Session management +│ ├── Validator.php # Input validation +│ └── Auth.php # Authentication helpers +├── Services/ +│ ├── EmailService.php +│ ├── PaymentService.php +│ └── NotificationService.php +├── Libraries/ +│ └── CustomAuth.php +└── Models/ # Optional: Domain models/entities + └── Client.php +``` + +**Example Helper:** +```php += $min; + } +} +``` + +**Example Service:** +```php + 1]); + +// Fetch single record +$client = Db::getRow("SELECT * FROM clients WHERE id = :id", [':id' => $id]); + +// Insert record +$clientId = Db::insert('clients', [ + 'name' => 'John Doe', + 'email' => 'john@example.com', + 'created_at' => date('Y-m-d H:i:s') +]); + +// Update record +$affected = Db::update('clients', + ['name' => 'Jane Doe', 'updated_at' => date('Y-m-d H:i:s')], + 'id = :id', + [':id' => $id] +); + +// Delete record +$affected = Db::delete('clients', 'id = :id', [':id' => $id]); + +// Execute custom query +Db::execute("UPDATE clients SET status = :status WHERE last_login < :date", [ + ':status' => 'inactive', + ':date' => date('Y-m-d', strtotime('-1 year')) +]); + +// Transactions +Db::beginTransaction(); +try { + Db::insert('orders', ['client_id' => $clientId, 'total' => 100]); + Db::update('clients', ['balance' => 'balance - 100'], 'id = :id', [':id' => $clientId]); + Db::commit(); +} catch (Exception $e) { + Db::rollback(); + throw $e; +} +``` + +### Common Controller Patterns +```php +// Render a view with data +$this->view->data['clients'] = $clients; +$this->view->render('clients/index', true, 'site'); + +// Render without wrapper (e.g., login page) +$this->view->render('auth/login', false); + +// Redirect +redirect('/clients'); + +// JSON response +header('Content-Type: application/json'); +echo json_encode(['success' => true, 'data' => $client]); + +// 404 response +http_response_code(404); +$this->view->render('errors/404', true, 'site'); +``` + +# Developer Workflows & Tutorials + +This section provides practical, step-by-step guides for common development tasks within the framework. + +## Request Lifecycle Overview + +Understanding the request lifecycle is key to knowing how the framework processes a request and generates a response. + +1. **Entry Point:** A user request first hits either `public/index.php` (for UI) or `api/index.php` (for API). +2. **Bootstrapping:** The `core/Bootstrap.php` file is initiated. It sets up the environment, loads the configuration from `config.php`, and initializes necessary services. +3. **Routing:** The incoming URL is processed by the routing engine. + * For simple URLs, **auto-routing** maps the URL directly to a `Controller/method` pair (e.g., `/clients/show` maps to `Clients::show()`). + * For complex URLs defined in the routing configuration, **AltoRouter** matches the pattern and executes the associated callback or controller. +4. **Controller Execution:** The matched controller method is called. +5. **Business Logic:** The controller interacts with the `app/` directory (e.g., calling helpers like `Db::select()`) to fetch data or perform business logic. +6. **Response Generation:** + * **UI (Public):** The controller calls `$this->view->render()`, passing data to a view file. The view is rendered within a wrapper (if specified), and the final HTML is sent to the browser. + * **API:** The controller typically returns data encoded as a JSON string and sets the appropriate `Content-Type` header. + +## Tutorial: Creating a New Page (UI) + +This tutorial walks through creating a "Clients" page that displays a list of clients. + +### Step 1: Create the Controller + +Create a new file at `public/controllers/Clients.php`. This controller will handle the logic for the clients page. +```php +view->data['clients'] = $clients; + $this->view->data['page_title'] = 'Our Clients'; + + // Render the view + $this->view->render('clients/index', true, 'site'); + } + + public function show($id) + { + // Fetch single client + $client = Db::getRow("SELECT * FROM clients WHERE id = :id", [':id' => $id]); + + if (!$client) { + http_response_code(404); + $this->view->render('errors/404', true, 'site'); + return; + } + + // Fetch related data + $orders = Db::select("SELECT * FROM orders WHERE client_id = :id ORDER BY created_at DESC", + [':id' => $id] + ); + + $this->view->data['client'] = $client; + $this->view->data['orders'] = $orders; + $this->view->data['page_title'] = $client['name']; + + $this->view->render('clients/show', true, 'site'); + } + + public function create() + { + // Show create form + $this->view->data['page_title'] = 'New Client'; + $this->view->render('clients/create', true, 'site'); + } + + public function store() + { + // Handle form submission + try { + // Validate input + if (!Validator::required($_POST['name'])) { + throw new Exception('Name is required'); + } + if (!Validator::email($_POST['email'])) { + throw new Exception('Valid email is required'); + } + + // Insert into database + $clientId = Db::insert('clients', [ + 'name' => $_POST['name'], + 'email' => $_POST['email'], + 'phone' => $_POST['phone'] ?? null, + 'created_at' => date('Y-m-d H:i:s') + ]); + + // Optional: Send welcome email + // EmailService::sendWelcomeEmail($_POST['email'], $_POST['name']); + + // Redirect to the new client's page + redirect('/clients/show/' . $clientId); + + } catch (Exception $e) { + // Handle errors + if (DEBUG) { + echo $e->getMessage(); + } else { + $this->view->data['error'] = 'Could not create client. Please try again.'; + $this->view->data['form_data'] = $_POST; + $this->view->render('clients/create', true, 'site'); + } + } + } + + public function edit($id) + { + $client = Db::getRow("SELECT * FROM clients WHERE id = :id", [':id' => $id]); + + if (!$client) { + http_response_code(404); + $this->view->render('errors/404', true, 'site'); + return; + } + + $this->view->data['client'] = $client; + $this->view->data['page_title'] = 'Edit ' . $client['name']; + $this->view->render('clients/edit', true, 'site'); + } + + public function update($id) + { + try { + // Validate + if (!Validator::required($_POST['name'])) { + throw new Exception('Name is required'); + } + + // Update + Db::update('clients', [ + 'name' => $_POST['name'], + 'email' => $_POST['email'], + 'phone' => $_POST['phone'] ?? null, + 'updated_at' => date('Y-m-d H:i:s') + ], 'id = :id', [':id' => $id]); + + redirect('/clients/show/' . $id); + + } catch (Exception $e) { + if (DEBUG) { + echo $e->getMessage(); + } else { + $this->view->data['error'] = 'Could not update client. Please try again.'; + $this->view->data['client'] = $_POST; + $this->view->data['client']['id'] = $id; + $this->view->render('clients/edit', true, 'site'); + } + } + } + + public function delete($id) + { + try { + Db::delete('clients', 'id = :id', [':id' => $id]); + redirect('/clients'); + } catch (Exception $e) { + if (DEBUG) { + echo $e->getMessage(); + } else { + redirect('/clients/show/' . $id . '?error=delete_failed'); + } + } + } +} +``` + +### Step 2: Create the Views + +Create view files at `public/views/clients/`: + +**Index View (`public/views/clients/index.php`):** +```php +
+

data['page_title']; ?>

+ + Add New Client + + data['clients'])): ?> +

No clients found.

+ + + + + + + + + + + + data['clients'] as $client): ?> + + + + + + + + +
NameEmailPhoneActions
+ View + Edit +
+ +
+``` + +**Show View (`public/views/clients/show.php`):** +```php +
+

data['client']['name']); ?>

+ +
+

Email: data['client']['email']); ?>

+

Phone: data['client']['phone'] ?? '-'); ?>

+
+ + + +

Orders

+ data['orders'])): ?> +

No orders yet.

+ + + + + + + + + + + data['orders'] as $order): ?> + + + + + + + +
Order IDTotalDate
#$
+ +
+``` + +**Create/Edit View (`public/views/clients/create.php`):** +```php +
+

data['page_title']; ?>

+ + data['error'])): ?> +
+ data['error']); ?> +
+ + +
+
+ + +
+ +
+ + +
+ +
+ + +
+ + + Cancel +
+
+``` + +### Step 3: Create Page-Specific Assets + +Create the corresponding CSS and JavaScript files for this page. + +* `public/assets/css/clients.css` +* `public/assets/js/clients.js` + +These will be loaded by the site's wrapper. + +### Step 4: Access Your New Page + +You can now access your new page by navigating to: +- `http://yoursite.com/clients` - List all clients +- `http://yoursite.com/clients/show/1` - View client #1 +- `http://yoursite.com/clients/create` - Create new client form +- `http://yoursite.com/clients/edit/1` - Edit client #1 + +The auto-routing will automatically map these URLs to the corresponding methods in your `Clients` controller. + +## Tutorial: Creating a New API Endpoint + +This tutorial shows how to create API endpoints for product management. + +### Step 1: Create the API Controller + +Create a new file at `api/controllers/Products.php`. +```php + true, + 'data' => $products, + 'count' => count($products) + ]); + + } catch (Exception $e) { + http_response_code(500); + echo json_encode([ + 'success' => false, + 'error' => DEBUG ? $e->getMessage() : 'Internal server error' + ]); + } + } + + /** + * GET /api/products/get/{id} + * Get a single product by ID + */ + public function get($id) + { + header('Content-Type: application/json'); + + try { + $product = Db::getRow("SELECT * FROM products WHERE id = :id", [':id' => $id]); + + if ($product) { + echo json_encode([ + 'success' => true, + 'data' => $product + ]); + } else { + http_response_code(404); + echo json_encode([ + 'success' => false, + 'error' => 'Product not found' + ]); + } + + } catch (Exception $e) { + http_response_code(500); + echo json_encode([ + 'success' => false, + 'error' => DEBUG ? $e->getMessage() : 'Internal server error' + ]); + } + } + + /** + * POST /api/products/create + * Create a new product + */ + public function create() + { + header('Content-Type: application/json'); + + try { + // Get JSON input + $input = json_decode(file_get_contents('php://input'), true); + + // Validate + if (!isset($input['name']) || !isset($input['price'])) { + http_response_code(400); + echo json_encode([ + 'success' => false, + 'error' => 'Name and price are required' + ]); + return; + } + + // Insert + $productId = Db::insert('products', [ + 'name' => $input['name'], + 'description' => $input['description'] ?? null, + 'price' => $input['price'], + 'category' => $input['category'] ?? null, + 'created_at' => date('Y-m-d H:i:s') + ]); + + http_response_code(201); + echo json_encode([ + 'success' => true, + 'data' => ['id' => $productId] + ]); + + } catch (Exception $e) { + http_response_code(500); + echo json_encode([ + 'success' => false, + 'error' => DEBUG ? $e->getMessage() : 'Internal server error' + ]); + } + } + + /** + * PUT /api/products/update/{id} + * Update an existing product + */ + public function update($id) + { + header('Content-Type: application/json'); + + try { + // Check if product exists + $product = Db::getRow("SELECT * FROM products WHERE id = :id", [':id' => $id]); + + if (!$product) { + http_response_code(404); + echo json_encode([ + 'success' => false, + 'error' => 'Product not found' + ]); + return; + } + + // Get JSON input + $input = json_decode(file_get_contents('php://input'), true); + + // Build update data + $updateData = []; + if (isset($input['name'])) $updateData['name'] = $input['name']; + if (isset($input['description'])) $updateData['description'] = $input['description']; + if (isset($input['price'])) $updateData['price'] = $input['price']; + if (isset($input['category'])) $updateData['category'] = $input['category']; + $updateData['updated_at'] = date('Y-m-d H:i:s'); + + // Update + Db::update('products', $updateData, 'id = :id', [':id' => $id]); + + echo json_encode([ + 'success' => true, + 'data' => ['id' => $id] + ]); + + } catch (Exception $e) { + http_response_code(500); + echo json_encode([ + 'success' => false, + 'error' => DEBUG ? $e->getMessage() : 'Internal server error' + ]); + } + } + + /** + * DELETE /api/products/delete/{id} + * Delete a product + */ + public function delete($id) + { + header('Content-Type: application/json'); + + try { + // Check if product exists + $product = Db::getRow("SELECT * FROM products WHERE id = :id", [':id' => $id]); + + if (!$product) { + http_response_code(404); + echo json_encode([ + 'success' => false, + 'error' => 'Product not found' + ]); + return; + } + + // Delete + Db::delete('products', 'id = :id', [':id' => $id]); + + echo json_encode([ + 'success' => true, + 'data' => ['id' => $id] + ]); + + } catch (Exception $e) { + http_response_code(500); + echo json_encode([ + 'success' => false, + 'error' => DEBUG ? $e->getMessage() : 'Internal server error' + ]); + } + } +} +``` + +### Step 2: Access the API Endpoints + +You can now make requests to these endpoints: + +**Example using cURL:** +```sh +# List all products +curl http://yoursite.com/api/products/list + +# List products with filtering +curl "http://yoursite.com/api/products/list?category=electronics&limit=10" + +# Get product with ID 123 +curl http://yoursite.com/api/products/get/123 + +# Create a new product +curl -X POST http://yoursite.com/api/products/create \ + -H "Content-Type: application/json" \ + -d '{"name":"New Product","price":29.99,"category":"electronics"}' + +# Update product +curl -X PUT http://yoursite.com/api/products/update/123 \ + -H "Content-Type: application/json" \ + -d '{"price":24.99}' + +# Delete product +curl -X DELETE http://yoursite.com/api/products/delete/123 +``` + +If `SECUREAPI` is enabled in `config.php`, you must include the authorization token in the header: +```sh +# Get the authorization key from the AUTHORIZATION define in config.php +API_KEY="your_secret_api_key" + +# Example with authorization +curl -H "Authorization: $API_KEY" http://yoursite.com/api/products/get/123 +``` + +**Example using JavaScript (Fetch API):** +```javascript +// GET request +fetch('/api/products/list?category=electronics') + .then(response => response.json()) + .then(data => console.log(data)); + +// POST request +fetch('/api/products/create', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + // Include if SECUREAPI is enabled + // 'Authorization': 'your_secret_api_key' + }, + body: JSON.stringify({ + name: 'New Product', + price: 29.99, + category: 'electronics' + }) +}) + .then(response => response.json()) + .then(data => console.log(data)); +``` + +## Tutorial: Creating and Running a CLI Command + +This tutorial shows how to create command-line tasks for various purposes. + +### Step 1: Create the Command File + +**Simple Command (`commands/SyncDataCommand.php`):** +```php + $record['id']] + ); + + if ($exists && !$forceSync) { + echo "Skipping existing record: {$record['id']}\n"; + continue; + } + + if ($exists) { + // Update existing + Db::update('synced_data', [ + 'data' => json_encode($record), + 'synced_at' => date('Y-m-d H:i:s') + ], 'external_id = :id', [':id' => $record['id']]); + echo "Updated record: {$record['id']}\n"; + } else { + // Insert new + Db::insert('synced_data', [ + 'external_id' => $record['id'], + 'data' => json_encode($record), + 'synced_at' => date('Y-m-d H:i:s') + ]); + echo "Inserted record: {$record['id']}\n"; + } + } + + echo "\nData sync completed successfully.\n"; + +} catch (Exception $e) { + echo "ERROR: " . $e->getMessage() . "\n"; + exit(1); +} +``` + +**Email Queue Processor (`commands/ProcessEmailQueueCommand.php`):** +```php + (int)$limit] + ); + + echo "Found " . count($emails) . " pending emails\n"; + + $sent = 0; + $failed = 0; + + foreach ($emails as $email) { + try { + // Send email using your EmailService + EmailService::send( + $email['to'], + $email['subject'], + $email['body'] + ); + + // Update status + Db::update('email_queue', [ + 'status' => 'sent', + 'sent_at' => date('Y-m-d H:i:s') + ], 'id = :id', [':id' => $email['id']]); + + $sent++; + echo "."; + + } catch (Exception $e) { + // Mark as failed + Db::update('email_queue', [ + 'status' => 'failed', + 'error_message' => $e->getMessage(), + 'failed_at' => date('Y-m-d H:i:s') + ], 'id = :id', [':id' => $email['id']]); + + $failed++; + echo "F"; + } + } + + echo "\n\nResults:\n"; + echo "Sent: $sent\n"; + echo "Failed: $failed\n"; + +} catch (Exception $e) { + echo "ERROR: " . $e->getMessage() . "\n"; + exit(1); +} +``` + +**Database Cleanup Command (`commands/CleanupDatabaseCommand.php`):** +```php + $cutoffDate] + ); + echo "Logs to delete: " . $oldLogs[0]['count'] . "\n"; + + if (!$dryRun) { + $deleted = Db::delete('logs', 'created_at < :date', [':date' => $cutoffDate]); + echo "Deleted: $deleted logs\n"; + } + + // Clean soft-deleted records + $oldDeleted = Db::select("SELECT COUNT(*) as count FROM clients WHERE deleted_at IS NOT NULL AND deleted_at < :date", + [':date' => $cutoffDate] + ); + echo "\nSoft-deleted clients to remove: " . $oldDeleted[0]['count'] . "\n"; + + if (!$dryRun) { + $deleted = Db::delete('clients', 'deleted_at IS NOT NULL AND deleted_at < :date', + [':date' => $cutoffDate] + ); + echo "Deleted: $deleted clients\n"; + } + + echo "\nCleanup completed.\n"; + +} catch (Exception $e) { + echo "ERROR: " . $e->getMessage() . "\n"; + exit(1); +} +``` + +### Step 2: Run the Commands + +Execute commands from the root directory of your project using the `console` entry point. +```sh +# Run data sync +php console SyncDataCommand + +# Run with options +php console SyncDataCommand --date="2024-10-28" --force + +# Process email queue +php console ProcessEmailQueueCommand --limit=50 + +# Database cleanup (dry run first) +php console CleanupDatabaseCommand --days=60 --dry-run + +# Then run for real +php console CleanupDatabaseCommand --days=60 +``` + +### Step 3: Set Up Cron Jobs + +Add these to your crontab for automated execution: +```sh +# Edit crontab +crontab -e + +# Add these lines: +# Process email queue every 5 minutes +*/5 * * * * cd /www/wwwroot/(project_directory) && php console ProcessEmailQueueCommand >> /var/log/email-queue.log 2>&1 + +# Sync data every hour +0 * * * * cd /www/wwwroot/(project_directory) && php console SyncDataCommand >> /var/log/data-sync.log 2>&1 + +# Cleanup database every night at 2 AM +0 2 * * * cd /www/wwwroot/(project_directory) && php console CleanupDatabaseCommand --days=30 >> /var/log/cleanup.log 2>&1 +``` + +## Error Handling Patterns + +### Controller Error Handling +```php +validateClientData($_POST); + + // Insert client + $clientId = Db::insert('clients', [ + 'name' => $_POST['name'], + 'email' => $_POST['email'] + ]); + + // Insert related data + Db::insert('client_metadata', [ + 'client_id' => $clientId, + 'source' => $_POST['source'] ?? 'website' + ]); + + // Commit transaction + Db::commit(); + + // Send notification email + try { + EmailService::sendWelcomeEmail($_POST['email'], $_POST['name']); + } catch (Exception $e) { + // Log email failure but don't fail the whole operation + error_log("Failed to send welcome email: " . $e->getMessage()); + } + + redirect('/clients/show/' . $clientId); + + } catch (ValidationException $e) { + // Validation errors - show to user + Db::rollback(); + $this->view->data['error'] = $e->getMessage(); + $this->view->data['form_data'] = $_POST; + $this->view->render('clients/create', true, 'site'); + + } catch (Exception $e) { + // Other errors + Db::rollback(); + + if (DEBUG) { + // Show full error in development + echo "
";
+                echo "Error: " . $e->getMessage() . "\n";
+                echo "File: " . $e->getFile() . "\n";
+                echo "Line: " . $e->getLine() . "\n";
+                echo "\nStack Trace:\n" . $e->getTraceAsString();
+                echo "
"; + } else { + // Generic error in production + $this->view->data['error'] = 'An error occurred. Please try again.'; + $this->view->data['form_data'] = $_POST; + $this->view->render('clients/create', true, 'site'); + + // Log the error + error_log("Client creation failed: " . $e->getMessage()); + } + } + } + + private function validateClientData($data) + { + if (!Validator::required($data['name'] ?? '')) { + throw new ValidationException('Name is required'); + } + + if (!Validator::email($data['email'] ?? '')) { + throw new ValidationException('Valid email is required'); + } + + // Check for duplicate email + $existing = Db::getRow("SELECT id FROM clients WHERE email = :email", + [':email' => $data['email']] + ); + + if ($existing) { + throw new ValidationException('A client with this email already exists'); + } + } +} + +// Custom exception for validation errors +class ValidationException extends Exception {} +``` + +### API Error Handling +```php +validateProduct($input); + + $productId = Db::insert('products', [ + 'name' => $input['name'], + 'price' => $input['price'], + 'created_at' => date('Y-m-d H:i:s') + ]); + + http_response_code(201); + echo json_encode([ + 'success' => true, + 'data' => ['id' => $productId] + ]); + + } catch (ApiException $e) { + http_response_code($e->getCode()); + echo json_encode([ + 'success' => false, + 'error' => $e->getMessage() + ]); + + } catch (Exception $e) { + http_response_code(500); + echo json_encode([ + 'success' => false, + 'error' => DEBUG ? $e->getMessage() : 'Internal server error' + ]); + + error_log("API Error: " . $e->getMessage()); + } + } + + private function validateProduct($data) + { + if (!isset($data['name']) || empty(trim($data['name']))) { + throw new ApiException('Product name is required', 400); + } + + if (!isset($data['price']) || !is_numeric($data['price'])) { + throw new ApiException('Valid price is required', 400); + } + + if ($data['price'] < 0) { + throw new ApiException('Price cannot be negative', 400); + } + } +} + +// Custom exception for API errors +class ApiException extends Exception { + public function __construct($message, $code = 400) { + parent::__construct($message, $code); + } +} +``` + +### Command Error Handling +```php + $order['id'], + 'error' => $e->getMessage() + ]; + echo "E"; + } + } + + echo "\n\n"; + echo "Processed: $processed\n"; + echo "Errors: " . count($errors) . "\n"; + + if (!empty($errors)) { + echo "\nFailed Orders:\n"; + foreach ($errors as $error) { + echo "Order #{$error['order_id']}: {$error['error']}\n"; + } + exit(1); // Exit with error code + } + +} catch (Exception $e) { + echo "\nFATAL ERROR: " . $e->getMessage() . "\n"; + exit(1); +} + +function processOrder($order) { + // Order processing logic + // Throw exceptions on failures +} +``` + +# Common Configurations & Features + +## `config.php` - Framework Configuration + +The `config.php` file is the central hub for all framework-wide configurations. It handles security-related keys, API keys, debugging settings, and various integration parameters. + +### Security Configurations + +It is **highly recommended** to change all security-related values for each new project. + +* **`define('DEBUG', false);`**: Set to `true` to enable error reporting for development. **Always set to `false` in production.** +* **`define('SECUREAPI', false);`**: Set to `true` to require an authorization key for API access. +* **`define('AUTHORIZATION', 'your_secret_api_key');`**: The secret key required when `SECUREAPI` is true. **Always change this to a strong, random value.** + +**Example: Generating a secure API key** +```php +// Generate a random API key +define('AUTHORIZATION', bin2hex(random_bytes(32))); +``` + +### Database Configuration +```php +// Database settings +define('DB_HOST', 'localhost'); +define('DB_NAME', 'your_database'); +define('DB_USER', 'your_username'); +define('DB_PASS', 'your_password'); +define('DB_CHARSET', 'utf8mb4'); +``` + +### API Keys and Integrations + +All third-party API keys (Stripe, PHPMailer, etc.) are managed in this file, making them globally accessible via their defined constants. +```php +// Stripe +define('STRIPE_SECRET_KEY', 'sk_test_...'); +define('STRIPE_PUBLIC_KEY', 'pk_test_...'); + +// Email (PHPMailer) +define('SMTP_HOST', 'smtp.gmail.com'); +define('SMTP_PORT', 587); +define('SMTP_USER', 'your-email@gmail.com'); +define('SMTP_PASS', 'your-app-password'); +define('SMTP_FROM', 'noreply@yoursite.com'); +define('SMTP_FROM_NAME', 'Your Site Name'); + +// AWS S3 +define('AWS_KEY', 'your-aws-key'); +define('AWS_SECRET', 'your-aws-secret'); +define('AWS_REGION', 'us-east-1'); +define('AWS_BUCKET', 'your-bucket-name'); + +// Other services +define('RECAPTCHA_SITE_KEY', 'your-site-key'); +define('RECAPTCHA_SECRET_KEY', 'your-secret-key'); +``` + +### Path Constants +```php +// Define base paths +define('ROOT_PATH', __DIR__); +define('APP_PATH', ROOT_PATH . '/app'); +define('PUBLIC_PATH', ROOT_PATH . '/public'); +define('TEMPLATES_PATH', ROOT_PATH . '/templates'); +define('UPLOADS_PATH', PUBLIC_PATH . '/uploads'); +``` + +### Environment-Specific Configuration +```php +// Determine environment +define('ENVIRONMENT', getenv('APP_ENV') ?: 'production'); + +// Environment-specific settings +if (ENVIRONMENT === 'development') { + define('DEBUG', true); + define('BASE_URL', 'http://localhost:8000'); +} else { + define('DEBUG', false); + define('BASE_URL', 'https://yoursite.com'); +} +``` + +## .htaccess and SSL + +The root `.htaccess` file includes a rule for automatic redirection to HTTPS. It is recommended to temporarily disable this rule when setting up a new SSL certificate (e.g., with Let's Encrypt) to avoid issues with the verification process. +```apache +# Force HTTPS +# Comment out these lines when setting up SSL certificate +RewriteCond %{HTTPS} off +RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301] + +# Standard routing +RewriteEngine On +RewriteCond %{REQUEST_FILENAME} !-f +RewriteCond %{REQUEST_FILENAME} !-d +RewriteRule ^(.*)$ index.php [QSA,L] +``` + +## Public Assets Structure + +The `public/assets` directory is organized to promote performance and streamline debugging by using page-specific CSS and JavaScript files. +``` +public/assets/ +├── css/ +│ ├── global.css # Site-wide styles +│ ├── clients.css # Page-specific: clients +│ ├── products.css # Page-specific: products +│ └── dashboard.css # Page-specific: dashboard +├── js/ +│ ├── global.js # Site-wide scripts +│ ├── clients.js # Page-specific: clients +│ ├── products.js # Page-specific: products +│ └── dashboard.js # Page-specific: dashboard +└── images/ + ├── logo.png + └── icons/ +``` + +**Wrapper automatically loads page-specific assets:** +```php + + +getControllerName()); +if (file_exists(PUBLIC_PATH . "/assets/css/{$controller}.css")) { + echo ""; +} +?> +``` + +## View System + +### Wrappers (Header & Footer) + +A "wrapper" is the main site template (`header.php` and `footer.php`). You can control which wrapper to use or disable it entirely, which is useful for pages like a login screen. + +**Wrapper Location:** +``` +templates/wrappers/ +├── site/ # Main site wrapper +│ ├── header.php +│ └── footer.php +├── admin/ # Admin panel wrapper +│ ├── header.php +│ └── footer.php +└── minimal/ # Minimal wrapper (login, etc.) + ├── header.php + └── footer.php +``` + +**Usage Examples:** +```php +// Use default 'site' wrapper +$this->view->render('clients/index', true, 'site'); + +// Use admin wrapper +$this->view->render('admin/dashboard', true, 'admin'); + +// Use minimal wrapper (login page) +$this->view->render('auth/login', true, 'minimal'); + +// No wrapper at all +$this->view->render('auth/login', false); +``` + +**Example Wrapper (`templates/wrappers/site/header.php`):** +```php + + + + + + <?php echo $this->data['page_title'] ?? 'My Site'; ?> + + + + + + getControllerName()); + if (file_exists(PUBLIC_PATH . "/assets/css/{$controller}.css")) { + echo ""; + } + ?> + + + + +
+``` + +**Example Footer (`templates/wrappers/site/footer.php`):** +```php +
+ +
+

© My Site

+
+ + + + + + getControllerName()); + if (file_exists(PUBLIC_PATH . "/assets/js/{$controller}.js")) { + echo ""; + } + ?> + + +``` + +### Partial Views + +Partial views allow you to load reusable HTML snippets (like sidebars or breadcrumbs) into other views, similar to `require_once()`. + +**Partial Location:** +``` +public/views/ +├── _partials/ +│ ├── breadcrumb.php +│ ├── sidebar.php +│ ├── pagination.php +│ └── client_card.php +``` + +**Usage in Views:** +```php + +
+ partial('_partials/breadcrumb', [ + 'Home' => '/', + 'Clients' => '/clients', + $this->data['client']['name'] => '' + ]); ?> + +
+
+

data['client']['name']); ?>

+ +
+ +
+ partial('_partials/sidebar'); ?> +
+
+
+``` + +**Example Partial (`public/views/_partials/breadcrumb.php`):** +```php + +``` + +## Routing + +The framework uses auto-routing by convention (`/controller/method`) for most cases. For more complex needs, it integrates `AltoRouter`. + +### Auto-Routing Examples +``` +URL: /clients +Maps to: Clients::index() + +URL: /clients/show/123 +Maps to: Clients::show(123) + +URL: /products/edit/456 +Maps to: Products::edit(456) +``` + +### AltoRouter for Complex Routes + +Define custom routes in `public/routes.php` or `api/routes.php`: +```php +map('GET', '/users/[i:id]', 'Users#show', 'user_show'); + +// Multiple parameters +$router->map('GET', '/blog/[i:year]/[i:month]/[*:slug]', 'Blog#show', 'blog_post'); + +// POST route +$router->map('POST', '/api/users/create', 'UsersAPI#create', 'api_user_create'); + +// Optional parameter +$router->map('GET', '/products/category/[a:category]?', 'Products#category', 'products_category'); +``` + +**Match Types:** +* **`[i:id]`**: Match an integer as parameter 'id' +* **`[a:action]`**: Match alphanumeric characters (A-Z, a-z, 0-9, -) +* **`[h:key]`**: Match hexadecimal characters +* **`[*:trailing]`**: Catch-all for the rest of the URL path +* **`[**:path]`**: Match everything including slashes + +**Using Named Routes:** +```php +// Generate URL for a named route +echo $router->generate('user_show', ['id' => 5]); +// Outputs: /users/5 + +echo $router->generate('blog_post', [ + 'year' => 2024, + 'month' => 10, + 'slug' => 'my-first-post' +]); +// Outputs: /blog/2024/10/my-first-post +``` + +**In Controllers:** +```php + $id]); + + $this->view->data['user'] = $user; + $this->view->render('users/show', true, 'site'); + } +} +``` + +## `templates/` - Reusable HTML Documents + +This directory holds reusable HTML documents, such as transactional emails (`/templates/emails/`). These templates use placeholders like `%FNAME%` that are replaced with dynamic data by PHP before being sent. + +**Template Structure:** +``` +templates/ +├── emails/ +│ ├── welcome.html +│ ├── password_reset.html +│ ├── invoice.html +│ └── notification.html +└── wrappers/ + ├── site/ + ├── admin/ + └── minimal/ +``` + +**Example Email Template (`templates/emails/welcome.html`):** +```html + + + + + + +
+
+

Welcome to %SITE_NAME%

+
+
+

Hi %FNAME%,

+

Thank you for signing up! We're excited to have you on board.

+

Your account email is: %EMAIL%

+

+ Verify Your Email +

+

Thanks,
The %SITE_NAME% Team

+
+
+ + +``` + +**Using Templates in Code:** +```php + $firstName, + '%EMAIL%' => $email, + '%SITE_NAME%' => 'My Awesome Site', + '%VERIFY_LINK%' => BASE_URL . '/verify?token=' . self::generateToken($email) + ]; + + $body = str_replace( + array_keys($replacements), + array_values($replacements), + $template + ); + + // Send email (using PHPMailer or other) + return self::send($email, 'Welcome to My Awesome Site', $body); + } + + private static function send($to, $subject, $body) + { + // PHPMailer implementation + $mail = new PHPMailer(true); + + try { + $mail->isSMTP(); + $mail->Host = SMTP_HOST; + $mail->SMTPAuth = true; + $mail->Username = SMTP_USER; + $mail->Password = SMTP_PASS; + $mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS; + $mail->Port = SMTP_PORT; + + $mail->setFrom(SMTP_FROM, SMTP_FROM_NAME); + $mail->addAddress($to); + + $mail->isHTML(true); + $mail->Subject = $subject; + $mail->Body = $body; + + $mail->send(); + return true; + + } catch (Exception $e) { + error_log("Email send failed: " . $mail->ErrorInfo); + return false; + } + } +} +``` + +## `plugins/` - Extendable Functionality (In Development) + +This directory is for modular, self-contained add-ons (like a CRM or weather widget) that can extend the framework's functionality, similar to WordPress plugins. + +**Planned Structure:** +``` +plugins/ +├── crm/ +│ ├── plugin.php # Plugin initialization +│ ├── controllers/ +│ ├── views/ +│ ├── assets/ +│ └── README.md +├── weather-widget/ +│ ├── plugin.php +│ ├── WeatherWidget.php +│ └── assets/ +└── analytics/ + ├── plugin.php + ├── services/ + └── config.php +``` + +# Core Classes Reference + +## Controller (`core/Controller.php`) + +Base controller class that all public controllers extend. + +**Properties:** +- `$this->view` - View renderer instance + +**Methods:** +- `redirect($url)` - Perform HTTP redirect +- `getControllerName()` - Get current controller name +- `getMethodName()` - Get current method name + +**Example:** +```php +data in the view + $this->view->data['clients'] = Db::select("SELECT * FROM clients"); + $this->view->render('clients/index', true, 'site'); + } + + protected function requireAuth() { + if (!Auth::isLoggedIn()) { + $this->redirect('/login'); + } + } +} +``` + +## View (`core/View.php`) + +Handles view rendering and data passing. + +**Properties:** +- `$this->data` - Array of variables to pass to view + +**Methods:** +- `render($viewPath, $useWrapper = true, $wrapperName = 'site')` - Render a view +- `partial($partialPath, $data = [])` - Load a partial view +- `getControllerName()` - Get current controller name (for asset loading) + +**Example:** +```php +// In controller +$this->view->data['title'] = 'My Page'; +$this->view->data['users'] = $users; +$this->view->render('users/index', true, 'site'); + +// In view +

data['title']; ?>

+data['users'] as $user): ?> +

+ + +// Using partials +partial('_partials/breadcrumb', [ + 'breadcrumb' => ['Home' => '/', 'Users' => ''] +]); ?> +``` + +## Db (`app/Helpers/DB.php`) + +Static database helper class for all database operations. + +**Connection Methods:** +- `Db::getConnection()` - Get PDO connection instance +- `Db::beginTransaction()` - Start transaction +- `Db::commit()` - Commit transaction +- `Db::rollback()` - Rollback transaction + +**Query Methods:** +- `Db::select($query, $params = [])` - Execute SELECT and return all rows +- `Db::getRow($query, $params = [])` - Execute SELECT and return first row +- `Db::execute($query, $params = [])` - Execute any query +- `Db::insert($table, $data)` - Insert row and return last insert ID +- `Db::update($table, $data, $where, $whereParams = [])` - Update rows and return affected count +- `Db::delete($table, $where, $whereParams = [])` - Delete rows and return affected count + +**Examples:** +```php +// Select multiple rows +$clients = Db::select("SELECT * FROM clients WHERE active = :active", [':active' => 1]); + +// Select single row +$client = Db::getRow("SELECT * FROM clients WHERE id = :id", [':id' => $id]); + +// Insert +$clientId = Db::insert('clients', [ + 'name' => 'John Doe', + 'email' => 'john@example.com', + 'created_at' => date('Y-m-d H:i:s') +]); + +// Update +$affected = Db::update('clients', + ['name' => 'Jane Doe', 'updated_at' => date('Y-m-d H:i:s')], + 'id = :id', + [':id' => $id] +); + +// Delete +$affected = Db::delete('clients', 'id = :id', [':id' => $id]); + +// Custom query +Db::execute("UPDATE clients SET last_login = NOW() WHERE id = :id", [':id' => $id]); + +// Transactions +Db::beginTransaction(); +try { + Db::insert('orders', ['client_id' => 1, 'total' => 100]); + Db::update('clients', ['balance' => 'balance - 100'], 'id = 1'); + Db::commit(); +} catch (Exception $e) { + Db::rollback(); + throw $e; +} +``` + +## Helper Classes + +### Validator (`app/Helpers/Validator.php`) + +Input validation helper. + +**Methods:** +```php +Validator::required($value) // Check if not empty +Validator::email($value) // Validate email format +Validator::minLength($value, $min) // Check minimum length +Validator::maxLength($value, $max) // Check maximum length +Validator::numeric($value) // Check if numeric +Validator::alpha($value) // Check if alphabetic +Validator::alphanumeric($value) // Check if alphanumeric +Validator::url($value) // Validate URL format +Validator::date($value) // Validate date format +``` + +### Session (`app/Helpers/Session.php`) + +Session management helper. + +**Methods:** +```php +Session::start() // Start session +Session::set($key, $value) // Set session variable +Session::get($key, $default = null) // Get session variable +Session::has($key) // Check if key exists +Session::delete($key) // Delete session variable +Session::destroy() // Destroy session +Session::flash($key, $value) // Set flash message +Session::getFlash($key) // Get and delete flash message +``` + +### Auth (`app/Helpers/Auth.php`) + +Authentication helper. + +**Methods:** +```php +Auth::login($userId) // Log in user +Auth::logout() // Log out user +Auth::isLoggedIn() // Check if user is logged in +Auth::getUserId() // Get current user ID +Auth::getUser() // Get current user data +Auth::check($permission) // Check user permission +``` + +# Database Usage + +The `Db` helper class (`app/Helpers/DB.php`) provides a static interface for all database interactions. All queries use prepared statements for security. + +## Basic Queries + +### SELECT - Multiple Rows +```php +// Simple select +$clients = Db::select("SELECT * FROM clients"); + +// With WHERE clause +$activeClients = Db::select( + "SELECT * FROM clients WHERE active = :active", + [':active' => 1] +); + +// With multiple conditions +$clients = Db::select( + "SELECT * FROM clients WHERE status = :status AND created_at > :date ORDER BY name", + [':status' => 'active', ':date' => '2024-01-01'] +); + +// With JOIN +$orders = Db::select( + "SELECT o.*, c.name as client_name + FROM orders o + JOIN clients c ON o.client_id = c.id + WHERE o.status = :status", + [':status' => 'pending'] +); +``` + +### SELECT - Single Row +```php +// Get single row +$client = Db::getRow("SELECT * FROM clients WHERE id = :id", [':id' => $id]); + +// Check if exists +if ($client) { + echo $client['name']; +} else { + echo "Client not found"; +} + +// Get specific columns +$email = Db::getRow("SELECT email FROM clients WHERE id = :id", [':id' => $id]); +``` + +## INSERT Operations +```php +// Simple insert +$clientId = Db::insert('clients', [ + 'name' => 'John Doe', + 'email' => 'john@example.com', + 'phone' => '555-1234' +]); + +// Insert with timestamp +$clientId = Db::insert('clients', [ + 'name' => 'Jane Doe', + 'email' => 'jane@example.com', + 'created_at' => date('Y-m-d H:i:s'), + 'updated_at' => date('Y-m-d H:i:s') +]); + +// Insert and use the ID +$orderId = Db::insert('orders', [ + 'client_id' => $clientId, + 'total' => 99.99, + 'status' => 'pending' +]); +``` + +## UPDATE Operations +```php +// Update single record +$affected = Db::update('clients', + ['name' => 'John Smith', 'updated_at' => date('Y-m-d H:i:s')], + 'id = :id', + [':id' => $id] +); + +// Update multiple records +$affected = Db::update('orders', + ['status' => 'shipped', 'shipped_at' => date('Y-m-d H:i:s')], + 'status = :old_status AND created_at < :date', + [':old_status' => 'pending', ':date' => '2024-01-01'] +); + +// Increment value +Db::execute( + "UPDATE products SET view_count = view_count + 1 WHERE id = :id", + [':id' => $productId] +); +``` + +## DELETE Operations +```php +// Delete single record +$affected = Db::delete('clients', 'id = :id', [':id' => $id]); + +// Delete multiple records +$affected = Db::delete('logs', 'created_at < :date', [':date' => '2024-01-01']); + +// Soft delete (recommended) +Db::update('clients', + ['deleted_at' => date('Y-m-d H:i:s')], + 'id = :id', + [':id' => $id] +); +``` + +## Transactions + +Use transactions for operations that must all succeed or all fail together. +```php +// Simple transaction +Db::beginTransaction(); +try { + // Create order + $orderId = Db::insert('orders', [ + 'client_id' => $clientId, + 'total' => 100.00, + 'status' => 'pending' + ]); + + // Deduct from client balance + Db::execute( + "UPDATE clients SET balance = balance - :amount WHERE id = :id", + [':amount' => 100.00, ':id' => $clientId] + ); + + // Insert order items + foreach ($items as $item) { + Db::insert('order_items', [ + 'order_id' => $orderId, + 'product_id' => $item['product_id'], + 'quantity' => $item['quantity'], + 'price' => $item['price'] + ]); + } + + Db::commit(); + echo "Order created successfully"; + +} catch (Exception $e) { + Db::rollback(); + echo "Order failed: " . $e->getMessage(); +} +``` + +## Complex Queries + +### Aggregation +```php +// COUNT +$count = Db::getRow("SELECT COUNT(*) as total FROM clients WHERE active = 1"); +echo $count['total']; + +// SUM, AVG, etc. +$stats = Db::getRow(" + SELECT + COUNT(*) as order_count, + SUM(total) as revenue, + AVG(total) as avg_order + FROM orders + WHERE status = 'completed' +"); +``` + +### Subqueries +```php +$clients = Db::select(" + SELECT c.*, + (SELECT COUNT(*) FROM orders WHERE client_id = c.id) as order_count, + (SELECT SUM(total) FROM orders WHERE client_id = c.id) as total_spent + FROM clients c + WHERE c.active = 1 +"); +``` + +### GROUP BY +```php +$monthlySales = Db::select(" + SELECT + DATE_FORMAT(created_at, '%Y-%m') as month, + COUNT(*) as order_count, + SUM(total) as revenue + FROM orders + WHERE status = 'completed' + GROUP BY DATE_FORMAT(created_at, '%Y-%m') + ORDER BY month DESC +"); +``` + +### CASE Statements +```php +$clients = Db::select(" + SELECT + name, + CASE + WHEN total_spent > 10000 THEN 'VIP' + WHEN total_spent > 5000 THEN 'Premium' + ELSE 'Standard' + END as tier + FROM clients +"); +``` + +## Raw Queries with execute() + +For queries that don't fit insert/update/delete patterns: +```php +// Custom UPDATE with calculations +Db::execute(" + UPDATE products + SET discount_price = price * 0.9 + WHERE category = :category", + [':category' => 'electronics'] +); + +// Batch operations +Db::execute(" + INSERT INTO archive_clients + SELECT * FROM clients + WHERE deleted_at < :date", + [':date' => date('Y-m-d', strtotime('-1 year'))] +); +``` + +## Performance Tips + +### Use LIMIT for Large Datasets +```php +// Paginated results +$page = $_GET['page'] ?? 1; +$perPage = 20; +$offset = ($page - 1) * $perPage; + +$clients = Db::select( + "SELECT * FROM clients LIMIT :limit OFFSET :offset", + [':limit' => $perPage, ':offset' => $offset] +); +``` + +### Index Your Queries +```sql +-- Add indexes for frequently queried columns +CREATE INDEX idx_clients_email ON clients(email); +CREATE INDEX idx_orders_status ON orders(status); +CREATE INDEX idx_orders_client_id ON orders(client_id); +``` + +### Use EXISTS Instead of COUNT +```php +// Slow +$exists = Db::getRow("SELECT COUNT(*) as c FROM clients WHERE email = :email", [':email' => $email]); +if ($exists['c'] > 0) { } + +// Fast +$exists = Db::getRow("SELECT 1 FROM clients WHERE email = :email LIMIT 1", [':email' => $email]); +if ($exists) { } +``` + +# Common Real-World Scenarios + +## File Upload Handling +```php + $maxSize) { + throw new Exception('File too large (max 5MB)'); + } + + $allowedTypes = ['application/pdf', 'image/jpeg', 'image/png']; + if (!in_array($file['type'], $allowedTypes)) { + throw new Exception('Invalid file type'); + } + + // Generate unique filename + $ext = pathinfo($file['name'], PATHINFO_EXTENSION); + $filename = uniqid() . '_' . time() . '.' . $ext; + $uploadPath = UPLOADS_PATH . '/documents/' . $filename; + + // Move file + if (!move_uploaded_file($file['tmp_name'], $uploadPath)) { + throw new Exception('Failed to save file'); + } + + // Save to database + $docId = Db::insert('documents', [ + 'user_id' => Auth::getUserId(), + 'filename' => $filename, + 'original_name' => $file['name'], + 'file_size' => $file['size'], + 'mime_type' => $file['type'], + 'uploaded_at' => date('Y-m-d H:i:s') + ]); + + redirect('/documents/show/' . $docId); + + } catch (Exception $e) { + $this->view->data['error'] = $e->getMessage(); + $this->view->render('documents/upload', true, 'site'); + } + } +} +``` + +## Pagination +```php + $perPage, ':offset' => $offset] + ); + + $this->view->data['products'] = $products; + $this->view->data['current_page'] = $page; + $this->view->data['total_pages'] = $totalPages; + $this->view->render('products/index', true, 'site'); + } +} +``` + +## Search with Filters +```php += :date_from"; + $params[':date_from'] = $_GET['date_from']; + } + if (!empty($_GET['date_to'])) { + $query .= " AND created_at <= :date_to"; + $params[':date_to'] = $_GET['date_to'] . ' 23:59:59'; + } + + $query .= " ORDER BY created_at DESC"; + + $clients = Db::select($query, $params); + + $this->view->data['clients'] = $clients; + $this->view->data['filters'] = $_GET; + $this->view->render('clients/search', true, 'site'); + } +} +``` + +## Authentication System +```php + $email]); + + if (!$user || !password_verify($password, $user['password'])) { + throw new Exception('Invalid credentials'); + } + + Session::set('user_id', $user['id']); + Session::set('user_name', $user['name']); + Session::set('user_role', $user['role']); + + // Update last login + Db::update('users', + ['last_login' => date('Y-m-d H:i:s')], + 'id = :id', + [':id' => $user['id']] + ); + + return true; + } + + public static function logout() + { + Session::destroy(); + } + + public static function isLoggedIn() + { + return Session::has('user_id'); + } + + public static function getUserId() + { + return Session::get('user_id'); + } + + public static function getUser() + { + if (!self::isLoggedIn()) { + return null; + } + + return Db::getRow("SELECT * FROM users WHERE id = :id", [':id' => self::getUserId()]); + } + + public static function requireRole($role) + { + if (Session::get('user_role') !== $role) { + throw new Exception('Unauthorized'); + } + } +} + +// Login controller +class Login extends Controller +{ + public function index() + { + if (Auth::isLoggedIn()) { + redirect('/dashboard'); + } + + $this->view->render('auth/login', false); + } + + public function submit() + { + try { + Auth::login($_POST['email'], $_POST['password']); + redirect('/dashboard'); + } catch (Exception $e) { + $this->view->data['error'] = $e->getMessage(); + $this->view->render('auth/login', false); + } + } + + public function logout() + { + Auth::logout(); + redirect('/login'); + } +} +``` +## Modal Integration System +Working with Modals; I have a dynamic modal intergration system. +data-url is the link to the modal +Modal Views are located under public/views/modals/(modal_name.php) + +## Modal Structure +``` +/www/wwwroot/(project_directory)/ +├── public/ + ├── views/ + └── modals/ +``` + +```html + +``` + +### Building the Modal +```html + + + + + + +``` diff --git a/.memory/llm.md b/.memory/llm.md new file mode 100644 index 0000000..a37d31b --- /dev/null +++ b/.memory/llm.md @@ -0,0 +1,313 @@ +# LLM Integration Documentation + +## Architecture + +``` +app/LLM/ +├── LLMProvider.php — Abstract base class (contract for all providers) +├── LLMManager.php — Factory: loads org config from DB, returns provider instance +├── svcOpenAI.php — OpenAI (fully implemented) +├── svcAnthropic.php — Anthropic/Claude (fully implemented) +├── svcGemini.php — Google Gemini (fully implemented) +├── svcMistral.php — Mistral AI (fully implemented) +└── svcDeepSeek.php — DeepSeek (fully implemented) +``` + +## Database Storage — sp_orgs_meta + +LLM configs are stored per-org in `sp_orgs_meta`: + +| column | description | +|------------|------------------------------------| +| orgid | Foreign key to sp_orgs | +| keyval | Provider key (e.g. `llmOpenAI`) | +| metval | JSON config blob | +| active | 1 = enabled, 0 = disabled | + +### keyval names +| Provider | keyval | +|------------|----------------| +| OpenAI | `llmOpenAI` | +| Anthropic | `llmAnthropic` | +| Gemini | `llmGemini` | +| Mistral | `llmMistral` | +| DeepSeek | `llmDeepSeek` | + +### metval JSON structure (all providers) +```json +{ + "secretKey": "sk-...", + "model": "gpt-4o", + "max_tokens": 4096, + "temperature": 0.7 +} +``` + +## LLMManager — Factory Class + +```php +use App\LLM\LLMManager; + +// Get a configured, ready-to-use provider instance +$llm = LLMManager::forOrg($orgId, 'openai'); + +// List all configured providers for an org +$providers = LLMManager::getAvailableProviders($orgId); +// returns: ['openai', 'gemini'] + +// Save or update a provider config +LLMManager::saveOrgConfig($orgId, 'openai', [ + 'secretKey' => 'sk-...', + 'model' => 'gpt-4o', + 'max_tokens' => 4096, + 'temperature' => 0.7, +]); +``` + +## LLMProvider — Base Class Interface + +All providers expose the same methods: + +```php +// Send a prompt, get full response +$response = $llm->sendPrompt(array $messages, array $options = []); +// Returns: ['success' => bool, 'content' => string, 'usage' => array, 'error' => string] + +// Stream a prompt, callback per chunk +$result = $llm->streamPrompt(array $messages, array $options, callable $callback); +// Returns: ['success' => bool, 'error' => string] + +// Get available models +$models = $llm->getModels(); +// Returns: [['id' => 'gpt-4o', 'label' => 'GPT-4o'], ...] + +// Test API key + connectivity +$status = $llm->testConnection(); +// Returns: ['success' => bool, 'latency_ms' => int, 'error' => string] +``` + +### Fluent setters (chainable) +```php +$llm->setModel('gpt-4o') + ->setMaxTokens(2048) + ->setTemperature(0.5) + ->setSystemPrompt('You are a helpful assistant.') + ->setTimeout(60); +``` + +## Usage Examples (PHP) + +### Basic chat +```php +$llm = LLMManager::forOrg($orgId, 'openai'); +$response = $llm->sendPrompt([ + ['role' => 'user', 'content' => 'Summarize this contract: ...'] +]); + +if ($response['success']) { + echo $response['content']; + // $response['usage'] = ['prompt_tokens' => 120, 'completion_tokens' => 80, 'total_tokens' => 200] +} +``` + +### With system prompt and custom options +```php +$llm = LLMManager::forOrg($orgId, 'anthropic'); +$llm->setSystemPrompt('You are a legal document assistant.'); + +$response = $llm->sendPrompt( + [['role' => 'user', 'content' => 'Draft an NDA for two parties.']], + ['model' => 'claude-sonnet-4-6', 'max_tokens' => 2048, 'temperature' => 0.3] +); +``` + +### Streaming (PHP — CLI or long-running process) +```php +$llm = LLMManager::forOrg($orgId, 'gemini'); +$llm->streamPrompt( + [['role' => 'user', 'content' => 'Write a report on...']], + [], + function(string $chunk) { + echo $chunk; + flush(); + } +); +``` + +### Multi-turn conversation +```php +$messages = [ + ['role' => 'user', 'content' => 'My name is Carlos.'], + ['role' => 'assistant', 'content' => 'Nice to meet you, Carlos!'], + ['role' => 'user', 'content' => 'What is my name?'], +]; + +$response = $llm->sendPrompt($messages); +``` + +## API Endpoints — appi/controllers/llm.php + +All endpoints are authenticated via `Auth::API()`. +Internal AJAX requests (X-Requested-With: XMLHttpRequest) pass through automatically. +External requests require `?apikey=` param. + +### POST /appi/llm/chat +Full response. +```json +// Request +{ + "provider": "openai", + "org_id": 100, + "messages": [{"role": "user", "content": "Hello"}], + "options": {"model": "gpt-4o", "max_tokens": 1024, "system_prompt": "..."} +} + +// Response +{ + "success": true, + "provider": "openai", + "content": "Hello! How can I help you?", + "usage": {"prompt_tokens": 10, "completion_tokens": 8, "total_tokens": 18} +} +``` + +### POST /appi/llm/stream +SSE streaming. Each chunk sent as: +``` +event: chunk +data: {"chunk":"partial text here"} + +event: done +data: "[DONE]" + +event: error +data: {"error":"something went wrong"} +``` + +### GET /appi/llm/models?provider=openai&org_id=100 +```json +{ + "success": true, + "provider": "openai", + "models": [{"id": "gpt-4o", "label": "GPT-4o"}, ...] +} +``` + +### GET /appi/llm/providers?org_id=100 +```json +{ + "success": true, + "org_id": 100, + "providers": ["openai", "gemini"] +} +``` + +### POST /appi/llm/test +```json +// Request +{"provider": "openai", "org_id": 100} + +// Response +{"success": true, "provider": "openai", "latency_ms": 342, "error": ""} +``` + +## Frontend JS Client — public/assets/js/llm-client.js + +Load in any page that needs LLM functionality: +```php +$this->JavaScript[] = '/public/assets/js/llm-client.js'; +$this->view->JavaScript = $this->JavaScript; +``` + +### LLMClient.chat() +```js +const res = await LLMClient.chat({ + provider: 'openai', + orgId: 100, + messages: [{ role: 'user', content: 'Hello' }], + options: { model: 'gpt-4o', system_prompt: 'You are helpful.' } +}); +console.log(res.content); +``` + +### LLMClient.stream() +```js +const output = document.getElementById('output'); + +await LLMClient.stream({ + provider: 'anthropic', + orgId: 100, + messages: [{ role: 'user', content: 'Write a report on...' }], + options: { model: 'claude-sonnet-4-6' }, + onChunk: (chunk) => { output.innerHTML += chunk; }, + onDone: () => { console.log('Stream complete'); }, + onError: (err) => { console.error('Error:', err); } +}); +``` + +### LLMClient.getModels() +```js +const { models } = await LLMClient.getModels('openai', 100); +// models = [{ id: 'gpt-4o', label: 'GPT-4o' }, ...] +``` + +### LLMClient.getProviders() +```js +const { providers } = await LLMClient.getProviders(100); +// providers = ['openai', 'gemini'] +``` + +### LLMClient.test() +```js +const { success, latency_ms, error } = await LLMClient.test('openai', 100); +``` + +## Provider API Differences (internals) + +| Provider | Auth Header | System Prompt | Role names | Quirks | +|------------|-------------------------|-----------------------|-------------------------|---------------------------------| +| OpenAI | `Authorization: Bearer` | `role: system` msg | user / assistant | No temperature on o1/o3 models | +| Anthropic | `x-api-key` | Top-level `system` key| user / assistant | Requires `anthropic-version` header | +| Gemini | `?key=` query param | `systemInstruction` | user / model | Messages use `parts: [{text}]` | +| Mistral | `Authorization: Bearer` | `role: system` msg | user / assistant | None | +| DeepSeek | `Authorization: Bearer` | `role: system` msg | user / assistant | No temperature on R1 (reasoner) | + +## Available Models + +### OpenAI +- `gpt-4o` — GPT-4o (default) +- `gpt-4o-mini` — GPT-4o Mini +- `gpt-4-turbo` — GPT-4 Turbo +- `o1` — o1 +- `o1-mini` — o1 Mini +- `o3-mini` — o3 Mini + +### Anthropic +- `claude-sonnet-4-6` — Claude Sonnet 4.6 (default) +- `claude-opus-4-6` — Claude Opus 4.6 +- `claude-haiku-4-5-20251001` — Claude Haiku 4.5 + +### Gemini +- `gemini-2.5-flash` — Gemini 2.5 Flash (default) +- `gemini-2.5-flash-lite` — Gemini 2.5 Flash Lite +- `gemini-2.0-flash` — Gemini 2.0 Flash +- `gemini-1.5-pro` — Gemini 1.5 Pro + +### Mistral +- `mistral-large-latest` — Mistral Large (default) +- `mistral-small-latest` — Mistral Small +- `codestral-latest` — Codestral +- `open-mistral-nemo` — Mistral Nemo + +### DeepSeek +- `deepseek-chat` — DeepSeek Chat V3 (default) +- `deepseek-reasoner` — DeepSeek Reasoner R1 + +## Adding a New Provider + +1. Create `app/LLM/svcNewProvider.php` extending `LLMProvider` +2. Implement: `__construct(array $config)`, `sendPrompt()`, `streamPrompt()`, `getModels()`, `testConnection()` +3. Register in `LLMManager.php`: + - Add to `$providers` array: `'newprovider' => svcNewProvider::class` + - Add to `$metaKeys` array: `'newprovider' => 'llmNewProvider'` +4. Add JSON config row to `sp_orgs_meta` with `keyval = 'llmNewProvider'` diff --git a/.reference_files/schema.sql b/.reference_files/schema.sql new file mode 100644 index 0000000..2bd9ec4 --- /dev/null +++ b/.reference_files/schema.sql @@ -0,0 +1,808 @@ +-- cpenginev2 schema dump +-- Generated: 2026-03-29 21:00:53 + +SET FOREIGN_KEY_CHECKS=0; + +-- ---------------------------- +-- Table: cpe_agent_messages +-- ---------------------------- +CREATE TABLE `cpe_agent_messages` ( + `msgid` int(11) NOT NULL AUTO_INCREMENT, + `sessionid` int(11) NOT NULL, + `agentid` int(11) NOT NULL DEFAULT 0, + `role` enum('user','assistant') NOT NULL, + `content` longtext DEFAULT NULL, + `latency_ms` int(11) DEFAULT NULL, + `input_tokens` int(11) DEFAULT NULL, + `output_tokens` int(11) DEFAULT NULL, + `cost_usd` decimal(10,6) DEFAULT NULL, + `createdate` datetime NOT NULL DEFAULT current_timestamp(), + PRIMARY KEY (`msgid`), + KEY `idx_sessionid` (`sessionid`) +) ENGINE=InnoDB AUTO_INCREMENT=27 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- ---------------------------- +-- Table: cpe_agent_sessions +-- ---------------------------- +CREATE TABLE `cpe_agent_sessions` ( + `sessionid` int(11) NOT NULL AUTO_INCREMENT, + `agentid` int(11) NOT NULL DEFAULT 0, + `orgid` int(11) NOT NULL DEFAULT 0, + `crid` int(11) DEFAULT NULL COMMENT 'cpe_creditpulls.crid — NULL = demo/mock mode', + `provider` varchar(50) DEFAULT NULL COMMENT 'anthropic, openai, etc.', + `model` varchar(100) DEFAULT NULL, + `message_count` int(11) NOT NULL DEFAULT 0, + `createdate` datetime NOT NULL DEFAULT current_timestamp(), + `updatedate` datetime NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(), + PRIMARY KEY (`sessionid`), + KEY `idx_agentid` (`agentid`), + KEY `idx_crid` (`crid`) +) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; + +-- ---------------------------- +-- Table: cpe_aiassistant +-- ---------------------------- +CREATE TABLE `cpe_aiassistant` ( + `aid` int(11) NOT NULL AUTO_INCREMENT, + `agentid` int(11) DEFAULT 0, + `orgid` int(11) DEFAULT 0, + `crid` int(11) DEFAULT 0, + `promptid` int(11) DEFAULT 0, + `response` mediumtext DEFAULT NULL, + `jsonrequest` mediumtext DEFAULT NULL, + `createdate` datetime DEFAULT current_timestamp(), + PRIMARY KEY (`aid`) +) ENGINE=InnoDB AUTO_INCREMENT=15 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; + +-- ---------------------------- +-- Table: cpe_creditpulls +-- ---------------------------- +CREATE TABLE `cpe_creditpulls` ( + `crid` int(11) NOT NULL AUTO_INCREMENT, + `orgid` int(1) DEFAULT 0, + `agentid` int(1) DEFAULT 0, + `priceid` varchar(255) DEFAULT '0', + `invid` int(5) DEFAULT 0, + `profid` varchar(255) DEFAULT '0', + `custname` varchar(255) DEFAULT '0', + `custdob` varchar(255) DEFAULT '0', + `last4ss` varchar(255) DEFAULT '0', + `success` int(5) DEFAULT 0, + `apicall` int(1) NOT NULL DEFAULT 0, + `cpejson` text DEFAULT NULL, + `createdate` datetime NOT NULL DEFAULT current_timestamp(), + `status` int(5) DEFAULT 0, + `active` int(1) DEFAULT 1, + PRIMARY KEY (`crid`), + KEY `idx_org_history_id_orgid` (`crid`,`orgid`), + KEY `idx_org_history_id_orgid_custID` (`crid`,`orgid`) +) ENGINE=MyISAM AUTO_INCREMENT=1140 DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci; + +-- ---------------------------- +-- Table: cpe_creditpulls_temp +-- ---------------------------- +CREATE TABLE `cpe_creditpulls_temp` ( + `cpeid` int(11) NOT NULL AUTO_INCREMENT, + `orgid` int(11) DEFAULT 0, + `evni` varchar(255) DEFAULT '0', + `rawjson` mediumtext DEFAULT NULL, + `directory` varchar(255) DEFAULT '0', + `jsonfilename` varchar(255) DEFAULT '0', + `createdate` datetime DEFAULT current_timestamp(), + `active` int(11) DEFAULT 1, + PRIMARY KEY (`cpeid`) +) ENGINE=InnoDB AUTO_INCREMENT=120 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; + +-- ---------------------------- +-- Table: cpe_org_pricing +-- ---------------------------- +CREATE TABLE `cpe_org_pricing` ( + `priceid` int(11) NOT NULL AUTO_INCREMENT, + `orgid` int(11) NOT NULL, + `package_name` varchar(100) DEFAULT 'Credit Report Package', + `retail_price` decimal(6,2) NOT NULL COMMENT 'Total price client pays', + `our_price` decimal(6,2) NOT NULL COMMENT 'What all services cost us combined', + `includes_fico` tinyint(1) DEFAULT 0, + `includes_vantage` tinyint(1) DEFAULT 0, + `includes_idscan` tinyint(1) DEFAULT 0, + `includes_directory` tinyint(1) DEFAULT 0, + `active` tinyint(1) DEFAULT 1, + `createdate` datetime DEFAULT current_timestamp(), + PRIMARY KEY (`priceid`), + UNIQUE KEY `orgid` (`orgid`) +) ENGINE=MyISAM AUTO_INCREMENT=109 DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci; + +-- ---------------------------- +-- Table: cpe_orgs_documents +-- ---------------------------- +CREATE TABLE `cpe_orgs_documents` ( + `docid` int(11) NOT NULL AUTO_INCREMENT, + `orgid` int(11) DEFAULT 0, + `userid` int(11) DEFAULT 0, + `document` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_uca1400_ai_ci DEFAULT '0', + `type` varchar(255) DEFAULT '0', + `location` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_uca1400_ai_ci DEFAULT '0', + `status` int(11) DEFAULT 0, + `createdate` datetime DEFAULT current_timestamp(), + PRIMARY KEY (`docid`) +) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin; + +-- ---------------------------- +-- Table: cpe_products +-- ---------------------------- +CREATE TABLE `cpe_products` ( + `prodid` int(11) NOT NULL AUTO_INCREMENT, + `provider` varchar(20) DEFAULT '0', + `product_code` varchar(20) NOT NULL DEFAULT '0' COMMENT 'CR, FICO, VANTAGE, IDSCAN, DIRECTORY', + `product_name` varchar(100) NOT NULL DEFAULT '0', + `our_cost` decimal(6,2) NOT NULL DEFAULT 0.00 COMMENT 'What we pay Equifax/TransUnion', + `is_base` tinyint(1) DEFAULT 0 COMMENT '1 for Credit Report base, 0 for add-ons', + `active` tinyint(1) DEFAULT 1, + `createdate` datetime DEFAULT current_timestamp(), + PRIMARY KEY (`prodid`), + UNIQUE KEY `product_code` (`product_code`) +) ENGINE=MyISAM AUTO_INCREMENT=6 DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci; + +-- ---------------------------- +-- Table: cpe_tempreports +-- ---------------------------- +CREATE TABLE `cpe_tempreports` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `agentid` int(11) DEFAULT NULL, + `orgid` int(11) DEFAULT NULL, + `template` varchar(255) DEFAULT '0', + `ceatedate` datetime DEFAULT current_timestamp(), + `active` int(1) DEFAULT 1, + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci; + +-- ---------------------------- +-- Table: sp_activitylog +-- ---------------------------- +CREATE TABLE `sp_activitylog` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `userid` int(11) DEFAULT NULL, + `status` varchar(255) DEFAULT NULL, + `trigval` varchar(255) DEFAULT NULL, + `controller` varchar(64) DEFAULT NULL, + `action` varchar(64) DEFAULT NULL, + `method` varchar(8) DEFAULT NULL, + `useragent` varchar(512) DEFAULT NULL, + `device_type` varchar(16) DEFAULT NULL, + `browser` varchar(64) DEFAULT NULL, + `os` varchar(64) DEFAULT NULL, + `msgval` text DEFAULT NULL, + `device` varchar(255) DEFAULT NULL, + `host` varchar(255) DEFAULT NULL, + `location` varchar(255) DEFAULT NULL, + `ipaddress` varchar(255) DEFAULT '000.000.000', + `public` int(11) DEFAULT NULL, + `createdate` datetime DEFAULT current_timestamp(), + PRIMARY KEY (`id`), + KEY `idx_activitylog_userid_trigval_date` (`userid`,`trigval`,`createdate` DESC) +) ENGINE=InnoDB AUTO_INCREMENT=420 DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci; + +-- ---------------------------- +-- Table: sp_api_requests +-- ---------------------------- +CREATE TABLE `sp_api_requests` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `apikeyid` varchar(11) DEFAULT '0', + `userid` int(11) DEFAULT 0, + `requesting_ip` varchar(255) DEFAULT '0', + `request` varchar(255) DEFAULT '0', + `service` varchar(255) DEFAULT '0', + `apitype` varchar(255) DEFAULT '0' COMMENT 'tracker, or api', + `sitepage` varchar(255) DEFAULT '0', + `useragent` varchar(255) DEFAULT '0', + `sessionid` varchar(255) DEFAULT '0', + `sessiontime` varchar(255) DEFAULT '0', + `visitduration` varchar(255) DEFAULT '0', + `devicetype` varchar(255) DEFAULT '0', + `domain` varchar(255) DEFAULT '0', + `paramsvar` varchar(255) DEFAULT '0', + `referral` varchar(255) DEFAULT '0', + `language` varchar(255) DEFAULT '0', + `ismobile` varchar(255) DEFAULT '0', + `hascookies` varchar(255) DEFAULT '0', + `scrolldepth` varchar(255) DEFAULT '0', + `createdate` datetime DEFAULT current_timestamp(), + PRIMARY KEY (`id`), + KEY `idx_userid_createdate` (`userid`,`createdate`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci; + +-- ---------------------------- +-- Table: sp_api_usage +-- ---------------------------- +CREATE TABLE `sp_api_usage` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `userid` int(11) NOT NULL, + `requests` int(11) NOT NULL DEFAULT 0, + `report_date` date NOT NULL, + `report_type` enum('daily','monthly','yearly') NOT NULL, + `created_at` datetime DEFAULT current_timestamp(), + `updated_at` datetime DEFAULT current_timestamp() ON UPDATE current_timestamp(), + PRIMARY KEY (`id`), + UNIQUE KEY `idx_user_date_type` (`userid`,`report_date`,`report_type`), + KEY `idx_report_date` (`report_date`), + KEY `idx_report_type` (`report_type`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + +-- ---------------------------- +-- Table: sp_broker_commissions +-- ---------------------------- +CREATE TABLE `sp_broker_commissions` ( + `commission_id` int(11) NOT NULL AUTO_INCREMENT, + `userid` int(11) DEFAULT 0, + `orgid` int(11) DEFAULT 0, + `crid` int(11) DEFAULT 0, + `pull_date` datetime DEFAULT '0000-00-00 00:00:00', + `retail_price` decimal(10,2) NOT NULL DEFAULT 0.00, + `commission_type` varchar(255) DEFAULT '0', + `commission_rate` decimal(10,4) NOT NULL DEFAULT 0.0000, + `flat_fee_amount` decimal(10,2) NOT NULL DEFAULT 0.00, + `commission_amount` decimal(10,2) NOT NULL DEFAULT 0.00, + `payout_eligible_date` datetime DEFAULT '0000-00-00 00:00:00', + `payout_id` int(11) DEFAULT 0, + `paid_date` datetime DEFAULT '0000-00-00 00:00:00', + `status` int(11) DEFAULT 0, + `notes` varchar(255) DEFAULT '0', + `createdate` datetime DEFAULT current_timestamp(), + PRIMARY KEY (`commission_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci; + +-- ---------------------------- +-- Table: sp_broker_orgs +-- ---------------------------- +CREATE TABLE `sp_broker_orgs` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `userid` int(11) DEFAULT 0, + `orgid` int(11) DEFAULT 0, + `commission_override_type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_uca1400_ai_ci DEFAULT '0', + `commission_override_rate` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_uca1400_ai_ci DEFAULT '0', + `flat_fee_override` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_uca1400_ai_ci DEFAULT '0', + `start_date` datetime DEFAULT '0000-00-00 00:00:00', + `end_date` datetime DEFAULT '0000-00-00 00:00:00', + `assigned_by` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_uca1400_ai_ci DEFAULT '0', + `notes` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_uca1400_ai_ci DEFAULT '0', + `createdate` datetime DEFAULT current_timestamp(), + `active` int(11) DEFAULT 0, + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin; + +-- ---------------------------- +-- Table: sp_broker_payouts +-- ---------------------------- +CREATE TABLE `sp_broker_payouts` ( + `payout_id` int(11) NOT NULL AUTO_INCREMENT, + `userid` int(11) DEFAULT NULL, + `payout_period_start` datetime DEFAULT '0000-00-00 00:00:00', + `payout_period_end` datetime DEFAULT '0000-00-00 00:00:00', + `total_commissions` decimal(10,2) NOT NULL DEFAULT 0.00, + `total_amount` decimal(10,2) NOT NULL DEFAULT 0.00, + `payment_method` varchar(255) DEFAULT NULL, + `payment_reference` varchar(255) DEFAULT NULL, + `payment_date` datetime DEFAULT '0000-00-00 00:00:00', + `status` int(11) DEFAULT NULL, + `processed_by` varchar(255) DEFAULT NULL, + `notes` varchar(255) DEFAULT NULL, + `createdate` datetime DEFAULT current_timestamp(), + `updatedate` datetime DEFAULT '0000-00-00 00:00:00' ON UPDATE current_timestamp(), + PRIMARY KEY (`payout_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci; + +-- ---------------------------- +-- Table: sp_countries +-- ---------------------------- +CREATE TABLE `sp_countries` ( + `country_id` int(11) NOT NULL AUTO_INCREMENT, + `code` char(2) NOT NULL COMMENT 'Two-letter country code (ISO 3166-1 alpha-2)', + `name` varchar(64) NOT NULL COMMENT 'English country name', + `full_name` varchar(128) NOT NULL COMMENT 'Full English country name', + `iso3` char(3) NOT NULL COMMENT 'Three-letter country code (ISO 3166-1 alpha-3)', + `number` smallint(3) unsigned zerofill NOT NULL COMMENT 'Three-digit country number (ISO 3166-1 numeric)', + `continent_code` char(2) NOT NULL, + `display_order` int(11) NOT NULL DEFAULT 900, + PRIMARY KEY (`country_id`), + UNIQUE KEY `idx_code` (`code`) USING BTREE, + KEY `idx_continent_code` (`continent_code`) USING BTREE, + CONSTRAINT `sp_countries_ibfk_1` FOREIGN KEY (`continent_code`) REFERENCES `snpi_continents` (`code`) ON UPDATE CASCADE +) ENGINE=InnoDB AUTO_INCREMENT=247 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_uca1400_ai_ci ROW_FORMAT=COMPACT; + +-- ---------------------------- +-- Table: sp_cron_tasks +-- ---------------------------- +CREATE TABLE `sp_cron_tasks` ( + `cronid` int(11) NOT NULL AUTO_INCREMENT, + `keyval` varchar(255) DEFAULT '0', + `varval` varchar(255) DEFAULT '0', + `task` varchar(255) DEFAULT '0', + `response` text DEFAULT NULL, + `completed` datetime DEFAULT '0000-00-00 00:00:00' ON UPDATE current_timestamp(), + `created` datetime DEFAULT current_timestamp(), + `active` int(11) DEFAULT 1, + PRIMARY KEY (`cronid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_uca1400_ai_ci; + +-- ---------------------------- +-- Table: sp_email_templates +-- ---------------------------- +CREATE TABLE `sp_email_templates` ( + `emailtemplateid` int(11) NOT NULL AUTO_INCREMENT, + `type` mediumtext NOT NULL, + `slug` varchar(100) NOT NULL, + `language` varchar(40) DEFAULT NULL, + `name` mediumtext NOT NULL, + `subject` mediumtext NOT NULL, + `message` mediumtext NOT NULL, + `fromname` mediumtext NOT NULL, + `fromemail` varchar(100) DEFAULT NULL, + `plaintext` int(11) NOT NULL DEFAULT 0, + `active` tinyint(4) NOT NULL DEFAULT 0, + `order` int(11) NOT NULL, + PRIMARY KEY (`emailtemplateid`) +) ENGINE=InnoDB AUTO_INCREMENT=82 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_uca1400_ai_ci; + +-- ---------------------------- +-- Table: sp_master +-- ---------------------------- +CREATE TABLE `sp_master` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(255) DEFAULT '''0''', + `jvalue` text DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci; + +-- ---------------------------- +-- Table: sp_menu_items +-- ---------------------------- +CREATE TABLE `sp_menu_items` ( + `item_id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `menu_id` int(10) unsigned NOT NULL, + `parent_id` int(10) unsigned DEFAULT NULL, + `label` varchar(100) NOT NULL, + `icon` varchar(100) DEFAULT NULL, + `type` enum('link','header','divider') DEFAULT 'link', + `url` varchar(255) DEFAULT NULL, + `perm_id` int(10) unsigned DEFAULT NULL, + `role_id` int(10) unsigned DEFAULT NULL, + `match_prefix` tinyint(4) DEFAULT 0, + `sort_order` int(11) DEFAULT 0, + `active` tinyint(4) DEFAULT 1, + PRIMARY KEY (`item_id`), + KEY `menu_id` (`menu_id`), + KEY `parent_id` (`parent_id`), + KEY `perm_id` (`perm_id`), + KEY `fk_menu_item_role` (`role_id`), + CONSTRAINT `fk_menu_item_role` FOREIGN KEY (`role_id`) REFERENCES `sp_roles` (`role_id`) ON DELETE SET NULL, + CONSTRAINT `sp_menu_items_ibfk_1` FOREIGN KEY (`perm_id`) REFERENCES `sp_permissions` (`perm_id`) ON DELETE SET NULL +) ENGINE=InnoDB AUTO_INCREMENT=54 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + +-- ---------------------------- +-- Table: sp_menus +-- ---------------------------- +CREATE TABLE `sp_menus` ( + `menu_id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `slug` varchar(50) NOT NULL, + `name` varchar(100) NOT NULL, + `active` tinyint(4) DEFAULT 1, + PRIMARY KEY (`menu_id`), + UNIQUE KEY `slug` (`slug`) +) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + +-- ---------------------------- +-- Table: sp_orgs +-- ---------------------------- +CREATE TABLE `sp_orgs` ( + `orgid` int(11) NOT NULL AUTO_INCREMENT, + `catid` int(11) DEFAULT 1, + `name` varchar(255) DEFAULT '0', + `address` varchar(255) DEFAULT '0', + `address2` varchar(255) DEFAULT '0', + `city` varchar(255) DEFAULT '0', + `state` varchar(255) DEFAULT '0', + `zip` int(11) DEFAULT 0, + `country` varchar(255) DEFAULT NULL, + `countrycode` varchar(11) DEFAULT NULL, + `phone` varchar(255) DEFAULT '0', + `email` varchar(255) DEFAULT '0', + `logo` varchar(255) DEFAULT '0', + `website` varchar(255) DEFAULT '0', + `json_social_media` text DEFAULT NULL, + `summary` text DEFAULT NULL, + `description` text DEFAULT NULL, + `updated_date` datetime DEFAULT '0000-00-00 00:00:00', + `taxid` varchar(255) DEFAULT '0', + `defaultorg` int(11) DEFAULT 0, + `creditlimit` varchar(20) DEFAULT '0', + `active` int(11) DEFAULT 1, + `createdate` datetime DEFAULT current_timestamp(), + PRIMARY KEY (`orgid`) +) ENGINE=MyISAM AUTO_INCREMENT=199 DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci; + +-- ---------------------------- +-- Table: sp_orgs_category +-- ---------------------------- +CREATE TABLE `sp_orgs_category` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'https://developer.bigcommerce.com/api-reference/9cc3a53863922-get-all-categories', + `name` varchar(255) DEFAULT '0', + `parentid` int(11) DEFAULT 0, + `description` text DEFAULT NULL, + `pagetitle` varchar(255) DEFAULT '0', + `image` varchar(255) DEFAULT '0', + `searchkeywords` text DEFAULT NULL, + `active` int(11) DEFAULT 1, + PRIMARY KEY (`id`) +) ENGINE=MyISAM AUTO_INCREMENT=10 DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci; + +-- ---------------------------- +-- Table: sp_orgs_meta +-- ---------------------------- +CREATE TABLE `sp_orgs_meta` ( + `orgmetaid` int(11) NOT NULL AUTO_INCREMENT, + `orgid` int(11) DEFAULT 0, + `keyval` varchar(255) DEFAULT '0', + `metval` text DEFAULT NULL, + `createdate` datetime DEFAULT current_timestamp(), + `active` int(1) DEFAULT 1, + PRIMARY KEY (`orgmetaid`) +) ENGINE=InnoDB AUTO_INCREMENT=218 DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci; + +-- ---------------------------- +-- Table: sp_orgs_users_join +-- ---------------------------- +CREATE TABLE `sp_orgs_users_join` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `userid` int(11) DEFAULT 0, + `orgid` int(11) DEFAULT 0, + `orgrole` int(11) DEFAULT 0 COMMENT 'orgrole is temp.. Not sure how i want to handle this.. i don''t want multiple role tables.', + `orgadmin` int(11) DEFAULT 0 COMMENT 'orgadmin are account that have access to modifying the org information.', + `guest` int(11) DEFAULT 0 COMMENT 'if user is a guest, or invited into the org. OrgAdmin can''t delete or modify the account. Another way of thinking about it is ownership. Org Doesn''t have Ownership of the account. Can''t modify it.', + `created_date` datetime DEFAULT current_timestamp(), + `active` int(11) DEFAULT 1, + PRIMARY KEY (`id`) +) ENGINE=MyISAM AUTO_INCREMENT=37 DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci; + +-- ---------------------------- +-- Table: sp_permissions +-- ---------------------------- +CREATE TABLE `sp_permissions` ( + `perm_id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `perm_controller` varchar(50) NOT NULL DEFAULT '0', + `perm_action` varchar(50) NOT NULL DEFAULT '0', + `name` varchar(255) DEFAULT '0', + `viewroleid` varchar(255) DEFAULT '0' COMMENT 'role that can see these options', + `menu` int(11) DEFAULT 0, + `menuorder` int(11) DEFAULT 0, + PRIMARY KEY (`perm_id`) +) ENGINE=InnoDB AUTO_INCREMENT=45 DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci; + +-- ---------------------------- +-- Table: sp_plugins +-- ---------------------------- +CREATE TABLE `sp_plugins` ( + `plugin_id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `slug` varchar(64) NOT NULL, + `name` varchar(128) NOT NULL, + `version` varchar(20) NOT NULL DEFAULT '1.0.0', + `description` text DEFAULT NULL, + `author` varchar(128) DEFAULT NULL, + `enabled` tinyint(1) NOT NULL DEFAULT 0, + `installed_at` datetime NOT NULL DEFAULT current_timestamp(), + `updated_at` datetime NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(), + PRIMARY KEY (`plugin_id`), + UNIQUE KEY `slug` (`slug`) +) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + +-- ---------------------------- +-- Table: sp_referral +-- ---------------------------- +CREATE TABLE `sp_referral` ( + `refid` int(11) NOT NULL AUTO_INCREMENT, + `refcode` varchar(255) DEFAULT '0', + `refurl` varchar(255) DEFAULT '0', + `ipaddress` varchar(255) DEFAULT '0', + `createdate` datetime DEFAULT current_timestamp(), + PRIMARY KEY (`refid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_uca1400_ai_ci; + +-- ---------------------------- +-- Table: sp_role_perm +-- ---------------------------- +CREATE TABLE `sp_role_perm` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `role_id` int(10) unsigned NOT NULL DEFAULT 0, + `perm_id` int(10) unsigned NOT NULL DEFAULT 0, + PRIMARY KEY (`id`), + KEY `role_id` (`role_id`), + KEY `perm_id` (`perm_id`), + CONSTRAINT `sp_role_perm_ibfk_1` FOREIGN KEY (`role_id`) REFERENCES `sp_roles` (`role_id`), + CONSTRAINT `sp_role_perm_ibfk_2` FOREIGN KEY (`perm_id`) REFERENCES `sp_permissions` (`perm_id`) +) ENGINE=InnoDB AUTO_INCREMENT=56 DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci; + +-- ---------------------------- +-- Table: sp_roles +-- ---------------------------- +CREATE TABLE `sp_roles` ( + `role_id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `role_name` varchar(50) NOT NULL, + `description` varchar(255) DEFAULT NULL, + `view` varchar(100) DEFAULT NULL, + `admin` int(11) DEFAULT NULL, + `onteam` int(11) DEFAULT NULL, + `menu` text DEFAULT NULL, + `defaultrole` int(11) DEFAULT NULL COMMENT 'default roleid is for the system to know which id to default the user to.', + `hidemenu` int(11) DEFAULT NULL, + `createdate` datetime DEFAULT current_timestamp(), + PRIMARY KEY (`role_id`) +) ENGINE=InnoDB AUTO_INCREMENT=115 DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci; + +-- ---------------------------- +-- Table: sp_saas_invoice_activity +-- ---------------------------- +CREATE TABLE `sp_saas_invoice_activity` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `invid` int(11) NOT NULL, + `orgid` int(11) NOT NULL DEFAULT 0, + `event_type` varchar(64) NOT NULL COMMENT 'created, payment_attempted, payment_succeeded, payment_failed, viewed, etc.', + `label` varchar(255) NOT NULL, + `note` varchar(512) DEFAULT NULL, + `amount` decimal(10,2) DEFAULT NULL, + `createdate` datetime DEFAULT current_timestamp(), + PRIMARY KEY (`id`), + KEY `idx_invid` (`invid`), + KEY `idx_orgid` (`orgid`) +) ENGINE=InnoDB AUTO_INCREMENT=15 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + +-- ---------------------------- +-- Table: sp_saas_invoice_items +-- ---------------------------- +CREATE TABLE `sp_saas_invoice_items` ( + `itemid` int(11) NOT NULL AUTO_INCREMENT, + `invid` int(11) DEFAULT 0, + `priceid` varchar(255) DEFAULT '0', + `description` varchar(255) DEFAULT '0', + `amount` decimal(10,2) DEFAULT 0.00, + `quantity` int(11) DEFAULT 0, + `void` decimal(10,2) DEFAULT 0.00, + `total` decimal(10,2) DEFAULT 0.00, + `createdate` datetime DEFAULT current_timestamp(), + `active` int(11) DEFAULT 1, + PRIMARY KEY (`itemid`), + KEY `idx_invid` (`invid`) +) ENGINE=InnoDB AUTO_INCREMENT=65 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_uca1400_ai_ci; + +-- ---------------------------- +-- Table: sp_saas_invoices +-- ---------------------------- +CREATE TABLE `sp_saas_invoices` ( + `invid` int(11) NOT NULL AUTO_INCREMENT, + `orgid` int(11) NOT NULL DEFAULT 0, + `subid` varchar(255) DEFAULT '0', + `sinvid` varchar(255) DEFAULT '0', + `stripe_pmtid` varchar(255) NOT NULL DEFAULT '' COMMENT 'pm_xxx that was charged', + `description` varchar(255) DEFAULT '0', + `amountdue` decimal(10,2) DEFAULT 0.00, + `paid` decimal(10,2) DEFAULT 0.00, + `paidlast4` varchar(255) DEFAULT '0', + `paidtype` varchar(255) DEFAULT '0', + `balance` decimal(10,2) DEFAULT 0.00, + `discount` decimal(10,2) DEFAULT 0.00, + `hostedinv` varchar(255) DEFAULT '0', + `effective_at` datetime DEFAULT '0000-00-00 00:00:00', + `due_date` datetime DEFAULT NULL, + `paid_at` datetime DEFAULT NULL, + `canceled_at` datetime DEFAULT '0000-00-00 00:00:00', + `createdate` datetime DEFAULT current_timestamp(), + `status` varchar(255) DEFAULT 'draft' COMMENT 'this will be status like Pending, Paid, Refunded etc..', + `active` int(11) DEFAULT 1, + PRIMARY KEY (`invid`), + KEY `idx_orgid` (`orgid`), + KEY `idx_subid` (`subid`(191)), + KEY `idx_sinvid` (`sinvid`(191)), + KEY `idx_status` (`status`(30)) +) ENGINE=InnoDB AUTO_INCREMENT=70 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci; + +-- ---------------------------- +-- Table: sp_saas_plans +-- ---------------------------- +CREATE TABLE `sp_saas_plans` ( + `planid` int(11) NOT NULL AUTO_INCREMENT, + `code` varchar(255) DEFAULT '0', + `codemonth` varchar(255) DEFAULT NULL, + `codeyear` varchar(255) DEFAULT NULL, + `name` varchar(255) DEFAULT NULL, + `description` varchar(255) DEFAULT NULL, + `html` mediumtext DEFAULT NULL, + `monthly` decimal(6,2) DEFAULT 0.00, + `yearly` decimal(6,2) DEFAULT 0.00, + `price` decimal(6,2) DEFAULT 0.00, + `limit_minute` int(11) DEFAULT 0, + `credits` int(11) DEFAULT 0, + `active` int(11) DEFAULT 1, + PRIMARY KEY (`planid`) +) ENGINE=InnoDB AUTO_INCREMENT=27 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci; + +-- ---------------------------- +-- Table: sp_saas_pmtmethods +-- ---------------------------- +CREATE TABLE `sp_saas_pmtmethods` ( + `pmtid` int(11) NOT NULL AUTO_INCREMENT, + `orgid` int(11) NOT NULL DEFAULT 0, + `stripe_cus_id` varchar(255) NOT NULL DEFAULT '', + `userid` int(11) DEFAULT 0, + `spmid` varchar(255) DEFAULT '0' COMMENT 'Stripe Payment Method ID', + `cardname` varchar(255) DEFAULT '0', + `cardtype` varchar(255) DEFAULT '0', + `last4` varchar(255) DEFAULT '0', + `exp_month` tinyint(2) NOT NULL DEFAULT 0, + `exp_year` smallint(4) NOT NULL DEFAULT 0, + `billingzip` varchar(20) NOT NULL DEFAULT '', + `is_primary` tinyint(1) NOT NULL DEFAULT 0, + `createdate` datetime DEFAULT current_timestamp(), + `active` int(11) DEFAULT 1, + PRIMARY KEY (`pmtid`), + UNIQUE KEY `idx_spmid` (`spmid`(191)), + KEY `idx_orgid` (`orgid`), + KEY `idx_userid` (`userid`) +) ENGINE=InnoDB AUTO_INCREMENT=65 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci; + +-- ---------------------------- +-- Table: sp_saas_stripe_response +-- ---------------------------- +CREATE TABLE `sp_saas_stripe_response` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `orgid` int(11) NOT NULL DEFAULT 0, + `event_type` varchar(100) NOT NULL DEFAULT '' COMMENT 'e.g. charge.succeeded, invoice.paid', + `userid` int(11) DEFAULT 0, + `name` varchar(255) DEFAULT '0', + `email` varchar(255) DEFAULT '0', + `strid` varchar(255) DEFAULT '0', + `requtype` varchar(255) DEFAULT '0', + `amount` decimal(10,2) NOT NULL DEFAULT 0.00, + `chargeid` varchar(255) DEFAULT '0', + `chargecode` varchar(255) DEFAULT '0', + `chargereason` varchar(255) DEFAULT '0', + `chargemsg` varchar(255) DEFAULT '0', + `cardtype` varchar(255) DEFAULT '0', + `createdate` datetime DEFAULT current_timestamp(), + PRIMARY KEY (`id`), + KEY `idx_orgid` (`orgid`), + KEY `idx_event_type` (`event_type`) +) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci; + +-- ---------------------------- +-- Table: sp_saas_subscription +-- ---------------------------- +CREATE TABLE `sp_saas_subscription` ( + `subid` int(11) NOT NULL AUTO_INCREMENT, + `orgid` int(11) NOT NULL DEFAULT 0, + `userid` int(11) DEFAULT 0, + `planid` int(11) DEFAULT 0, + `stsubid` varchar(255) DEFAULT '0', + `status` varchar(30) NOT NULL DEFAULT 'active' COMMENT 'active | canceled | past_due | trialing | paused', + `trial_ends_at` datetime DEFAULT NULL, + `current_period_start` datetime DEFAULT NULL, + `current_period_end` datetime DEFAULT NULL, + `cancels_at` date DEFAULT '0000-00-00', + `cancelled_at` datetime DEFAULT '0000-00-00 00:00:00', + `reasoncancelled` varchar(255) DEFAULT '0', + `credits_usage` int(11) DEFAULT 0, + `bonus_credits` int(11) DEFAULT 0, + `nextbilling` datetime DEFAULT '0000-00-00 00:00:00', + `createdate` datetime DEFAULT current_timestamp(), + `updatedate` datetime DEFAULT '0000-00-00 00:00:00' ON UPDATE current_timestamp(), + `active` int(11) DEFAULT 0, + PRIMARY KEY (`subid`), + KEY `idx_orgid` (`orgid`), + KEY `idx_userid` (`userid`), + KEY `idx_status` (`status`) +) ENGINE=InnoDB AUTO_INCREMENT=487 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci; + +-- ---------------------------- +-- Table: sp_settings +-- ---------------------------- +CREATE TABLE `sp_settings` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `keyval` varchar(255) DEFAULT '0', + `group` varchar(100) NOT NULL DEFAULT 'general', + `metval` text DEFAULT NULL, + `status` tinyint(1) NOT NULL DEFAULT 0, + `encrypted` tinyint(1) NOT NULL DEFAULT 0, + `createdate` datetime DEFAULT current_timestamp(), + `updated_at` datetime DEFAULT NULL ON UPDATE current_timestamp(), + PRIMARY KEY (`id`), + UNIQUE KEY `uq_keyval` (`keyval`), + KEY `idx_group` (`group`) +) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci; + +-- ---------------------------- +-- Table: sp_states +-- ---------------------------- +CREATE TABLE `sp_states` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `code` char(2) NOT NULL, + `name` varchar(64) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `id` (`id`) +) ENGINE=MyISAM AUTO_INCREMENT=60 DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci; + +-- ---------------------------- +-- Table: sp_users +-- ---------------------------- +CREATE TABLE `sp_users` ( + `userid` int(10) unsigned NOT NULL AUTO_INCREMENT, + `username` varchar(50) DEFAULT '0', + `password` varchar(64) DEFAULT '0', + `email` varchar(255) DEFAULT '0', + `avatar` varchar(255) DEFAULT '0', + `autho` varchar(255) DEFAULT '0' COMMENT 'this is for google authorization', + `mobile` varchar(255) DEFAULT '0' COMMENT 'otp authorization or 2step', + `address` varchar(255) NOT NULL DEFAULT '', + `city` varchar(255) NOT NULL DEFAULT '', + `state` varchar(255) NOT NULL DEFAULT '', + `zip` varchar(20) NOT NULL DEFAULT '', + `country` varchar(255) NOT NULL DEFAULT '', + `fname` varchar(255) DEFAULT '0', + `lname` varchar(255) DEFAULT '0', + `role_id` int(10) unsigned DEFAULT 2, + `sysadmin` int(11) DEFAULT 0, + `lastlogin` datetime DEFAULT '0000-00-00 00:00:00', + `created_date` datetime DEFAULT current_timestamp(), + `updated_date` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(), + `ipaddress` varchar(255) DEFAULT '000.000.000.000', + `pwcodedate` datetime DEFAULT '0000-00-00 00:00:00', + `pwcode` varchar(255) DEFAULT '0', + `verify` varchar(255) DEFAULT '0', + `active` int(11) DEFAULT 1, + PRIMARY KEY (`userid`), + UNIQUE KEY `username` (`username`), + KEY `role_id` (`role_id`), + CONSTRAINT `sp_users_ibfk_1` FOREIGN KEY (`role_id`) REFERENCES `sp_roles` (`role_id`) +) ENGINE=InnoDB AUTO_INCREMENT=2566 DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci; + +-- ---------------------------- +-- Table: sp_users_api +-- ---------------------------- +CREATE TABLE `sp_users_api` ( + `apiid` int(11) NOT NULL AUTO_INCREMENT, + `orgid` int(11) DEFAULT 0, + `userid` int(11) DEFAULT 0, + `projectid` int(11) DEFAULT 0, + `apikey` varchar(100) DEFAULT NULL, + `apiname` varchar(255) DEFAULT '0', + `website` varchar(255) DEFAULT '0', + `description` text DEFAULT NULL, + `whitelist` text DEFAULT NULL, + `lockdomain` int(11) DEFAULT 0, + `createdate` datetime DEFAULT current_timestamp(), + `active` int(11) DEFAULT 1, + PRIMARY KEY (`apiid`), + UNIQUE KEY `ApiDups` (`apikey`) +) ENGINE=InnoDB AUTO_INCREMENT=5500 DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci; + +-- ---------------------------- +-- Table: sp_users_meta +-- ---------------------------- +CREATE TABLE `sp_users_meta` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `userid` int(11) DEFAULT 0, + `keyval` varchar(255) DEFAULT '0', + `metval` text DEFAULT NULL, + `createdate` datetime DEFAULT current_timestamp(), + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=212 DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci; + +-- ---------------------------- +-- Table: tmp_session +-- ---------------------------- +CREATE TABLE `tmp_session` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `ip` varchar(255) DEFAULT '000.000.000.000', + `lastcall` datetime DEFAULT current_timestamp(), + `created_date` datetime DEFAULT current_timestamp(), + `count` int(11) DEFAULT 1, + PRIMARY KEY (`id`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci; + +SET FOREIGN_KEY_CHECKS=1; diff --git a/app/Components/Role.php b/app/Components/Role.php new file mode 100644 index 0000000..6c2bdaa --- /dev/null +++ b/app/Components/Role.php @@ -0,0 +1,158 @@ + $PerMethod) { + if($permContrller == $controller) { + $AllowAccess = 1; + break; + } + } + + if($AllowAccess == 1) { + return true; + } else { + print_array('Deny Access ' . $controller); + } + + } + + + public static function AccessGranted($permission, $method) { + //print_array($permission); + + if($permission[0] == "*") { return true; } else { + if (in_array($method, $permission)) { + return true; + } else { + die('Access Denied - ' . $method); + } + } + } + + +//////////////////////// Manage Role Application //////////////////////////// + + public function Group() { + $Return = $this->db->select("SELECT COUNT(*) AS accounts, a.role_id, b.role_name AS RoleName, b.createdate AS CreateDate FROM usergen a JOIN roles b ON a.role_id=b.role_id GROUP BY a.role_id"); + return $Return; + } + + public function getPerms($data=""){ + if($data){ + $Search .= "WHERE 1"; + foreach($data as $key => $val) : + if(!$val) continue; + $string = strtolower($val); + $Search .= " AND {$key} LIKE '%{$string}%'"; + endforeach; + } + $Return = $this->db->select("SELECT * FROM permissions {$Search} ORDER BY perm_controller ASC"); + // sendlog("SELECT * FROM permissions {$Search} ORDER BY perm_controller ASC"); + return $Return; + } + + public function insertPerm($data){ + $data['perm_controller'] = strtolower($data['perm_controller']); + $data['perm_action'] = strtolower($data['perm_action']); + + $Return = $this->db->insert('permissions', $data); +// sendlog($Return); + return $Return; + } + + + public function deletePerm($id) { + $Return = $this->db->query("DELETE FROM permissions WHERE perm_id='{$id}'"); + return $Return; + } + + + public function updatePerm($data, $pid){ + $Return = $this->db->update('permissions', $data, "perm_id='{$pid}'"); + return $Return; + } + + + + // insert a new role + public function insertRole($data) { + $rdata['role_name'] = $data['groupname']; + $rdata['controller'] = $data['grouprole']; + $Return = $this->db->insert('roles', $rdata); + $getID = json_decode($Return); + + foreach($data['permission'] as $permid) : + $permData['role_id']=$getID->ID ; + $permData['perm_id']=$permid; + $RolePerms = $this->db->insert('role_perm', $permData); + endforeach; + + return $Return; + } + + // insert array of roles for specified user id + public function insertUserRoles($userid, $roles) { + $sql = "INSERT INTO user_role (userid, role_id) VALUES (:userid, :role_id)"; + $sth = $GLOBALS["DB"]->prepare($sql); + $sth->bindParam(":userid", $userid, PDO::PARAM_STR); + $sth->bindParam(":role_id", $role_id, PDO::PARAM_INT); + foreach ($roles as $role_id) { + $sth->execute(); + } + return true; + } + + // delete array of roles, and all associations + public function deleteRoles($roles) { + $sql = "DELETE t1, t2, t3 FROM roles as t1 + JOIN user_role as t2 on t1.role_id = t2.role_id + JOIN role_perm as t3 on t1.role_id = t3.role_id + WHERE t1.role_id = :role_id"; + $sth = $GLOBALS["DB"]->prepare($sql); + $sth->bindParam(":role_id", $role_id, PDO::PARAM_INT); + foreach ($roles as $role_id) { + $sth->execute(); + } + return true; + } + + + // delete ALL roles for specified user id + public function deleteUserRoles($userid) { + $sql = "DELETE FROM user_role WHERE userid = :userid"; + $sth = $GLOBALS["DB"]->prepare($sql); + return $sth->execute(array(":userid" => $userid)); + } + + // check if a user has a specific role + public function hasRole($role_name) { + return isset($this->roles[$role_name]); + } + + + + public static function createRole(){ + die('testing'); + } + + public function roleType(){ + $SQL = "SELECT role_id, role_name FROM roles"; + $Return = $this->db->select($SQL); + + return $Return; + } + + +} // End Class \ No newline at end of file diff --git a/app/Components/Template.php b/app/Components/Template.php new file mode 100644 index 0000000..4e07023 --- /dev/null +++ b/app/Components/Template.php @@ -0,0 +1,31 @@ +view->UserProfile = $profile; + + // Build tokenized slug for account settings link: "first-last.encryptedId" + if ($profile) { + $namePart = strtolower(trim(($profile['fname'] ?? '') . '-' . ($profile['lname'] ?? ''), '-')); + $namePart = preg_replace('/[^a-z0-9\-]/', '', $namePart); + $token = Functions::encryptData((string)$profile['userid'], HASH_PASSWORD_KEY); + $this->view->UserSlug = $namePart . '.' . $token; + } + } + } +} diff --git a/app/Core/Email.php b/app/Core/Email.php new file mode 100644 index 0000000..5d251cb --- /dev/null +++ b/app/Core/Email.php @@ -0,0 +1,82 @@ +SMTPDebug = 0; // Enable verbose debug output + $mail->isSMTP(); // Set mailer to use SMTP + $mail->Host = EMAILHOST; // Specify main and backup SMTP servers + $mail->SMTPAuth = true; // Enable SMTP authentication + $mail->Username = EMAILUSER; // SMTP username + $mail->Password = EMAILPASS; // SMTP password + $mail->SMTPSecure = 'tls'; // Enable TLS encryption, `ssl` also accepted + $mail->Port = 587; // TCP port to connect to + + //Recipients + $mail->setFrom('noreply@snoopi.io', 'Snoopi.io'); + //Set an alternative reply-to address + $mail->addReplyTo('support@snoopi.io', 'Snoopi.io'); + + #TODO: Need to have it loop multiple email addresses + $mail->AddBCC('carlosja80@gmail.com ', 'Carlos Arias'); // Add a recipient + + if($this->ccEmail) { + foreach($this->ccEmail as $ccdemail) { + $mail->AddCC(trim($ccdemail)); + } + } + $mail->addAddress(trim($this->SendToEmail)); // Name is optional + + //Attachments + if($this->Attachment) $mail->addAttachment($this->Attachment); + //$mail->addAttachment('/tmp/image.jpg', 'new.jpg'); // Optional name + + //Content + $mail->isHTML(true); // Set email format to HTML + $mail->Subject = $this->Subject; + $mail->Body = $this->BodyMessage; + $mail->AltBody = 'This is the body in plain text for non-HTML mail clients'; + + + $HTMLMessage = file_get_contents($_SERVER['DOCUMENT_ROOT'] . '/templates/emails/' . strtolower($this->Template) . '.html'); + foreach($this->EmailParam as $Key => $Value) { + $HTMLMessage = str_replace('%'. strtoupper($Key) .'%', $Value, $HTMLMessage); + } + + #Example: $HTMLMessage = str_replace('%ACTIVATION%', $this->EmailParam['activation'] , $HTMLMessage); + + + $mail->MsgHTML($HTMLMessage); + + $mail->send(); + return true; + } catch (Exception $e) { + echo 'Message could not be sent.'; + echo 'Mailer Error: ' . $mail->ErrorInfo; + } + } + + +} // End Class \ No newline at end of file diff --git a/app/Gateways/Stripe.php b/app/Gateways/Stripe.php new file mode 100644 index 0000000..418811d --- /dev/null +++ b/app/Gateways/Stripe.php @@ -0,0 +1,152 @@ + $description, + "name" => $name, + "source" => $token, + "email" => $email + )); + return ($Customer); + } catch (\Stripe\Error\Card $e){ + return ($e->getMessage()); + } + } + + + # Retrieve Subscription Information + public function RetrieveSubscription($str){ + try { + $subscription = \Stripe\Subscription::retrieve($str); + return($subscription); + } catch (\Stripe\Error\Base $e) { + return ($e->getMessage()); + } + + + } + + + #Create Subscription + # https://stripe.com/docs/api/subscriptions/create + // a Customer ID is required - this is retrieve from the CreateAccount method. + // a PlanID is required. This is grabbed from stripe plans (https://dashboard.stripe.com/subscriptions/products) + // My Recommendation is to use Method ($this->getPlans) to get the plans. Stripe doesn't make it easy to get the planid + + public function CreateSubscription($CustID, $PlanID){ + $CustomerSubscription = \Stripe\Subscription::create(array( + "customer" => $CustID, + "items" => array( + array( + "plan" => $PlanID, + "quantity" => 1, + ) + ) + )); + + return ($CustomerSubscription); + } + + + # Cancels Subscription + // Subscription ID is required + // https://stripe.com/docs/api/subscriptions/cancel + + public function CancelSubscription($str){ + try { + $subscription = \Stripe\Subscription::retrieve($str); + $subscription->cancel(); + return($subscription); + } catch (\Stripe\Error\Base $e) { + return ($e->getMessage()); + } + } + + + # Update Subscription. + # subscription code required from stripe. + // Provide with a new PlanID + // https://stripe.com/docs/api/subscriptions/update + + public function UpdateSubscription($SubID, $PlanID){ + $subscription = \Stripe\Subscription::retrieve($SubID); + + try { + $UpdateSubscription = \Stripe\Subscription::update($SubID, [ + 'cancel_at_period_end' => false, + 'proration_behavior' => 'always_invoice', + 'items' => [ + [ + 'id' => $subscription->items->data[0]->id, + 'plan' => $PlanID, + ], + ] + ]); + return($UpdateSubscription); + + } catch (\Stripe\Error\Base $e) { + return ($e->getMessage()); + } + } + + #This get customer information everything about the customer from Stripe + // Just Need Customer id which looks like cust_348398439 + // https://stripe.com/docs/api/customers/retrieve + public function getCustomerInfo($str){ + $Return = \Stripe\Customer::retrieve($str); + return $Return; + } + + + #Retrieve a list of plans + public function getPlans($limit = 10){ + $Return = \Stripe\Plan::all(["limit" => $limit]); + return $Return; + } + + + # Retrieves the Plans detail information + public function ProductInfo($planId){ + $Return = \Stripe\Plan::retrieve($planId); + return $Return; + } + + + # Update Credit Card Info + // Coming Soon + public function UpdateCard($token, $custID){ + try { + $cu = \Stripe\Customer::update( + $custID, // stored in your application + [ + 'source' => $token // obtained with Checkout + ] + ); + + return $cu; + //$Message['Success'] = "Your card details have been updated!"; + } + catch(\Stripe\Error\Card $e) { + return ($e->getMessage()); + // $e->getJsonBody(); // show fulll error message + } + } + +} // End Class diff --git a/app/Helpers/Auth.php b/app/Helpers/Auth.php new file mode 100644 index 0000000..d1e5887 --- /dev/null +++ b/app/Helpers/Auth.php @@ -0,0 +1,119 @@ + 0, + 'trigval' => 'security.api_auth_failed', + 'msgval' => 'Invalid API key presented', + 'status' => 1, + ]); + $Return = ['code' => '401', 'msg' => 'Unauthorized: Api Invalid']; + header('Content-Type: application/json;charset=UTF-8'); + echo json_encode($Return); + exit(); + } + + $Return['code'] = '1'; + $Return['userid'] = $ApiUser['userid']; + return $Return; + } + } + + // ─── Session Login Check ────────────────────────────────────────────────── + + public static function handleLogin() { + $logged = $_SESSION['login']; + if ($logged == false) { + session_destroy(); + header('location: /login'); + exit; + } + return $logged; + } + + // ─── Load Permissions into Session ─────────────────────────────────────── + // Call this once after successful login. + + public static function loadPermissions(int $roleId): void { + $rows = \Db::select(" + SELECT p.perm_controller, p.perm_action + FROM sp_role_perm rp + JOIN sp_permissions p ON rp.perm_id = p.perm_id + WHERE rp.role_id = ? + ", [$roleId]); + + $permissions = []; + foreach ($rows as $row) { + $permissions[] = $row['perm_controller'] . '.' . $row['perm_action']; + } + + $_SESSION['permissions'] = $permissions; + } + + // ─── Permission Checks ──────────────────────────────────────────────────── + + /** + * Returns true if the current user has the given permission. + * Permission format: 'controller.action' e.g. 'users.create' + */ + public static function can(string $permission): bool { + $permissions = $_SESSION['permissions'] ?? []; + return in_array($permission, $permissions); + } + + /** + * Halts execution with a 403 JSON response if the user lacks the permission. + * Use in API controllers. + */ + public static function requirePermission(string $permission): void { + if (!self::can($permission)) { + Logger::Entry([ + 'userid' => $_SESSION['login']['userid'] ?? 0, + 'trigval' => 'security.permission_denied', + 'msgval' => 'Permission denied: ' . $permission, + 'status' => 1, + ]); + http_response_code(403); + header('Content-Type: application/json'); + echo json_encode(['success' => false, 'error' => 'Permission denied']); + exit; + } + } + + /** + * Returns all permissions for the current user as an array. + */ + public static function getPermissions(): array { + return $_SESSION['permissions'] ?? []; + } + + /** + * Returns a JS-safe array of the current user's permissions. + * Use in header partial to inject into window.AppUser. + */ + public static function getPermissionsJson(): string { + return json_encode(self::getPermissions()); + } + +} diff --git a/app/Helpers/DB.php b/app/Helpers/DB.php new file mode 100644 index 0000000..b524e26 --- /dev/null +++ b/app/Helpers/DB.php @@ -0,0 +1,366 @@ + PDO::ERRMODE_EXCEPTION, + PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8; SET time_zone = 'America/New_York'" + ]; + + /** + * Set connection information + * + * @example Db::setConnectionInfo('mysql', 'dbname', 'username', 'password', 'hostname', 'connectionName'); + */ + public static function setConnectionInfo( + $driver, + $dbname, + $username, + $password, + $hostname = 'localhost', + $connectionName = 'default' + ) { + self::$_connections[$connectionName] = [ + 'driver' => $driver, + 'connectionString' => "{$driver}:dbname={$dbname};host={$hostname}", + 'username' => $username, + 'password' => $password, + ]; + + if (self::$_activeConnection === null) { + self::$_activeConnection = $connectionName; + } + } + + /** + * Switch to a different connection + */ + public static function useConnection($connectionName) + { + if (!isset(self::$_connections[$connectionName])) { + throw new \Exception("Connection '{$connectionName}' does not exist."); + } + self::$_activeConnection = $connectionName; + } + + /** + * Get the current PDO object + */ + public static function getPDOObject() + { + return self::_getConnection(); + } + + /** + * Execute a statement and returns the first row + */ + public static function getRow($sql, $params = []) + { + $statement = self::_query($sql, $params); + return $statement->fetch(self::$_fetchMode); + } + + /** + * Execute a statement and returns all rows + */ + public static function select($sql, $params = []) + { + $statement = self::_query($sql, $params); + return $statement->fetchAll(self::$_fetchMode); + } + + + /** + * Insert a new row into the database + */ + public static function insert($table, $data) + { + $pdo = self::_getConnection(); + + ksort($data); + $fieldNames = implode('`, `', array_keys($data)); + $fieldValues = ':' . implode(', :', array_keys($data)); + + try { + $sth = $pdo->prepare("INSERT INTO $table (`$fieldNames`) VALUES ($fieldValues)"); + + foreach ($data as $key => $value) { + $sth->bindValue(":$key", $value); + } + + $sth->execute(); + + return json_encode([ + 'Code' => 1, + 'Message' => 'Created', + 'ID' => $pdo->lastInsertId() + ]); + } catch (\PDOException $e) { + return json_encode([ + 'Code' => 0, + 'Message' => 'Error Creating Entry: ' . $e->getMessage() + ]); + } + } + + /** + * Update existing rows in the database + */ + public static function update($table, $data, $where, $whereParams = []) + { + $pdo = self::_getConnection(); + + ksort($data); + $usesPositionalWhere = str_contains($where, '?'); + + if ($usesPositionalWhere) { + $fieldDetails = implode(', ', array_map(function($key) { + return "`$key` = ?"; + }, array_keys($data))); + } else { + $fieldDetails = implode(', ', array_map(function($key) { + return "`$key` = :set_$key"; + }, array_keys($data))); + } + + try { + $sth = $pdo->prepare("UPDATE $table SET $fieldDetails WHERE $where"); + + if ($usesPositionalWhere) { + // Single positional style: SET values first, WHERE values after. + $params = array_values($data); + if (!empty($whereParams)) { + $params = array_merge($params, array_values($whereParams)); + } + $sth->execute($params); + } else { + // Single named style for SET; allow named WHERE params if supplied. + $params = []; + foreach ($data as $key => $value) { + $params["set_$key"] = $value; + } + foreach ($whereParams as $key => $value) { + $params[ltrim((string)$key, ':')] = $value; + } + $sth->execute($params); + } + + $count = $sth->rowCount(); + + return json_encode([ + 'Code' => ($count > 0) ? 1 : 0, + 'Rows' => $count, + 'Message' => ($count > 0) ? 'Updated' : 'No Records Updated' + ]); + } catch (\PDOException $e) { + return json_encode([ + 'Code' => $e->errorInfo[1], + 'Message' => 'Error Updating Database: ' . $e->getMessage() + ]); + } + } + + /** + * Execute a statement and returns number of affected rows + */ + public static function execute($sql, $params = []) + { + $statement = self::_query($sql, $params); + return $statement->rowCount(); + } + + + + /** + * Execute a statement and returns a single value + */ + public static function getValue($sql, $params = []) + { + $statement = self::_query($sql, $params); + return $statement->fetchColumn(0); + } + + /** + * Set PDO fetch mode + */ + public static function setFetchMode($fetchMode) + { + self::$_fetchMode = $fetchMode; + } + + /** + * Begin a transaction + */ + public static function beginTransaction() + { + self::_getConnection()->beginTransaction(); + } + + /** + * Commit a transaction + */ + public static function commitTransaction() + { + self::_getConnection()->commit(); + } + + /** + * Rollback a transaction + */ + public static function rollbackTransaction() + { + self::_getConnection()->rollBack(); + } + + /** + * Backward-compatible aliases used across controllers. + */ + public static function commit() + { + self::commitTransaction(); + } + + public static function rollback() + { + self::rollbackTransaction(); + } + + /** + * Set PDO driver options + */ + public static function setDriverOptions(array $options) + { + self::$_driverOptions = $options; + } + + /** + * Get or create the PDO connection + */ + private static function _getConnection() + { + $connectionInfo = self::$_connections[self::$_activeConnection]; + + if (!isset($connectionInfo['pdo'])) { + $connectionInfo['pdo'] = new \PDO( + $connectionInfo['connectionString'], + $connectionInfo['username'], + $connectionInfo['password'], + self::$_driverOptions + ); + self::$_connections[self::$_activeConnection] = $connectionInfo; + } + + return $connectionInfo['pdo']; + } + + /** + * Prepare and execute a PDO statement + */ + private static function _query($sql, $params = []) + { + $pdo = self::_getConnection(); + $statement = $pdo->prepare($sql); + + if (!$statement) { + $errorInfo = $pdo->errorInfo(); + throw new \PDOException("Database error [{$errorInfo[0]}]: {$errorInfo[2]}, driver error code is $errorInfo[1]"); + } + + if (!$statement->execute($params) || $statement->errorCode() != '00000') { + $errorInfo = $statement->errorInfo(); + throw new \PDOException("Database error [{$errorInfo[0]}]: {$errorInfo[2]}, driver error code is $errorInfo[1]"); + } + + return $statement; + } + + /** + * Delete rows from the database + */ + public static function delete($table, $where) + { + $pdo = self::_getConnection(); + + // Build the WHERE clause dynamically + $whereConditions = implode(' AND ', array_map(function($key) { + return "`$key` = :$key"; + }, array_keys($where))); + + try { + $sth = $pdo->prepare("DELETE FROM $table WHERE $whereConditions"); + + // Bind the parameters from the where array + foreach ($where as $key => $value) { + $sth->bindValue(":$key", $value); + } + + $sth->execute(); + $count = $sth->rowCount(); + + return json_encode([ + 'Code' => ($count > 0) ? 1 : 0, + 'Rows' => $count, + 'Message' => ($count > 0) ? 'Deleted' : 'No Records Deleted' + ]); + } catch (\PDOException $e) { + return json_encode([ + 'Code' => $e->errorInfo[1], + 'Message' => 'Error Deleting From Database: ' . $e->getMessage() + ]); + } + } + + public static function batchInsert($table, array $data) + { + if (empty($data)) { + return json_encode([ + 'Code' => 0, + 'Message' => 'No data provided for insertion' + ]); + } + + $pdo = self::_getConnection(); + + // Assume all rows have the same structure as the first row + $firstRow = reset($data); + $columns = array_keys($firstRow); + $columnString = '`' . implode('`, `', $columns) . '`'; + + // Create placeholders for each row + $rowPlaceholder = '(' . implode(', ', array_fill(0, count($columns), '?')) . ')'; + $valuePlaceholders = implode(', ', array_fill(0, count($data), $rowPlaceholder)); + + $sql = "INSERT INTO $table ($columnString) VALUES $valuePlaceholders"; + + try { + $stmt = $pdo->prepare($sql); + + // Flatten the data array and bind values + $values = []; + foreach ($data as $row) { + foreach ($columns as $column) { + $values[] = $row[$column]; + } + } + + $stmt->execute($values); + + $insertedCount = $stmt->rowCount(); + + return json_encode([ + 'Code' => 1, + 'Message' => 'Batch insert successful', + 'InsertedRows' => $insertedCount + ]); + } catch (\PDOException $e) { + return json_encode([ + 'Code' => 0, + 'Message' => 'Error performing batch insert: ' . $e->getMessage() + ]); + } + } +} diff --git a/app/Helpers/Debug.php b/app/Helpers/Debug.php new file mode 100644 index 0000000..6302952 --- /dev/null +++ b/app/Helpers/Debug.php @@ -0,0 +1,22 @@ +'; + print_r($f); + echo ''; + + + if($kill){ + die('-- Debugging --'); + } + } + + + public static function sendlog($string){ + error_log($string, 0); + } + +} // End Class \ No newline at end of file diff --git a/app/Helpers/Format.php b/app/Helpers/Format.php new file mode 100644 index 0000000..37f6af6 --- /dev/null +++ b/app/Helpers/Format.php @@ -0,0 +1,84 @@ + 10) { + $countryCode = substr($phoneNumber, 0, strlen($phoneNumber)-10); + $areaCode = substr($phoneNumber, -10, 3); + $nextThree = substr($phoneNumber, -7, 3); + $lastFour = substr($phoneNumber, -4, 4); + + $phoneNumber = '+'.$countryCode.' ('.$areaCode.') '.$nextThree.'-'.$lastFour; + } + else if(strlen($phoneNumber) == 10) { + $areaCode = substr($phoneNumber, 0, 3); + $nextThree = substr($phoneNumber, 3, 3); + $lastFour = substr($phoneNumber, 6, 4); + + $phoneNumber = '('.$areaCode.') '.$nextThree.'-'.$lastFour; + } + else if(strlen($phoneNumber) == 7) { + $nextThree = substr($phoneNumber, 0, 3); + $lastFour = substr($phoneNumber, 3, 4); + + $phoneNumber = $nextThree.'-'.$lastFour; + } + + return $phoneNumber; + } + + public static function formatCurrency($amount='', $thousands='') { + // Convert to float and divide by 100 + $formattedAmount = number_format($amount / 100, 2, '.', $thousands); + + return $formattedAmount; + } + + + /** + * Convert Equifax date strings to MM/DD/YYYY display format. + * Handles: YYYYMMDD, YYYY-MM-DD, MM/YYYY, MM/DD/YYYY passthrough. + * Returns null on empty/null input. + */ + public static function eqFormatDate($date) { + if (empty($date)) return null; + $date = trim((string)$date); + + // YYYYMMDD (8 digits) + if (preg_match('/^\d{8}$/', $date)) { + $d = \DateTime::createFromFormat('Ymd', $date); + return $d ? $d->format('m/d/Y') : null; + } + + // YYYY-MM-DD + if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $date)) { + $d = \DateTime::createFromFormat('Y-m-d', $date); + return $d ? $d->format('m/d/Y') : null; + } + + // MM/YYYY — partial date, use first of month + if (preg_match('/^\d{2}\/\d{4}$/', $date)) { + $d = \DateTime::createFromFormat('m/Y', $date); + return $d ? $d->format('m/01/Y') : null; + } + + // MM/DD/YYYY — already correct format, passthrough + if (preg_match('/^\d{2}\/\d{2}\/\d{4}$/', $date)) { + return $date; + } + + // Fallback: try strtotime + $ts = strtotime($date); + return $ts ? date('m/d/Y', $ts) : null; + } + +} // End Class \ No newline at end of file diff --git a/app/Helpers/Functions.php b/app/Helpers/Functions.php new file mode 100644 index 0000000..daebe19 --- /dev/null +++ b/app/Helpers/Functions.php @@ -0,0 +1,287 @@ + $fQuery) { + $SQLWhere .= "AND {$key} like '%{$fQuery}%' "; + } + $Return = \Db::getResult("select * from sp_roles WHERE 1 {$SQLWhere}"); + return $Return; + } + + public static function getUserInfo($userid=''){ + if(!$userid) die('UserID Required...'); + $Return = \Db::getRow("SELECT * FROM sp_users a JOIN sp_roles b ON a.role_id=b.role_id WHERE userid='{$userid}'"); + return $Return; + } + + public static function getStates(){ + + } + + public static function getCountries(){ + + } + + public static function isSysAdmin() { + // \Session::get('login'); + } + + /** + * Check if the user exist in the database by email. + * @param $f + * @return array|false + */ + public static function CheckUserExist($f){ + $j = \Db::getRow("select * from sp_users where email ='{$f}'"); + return (($j) ? $j : false); + } + + /** + * Get either a Gravatar URL or complete image tag for a specified email address. + * + * @param string $email The email address + * @param string $s Size in pixels, defaults to 80px [ 1 - 2048 ] + * @param string $d Default imageset to use [ 404 | mp | identicon | monsterid | wavatar ] + * @param string $r Maximum rating (inclusive) [ g | pg | r | x ] + * @param boole $img True to return a complete IMG tag False for just the URL + * @param array $atts Optional, additional key/value attributes to include in the IMG tag + * @return String containing either just a URL or a complete image tag + * @source https://gravatar.com/site/implement/images/php/ + */ + public static function getAvatar($email, $s = 80, $d = 'mp', $r = 'g', $img = false, $atts = array()) { + + $url = 'https://www.gravatar.com/avatar/'; + $url .= md5( strtolower( trim( $email ) ) ); + $url .= "?s=$s&d=$d&r=$r"; + if ( $img ) { + $url = ' $val ) + $url .= ' ' . $key . '="' . $val . '"'; + $url .= ' />'; + } + return $url; + + } + + /** Create Encryption for security in the application + * + * @param string $st The string that needs to be encrypted + * Usage: echo \Functions::Encrypt("Carlos"); + * + */ + public static function Encrypt($st){ + $encrypted = openssl_encrypt($st, 'AES-128-CTR', HASH_PASSWORD_KEY, OPENSSL_RAW_DATA, '1234567890123456'); + return base64_encode($encrypted); + } + + + /** Create Encryption for security in the application + * + * @param string $st The string that needs to be encrypted + * Usage: echo \Functions::Decrypt("a/pxWERALz2YMd4l32U3ew=="); + * + */ + public static function Decrypt($st){ + $decrypted = openssl_decrypt(base64_decode($st), 'AES-128-CTR', HASH_PASSWORD_KEY, OPENSSL_RAW_DATA, '1234567890123456'); + return $decrypted; + } + + public static function DefualtRole(){ + Return \Db::getRow("SELECT role_id, role_name FROM sp_roles WHERE defaultrole='1'"); + } + + + public static function encryptData($data, $key) { + $ivLength = openssl_cipher_iv_length($cipher = 'AES-256-CBC'); + $iv = openssl_random_pseudo_bytes($ivLength); + $encrypted = openssl_encrypt($data, $cipher, $key, OPENSSL_RAW_DATA, $iv); + // Convert to hex to ensure binary safety, then to base64 to make it URL-friendly + $encryptedBase64 = base64_encode($iv . $encrypted); + // Replace URL-unfriendly characters from base64 encoding + $urlSafeEncrypted = strtr($encryptedBase64, '+/', '-_'); + // Optionally remove '=' if present + $urlSafeEncrypted = rtrim($urlSafeEncrypted, '='); + return $urlSafeEncrypted; + } + + public static function decryptData($urlSafeEncrypted, $key) { + $ivLength = openssl_cipher_iv_length($cipher = 'AES-256-CBC'); + // Reverse the URL-safe transformations + $base64Encrypted = strtr($urlSafeEncrypted, '-_', '+/'); + // Decode from base64 to binary + $binaryData = base64_decode($base64Encrypted); + $iv = substr($binaryData, 0, $ivLength); + $encrypted = substr($binaryData, $ivLength); + $decrypted = openssl_decrypt($encrypted, $cipher, $key, OPENSSL_RAW_DATA, $iv); + return $decrypted; + } + + + /*// Usage + $key = 'your-256-bit-secret-key'; // Make sure to use a secure key + $originalData = "Your secret data"; + + $encryptedData = encryptData($originalData, $key); + echo "Encrypted: " . $encryptedData . "\n"; + + $decryptedData = decryptData($encryptedData, $key); + echo "Decrypted: " . $decryptedData . "\n"; + */ + + /** + * Resolve an org from a tokenized slug (e.g. "acme-corp.AbcToken"). + * Decrypts the token, validates it, and returns the active org row. + * Calls die() with an error message on any failure. + * + * @param string $slug + * @return array + */ + public static function resolveOrg($slug) { + if (!$slug || strpos($slug, '.') === false) die('Invalid link'); + [, $token] = explode('.', $slug, 2); + $orgid = self::decryptData($token, HASH_PASSWORD_KEY); + if (!$orgid || !is_numeric($orgid)) die('Invalid link'); + $org = \Db::getRow("SELECT * FROM sp_orgs WHERE orgid = ? AND active = 1", [$orgid]); + if (!$org) die('Organization not found'); + return $org; + } + + /** + * Resolve a user from a tokenized slug (e.g. "john-doe.AbcToken"). + * Decrypts the token, validates it, and returns the user row (with role). + * Calls die() with an error message on any failure. + * + * @param string $slug + * @return array + */ + public static function resolveUser($slug) { + if (!$slug || strpos($slug, '.') === false) die('Invalid link'); + [, $token] = explode('.', $slug, 2); + $userid = self::decryptData($token, HASH_PASSWORD_KEY); + if (!$userid || !is_numeric($userid)) die('Invalid link'); + $user = \Db::getRow("SELECT u.*, r.role_name FROM sp_users u LEFT JOIN sp_roles r ON u.role_id = r.role_id WHERE u.userid = ?", [$userid]); + if (!$user) die('User not found'); + return $user; + } + + // This gets the system default organization. + // Ideally for single organization applications with users going straight into the org or for api calls that have no business name assigned to the field. + public static function DefaultOrg(){ + Return \Db::getRow("SELECT orgid, catid, name FROM sp_orgs WHERE defaultorg='1'"); + } + + public static function randomPassword() { + $alphabet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890'; + $pass = array(); //remember to declare $pass as an array + $alphaLength = strlen($alphabet) - 1; //put the length -1 in cache + for ($i = 0; $i < 8; $i++) { + $n = rand(0, $alphaLength); + $pass[] = $alphabet[$n]; + } + return implode($pass); //turn the array into a string + } + + + + public static function ip_in_range($ip, $range) { + if (strpos($range, '/') == false) + $range .= '/32'; + + // $range is in IP/CIDR format eg 127.0.0.1/24 + list($range, $netmask) = explode('/', $range, 2); + $range_decimal = ip2long($range); + $ip_decimal = ip2long($ip); + $wildcard_decimal = pow(2, (32 - $netmask)) - 1; + $netmask_decimal = ~ $wildcard_decimal; + return (($ip_decimal & $netmask_decimal) == ($range_decimal & $netmask_decimal)); + } + + public static function CheckCloudFlare($ip) { + $cf_ips = array( + '173.245.48.0/20', + '103.21.244.0/22', + '103.22.200.0/22', + '103.31.4.0/22', + '141.101.64.0/18', + '108.162.192.0/18', + '190.93.240.0/20', + '188.114.96.0/20', + '197.234.240.0/22', + '198.41.128.0/17', + '162.158.0.0/15', + '104.16.0.0/13', + '104.24.0.0/14', + '172.64.0.0/13', + '131.0.72.0/22' + ); + $is_cf_ip = false; + foreach ($cf_ips as $cf_ip) { + if (self::ip_in_range($ip, $cf_ip)) { + + $is_cf_ip = true; + break; + } + } return $is_cf_ip; + } + + public static function in_array_all($needles, $haystack) { + return empty(array_diff($needles, $haystack)); + } + + public static function hasOrgAdminAccess($userArray) { + foreach ($userArray as $org) { + if ($org['OrgAdmin'] == 1) { + return true; + } + } + return false; + } + + + static function getDomainIP($domain) { + // Validate domain format + if (!filter_var($domain, FILTER_VALIDATE_DOMAIN)) { + return "Invalid domain format"; + } + + $result = []; + + try { + // Method 1: Get single IP using gethostbyname() + $ip = gethostbyname($domain); + if ($ip !== $domain) { + $result['primary_ip'] = $ip; + } + + // Method 2: Get all DNS A records + $dns_records = dns_get_record($domain, DNS_A); + if (!empty($dns_records)) { + $result['dns_records'] = array_column($dns_records, 'ip'); + } + + // Method 3: Get all IPv4 addresses + if (checkdnsrr($domain, 'A')) { + $ip_array = gethostbynamel($domain); + if ($ip_array) { + $result['all_ipv4'] = $ip_array; + } + } + + if (empty($result)) { + return "Could not resolve domain"; + } + + return $result; + + } catch (Exception $e) { + return "Error: " . $e->getMessage(); + } + } + + + +} // End Class + diff --git a/app/Helpers/GlobalConst.php b/app/Helpers/GlobalConst.php new file mode 100644 index 0000000..869b79b --- /dev/null +++ b/app/Helpers/GlobalConst.php @@ -0,0 +1,16 @@ +format('Ymd') : $dob; + } + + // Fallback + $ts = strtotime($dob); + return $ts ? date('Ymd', $ts) : $dob; + } + + /** + * Create a stable consumer profile ID from DOB + SSN. + * Returns first 32 chars of HMAC-SHA256. + */ + public static function createConsumerProfileId($dob, $ssn, $salt) { + $normalized = self::normalizeDob($dob) . preg_replace('/[^0-9]/', '', $ssn); + return substr(hash_hmac('sha256', $normalized, $salt), 0, 32); + } +} \ No newline at end of file diff --git a/app/Helpers/Logger.php b/app/Helpers/Logger.php new file mode 100644 index 0000000..6f28f0d --- /dev/null +++ b/app/Helpers/Logger.php @@ -0,0 +1,72 @@ + 10) { + $countryCode = substr($phoneNumber, 0, strlen($phoneNumber)-10); + $areaCode = substr($phoneNumber, -10, 3); + $nextThree = substr($phoneNumber, -7, 3); + $lastFour = substr($phoneNumber, -4, 4); + + $phoneNumber = '+'.$countryCode.' ('.$areaCode.') '.$nextThree.'-'.$lastFour; + } + else if(strlen($phoneNumber) == 10) { + $areaCode = substr($phoneNumber, 0, 3); + $nextThree = substr($phoneNumber, 3, 3); + $lastFour = substr($phoneNumber, 6, 4); + + $phoneNumber = '('.$areaCode.') '.$nextThree.'-'.$lastFour; + } + else if(strlen($phoneNumber) == 7) { + $nextThree = substr($phoneNumber, 0, 3); + $lastFour = substr($phoneNumber, 3, 4); + + $phoneNumber = $nextThree.'-'.$lastFour; + } + + return $phoneNumber; + } + +} // End Class \ No newline at end of file diff --git a/app/Helpers/Pagination.php b/app/Helpers/Pagination.php new file mode 100644 index 0000000..c0ba21f --- /dev/null +++ b/app/Helpers/Pagination.php @@ -0,0 +1,177 @@ +_instance = $instance; + $this->_perPage = $perPage; + $this->set_instance(); + } + /** + * get_start + * + * creates the starting point for limiting the dataset + * @return numeric + */ + public function get_start(){ + return ($this->_page * $this->_perPage) - $this->_perPage; + } + /** + * set_instance + * + * sets the instance parameter, if numeric value is 0 then set to 1 + * + * @var numeric + */ + private function set_instance(){ + $this->_page = (int) (!isset($_GET[$this->_instance]) ? 1 : $_GET[$this->_instance]); + $this->_page = ($this->_page == 0 ? 1 : $this->_page); + } + /** + * set_total + * + * collect a numberic value and assigns it to the totalRows + * + * @var numeric + */ + public function set_total($_totalRows){ + $this->_totalRows = $_totalRows; + } + /** + * get_limit + * + * returns the limit for the data source, calling the get_start method and passing in the number of items perp page + * + * @return string + */ + public function get_limit(){ + return "LIMIT ".$this->get_start().",$this->_perPage"; + } + /** + * page_links + * + * create the html links for navigating through the dataset + * + * @var sting $path optionally set the path for the link + * @var sting $ext optionally pass in extra parameters to the GET + * @return string returns the html menu + */ + public function page_links($path='?',$ext=null) + { + $adjacents = "2"; + $prev = $this->_page - 1; + $next = $this->_page + 1; + $lastpage = ceil($this->_totalRows/$this->_perPage); + $lpm1 = $lastpage - 1; + $pagination = ""; + if($lastpage > 1) + { + $pagination .= "\n"; + } + return $pagination; + } +} \ No newline at end of file diff --git a/app/Helpers/PluginManager.php b/app/Helpers/PluginManager.php new file mode 100644 index 0000000..4d06470 --- /dev/null +++ b/app/Helpers/PluginManager.php @@ -0,0 +1,353 @@ +match(). + */ + public static function boot(AltoRouter $router): void { + self::init(); + + $plugins = Db::select( + "SELECT slug FROM sp_plugins WHERE enabled = 1", + [] + ); + + if (!$plugins) return; + + foreach ($plugins as $row) { + $slug = $row['slug']; + $manifestPath = self::$pluginsDir . '/' . $slug . '/plugin.php'; + if (!file_exists($manifestPath)) continue; + + $manifest = require $manifestPath; + foreach ($manifest['routes'] ?? [] as $rule) { + // Each rule: [method, pattern, target, name] + if (count($rule) >= 3) { + $router->map($rule[0], $rule[1], $rule[2], $rule[3] ?? null); + } + } + } + } + + // ─── Install ────────────────────────────────────────────────────────────── + + /** + * Install a plugin: create symlinks, run install.php, upsert DB row (enabled=0). + */ + public static function install(string $slug): array { + self::init(); + + $slug = self::sanitizeSlug($slug); + if (!$slug) return ['success' => false, 'error' => 'Invalid slug']; + + $pluginDir = self::$pluginsDir . '/' . $slug; + if (!is_dir($pluginDir)) { + return ['success' => false, 'error' => "Plugin directory not found: {$slug}"]; + } + + $manifestPath = $pluginDir . '/plugin.php'; + if (!file_exists($manifestPath)) { + return ['success' => false, 'error' => "plugin.php manifest missing for: {$slug}"]; + } + + $manifest = require $manifestPath; + + // Create symlinks + $symlinkResult = self::createSymlinks($slug, $manifest); + if (!$symlinkResult['success']) return $symlinkResult; + + // Run install.php + $installScript = $pluginDir . '/install.php'; + if (file_exists($installScript)) { + try { + require $installScript; + } catch (Throwable $e) { + return ['success' => false, 'error' => 'install.php failed: ' . $e->getMessage()]; + } + } + + // Upsert sp_plugins row (enabled=0 — admin must explicitly enable) + $existing = Db::getRow("SELECT plugin_id FROM sp_plugins WHERE slug = ?", [$slug]); + if ($existing) { + Db::update('sp_plugins', [ + 'name' => $manifest['name'] ?? $slug, + 'version' => $manifest['version'] ?? '1.0.0', + 'description' => $manifest['description'] ?? '', + 'author' => $manifest['author'] ?? '', + 'updated_at' => date('Y-m-d H:i:s'), + ], 'slug = ?', [$slug]); + } else { + Db::insert('sp_plugins', [ + 'slug' => $slug, + 'name' => $manifest['name'] ?? $slug, + 'version' => $manifest['version'] ?? '1.0.0', + 'description' => $manifest['description'] ?? '', + 'author' => $manifest['author'] ?? '', + 'enabled' => 0, + 'installed_at' => date('Y-m-d H:i:s'), + 'updated_at' => date('Y-m-d H:i:s'), + ]); + } + + return ['success' => true, 'message' => "Plugin '{$slug}' installed. Enable it to activate."]; + } + + // ─── Enable ─────────────────────────────────────────────────────────────── + + /** + * Enable a plugin: ensure symlinks exist, set enabled=1. + */ + public static function enable(string $slug): array { + self::init(); + + $slug = self::sanitizeSlug($slug); + $row = Db::getRow("SELECT plugin_id FROM sp_plugins WHERE slug = ?", [$slug]); + if (!$row) return ['success' => false, 'error' => "Plugin '{$slug}' is not installed"]; + + $manifest = self::loadManifest($slug); + if (!$manifest) return ['success' => false, 'error' => "Cannot load manifest for '{$slug}'"]; + + $symlinkResult = self::createSymlinks($slug, $manifest); + if (!$symlinkResult['success']) return $symlinkResult; + + Db::update('sp_plugins', ['enabled' => 1, 'updated_at' => date('Y-m-d H:i:s')], 'slug = ?', [$slug]); + + return ['success' => true, 'message' => "Plugin '{$slug}' enabled"]; + } + + // ─── Disable ────────────────────────────────────────────────────────────── + + /** + * Disable a plugin: remove symlinks, set enabled=0. + * Symlink removal causes natural 404 without any guard code. + */ + public static function disable(string $slug): array { + self::init(); + + $slug = self::sanitizeSlug($slug); + $row = Db::getRow("SELECT plugin_id FROM sp_plugins WHERE slug = ?", [$slug]); + if (!$row) return ['success' => false, 'error' => "Plugin '{$slug}' is not installed"]; + + self::removeSymlinks($slug); + + Db::update('sp_plugins', ['enabled' => 0, 'updated_at' => date('Y-m-d H:i:s')], 'slug = ?', [$slug]); + + return ['success' => true, 'message' => "Plugin '{$slug}' disabled"]; + } + + // ─── Uninstall ──────────────────────────────────────────────────────────── + + /** + * Uninstall a plugin: remove symlinks, run uninstall.php, delete DB row. + */ + public static function uninstall(string $slug): array { + self::init(); + + $slug = self::sanitizeSlug($slug); + + self::removeSymlinks($slug); + + $uninstallScript = self::$pluginsDir . '/' . $slug . '/uninstall.php'; + if (file_exists($uninstallScript)) { + try { + require $uninstallScript; + } catch (Throwable $e) { + // Log but don't abort — clean up DB anyway + } + } + + Db::delete('sp_plugins', ['slug' => $slug]); + + return ['success' => true, 'message' => "Plugin '{$slug}' uninstalled"]; + } + + // ─── Discover ───────────────────────────────────────────────────────────── + + /** + * Scan plugins/ directory and merge with DB rows. + * Returns array of plugin info for the admin UI. + */ + public static function discover(): array { + self::init(); + + // Get all DB rows keyed by slug + $dbRows = []; + $rows = Db::select("SELECT * FROM sp_plugins", []); + foreach ($rows as $row) { + $dbRows[$row['slug']] = $row; + } + + $plugins = []; + + if (!is_dir(self::$pluginsDir)) return $plugins; + + $dirs = glob(self::$pluginsDir . '/*/plugin.php'); + if (!$dirs) return $plugins; + + foreach ($dirs as $manifestPath) { + $slug = basename(dirname($manifestPath)); + $manifest = require $manifestPath; + $dbRow = $dbRows[$slug] ?? null; + + $plugins[] = [ + 'slug' => $slug, + 'name' => $manifest['name'] ?? $slug, + 'version' => $manifest['version'] ?? '1.0.0', + 'description' => $manifest['description'] ?? '', + 'author' => $manifest['author'] ?? '', + 'installed' => $dbRow !== null, + 'enabled' => (bool)($dbRow['enabled'] ?? false), + 'installed_at' => $dbRow['installed_at'] ?? null, + 'plugin_id' => $dbRow['plugin_id'] ?? null, + ]; + } + + return $plugins; + } + + // ─── Helpers ────────────────────────────────────────────────────────────── + + public static function pluginPath(string $slug): string { + self::init(); + return self::$pluginsDir . '/' . $slug; + } + + private static function sanitizeSlug(string $slug): string { + return preg_replace('/[^a-z0-9_-]/', '', strtolower(trim($slug))); + } + + private static function loadManifest(string $slug): ?array { + self::init(); + $path = self::$pluginsDir . '/' . $slug . '/plugin.php'; + return file_exists($path) ? require $path : null; + } + + /** + * Copy plugin files into the framework directories. + * public/controllers/{slug}.php ← plugins/{slug}/{Slug}Controller.php + * public/views/plugins/{slug}/ ← plugins/{slug}/views/{slug}/ + * public/assets/plugins/{slug}/ ← plugins/{slug}/assets/ + */ + private static function createSymlinks(string $slug, array $manifest): array { + self::init(); + + $pluginDir = self::$pluginsDir . '/' . $slug; + $controllerClass = ucfirst($slug) . 'Controller'; + + // Ensure parent dirs exist + foreach ([self::$viewsDir, self::$assetsDir] as $dir) { + if (!is_dir($dir)) { + mkdir($dir, 0755, true); + } + } + + // Copy controller file + $controllerSrc = $pluginDir . '/' . $controllerClass . '.php'; + $controllerDest = self::$controllersDir . '/' . $slug . '.php'; + if (!file_exists($controllerSrc)) { + return ['success' => false, 'error' => "Controller not found: {$controllerClass}.php"]; + } + if (!copy($controllerSrc, $controllerDest)) { + return ['success' => false, 'error' => "Failed to copy controller to: {$controllerDest}"]; + } + + // Copy views directory (optional) + $viewsSrc = $pluginDir . '/views/' . $slug; + $viewsDest = self::$viewsDir . '/' . $slug; + if (is_dir($viewsSrc)) { + $result = self::copyDir($viewsSrc, $viewsDest); + if (!$result) { + return ['success' => false, 'error' => "Failed to copy views to: {$viewsDest}"]; + } + } + + // Copy assets directory (optional) + $assetsSrc = $pluginDir . '/assets'; + $assetsDest = self::$assetsDir . '/' . $slug; + if (is_dir($assetsSrc)) { + $result = self::copyDir($assetsSrc, $assetsDest); + if (!$result) { + return ['success' => false, 'error' => "Failed to copy assets to: {$assetsDest}"]; + } + } + + return ['success' => true]; + } + + /** + * Remove copied plugin files from framework directories. + */ + private static function removeSymlinks(string $slug): void { + self::init(); + + $controllerFile = self::$controllersDir . '/' . $slug . '.php'; + if (file_exists($controllerFile)) { + unlink($controllerFile); + } + + foreach ([self::$viewsDir . '/' . $slug, self::$assetsDir . '/' . $slug] as $dir) { + if (is_dir($dir)) { + self::removeDir($dir); + } + } + } + + /** + * Recursively copy a directory. + */ + private static function copyDir(string $src, string $dest): bool { + if (!is_dir($dest)) { + mkdir($dest, 0755, true); + } + foreach (scandir($src) as $item) { + if ($item === '.' || $item === '..') continue; + $s = $src . '/' . $item; + $d = $dest . '/' . $item; + if (is_dir($s)) { + if (!self::copyDir($s, $d)) return false; + } else { + if (!copy($s, $d)) return false; + } + } + return true; + } + + /** + * Recursively delete a directory. + */ + private static function removeDir(string $dir): void { + foreach (scandir($dir) as $item) { + if ($item === '.' || $item === '..') continue; + $path = $dir . '/' . $item; + is_dir($path) ? self::removeDir($path) : unlink($path); + } + rmdir($dir); + } +} diff --git a/app/Helpers/Router.php b/app/Helpers/Router.php new file mode 100644 index 0000000..1af81d6 --- /dev/null +++ b/app/Helpers/Router.php @@ -0,0 +1,31 @@ +setBasePath(''); + + // Rules Set Here + // $router->map('GET|POST', '/orgs/[*:orgid]', array('c' => 'orgs', 'a' => 'index')); + + PluginManager::boot($router); + + return $router->match(); + + } +} \ No newline at end of file diff --git a/app/Helpers/Validate.php b/app/Helpers/Validate.php new file mode 100644 index 0000000..c0f2cc0 --- /dev/null +++ b/app/Helpers/Validate.php @@ -0,0 +1,26 @@ + 1]); + +// SELECT single row +$user = Db::getRow("SELECT * FROM sp_users WHERE id = :id", [':id' => $id]); +if (!$user) { die('Not found'); } + +// INSERT +$userId = Db::insert('sp_users', [ + 'name' => 'Jane Doe', + 'email' => 'jane@example.com', + 'created_at' => date('Y-m-d H:i:s') +]); + +// UPDATE +Db::update('sp_users', + ['name' => 'Jane Smith', 'updated_at' => date('Y-m-d H:i:s')], + 'id = :id', + [':id' => $userId] +); + +// DELETE +Db::delete('sp_users', 'id = :id', [':id' => $userId]); + +// Soft delete (preferred) +Db::update('sp_users', ['deleted_at' => date('Y-m-d H:i:s')], 'id = :id', [':id' => $userId]); + +// Custom query +Db::execute("UPDATE sp_users SET last_login = NOW() WHERE id = :id", [':id' => $id]); + +// Transaction +Db::beginTransaction(); +try { + $orderId = Db::insert('sp_orders', ['user_id' => $userId, 'total' => 100]); + Db::execute("UPDATE sp_users SET balance = balance - 100 WHERE id = :id", [':id' => $userId]); + Db::commit(); +} catch (Exception $e) { + Db::rollback(); + throw $e; +} +``` + +### Complex Query Patterns + +```php +// Aggregation +$stats = Db::getRow("SELECT COUNT(*) as total, SUM(amount) as revenue FROM sp_orders WHERE status = 'completed'"); +echo $stats['total']; + +// Subquery +$users = Db::select(" + SELECT u.*, + (SELECT COUNT(*) FROM sp_orders WHERE user_id = u.id) as order_count + FROM sp_users u WHERE u.active = 1 +"); + +// Dynamic filter building +$query = "SELECT * FROM sp_clients WHERE 1=1"; +$params = []; +if (!empty($_GET['q'])) { + $query .= " AND (name LIKE :q OR email LIKE :q)"; + $params[':q'] = '%' . $_GET['q'] . '%'; +} +if (!empty($_GET['status'])) { + $query .= " AND status = :status"; + $params[':status'] = $_GET['status']; +} +$clients = Db::select($query, $params); + +// Pagination +$page = max(1, (int)($_GET['page'] ?? 1)); +$perPage = 20; +$offset = ($page - 1) * $perPage; +$total = Db::getRow("SELECT COUNT(*) as count FROM sp_clients"); +$rows = Db::select("SELECT * FROM sp_clients LIMIT :limit OFFSET :offset", + [':limit' => $perPage, ':offset' => $offset] +); + +// Use EXISTS instead of COUNT for existence checks (faster) +$exists = Db::getRow("SELECT 1 FROM sp_users WHERE email = :email LIMIT 1", [':email' => $email]); +if ($exists) { ... } +``` + +--- + +## `Auth.php` — Authentication + +```php +Auth::handleLogin() // redirect to /login if not authenticated +Auth::can('users.create') // returns bool — check permission +Auth::requirePermission('users.delete') // halts with 403 JSON if denied +Auth::isLoggedIn() // returns bool +Auth::getUserId() // returns current user ID from session +Auth::getUser() // returns current user row +Auth::login($userId) // set session login state +Auth::logout() // destroy session +``` + +--- + +## `Session.php` — Session Management + +```php +Session::start() +Session::set($key, $value) +Session::get($key, $default = null) +Session::has($key) +Session::delete($key) +Session::destroy() +Session::flash($key, $value) // set one-time flash message +Session::getFlash($key) // get and delete flash message +``` + +--- + +## `Validator.php` — Input Validation + +```php +Validator::required($value) // not empty +Validator::email($value) // valid email format +Validator::minLength($value, $min) +Validator::maxLength($value, $max) +Validator::numeric($value) +Validator::alpha($value) +Validator::alphanumeric($value) +Validator::url($value) +Validator::date($value) +``` + +--- + +## `Functions.php` — General Purpose + +Key reusable helpers: + +```php +// Decrypt tokenized org slug → fetch active org row (dies on invalid) +Functions::resolveOrg($slug) + +// Decrypt tokenized user slug → fetch user+role row (dies on invalid) +Functions::resolveUser($slug) + +// AES-256-CBC URL-safe encrypt / decrypt +Functions::encryptData($data, $key) +Functions::decryptData($token, $key) +``` + +--- + +## `Hash.php` — Cryptography + +Crypto utilities (hashing, token generation, etc.). + +--- + +## Adding New Helpers + +1. Find the most semantically appropriate existing class. +2. Add a `public static function` to it. +3. If nothing fits, create `app/Helpers/NewHelper.php` as a static class. +4. Call from any controller or other helper — no instantiation needed. diff --git a/app/Helpers/helpers.php b/app/Helpers/helpers.php new file mode 100644 index 0000000..3796c29 --- /dev/null +++ b/app/Helpers/helpers.php @@ -0,0 +1,8 @@ + 'claude-opus-4-20250514', + 'sonnet' => 'claude-sonnet-4-5-20250929', + 'haiku' => 'claude-haiku-4-5-20251001', + ]; + + /** + * Constructor + */ + public function __construct(?string $apiKey = null, ?string $templatePath = null, int $timeout = 300) { + $this->apiKey = $apiKey ?? ANTHROPIC_API_KEY; + $this->templatePath = $templatePath ?? dirname(__DIR__, 2) . '/templates/prompts/'; + + + if (empty($this->apiKey)) { + throw new Exception('Anthropic API key is required.'); + } + + if (!is_dir($this->templatePath)) { + throw new Exception("Template directory not found: {$this->templatePath}"); + } + + if (!is_readable($this->templatePath)) { + throw new Exception("Template directory is not readable: {$this->templatePath}"); + } + + set_time_limit($timeout); + ini_set('default_socket_timeout', (string) $timeout); + + $this->client = new Client(apiKey: $this->apiKey); + } + + + // ========================================================================= + // CONNECTION & HEALTH + // ========================================================================= + + public function testConnection(): array + { + $startTime = microtime(true); + + try { + $response = $this->client->messages->create( + model: $this->defaultModel, + maxTokens: 10, + messages: [ + MessageParam::with(role: 'user', content: 'Reply with only: OK') + ] + ); + + $latency = round((microtime(true) - $startTime) * 1000); + + return [ + 'success' => true, + 'message' => 'Connected to Anthropic API', + 'model' => $this->defaultModel, + 'response' => trim($response->content[0]->text), + 'latency_ms' => $latency, + 'usage' => [ + 'input_tokens' => $response->usage->inputTokens, + 'output_tokens' => $response->usage->outputTokens, + ] + ]; + + } catch (Exception $e) { + return [ + 'success' => false, + 'message' => 'Connection failed', + 'error' => $e->getMessage(), + 'error_type' => get_class($e) + ]; + } + } + + public function isConnected(): bool + { + return $this->testConnection()['success']; + } + + public function getStatus(): array + { + $connection = $this->testConnection(); + + return [ + 'connected' => $connection['success'], + 'model' => $this->defaultModel, + 'max_tokens' => $this->defaultMaxTokens, + 'api_key_preview' => substr($this->apiKey, 0, 10) . '...' . substr($this->apiKey, -4), + 'template_path' => $this->templatePath, + 'connection_details' => $connection + ]; + } + + // ========================================================================= + // TEMPLATE LOADING + // ========================================================================= + + public function loadTemplate(string $filename): string + { + $path = $this->templatePath . '/' . $filename; + + if (!file_exists($path)) { + throw new Exception("Template not found: {$path}"); + } + + $content = file_get_contents($path); + + if ($content === false) { + throw new Exception("Failed to read template: {$path}"); + } + + return $content; + } + + public function setTemplatePath(string $path): self + { + $this->templatePath = rtrim($path, '/'); + return $this; + } + + public function getTemplatePath(): string + { + return $this->templatePath; + } + + // ========================================================================= + // BASIC MESSAGING + // ========================================================================= + + public function message(string $prompt, ?string $systemPrompt = null, array $options = []): string + { + $response = $this->messageWithMeta($prompt, $systemPrompt, $options); + return $response['content']; + } + + public function messageWithMeta(string $prompt, ?string $systemPrompt = null, array $options = []): array + { + $startTime = microtime(true); + + $messages = $this->buildMessages($prompt, $options['conversation'] ?? []); + + $params = [ + 'model' => $options['model'] ?? $this->defaultModel, + 'maxTokens' => $options['max_tokens'] ?? $this->defaultMaxTokens, + 'messages' => $messages, + ]; + + if ($systemPrompt) { + $params['system'] = $systemPrompt; + } + + if (isset($options['temperature'])) { + $params['temperature'] = $options['temperature']; + } + + if (isset($options['stop_sequences'])) { + $params['stopSequences'] = $options['stop_sequences']; + } + + $response = $this->client->messages->create(...$params); + + return [ + 'content' => $response->content[0]->text, + 'model' => $response->model, + 'stop_reason' => $response->stopReason, + 'latency_ms' => round((microtime(true) - $startTime) * 1000), + 'usage' => [ + 'input_tokens' => $response->usage->inputTokens, + 'output_tokens' => $response->usage->outputTokens, + 'total_tokens' => $response->usage->inputTokens + $response->usage->outputTokens, + ] + ]; + } + + public function conversation(array $messages, ?string $systemPrompt = null, array $options = []): array + { + $messageParams = []; + foreach ($messages as $msg) { + $messageParams[] = MessageParam::with(role: $msg['role'], content: $msg['content']); + } + + $params = [ + 'model' => $options['model'] ?? $this->defaultModel, + 'maxTokens' => $options['max_tokens'] ?? $this->defaultMaxTokens, + 'messages' => $messageParams, + ]; + + if ($systemPrompt) { + $params['system'] = $systemPrompt; + } + + $response = $this->client->messages->create(...$params); + + return [ + 'content' => $response->content[0]->text, + 'usage' => [ + 'input_tokens' => $response->usage->inputTokens, + 'output_tokens' => $response->usage->outputTokens, + ] + ]; + } + + + // ========================================================================= + // CACHED MESSAGING + // ========================================================================= + + /** + * Message with prompt caching - template is cached, variables are not + */ + public function messageWithCache( + string $prompt, + string $cachedContext, + ?string $dynamicContext = null, + array $options = [] + ): array { + $startTime = microtime(true); + + $systemBlocks = [ + [ + 'type' => 'text', + 'text' => $cachedContext, + 'cache_control' => ['type' => 'ephemeral'] + ] + ]; + + if ($dynamicContext) { + $systemBlocks[] = [ + 'type' => 'text', + 'text' => $dynamicContext + ]; + } + + $response = $this->client->messages->create( + model: $options['model'] ?? $this->defaultModel, + maxTokens: $options['max_tokens'] ?? $this->defaultMaxTokens, + system: $systemBlocks, + messages: [ + MessageParam::with(role: 'user', content: $prompt) + ] + ); + + $cacheCreation = $response->usage->cacheCreationInputTokens ?? 0; + $cacheRead = $response->usage->cacheReadInputTokens ?? 0; + + return [ + 'content' => $response->content[0]->text, + 'model' => $response->model, + 'latency_ms' => round((microtime(true) - $startTime) * 1000), + 'usage' => [ + 'input_tokens' => $response->usage->inputTokens, + 'output_tokens' => $response->usage->outputTokens, + 'cache_creation_tokens' => $cacheCreation, + 'cache_read_tokens' => $cacheRead, + ], + 'cache_hit' => $cacheRead > 0, + 'cache_status' => $this->getCacheStatus($cacheCreation, $cacheRead) + ]; + } + + private function getCacheStatus(int $created, int $read): string + { + if ($read > 0) return 'hit'; + if ($created > 0) return 'created'; + return 'none'; + } + + // ========================================================================= + // ARTICLE GENERATION + // ========================================================================= + + /** + * Build the dynamic variables block (topic, word count, etc.) + * This is NOT cached - changes per article + */ + public function buildArticleVariables(array $config): string + { + $lines = []; + + if (!empty($config['topic'])) { + $lines[] = "TOPIC: {$config['topic']}"; + } + if (!empty($config['domain'])) { + $lines[] = "DOMAIN: {$config['domain']}"; + } + if (!empty($config['industry'])) { + $lines[] = "INDUSTRY: {$config['industry']}"; + } + if (!empty($config['target_audience'])) { + $lines[] = "AUDIENCE: {$config['target_audience']}"; + } + if (!empty($config['knowledge_level'])) { + $lines[] = "KNOWLEDGE_LEVEL: {$config['knowledge_level']}"; + } + if (!empty($config['tone'])) { + $lines[] = "TONE: {$config['tone']}"; + } + if (!empty($config['word_count'])) { + $lines[] = "WORD_COUNT: {$config['word_count']}"; + } + if (!empty($config['location'])) { + $lines[] = "LOCATION: {$config['location']}"; + } + + $lines[] = "CURRENT_DATE: " . date('Y-m-d'); + + return implode("\n", $lines); + } + + /** + * Generate article using template + variables (legacy single-file method) + * Template (with business profile) is cached + * Variables (topic, word count, etc.) are dynamic + */ + public function generateArticle(string $templateFile, array $config): array + { + // Load template - this includes the master prompt + business profile + // This part gets cached + $template = $this->loadTemplate($templateFile); + + // Build dynamic variables - this changes per article + $variables = $this->buildArticleVariables($config); + + return $this->messageWithCache( + prompt: $config['prompt'] ?? 'BEGIN GENERATION NOW.', + cachedContext: $template, + dynamicContext: $variables, + options: [ + 'max_tokens' => $config['max_tokens'] ?? 8192, + 'model' => $config['model'] ?? $this->defaultModel + ] + ); + } + + /** + * Generate article using 3-block structure with prompt caching + * + * Block 1: Business Profile (cached - client specific) + * Block 2: Article Instructions (cached - shared across all clients) + * Block 3: Config Variables (dynamic - not cached) + * + * Cache behavior: + * - Blocks 1 & 2 are cached together as a prefix + * - When processing multiple articles for same client, cache hits on blocks 1+2 + * - Block 3 varies per article, never cached + * + * @param string $businessProfile Client-specific business profile content + * @param string $articleInstructions Shared writing instructions content + * @param array $config Article configuration (topic, word_count, etc.) + * - 'enable_web_search' => bool (default: false) + * - 'reference_urls' => array of URLs to research + * @return array Response with content, usage, and cache status + */ +// ============================================================================= +// UPDATED Anthropic::generateArticleWithBlocks() +// ============================================================================= + public function generateArticleWithBlocks( + string $businessProfile, + string $articleInstructions, + array $projectConfig, + string $referenceContent = '', + bool $useWebSearch = false + ): array { + $startTime = microtime(true); + + // Build article variables (Block 3) + $articleVariables = $this->buildArticleVariables($projectConfig); + + // DEEPCRAWL MODE: Inject pre-fetched content into Block 3 + if (!empty($referenceContent)) { + $articleVariables .= "\n\n" . $referenceContent; + } + + // WEBSEARCH MODE: Add URL hints for Claude to search + if ($useWebSearch && !empty($projectConfig['reference_urls'])) { + $urlHints = "\n\n\n"; + $urlHints .= "Consider searching these sources for authoritative information:\n"; + foreach ($projectConfig['reference_urls'] as $ref) { + $url = is_array($ref) ? $ref['url'] : $ref; + $notes = is_array($ref) ? ($ref['notes'] ?? '') : ''; + $urlHints .= "- {$url}"; + if ($notes) $urlHints .= " ({$notes})"; + $urlHints .= "\n"; + } + $urlHints .= ""; + $articleVariables .= $urlHints; + } + + // ========================================================================= + // BUILD THE FULL PROMPT (for logging) + // ========================================================================= + $block1 = "\n{$businessProfile}\n"; + $block2 = "\n{$articleInstructions}\n"; + $block3 = "\n{$articleVariables}\n"; + + $fullPrompt = $block1 . "\n\n" . $block2 . "\n\n" . $block3; + + // Build messages + $messages = [ + [ + 'role' => 'user', + 'content' => [ + // Block 1: Business Profile (cached) + [ + 'type' => 'text', + 'text' => $block1, + 'cache_control' => ['type' => 'ephemeral'] + ], + // Block 2: Article Instructions (cached) + [ + 'type' => 'text', + 'text' => $block2, + 'cache_control' => ['type' => 'ephemeral'] + ], + // Block 3: Article Variables + References (dynamic) + [ + 'type' => 'text', + 'text' => $block3 + ] + ] + ] + ]; + + // Call API + if ($useWebSearch) { + $response = $this->client->messages->create( + model: 'claude-sonnet-4-5-20250929', + maxTokens: 8192, + messages: $messages, + tools: [ + [ + 'type' => 'web_search_20250305', + 'name' => 'web_search', + ] + ] + ); + } else { + $response = $this->client->messages->create( + model: 'claude-sonnet-4-5-20250929', + maxTokens: 8192, + messages: $messages + ); + } + + // Parse response + $cacheCreation = $response->usage->cacheCreationInputTokens ?? 0; + $cacheRead = $response->usage->cacheReadInputTokens ?? 0; + + return [ + 'content' => $response->content[0]->text, + 'model' => $response->model, + 'stop_reason' => $response->stopReason, + 'latency_ms' => round((microtime(true) - $startTime) * 1000), + 'usage' => [ + 'input_tokens' => $response->usage->inputTokens, + 'output_tokens' => $response->usage->outputTokens, + 'cache_creation_tokens' => $cacheCreation, + 'cache_read_tokens' => $cacheRead, + ], + 'cache_status' => $this->getCacheStatus($cacheCreation, $cacheRead), + 'prompt_sent' => $fullPrompt, // NEW: Full prompt for logging + ]; + } + + + /** + * Build instruction for reference URLs + */ + private function buildReferenceUrlsInstruction(array $urls): string + { + $instruction = "REFERENCE URLS FOR RESEARCH:\n"; + $instruction .= "Search and reference the following URLs for accurate, current information:\n\n"; + + foreach ($urls as $index => $url) { + $num = $index + 1; + if (is_array($url)) { + // URL with description: ['url' => '...', 'description' => '...'] + $instruction .= "{$num}. {$url['url']}\n"; + if (!empty($url['description'])) { + $instruction .= " Purpose: {$url['description']}\n"; + } + } else { + // Simple URL string + $instruction .= "{$num}. {$url}\n"; + } + } + + $instruction .= "\nUse web search to fetch current information from these sources. "; + $instruction .= "Cite specific facts, statistics, or requirements found on these pages. "; + $instruction .= "You may also search for additional supporting information as needed."; + + return $instruction; + } + + /** + * Extract text content from response blocks (handles mixed content with tool use) + */ + private function extractTextContent(array $contentBlocks): string + { + $textParts = []; + + foreach ($contentBlocks as $block) { + if (isset($block->type) && $block->type === 'text') { + $textParts[] = $block->text; + } + } + + return implode("\n", $textParts); + } + + /** + * Generate article using multi-turn conversation style + * + * This approach chunks the prompt into conversational turns: + * - Turn 1: User sends business profile + * - Turn 2: Assistant acknowledges persona + * - Turn 3: User sends article instructions + * - Turn 4: Assistant confirms understanding + * - Turn 5: User sends config and triggers generation + * + * Benefits: May improve instruction following for complex prompts + * Tradeoff: Extra tokens for assistant acknowledgments + * + * @param string $businessProfile Client-specific business profile content + * @param string $articleInstructions Shared writing instructions content + * @param array $config Article configuration (topic, word_count, etc.) + * @return array Response with content, usage, and cache status + */ + public function generateArticleMultiTurn( + string $businessProfile, + string $articleInstructions, + array $config + ): array { + $startTime = microtime(true); + + // Build dynamic config variables + $configVariables = $this->buildArticleVariables($config); + + $response = $this->client->messages->create( + model: $config['model'] ?? $this->defaultModel, + maxTokens: $config['max_tokens'] ?? 8192, + messages: [ + // Turn 1: Business Profile + [ + 'role' => 'user', + 'content' => [ + [ + 'type' => 'text', + 'text' => "Here is the business profile you will write as:\n\n" . $businessProfile, + 'cache_control' => ['type' => 'ephemeral'], + ], + ], + ], + // Turn 2: Assistant acknowledges persona + [ + 'role' => 'assistant', + 'content' => [ + [ + 'type' => 'text', + 'text' => "I understand. I am now embodying this business identity and will write from their perspective, using their voice, expertise, and experience as defined in the profile.", + 'cache_control' => ['type' => 'ephemeral'], + ], + ], + ], + // Turn 3: Article Instructions + [ + 'role' => 'user', + 'content' => [ + [ + 'type' => 'text', + 'text' => "Here are the article writing instructions:\n\n" . $articleInstructions, + 'cache_control' => ['type' => 'ephemeral'], + ], + ], + ], + // Turn 4: Assistant confirms understanding + [ + 'role' => 'assistant', + 'content' => [ + [ + 'type' => 'text', + 'text' => "Ready. I will follow these writing rules: early answer with recommendation, 'This breaks down when...' statement, field notes from practitioner experience, no blacklisted phrases, boundary conditions section, and output valid JSON only.", + 'cache_control' => ['type' => 'ephemeral'], + ], + ], + ], + // Turn 5: Config Variables + Generate + [ + 'role' => 'user', + 'content' => $configVariables . "\n\nBEGIN GENERATION.", + ], + ], + ); + + $cacheCreation = $response->usage->cacheCreationInputTokens ?? 0; + $cacheRead = $response->usage->cacheReadInputTokens ?? 0; + + return [ + 'content' => $response->content[0]->text, + 'model' => $response->model, + 'stop_reason' => $response->stopReason, + 'latency_ms' => round((microtime(true) - $startTime) * 1000), + 'usage' => [ + 'input_tokens' => $response->usage->inputTokens, + 'output_tokens' => $response->usage->outputTokens, + 'cache_creation_tokens' => $cacheCreation, + 'cache_read_tokens' => $cacheRead, + ], + 'cache_hit' => $cacheRead > 0, + 'cache_status' => $this->getCacheStatus($cacheCreation, $cacheRead), + ]; + } + + /** + * Generate multiple articles (same template = cache hits after first) + */ + public function generateArticleBatch(string $templateFile, array $articles): array + { + $results = []; + + foreach ($articles as $index => $config) { + $results[] = [ + 'index' => $index, + 'topic' => $config['topic'] ?? 'Unknown', + 'result' => $this->generateArticle($templateFile, $config) + ]; + } + + return $results; + } + + // ========================================================================= + // UTILITY METHODS + // ========================================================================= + + private function buildMessages(string $prompt, array $conversation = []): array + { + $messages = []; + + foreach ($conversation as $msg) { + $messages[] = MessageParam::with(role: $msg['role'], content: $msg['content']); + } + + $messages[] = MessageParam::with(role: 'user', content: $prompt); + + return $messages; + } + + public function estimateTokens(string $text): int + { + return (int) ceil(strlen($text) / 4); + } + + /** + * Check if content meets minimum cache threshold (1024 tokens for Sonnet) + */ + public function meetsCacheMinimum(string $text): bool + { + return $this->estimateTokens($text) >= 1024; + } + + public function estimateCost(int $inputTokens, int $outputTokens, ?string $model = null): array + { + $model = $model ?? $this->defaultModel; + + $pricing = [ + 'claude-opus-4-20250514' => ['input' => 15.00, 'output' => 75.00], + 'claude-sonnet-4-5-20250929' => ['input' => 3.00, 'output' => 15.00], + 'claude-haiku-4-5-20251001' => ['input' => 0.80, 'output' => 4.00], + ]; + + $rates = $pricing[$model] ?? $pricing['claude-sonnet-4-5-20250929']; + + $inputCost = ($inputTokens / 1_000_000) * $rates['input']; + $outputCost = ($outputTokens / 1_000_000) * $rates['output']; + + return [ + 'input_cost' => round($inputCost, 6), + 'output_cost' => round($outputCost, 6), + 'total_cost' => round($inputCost + $outputCost, 6), + 'model' => $model + ]; + } + + public function formatCost(array $usage): string { + $cost = $this->estimateCostWithCache($usage); + + $output = "Cost Breakdown:\n"; + $output .= " Input: $" . number_format($cost['input_cost'], 4) . "\n"; + $output .= " Output: $" . number_format($cost['output_cost'], 4) . "\n"; + + if ($cost['cache_write_cost'] > 0) { + $output .= " Cache Write: $" . number_format($cost['cache_write_cost'], 4) . "\n"; + } + if ($cost['cache_read_cost'] > 0) { + $output .= " Cache Read: $" . number_format($cost['cache_read_cost'], 4) . "\n"; + } + + $output .= " ─────────────\n"; + $output .= " Total: $" . number_format($cost['total_cost'], 4) . "\n"; + + if ($cost['savings'] > 0) { + $output .= " Saved: $" . number_format($cost['savings'], 4) . " ({$cost['savings_percent']}%)\n"; + } + + return $output; + } + + public function estimateCostWithCache(array $usage, ?string $model = null): array + { + $model = $model ?? $this->defaultModel; + + $pricing = [ + 'claude-opus-4-20250514' => ['input' => 15.00, 'output' => 75.00, 'cache_write' => 18.75, 'cache_read' => 1.50], + 'claude-sonnet-4-5-20250929' => ['input' => 3.00, 'output' => 15.00, 'cache_write' => 3.75, 'cache_read' => 0.30], + 'claude-haiku-4-5-20251001' => ['input' => 0.80, 'output' => 4.00, 'cache_write' => 1.00, 'cache_read' => 0.08], + ]; + + $rates = $pricing[$model] ?? $pricing['claude-sonnet-4-5-20250929']; + + $inputCost = (($usage['input_tokens'] ?? 0) / 1_000_000) * $rates['input']; + $outputCost = (($usage['output_tokens'] ?? 0) / 1_000_000) * $rates['output']; + $cacheWriteCost = (($usage['cache_creation_tokens'] ?? 0) / 1_000_000) * $rates['cache_write']; + $cacheReadCost = (($usage['cache_read_tokens'] ?? 0) / 1_000_000) * $rates['cache_read']; + + $totalCost = $inputCost + $outputCost + $cacheWriteCost + $cacheReadCost; + + $cacheTokens = ($usage['cache_creation_tokens'] ?? 0) + ($usage['cache_read_tokens'] ?? 0); + $costWithoutCache = (($usage['input_tokens'] + $cacheTokens) / 1_000_000) * $rates['input'] + $outputCost; + $savings = $costWithoutCache - $totalCost; + + return [ + 'input_cost' => round($inputCost, 6), + 'output_cost' => round($outputCost, 6), + 'cache_write_cost' => round($cacheWriteCost, 6), + 'cache_read_cost' => round($cacheReadCost, 6), + 'total_cost' => round($totalCost, 6), + 'cost_without_cache' => round($costWithoutCache, 6), + 'savings' => round($savings, 6), + 'savings_percent' => $costWithoutCache > 0 ? round(($savings / $costWithoutCache) * 100, 2) : 0, + 'model' => $model + ]; + } + + // ========================================================================= + // CONFIGURATION + // ========================================================================= + + public function setModel(string $model): self + { + $this->defaultModel = $model; + return $this; + } + + public function useModel(string $preset): self + { + if (isset(self::MODELS[$preset])) { + $this->defaultModel = self::MODELS[$preset]; + } + return $this; + } + + public function setMaxTokens(int $tokens): self + { + $this->defaultMaxTokens = $tokens; + return $this; + } + + public function getModel(): string + { + return $this->defaultModel; + } + + public function getClient(): Client + { + return $this->client; + } + + public function getAvailableModels(): array + { + return self::MODELS; + } +} \ No newline at end of file diff --git a/app/LLM/GeminiService.php b/app/LLM/GeminiService.php new file mode 100644 index 0000000..af48ce3 --- /dev/null +++ b/app/LLM/GeminiService.php @@ -0,0 +1,749 @@ + 'gemini-3-pro-preview', // Best for humanizing, SEO strategy + 'flash' => 'gemini-3-flash-preview', // Faster, good for SEO review pass + 'flash-lite' => 'gemini-2.5-flash-lite', // Cost saver for simple tasks + 'flash-2.5' => 'gemini-2.5-flash', // Legacy flash model + ]; + + // Human-readable labels for UI dropdowns + public const MODEL_LABELS = [ + 'gemini-3-pro-preview' => 'Gemini 3 Pro (Best for Humanizing)', + 'gemini-3-flash-preview' => 'Gemini 3 Flash (Fast & Smart)', + 'gemini-2.5-flash' => 'Gemini 2.5 Flash (Legacy)', + 'gemini-2.5-flash-lite' => 'Gemini 2.5 Flash Lite (Budget)', + ]; + + /** + * Constructor + */ + public function __construct(?string $apiKey = null, ?string $templatePath = null, int $timeout = 300) + { + $this->apiKey = $apiKey ?? (defined('GEMINI_API_KEY') ? GEMINI_API_KEY : null); + + if (empty($this->apiKey)) { + throw new Exception('Gemini API key is required.'); + } + + $this->templatePath = $templatePath ?? '/www/wwwroot/appCarlos/templates/prompts'; + + if (!is_dir($this->templatePath)) { + throw new Exception("Template directory not found: {$this->templatePath}"); + } + + if (!is_readable($this->templatePath)) { + throw new Exception("Template directory is not readable: {$this->templatePath}"); + } + + set_time_limit($timeout); + ini_set('default_socket_timeout', (string) $timeout); + + $this->client = GeminiClient::client($this->apiKey); + } + + // ========================================================================= + // CONNECTION & HEALTH + // ========================================================================= + + public function testConnection(): array + { + $startTime = microtime(true); + + try { + $response = $this->client + ->generativeModel(model: $this->defaultModel) + ->generateContent('Reply with only: OK'); + + $latency = round((microtime(true) - $startTime) * 1000); + + // Extract usage from response + $usage = $this->extractUsage($response); + + return [ + 'success' => true, + 'message' => 'Connected to Gemini API', + 'model' => $this->defaultModel, + 'response' => trim($response->text()), + 'latency_ms' => $latency, + 'usage' => $usage, + ]; + + } catch (Exception $e) { + return [ + 'success' => false, + 'message' => 'Connection failed', + 'error' => $e->getMessage(), + 'error_type' => get_class($e), + ]; + } + } + + public function isConnected(): bool + { + return $this->testConnection()['success']; + } + + public function getStatus(): array + { + $connection = $this->testConnection(); + + return [ + 'connected' => $connection['success'], + 'model' => $this->defaultModel, + 'max_tokens' => $this->defaultMaxTokens, + 'api_key_preview' => substr($this->apiKey, 0, 10) . '...' . substr($this->apiKey, -4), + 'template_path' => $this->templatePath, + 'connection_details' => $connection, + ]; + } + + // ========================================================================= + // TEMPLATE LOADING + // ========================================================================= + + public function loadTemplate(string $filename): string + { + $path = $this->templatePath . '/' . $filename; + + if (!file_exists($path)) { + throw new Exception("Template not found: {$path}"); + } + + $content = file_get_contents($path); + + if ($content === false) { + throw new Exception("Failed to read template: {$path}"); + } + + return $content; + } + + public function setTemplatePath(string $path): self + { + $this->templatePath = rtrim($path, '/'); + return $this; + } + + public function getTemplatePath(): string + { + return $this->templatePath; + } + + // ========================================================================= + // BASIC MESSAGING + // ========================================================================= + + public function message(string $prompt, ?string $systemPrompt = null, array $options = []): string + { + $response = $this->messageWithMeta($prompt, $systemPrompt, $options); + return $response['content']; + } + + /** + * GEMINI SERVICE UPDATE + * + * Replace the existing messageWithMeta() method with this updated version + * that supports JSON response mode via 'response_mime_type' option. + * + * Location: App\Components\GeminiService + * Method: messageWithMeta() + */ + + public function messageWithMeta(string $prompt, ?string $systemPrompt = null, array $options = []): array + { + $startTime = microtime(true); + + $model = $this->client->generativeModel( + model: $options['model'] ?? $this->defaultModel + ); + + // Add system instruction if provided + if ($systemPrompt) { + $model = $model->withSystemInstruction(Content::parse($systemPrompt)); + } + + // Build generation config + $configParams = [ + 'maxOutputTokens' => $options['max_tokens'] ?? $this->defaultMaxTokens, + ]; + + if (isset($options['response_mime_type']) && $options['response_mime_type'] === 'application/json') { + $configParams['responseMimeType'] = ResponseMimeType::APPLICATION_JSON; + } + + if (isset($options['temperature'])) { + $configParams['temperature'] = $options['temperature']; + } + + if (isset($options['stop_sequences'])) { + $configParams['stopSequences'] = $options['stop_sequences']; + } + + // Support JSON response mode + if (isset($options['response_mime_type']) && $options['response_mime_type'] === 'application/json') { + $configParams['responseMimeType'] = ResponseMimeType::APPLICATION_JSON; + } + + $model = $model->withGenerationConfig(new GenerationConfig(...$configParams)); + + // Enable Google Search if requested + if ($options['use_search'] ?? false) { + $model = $model->withTool(new Tool(googleSearch: new GoogleSearch())); + } + + $response = $model->generateContent($prompt); + + $usage = $this->extractUsage($response); + + return [ + 'content' => $response->text(), + 'model' => $options['model'] ?? $this->defaultModel, + 'stop_reason' => $response->candidates[0]->finishReason ?? null, + 'latency_ms' => round((microtime(true) - $startTime) * 1000), + 'usage' => $usage, + ]; + } + + + // ========================================================================= + // ARTICLE GENERATION + // ========================================================================= + + /** + * Build the dynamic variables block (topic, word count, etc.) + */ + public function buildArticleVariables(array $config): string + { + $lines = []; + + if (!empty($config['topic'])) { + $lines[] = "TOPIC: {$config['topic']}"; + } + if (!empty($config['domain'])) { + $lines[] = "DOMAIN: {$config['domain']}"; + } + if (!empty($config['industry'])) { + $lines[] = "INDUSTRY: {$config['industry']}"; + } + if (!empty($config['target_audience'])) { + $lines[] = "AUDIENCE: {$config['target_audience']}"; + } + if (!empty($config['knowledge_level'])) { + $lines[] = "KNOWLEDGE_LEVEL: {$config['knowledge_level']}"; + } + if (!empty($config['tone'])) { + $lines[] = "TONE: {$config['tone']}"; + } + if (!empty($config['word_count'])) { + $lines[] = "WORD_COUNT: {$config['word_count']}"; + } + if (!empty($config['location'])) { + $lines[] = "LOCATION: {$config['location']}"; + } + + return implode("\n", $lines); + } + + /** + * Generate article using template + variables + * Uses Google Search for fact verification + */ + public function generateArticle(string $templateFile, array $config): array + { + $startTime = microtime(true); + + // Load template - this includes the master prompt + business profile + $template = $this->loadTemplate($templateFile); + + // Build dynamic variables + $variables = $this->buildArticleVariables($config); + + // Combine template with variables + $systemPrompt = $template . "\n\n" . $variables; + + // Build the model with configuration + $model = $this->client->generativeModel( + model: $config['model'] ?? $this->defaultModel + ); + + // Add system instruction (template + variables) + $model = $model->withSystemInstruction(Content::parse($systemPrompt)); + + // Add generation config + $model = $model->withGenerationConfig(new GenerationConfig( + maxOutputTokens: $config['max_tokens'] ?? $this->defaultMaxTokens, + )); + + // Enable Google Search for fact verification + if ($config['use_search'] ?? true) { + $model = $model->withTool(new Tool(googleSearch: new GoogleSearch())); + } + + // Generate content + $userPrompt = $config['prompt'] ?? 'BEGIN GENERATION NOW.'; + $response = $model->generateContent($userPrompt); + + $usage = $this->extractUsage($response); + $latency = round((microtime(true) - $startTime) * 1000); + + // Check if grounding was used + $groundingUsed = $this->checkGroundingUsed($response); + + return [ + 'content' => $response->text(), + 'model' => $config['model'] ?? $this->defaultModel, + 'latency_ms' => $latency, + 'grounding_used' => $groundingUsed, + 'usage' => $usage, + ]; + } + + /** + * Generate article with structured JSON output + */ + public function generateArticleStructured(string $templateFile, array $config): array + { + $startTime = microtime(true); + + // Load template + $template = $this->loadTemplate($templateFile); + + // Build dynamic variables + $variables = $this->buildArticleVariables($config); + + // Define JSON structure for article + $jsonSchema = $config['json_schema'] ?? [ + 'title' => 'Article title', + 'meta_description' => 'SEO meta description', + 'content' => 'Full article content in HTML format', + 'sections' => [ + ['heading' => 'Section heading', 'content' => 'Section content'] + ], + 'tags' => ['relevant', 'tags'], + 'sources' => [ + ['title' => 'Source title', 'url' => 'Source URL'] + ], + ]; + + $schemaJson = json_encode($jsonSchema, JSON_PRETTY_PRINT); + + // Combine template with variables and JSON instruction + $systemPrompt = $template . "\n\n" . $variables . "\n\n" . + "IMPORTANT: Respond ONLY with valid JSON using this structure:\n" . $schemaJson; + + // Build the model + $model = $this->client->generativeModel( + model: $config['model'] ?? $this->defaultModel + ); + + $model = $model->withSystemInstruction(Content::parse($systemPrompt)); + + // Enable JSON output + $model = $model->withGenerationConfig(new GenerationConfig( + maxOutputTokens: $config['max_tokens'] ?? $this->defaultMaxTokens, + responseMimeType: ResponseMimeType::APPLICATION_JSON, + )); + + // Enable Google Search + if ($config['use_search'] ?? true) { + $model = $model->withTool(new Tool(googleSearch: new GoogleSearch())); + } + + $userPrompt = $config['prompt'] ?? 'BEGIN GENERATION NOW.'; + $response = $model->generateContent($userPrompt); + + $usage = $this->extractUsage($response); + $latency = round((microtime(true) - $startTime) * 1000); + $groundingUsed = $this->checkGroundingUsed($response); + + // Parse JSON response + $articleData = null; + try { + $articleData = $response->json(); + } catch (Exception $e) { + // Fall back to text if JSON parsing fails + $articleData = ['content' => $response->text(), 'parse_error' => $e->getMessage()]; + } + + return [ + 'content' => $response->text(), + 'article' => $articleData, + 'model' => $config['model'] ?? $this->defaultModel, + 'latency_ms' => $latency, + 'grounding_used' => $groundingUsed, + 'usage' => $usage, + ]; + } + + /** + * Generate multiple articles + */ + public function generateArticleBatch(string $templateFile, array $articles): array + { + $results = []; + + foreach ($articles as $index => $config) { + $results[] = [ + 'index' => $index, + 'topic' => $config['topic'] ?? 'Unknown', + 'result' => $this->generateArticle($templateFile, $config), + ]; + } + + return $results; + } + + // ========================================================================= + // ARTICLE REVIEW + // ========================================================================= + + /** + * Review an article for quality and factual accuracy + */ + public function reviewArticle(string $articleContent, array $options = []): array + { + $startTime = microtime(true); + + $criteria = $options['criteria'] ?? [ + 'grammar_spelling', + 'factual_accuracy', + 'readability', + 'seo_optimization', + 'engagement', + 'structure', + 'tone_consistency', + ]; + + $criteriaList = implode(', ', $criteria); + + $systemPrompt = << 85, + 'summary' => 'Brief overall assessment', + 'criteria_scores' => [ + 'example_criterion' => [ + 'score' => 90, + 'feedback' => 'Detailed feedback', + ], + ], + 'strengths' => ['List of article strengths'], + 'improvements' => [ + [ + 'priority' => 'high|medium|low', + 'issue' => 'Issue description', + 'suggestion' => 'How to fix it', + ], + ], + 'fact_check' => [ + [ + 'claim' => 'Claim from the article', + 'verified' => true, + 'source' => 'Source or note', + ], + ], + ]; + + $schemaJson = json_encode($jsonSchema, JSON_PRETTY_PRINT); + + $userPrompt = <<client->generativeModel( + model: $options['model'] ?? $this->defaultModel + ); + + $model = $model->withSystemInstruction(Content::parse($systemPrompt)); + + $model = $model->withGenerationConfig(new GenerationConfig( + maxOutputTokens: $options['max_tokens'] ?? $this->defaultMaxTokens, + responseMimeType: ResponseMimeType::APPLICATION_JSON, + )); + + // Enable Google Search for fact-checking + $model = $model->withTool(new Tool(googleSearch: new GoogleSearch())); + + $response = $model->generateContent($userPrompt); + + $usage = $this->extractUsage($response); + $latency = round((microtime(true) - $startTime) * 1000); + + $reviewData = null; + try { + $reviewData = $response->json(); + } catch (Exception $e) { + $reviewData = ['content' => $response->text(), 'parse_error' => $e->getMessage()]; + } + + return [ + 'content' => $response->text(), + 'review' => $reviewData, + 'model' => $options['model'] ?? $this->defaultModel, + 'latency_ms' => $latency, + 'usage' => $usage, + ]; + } + + // ========================================================================= + // RESEARCH + // ========================================================================= + + /** + * Research a topic using Google Search + */ + public function research(string $query, array $options = []): array + { + $startTime = microtime(true); + + $depth = $options['depth'] ?? 2; + + $depthInstructions = match ($depth) { + 1 => 'Provide a quick overview with 3-5 key points.', + 2 => 'Provide a comprehensive overview with detailed findings.', + 3 => 'Provide an exhaustive analysis covering all aspects.', + default => 'Provide a comprehensive overview.', + }; + + $systemPrompt = << 'The research topic', + 'summary' => 'Executive summary', + 'key_findings' => [ + ['finding' => 'Key finding', 'confidence' => 'high|medium|low'], + ], + 'statistics' => [ + ['stat' => 'Statistic', 'source' => 'Source'], + ], + 'sources' => [ + ['title' => 'Source title', 'url' => 'URL', 'credibility' => 'high|medium|low'], + ], + ]; + + $schemaJson = json_encode($jsonSchema, JSON_PRETTY_PRINT); + + $userPrompt = <<client->generativeModel( + model: $options['model'] ?? $this->defaultModel + ); + + $model = $model->withSystemInstruction(Content::parse($systemPrompt)); + + $model = $model->withGenerationConfig(new GenerationConfig( + maxOutputTokens: $options['max_tokens'] ?? $this->defaultMaxTokens, + responseMimeType: ResponseMimeType::APPLICATION_JSON, + )); + + $model = $model->withTool(new Tool(googleSearch: new GoogleSearch())); + + $response = $model->generateContent($userPrompt); + + $usage = $this->extractUsage($response); + $latency = round((microtime(true) - $startTime) * 1000); + + $researchData = null; + try { + $researchData = $response->json(); + } catch (Exception $e) { + $researchData = ['content' => $response->text(), 'parse_error' => $e->getMessage()]; + } + + return [ + 'content' => $response->text(), + 'research' => $researchData, + 'model' => $options['model'] ?? $this->defaultModel, + 'latency_ms' => $latency, + 'usage' => $usage, + ]; + } + + // ========================================================================= + // UTILITY METHODS + // ========================================================================= + + /** + * Extract usage information from response + */ + private function extractUsage($response): array + { + $usage = [ + 'input_tokens' => 0, + 'output_tokens' => 0, + 'total_tokens' => 0, + ]; + + // Try to get usage metadata from response + if (isset($response->usageMetadata)) { + $usage['input_tokens'] = $response->usageMetadata->promptTokenCount ?? 0; + $usage['output_tokens'] = $response->usageMetadata->candidatesTokenCount ?? 0; + $usage['total_tokens'] = $response->usageMetadata->totalTokenCount ?? + ($usage['input_tokens'] + $usage['output_tokens']); + } + + return $usage; + } + + /** + * Check if grounding/search was used in the response + */ + private function checkGroundingUsed($response): bool + { + // Check for grounding metadata in response + if (isset($response->candidates[0]->groundingMetadata)) { + return true; + } + + return false; + } + + public function estimateTokens(string $text): int + { + return (int) ceil(strlen($text) / 4); + } + + public function estimateCost(array $usage, ?string $model = null): array + { + $model = $model ?? $this->defaultModel; + + // Gemini pricing per 1M tokens (as of January 2026) + $pricing = [ + 'gemini-3-pro-preview' => ['input' => 2.00, 'output' => 12.00], + 'gemini-3-flash-preview' => ['input' => 0.20, 'output' => 0.80], + 'gemini-2.5-flash' => ['input' => 0.15, 'output' => 0.60], + 'gemini-2.5-flash-lite' => ['input' => 0.075, 'output' => 0.30], + ]; + + $rates = $pricing[$model] ?? $pricing['gemini-3-pro-preview']; + + $inputCost = (($usage['input_tokens'] ?? 0) / 1_000_000) * $rates['input']; + $outputCost = (($usage['output_tokens'] ?? 0) / 1_000_000) * $rates['output']; + + return [ + 'input_cost' => round($inputCost, 6), + 'output_cost' => round($outputCost, 6), + 'total_cost' => round($inputCost + $outputCost, 6), + 'model' => $model, + ]; + } + + public function formatCost(array $usage): string + { + $cost = $this->estimateCost($usage); + + $output = "Cost Breakdown:\n"; + $output .= " Input: $" . number_format($cost['input_cost'], 4) . "\n"; + $output .= " Output: $" . number_format($cost['output_cost'], 4) . "\n"; + $output .= " ─────────────\n"; + $output .= " Total: $" . number_format($cost['total_cost'], 4) . "\n"; + + return $output; + } + + + + + // ========================================================================= + // CONFIGURATION + // ========================================================================= + + public function setModel(string $model): self + { + $this->defaultModel = $model; + return $this; + } + + public function useModel(string $preset): self + { + if (isset(self::MODELS[$preset])) { + $this->defaultModel = self::MODELS[$preset]; + } + return $this; + } + + public function setMaxTokens(int $tokens): self + { + $this->defaultMaxTokens = $tokens; + return $this; + } + + public function getModel(): string + { + return $this->defaultModel; + } + + public function getClient() + { + return $this->client; + } + + public function getAvailableModels(): array + { + return self::MODELS; + } + + /** + * Get model labels for UI dropdowns + */ + public static function getModelLabels(): array + { + return self::MODEL_LABELS; + } + + /** + * Get model ID from preset name + */ + public static function getModelId(string $preset): string + { + return self::MODELS[$preset] ?? $preset; + } +} \ No newline at end of file diff --git a/app/LLM/LLMManager.php b/app/LLM/LLMManager.php new file mode 100644 index 0000000..e4da024 --- /dev/null +++ b/app/LLM/LLMManager.php @@ -0,0 +1,254 @@ + svcOpenAI::class, + 'anthropic' => svcAnthropic::class, + 'gemini' => svcGemini::class, + 'mistral' => svcMistral::class, + 'deepseek' => svcDeepSeek::class, + ]; + + // Maps provider key to sp_settings.keyval (application-level) + private static array $settingsKeys = [ + 'openai' => 'llm_openai', + 'anthropic' => 'llm_anthropic', + 'gemini' => 'llm_gemini', + 'mistral' => 'llm_mistral', + 'deepseek' => 'llm_deepseek', + ]; + + // Maps provider key to sp_orgs_meta.keyval (org-level overrides) + private static array $metaKeys = [ + 'openai' => 'llmOpenAI', + 'anthropic' => 'llmAnthropic', + 'gemini' => 'llmGemini', + 'mistral' => 'llmMistral', + 'deepseek' => 'llmDeepSeek', + ]; + + // Provider preference order for auto-selection + public static array $priority = ['anthropic', 'openai', 'gemini', 'mistral', 'deepseek']; + + /** + * Instantiate a provider directly from a pre-loaded config array. + * Use this when you already have the config and don't need another DB lookup. + * + * @throws \Exception if provider unknown + */ + public static function make(string $provider, array $config): LLMProvider + { + $provider = strtolower($provider); + if (!isset(self::$providers[$provider])) { + throw new \Exception("Unknown LLM provider: '$provider'"); + } + $class = self::$providers[$provider]; + return new $class($config); + } + + // ─── Application-level (sp_settings) ──────────────────────────────────── + + /** + * Return a configured provider instance from the application-level sp_settings table. + * + * @throws \Exception if provider unknown or not configured + */ + public static function forApp(string $provider): LLMProvider + { + $provider = strtolower($provider); + + if (!isset(self::$providers[$provider])) { + throw new \Exception("Unknown LLM provider: '$provider'"); + } + + $config = self::getAppConfig($provider); + + if (empty($config)) { + throw new \Exception("No '$provider' configuration found. Add one under Admin → Integrations."); + } + + $class = self::$providers[$provider]; + return new $class($config); + } + + /** + * Read and normalize a provider's config from sp_settings. + * Returns [] if not found or api_key is empty. + */ + public static function getAppConfig(string $provider): array + { + $provider = strtolower($provider); + $settingsKey = self::$settingsKeys[$provider] ?? null; + if (!$settingsKey) return []; + + $row = \Db::getRow( + "SELECT metval FROM sp_settings WHERE `group` = 'llm' AND keyval = ? LIMIT 1", + [$settingsKey] + ); + + if (!$row || empty($row['metval'])) return []; + + $raw = json_decode($row['metval'], true); + if (!is_array($raw) || empty($raw['api_key'])) return []; + + // Normalize sp_settings field names to LLMProvider expected names + return array_filter([ + 'secretKey' => $raw['api_key'], + 'model' => $raw['model'] ?? null, + 'max_tokens' => $raw['max_tokens'] ?? null, + 'temperature' => $raw['temperature'] ?? null, + ]); + } + + /** + * Return the list of application-level providers that have a non-empty api_key. + * + * @return array e.g. ['anthropic', 'openai'] + */ + public static function getAppProviders(): array + { + $available = []; + + foreach (self::$settingsKeys as $provider => $keyval) { + $row = \Db::getRow( + "SELECT metval FROM sp_settings WHERE `group` = 'llm' AND keyval = ? LIMIT 1", + [$keyval] + ); + if (!$row || empty($row['metval'])) continue; + $cfg = json_decode($row['metval'], true); + if (!empty($cfg['api_key'])) { + $available[] = $provider; + } + } + + return $available; + } + + /** + * Return the first configured application-level provider (by priority), or null. + */ + public static function getDefaultAppProvider(): ?string + { + $available = self::getAppProviders(); + foreach (self::$priority as $p) { + if (in_array($p, $available)) return $p; + } + return null; + } + + // ─── Org-level (sp_orgs_meta) ──────────────────────────────────────────── + + /** + * Return a configured provider instance from sp_orgs_meta (org-level override). + * + * @throws \Exception if provider unknown or not configured for the org + */ + public static function forOrg(int $orgId, string $provider): LLMProvider + { + $provider = strtolower($provider); + + if (!isset(self::$providers[$provider])) { + throw new \Exception("Unknown LLM provider: '$provider'"); + } + + $config = self::getOrgConfig($orgId, $provider); + + if (empty($config)) { + throw new \Exception("No '$provider' config found for org $orgId"); + } + + $class = self::$providers[$provider]; + return new $class($config); + } + + /** + * Read and decode a provider config from sp_orgs_meta for the given org. + */ + public static function getOrgConfig(int $orgId, string $provider): array + { + $provider = strtolower($provider); + $metaKey = self::$metaKeys[$provider] ?? null; + if (!$metaKey) return []; + + $row = \Db::getRow( + "SELECT metval FROM sp_orgs_meta WHERE orgid = ? AND keyval = ? AND active = 1 LIMIT 1", + [$orgId, $metaKey] + ); + + if (!$row || empty($row['metval'])) return []; + + $config = json_decode($row['metval'], true); + return is_array($config) ? $config : []; + } + + /** + * Return which providers are configured for the given org (sp_orgs_meta only). + */ + public static function getAvailableProviders(int $orgId): array + { + $metaKeys = array_values(self::$metaKeys); + $rows = \Db::select( + "SELECT keyval FROM sp_orgs_meta WHERE orgid = ? AND keyval IN ('" . implode("','", $metaKeys) . "') AND active = 1", + [$orgId] + ); + + $flip = array_flip(self::$metaKeys); + $available = []; + + foreach ($rows as $row) { + if (isset($flip[$row['keyval']])) { + $available[] = $flip[$row['keyval']]; + } + } + + return $available; + } + + /** + * Save or update a provider config for an org in sp_orgs_meta. + */ + public static function saveOrgConfig(int $orgId, string $provider, array $config): void + { + $provider = strtolower($provider); + $metaKey = self::$metaKeys[$provider] ?? null; + + if (!$metaKey) { + throw new \Exception("Unknown LLM provider: '$provider'"); + } + + $existing = \Db::getRow( + "SELECT orgmetaid FROM sp_orgs_meta WHERE orgid = ? AND keyval = ? LIMIT 1", + [$orgId, $metaKey] + ); + + if ($existing) { + \Db::update('sp_orgs_meta', + ['metval' => json_encode($config)], + 'orgmetaid = ?', + [$existing['orgmetaid']] + ); + } else { + \Db::insert('sp_orgs_meta', [ + 'orgid' => $orgId, + 'keyval' => $metaKey, + 'metval' => json_encode($config), + 'active' => 1, + ]); + } + } +} diff --git a/app/LLM/LLMProvider.php b/app/LLM/LLMProvider.php new file mode 100644 index 0000000..d35c46e --- /dev/null +++ b/app/LLM/LLMProvider.php @@ -0,0 +1,191 @@ + 'user', 'content' => '...']] + * @param array $options Override defaults (model, max_tokens, temperature, etc.) + * @return array ['success' => bool, 'content' => string, 'usage' => array, 'error' => string] + */ + abstract public function sendPrompt(array $messages, array $options = []): array; + + /** + * Stream a prompt response chunk by chunk. + * + * @param array $messages + * @param array $options + * @param callable $callback Called with each chunk: function(string $chunk) + * @return array ['success' => bool, 'error' => string] + */ + abstract public function streamPrompt(array $messages, array $options, callable $callback): array; + + /** + * Return available models for this provider. + * + * @return array [['id' => '...', 'label' => '...']] + */ + abstract public function getModels(): array; + + /** + * Test the API key is valid and the provider is reachable. + * + * @return array ['success' => bool, 'latency_ms' => int, 'error' => string] + */ + abstract public function testConnection(): array; + + // ─── Fluent Setters ────────────────────────────────────────────────────── + + public function getModel(): string + { + return $this->model; + } + + public function setModel(string $model): static + { + $this->model = $model; + return $this; + } + + public function setMaxTokens(int $tokens): static + { + $this->maxTokens = $tokens; + return $this; + } + + public function setSystemPrompt(string $prompt): static + { + $this->systemPrompt = $prompt; + return $this; + } + + public function setTemperature(float $temp): static + { + $this->temperature = $temp; + return $this; + } + + public function setTimeout(int $seconds): static + { + $this->timeout = $seconds; + return $this; + } + + // ─── Shared Helpers ────────────────────────────────────────────────────── + + /** + * Build a standard error response. + */ + protected function errorResponse(string $message): array + { + return ['success' => false, 'content' => '', 'usage' => [], 'error' => $message]; + } + + /** + * Build a standard success response. + */ + protected function successResponse(string $content, array $usage = []): array + { + return ['success' => true, 'content' => $content, 'usage' => $usage, 'error' => '']; + } + + /** + * Execute a cURL request and return the decoded JSON response. + */ + protected function curlPost(string $url, array $headers, array $payload): array + { + $ch = curl_init($url); + curl_setopt_array($ch, [ + CURLOPT_RETURNTRANSFER => true, + CURLOPT_POST => true, + CURLOPT_POSTFIELDS => json_encode($payload), + CURLOPT_HTTPHEADER => $headers, + CURLOPT_TIMEOUT => $this->timeout, + ]); + + $body = curl_exec($ch); + $error = curl_error($ch); + $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + + if ($error) { + throw new \Exception("cURL error: $error"); + } + + $decoded = json_decode($body, true); + + if ($code >= 400) { + $msg = $decoded['error']['message'] ?? $decoded['message'] ?? "HTTP $code error"; + throw new \Exception($msg); + } + + return $decoded; + } + + /** + * Execute a streaming cURL request, calling $callback for each SSE data chunk. + */ + protected function curlStream(string $url, array $headers, array $payload, callable $callback): void + { + $payload['stream'] = true; + + $errorBody = ''; + + $ch = curl_init($url); + curl_setopt_array($ch, [ + CURLOPT_RETURNTRANSFER => false, + CURLOPT_POST => true, + CURLOPT_POSTFIELDS => json_encode($payload), + CURLOPT_HTTPHEADER => $headers, + CURLOPT_TIMEOUT => $this->timeout, + CURLOPT_WRITEFUNCTION => function($ch, $data) use ($callback, &$errorBody) { + $lines = explode("\n", $data); + $hasSseData = false; + foreach ($lines as $line) { + $line = trim($line); + if (str_starts_with($line, 'data: ')) { + $hasSseData = true; + $json = substr($line, 6); + if ($json === '[DONE]') break; + $chunk = json_decode($json, true); + if ($chunk) $callback($chunk); + } + } + // If no SSE data lines were found this chunk may be an error body + if (!$hasSseData) { + $errorBody .= $data; + } + return strlen($data); + }, + ]); + + curl_exec($ch); + $curlError = curl_error($ch); + $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + + if ($curlError) { + throw new \Exception("cURL stream error: $curlError"); + } + + if ($httpCode >= 400) { + $decoded = json_decode(trim($errorBody), true); + $msg = $decoded['error']['message'] ?? $decoded['message'] ?? "API error (HTTP $httpCode)"; + throw new \Exception($msg); + } + } +} diff --git a/app/LLM/OpenAIsvc.php b/app/LLM/OpenAIsvc.php new file mode 100644 index 0000000..678c850 --- /dev/null +++ b/app/LLM/OpenAIsvc.php @@ -0,0 +1,11 @@ +sendPrompt([['role' => 'user', 'content' => 'Hello']]); + * echo $res['content']; + */ +class svcAnthropic extends LLMProvider +{ + private const API_BASE = 'https://api.anthropic.com/v1'; + private const API_CHAT = self::API_BASE . '/messages'; + private const API_VERSION = '2023-06-01'; + + private static array $availableModels = [ + ['id' => 'claude-opus-4-6', 'label' => 'Claude Opus 4.6'], + ['id' => 'claude-sonnet-4-6', 'label' => 'Claude Sonnet 4.6'], + ['id' => 'claude-haiku-4-5-20251001', 'label' => 'Claude Haiku 4.5'], + ]; + + public function __construct(array $config) + { + if (empty($config['secretKey'])) { + throw new \Exception('Anthropic secretKey is required'); + } + + $this->apiKey = $config['secretKey']; + $this->model = $config['model'] ?? 'claude-sonnet-4-6'; + $this->maxTokens = (int)($config['max_tokens'] ?? 4096); + $this->temperature = (float)($config['temperature'] ?? 0.7); + } + + /** + * Send a chat prompt and return the full response. + */ + public function sendPrompt(array $messages, array $options = []): array + { + try { + $payload = $this->buildPayload($messages, $options); + $response = $this->curlPost(self::API_CHAT, $this->headers(!empty($options['cache_system'])), $payload); + + $content = $response['content'][0]['text'] ?? ''; + $usage = $response['usage'] ?? []; + + return $this->successResponse($content, [ + 'prompt_tokens' => $usage['input_tokens'] ?? 0, + 'completion_tokens' => $usage['output_tokens'] ?? 0, + 'total_tokens' => ($usage['input_tokens'] ?? 0) + ($usage['output_tokens'] ?? 0), + ]); + + } catch (\Exception $e) { + return $this->errorResponse($e->getMessage()); + } + } + + /** + * Stream a chat prompt, calling $callback with each text chunk. + * + * @param callable $callback function(string $chunk) — called with each partial text + */ + public function streamPrompt(array $messages, array $options, callable $callback): array + { + try { + $payload = $this->buildPayload($messages, $options); + $streamError = null; + + $this->curlStream(self::API_CHAT, $this->headers(!empty($options['cache_system'])), $payload, function(array $chunk) use ($callback, &$streamError) { + $type = $chunk['type'] ?? ''; + + if ($type === 'content_block_delta') { + $delta = $chunk['delta']['text'] ?? ''; + if ($delta !== '') { + $callback($delta); + } + } elseif ($type === 'error') { + // Anthropic sends inline error events during streaming + $streamError = $chunk['error']['message'] ?? 'Anthropic streaming error'; + } + }); + + if ($streamError) { + return ['success' => false, 'error' => $streamError]; + } + + return ['success' => true, 'error' => '']; + + } catch (\Exception $e) { + return ['success' => false, 'error' => $e->getMessage()]; + } + } + + /** + * Return supported Claude models. + */ + public function getModels(): array + { + return self::$availableModels; + } + + /** + * Validate the API key with a minimal single-token request. + */ + public function testConnection(): array + { + try { + $start = microtime(true); + + $payload = [ + 'model' => $this->model, + 'max_tokens' => 1, + 'messages' => [['role' => 'user', 'content' => 'hi']], + ]; + + $this->curlPost(self::API_CHAT, $this->headers(), $payload); + + $latency = (int)((microtime(true) - $start) * 1000); + + return ['success' => true, 'latency_ms' => $latency, 'error' => '']; + + } catch (\Exception $e) { + return ['success' => false, 'latency_ms' => 0, 'error' => $e->getMessage()]; + } + } + + // ─── Private Helpers ───────────────────────────────────────────────────── + + private function headers(bool $withCache = false): array + { + $h = [ + 'Content-Type: application/json', + 'x-api-key: ' . $this->apiKey, + 'anthropic-version: ' . self::API_VERSION, + ]; + if ($withCache) { + $h[] = 'anthropic-beta: prompt-caching-2024-07-31'; + } + return $h; + } + + private function buildPayload(array $messages, array $options): array + { + $model = $options['model'] ?? $this->model; + $maxTokens = (int)($options['max_tokens'] ?? $this->maxTokens); + $temperature = (float)($options['temperature'] ?? $this->temperature); + $cacheSystem = !empty($options['cache_system']); + + $payload = [ + 'model' => $model, + 'max_tokens' => $maxTokens, + 'temperature' => $temperature, + 'messages' => $messages, + ]; + + // Anthropic uses a top-level 'system' key. + // When cache_system is set, use block format with cache_control so + // the large static report payload is cached between turns. + if ($this->systemPrompt !== '') { + if ($cacheSystem) { + $payload['system'] = [[ + 'type' => 'text', + 'text' => $this->systemPrompt, + 'cache_control' => ['type' => 'ephemeral'], + ]]; + } else { + $payload['system'] = $this->systemPrompt; + } + } + + return $payload; + } +} diff --git a/app/LLM/svcDeepSeek.php b/app/LLM/svcDeepSeek.php new file mode 100644 index 0000000..977708b --- /dev/null +++ b/app/LLM/svcDeepSeek.php @@ -0,0 +1,177 @@ +sendPrompt([['role' => 'user', 'content' => 'Hello']]); + * echo $res['content']; + */ +class svcDeepSeek extends LLMProvider +{ + private const API_BASE = 'https://api.deepseek.com/v1'; + private const API_CHAT = self::API_BASE . '/chat/completions'; + private const API_MODELS = self::API_BASE . '/models'; + + private static array $availableModels = [ + ['id' => 'deepseek-chat', 'label' => 'DeepSeek Chat (V3)'], + ['id' => 'deepseek-reasoner', 'label' => 'DeepSeek Reasoner (R1)'], + ]; + + public function __construct(array $config) + { + if (empty($config['secretKey'])) { + throw new \Exception('DeepSeek secretKey is required'); + } + + $this->apiKey = $config['secretKey']; + $this->model = $config['model'] ?? 'deepseek-chat'; + $this->maxTokens = (int)($config['max_tokens'] ?? 4096); + $this->temperature = (float)($config['temperature'] ?? 0.7); + } + + /** + * Send a chat prompt and return the full response. + */ + public function sendPrompt(array $messages, array $options = []): array + { + try { + $payload = $this->buildPayload($messages, $options); + $response = $this->curlPost(self::API_CHAT, $this->headers(), $payload); + + $content = $response['choices'][0]['message']['content'] ?? ''; + $usage = $response['usage'] ?? []; + + // DeepSeek Reasoner includes reasoning_content separately + $reasoning = $response['choices'][0]['message']['reasoning_content'] ?? ''; + + return $this->successResponse($content, [ + 'prompt_tokens' => $usage['prompt_tokens'] ?? 0, + 'completion_tokens' => $usage['completion_tokens'] ?? 0, + 'total_tokens' => $usage['total_tokens'] ?? 0, + 'reasoning_tokens' => $usage['completion_tokens_details']['reasoning_tokens'] ?? 0, + 'reasoning_content' => $reasoning, + ]); + + } catch (\Exception $e) { + return $this->errorResponse($e->getMessage()); + } + } + + /** + * Stream a chat prompt, calling $callback with each text chunk. + * + * @param callable $callback function(string $chunk) + */ + public function streamPrompt(array $messages, array $options, callable $callback): array + { + try { + $payload = $this->buildPayload($messages, $options); + + $this->curlStream(self::API_CHAT, $this->headers(), $payload, function(array $chunk) use ($callback) { + $delta = $chunk['choices'][0]['delta']['content'] ?? ''; + if ($delta !== '') { + $callback($delta); + } + }); + + return ['success' => true, 'error' => '']; + + } catch (\Exception $e) { + return ['success' => false, 'error' => $e->getMessage()]; + } + } + + /** + * Return supported DeepSeek models. + */ + public function getModels(): array + { + return self::$availableModels; + } + + /** + * Validate the API key via the models endpoint. + */ + public function testConnection(): array + { + try { + $start = microtime(true); + + $ch = curl_init(self::API_MODELS); + curl_setopt_array($ch, [ + CURLOPT_RETURNTRANSFER => true, + CURLOPT_HTTPHEADER => $this->headers(), + CURLOPT_TIMEOUT => $this->timeout, + ]); + + $body = curl_exec($ch); + $error = curl_error($ch); + $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + + $latency = (int)((microtime(true) - $start) * 1000); + + if ($error) throw new \Exception("cURL error: $error"); + + $decoded = json_decode($body, true); + + if ($code !== 200) { + $msg = $decoded['error']['message'] ?? "HTTP $code"; + throw new \Exception($msg); + } + + return ['success' => true, 'latency_ms' => $latency, 'error' => '']; + + } catch (\Exception $e) { + return ['success' => false, 'latency_ms' => 0, 'error' => $e->getMessage()]; + } + } + + // ─── Private Helpers ───────────────────────────────────────────────────── + + private function headers(): array + { + return [ + 'Content-Type: application/json', + 'Authorization: Bearer ' . $this->apiKey, + ]; + } + + private function buildPayload(array $messages, array $options): array + { + $model = $options['model'] ?? $this->model; + $maxTokens = (int)($options['max_tokens'] ?? $this->maxTokens); + $temperature = (float)($options['temperature'] ?? $this->temperature); + + if ($this->systemPrompt !== '') { + array_unshift($messages, ['role' => 'system', 'content' => $this->systemPrompt]); + } + + $payload = [ + 'model' => $model, + 'messages' => $messages, + 'max_tokens' => $maxTokens, + 'temperature' => $temperature, + ]; + + // deepseek-reasoner does not support temperature + if ($model === 'deepseek-reasoner') { + unset($payload['temperature']); + } + + return $payload; + } +} diff --git a/app/LLM/svcGemini.php b/app/LLM/svcGemini.php new file mode 100644 index 0000000..6d67d96 --- /dev/null +++ b/app/LLM/svcGemini.php @@ -0,0 +1,172 @@ +sendPrompt([['role' => 'user', 'content' => 'Hello']]); + * echo $res['content']; + */ +class svcGemini extends LLMProvider +{ + private const API_BASE = 'https://generativelanguage.googleapis.com/v1beta/models'; + private const API_CHAT = ':generateContent'; + private const API_STREAM = ':streamGenerateContent'; + + private static array $availableModels = [ + ['id' => 'gemini-2.5-flash', 'label' => 'Gemini 2.5 Flash'], + ['id' => 'gemini-2.5-flash-lite', 'label' => 'Gemini 2.5 Flash Lite'], + ['id' => 'gemini-2.0-flash', 'label' => 'Gemini 2.0 Flash'], + ['id' => 'gemini-1.5-pro', 'label' => 'Gemini 1.5 Pro'], + ['id' => 'gemini-1.5-flash', 'label' => 'Gemini 1.5 Flash'], + ]; + + public function __construct(array $config) + { + if (empty($config['secretKey'])) { + throw new \Exception('Gemini secretKey is required'); + } + + $this->apiKey = $config['secretKey']; + $this->model = $config['model'] ?? 'gemini-2.5-flash'; + $this->maxTokens = (int)($config['max_tokens'] ?? 4096); + $this->temperature = (float)($config['temperature'] ?? 0.7); + } + + /** + * Send a chat prompt and return the full response. + */ + public function sendPrompt(array $messages, array $options = []): array + { + try { + $model = $options['model'] ?? $this->model; + $url = self::API_BASE . '/' . $model . self::API_CHAT . '?key=' . $this->apiKey; + $payload = $this->buildPayload($messages, $options); + + $response = $this->curlPost($url, $this->headers(), $payload); + + $content = $response['candidates'][0]['content']['parts'][0]['text'] ?? ''; + $usage = $response['usageMetadata'] ?? []; + + return $this->successResponse($content, [ + 'prompt_tokens' => $usage['promptTokenCount'] ?? 0, + 'completion_tokens' => $usage['candidatesTokenCount'] ?? 0, + 'total_tokens' => $usage['totalTokenCount'] ?? 0, + ]); + + } catch (\Exception $e) { + return $this->errorResponse($e->getMessage()); + } + } + + /** + * Stream a chat prompt, calling $callback with each text chunk. + * + * @param callable $callback function(string $chunk) + */ + public function streamPrompt(array $messages, array $options, callable $callback): array + { + try { + $model = $options['model'] ?? $this->model; + $url = self::API_BASE . '/' . $model . self::API_STREAM . '?key=' . $this->apiKey . '&alt=sse'; + $payload = $this->buildPayload($messages, $options); + + $this->curlStream($url, $this->headers(), $payload, function(array $chunk) use ($callback) { + $text = $chunk['candidates'][0]['content']['parts'][0]['text'] ?? ''; + if ($text !== '') { + $callback($text); + } + }); + + return ['success' => true, 'error' => '']; + + } catch (\Exception $e) { + return ['success' => false, 'error' => $e->getMessage()]; + } + } + + /** + * Return supported Gemini models. + */ + public function getModels(): array + { + return self::$availableModels; + } + + /** + * Validate the API key with a minimal request. + */ + public function testConnection(): array + { + try { + $start = microtime(true); + $model = $this->model; + $url = self::API_BASE . '/' . $model . self::API_CHAT . '?key=' . $this->apiKey; + $payload = [ + 'contents' => [['role' => 'user', 'parts' => [['text' => 'hi']]]], + 'generationConfig' => ['maxOutputTokens' => 1], + ]; + + $this->curlPost($url, $this->headers(), $payload); + + $latency = (int)((microtime(true) - $start) * 1000); + + return ['success' => true, 'latency_ms' => $latency, 'error' => '']; + + } catch (\Exception $e) { + return ['success' => false, 'latency_ms' => 0, 'error' => $e->getMessage()]; + } + } + + // ─── Private Helpers ───────────────────────────────────────────────────── + + private function headers(): array + { + return ['Content-Type: application/json']; + } + + private function buildPayload(array $messages, array $options): array + { + $maxTokens = (int)($options['max_tokens'] ?? $this->maxTokens); + $temperature = (float)($options['temperature'] ?? $this->temperature); + + // Convert OpenAI-style messages to Gemini format + // Roles: 'user' stays 'user', 'assistant' becomes 'model' + $contents = []; + foreach ($messages as $msg) { + $contents[] = [ + 'role' => $msg['role'] === 'assistant' ? 'model' : 'user', + 'parts' => [['text' => $msg['content']]], + ]; + } + + $payload = [ + 'contents' => $contents, + 'generationConfig' => [ + 'maxOutputTokens' => $maxTokens, + 'temperature' => $temperature, + ], + ]; + + // Gemini uses a top-level 'systemInstruction' for system prompts + if ($this->systemPrompt !== '') { + $payload['systemInstruction'] = [ + 'parts' => [['text' => $this->systemPrompt]], + ]; + } + + return $payload; + } +} diff --git a/app/LLM/svcMistral.php b/app/LLM/svcMistral.php new file mode 100644 index 0000000..a6a9586 --- /dev/null +++ b/app/LLM/svcMistral.php @@ -0,0 +1,169 @@ +sendPrompt([['role' => 'user', 'content' => 'Hello']]); + * echo $res['content']; + */ +class svcMistral extends LLMProvider +{ + private const API_BASE = 'https://api.mistral.ai/v1'; + private const API_CHAT = self::API_BASE . '/chat/completions'; + private const API_MODELS = self::API_BASE . '/models'; + + private static array $availableModels = [ + ['id' => 'mistral-large-latest', 'label' => 'Mistral Large'], + ['id' => 'mistral-medium-latest', 'label' => 'Mistral Medium'], + ['id' => 'mistral-small-latest', 'label' => 'Mistral Small'], + ['id' => 'codestral-latest', 'label' => 'Codestral'], + ['id' => 'open-mistral-nemo', 'label' => 'Mistral Nemo'], + ['id' => 'open-mixtral-8x22b', 'label' => 'Mixtral 8x22B'], + ]; + + public function __construct(array $config) + { + if (empty($config['secretKey'])) { + throw new \Exception('Mistral secretKey is required'); + } + + $this->apiKey = $config['secretKey']; + $this->model = $config['model'] ?? 'mistral-large-latest'; + $this->maxTokens = (int)($config['max_tokens'] ?? 4096); + $this->temperature = (float)($config['temperature'] ?? 0.7); + } + + /** + * Send a chat prompt and return the full response. + */ + public function sendPrompt(array $messages, array $options = []): array + { + try { + $payload = $this->buildPayload($messages, $options); + $response = $this->curlPost(self::API_CHAT, $this->headers(), $payload); + + $content = $response['choices'][0]['message']['content'] ?? ''; + $usage = $response['usage'] ?? []; + + return $this->successResponse($content, [ + 'prompt_tokens' => $usage['prompt_tokens'] ?? 0, + 'completion_tokens' => $usage['completion_tokens'] ?? 0, + 'total_tokens' => $usage['total_tokens'] ?? 0, + ]); + + } catch (\Exception $e) { + return $this->errorResponse($e->getMessage()); + } + } + + /** + * Stream a chat prompt, calling $callback with each text chunk. + * + * @param callable $callback function(string $chunk) + */ + public function streamPrompt(array $messages, array $options, callable $callback): array + { + try { + $payload = $this->buildPayload($messages, $options); + + $this->curlStream(self::API_CHAT, $this->headers(), $payload, function(array $chunk) use ($callback) { + $delta = $chunk['choices'][0]['delta']['content'] ?? ''; + if ($delta !== '') { + $callback($delta); + } + }); + + return ['success' => true, 'error' => '']; + + } catch (\Exception $e) { + return ['success' => false, 'error' => $e->getMessage()]; + } + } + + /** + * Return supported Mistral models. + */ + public function getModels(): array + { + return self::$availableModels; + } + + /** + * Validate the API key via the models endpoint. + */ + public function testConnection(): array + { + try { + $start = microtime(true); + + $ch = curl_init(self::API_MODELS); + curl_setopt_array($ch, [ + CURLOPT_RETURNTRANSFER => true, + CURLOPT_HTTPHEADER => $this->headers(), + CURLOPT_TIMEOUT => $this->timeout, + ]); + + $body = curl_exec($ch); + $error = curl_error($ch); + $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + + $latency = (int)((microtime(true) - $start) * 1000); + + if ($error) throw new \Exception("cURL error: $error"); + + $decoded = json_decode($body, true); + + if ($code !== 200) { + $msg = $decoded['message'] ?? "HTTP $code"; + throw new \Exception($msg); + } + + return ['success' => true, 'latency_ms' => $latency, 'error' => '']; + + } catch (\Exception $e) { + return ['success' => false, 'latency_ms' => 0, 'error' => $e->getMessage()]; + } + } + + // ─── Private Helpers ───────────────────────────────────────────────────── + + private function headers(): array + { + return [ + 'Content-Type: application/json', + 'Authorization: Bearer ' . $this->apiKey, + ]; + } + + private function buildPayload(array $messages, array $options): array + { + $model = $options['model'] ?? $this->model; + $maxTokens = (int)($options['max_tokens'] ?? $this->maxTokens); + $temperature = (float)($options['temperature'] ?? $this->temperature); + + if ($this->systemPrompt !== '') { + array_unshift($messages, ['role' => 'system', 'content' => $this->systemPrompt]); + } + + return [ + 'model' => $model, + 'messages' => $messages, + 'max_tokens' => $maxTokens, + 'temperature' => $temperature, + ]; + } +} diff --git a/app/LLM/svcOpenAI.php b/app/LLM/svcOpenAI.php new file mode 100644 index 0000000..3ac932f --- /dev/null +++ b/app/LLM/svcOpenAI.php @@ -0,0 +1,175 @@ +sendPrompt([['role' => 'user', 'content' => 'Hello']]); + * echo $res['content']; + */ +class svcOpenAI extends LLMProvider +{ + private const API_BASE = 'https://api.openai.com/v1'; + private const API_CHAT = self::API_BASE . '/chat/completions'; + private const API_MODELS = self::API_BASE . '/models'; + + private static array $availableModels = [ + ['id' => 'gpt-4o', 'label' => 'GPT-4o'], + ['id' => 'gpt-4o-mini', 'label' => 'GPT-4o Mini'], + ['id' => 'gpt-4-turbo', 'label' => 'GPT-4 Turbo'], + ['id' => 'gpt-4', 'label' => 'GPT-4'], + ['id' => 'gpt-3.5-turbo', 'label' => 'GPT-3.5 Turbo'], + ['id' => 'o1', 'label' => 'o1'], + ['id' => 'o1-mini', 'label' => 'o1 Mini'], + ['id' => 'o3-mini', 'label' => 'o3 Mini'], + ]; + + public function __construct(array $config) + { + if (empty($config['secretKey'])) { + throw new \Exception('OpenAI secretKey is required'); + } + + $this->apiKey = $config['secretKey']; + $this->model = $config['model'] ?? 'gpt-4o'; + $this->maxTokens = (int)($config['max_tokens'] ?? 4096); + $this->temperature = (float)($config['temperature'] ?? 0.7); + } + + /** + * Send a chat prompt and return the full response. + */ + public function sendPrompt(array $messages, array $options = []): array + { + try { + $payload = $this->buildPayload($messages, $options); + $response = $this->curlPost(self::API_CHAT, $this->headers(), $payload); + + $content = $response['choices'][0]['message']['content'] ?? ''; + $usage = $response['usage'] ?? []; + + return $this->successResponse($content, [ + 'prompt_tokens' => $usage['prompt_tokens'] ?? 0, + 'completion_tokens' => $usage['completion_tokens'] ?? 0, + 'total_tokens' => $usage['total_tokens'] ?? 0, + ]); + + } catch (\Exception $e) { + return $this->errorResponse($e->getMessage()); + } + } + + /** + * Stream a chat prompt, calling $callback with each text chunk. + * + * @param callable $callback function(string $chunk) — called with each partial text + */ + public function streamPrompt(array $messages, array $options, callable $callback): array + { + try { + $payload = $this->buildPayload($messages, $options); + + $this->curlStream(self::API_CHAT, $this->headers(), $payload, function(array $chunk) use ($callback) { + $delta = $chunk['choices'][0]['delta']['content'] ?? ''; + if ($delta !== '') { + $callback($delta); + } + }); + + return ['success' => true, 'error' => '']; + + } catch (\Exception $e) { + return ['success' => false, 'error' => $e->getMessage()]; + } + } + + /** + * Return the static list of supported OpenAI models. + */ + public function getModels(): array + { + return self::$availableModels; + } + + /** + * Validate the API key by calling the models endpoint. + */ + public function testConnection(): array + { + try { + $start = microtime(true); + + $ch = curl_init(self::API_MODELS); + curl_setopt_array($ch, [ + CURLOPT_RETURNTRANSFER => true, + CURLOPT_HTTPHEADER => $this->headers(), + CURLOPT_TIMEOUT => $this->timeout, + ]); + + $body = curl_exec($ch); + $error = curl_error($ch); + $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + + $latency = (int)((microtime(true) - $start) * 1000); + + if ($error) throw new \Exception("cURL error: $error"); + + $decoded = json_decode($body, true); + + if ($code !== 200) { + $msg = $decoded['error']['message'] ?? "HTTP $code"; + throw new \Exception($msg); + } + + return ['success' => true, 'latency_ms' => $latency, 'error' => '']; + + } catch (\Exception $e) { + return ['success' => false, 'latency_ms' => 0, 'error' => $e->getMessage()]; + } + } + + // ─── Private Helpers ───────────────────────────────────────────────────── + + private function headers(): array + { + return [ + 'Content-Type: application/json', + 'Authorization: Bearer ' . $this->apiKey, + ]; + } + + private function buildPayload(array $messages, array $options): array + { + $model = $options['model'] ?? $this->model; + $maxTokens = (int)($options['max_tokens'] ?? $this->maxTokens); + $temperature = (float)($options['temperature'] ?? $this->temperature); + + // Prepend system prompt if set + if ($this->systemPrompt !== '') { + array_unshift($messages, ['role' => 'system', 'content' => $this->systemPrompt]); + } + + $payload = [ + 'model' => $model, + 'messages' => $messages, + 'max_tokens' => $maxTokens, + ]; + + // o1/o3 models don't support temperature + if (!str_starts_with($model, 'o1') && !str_starts_with($model, 'o3')) { + $payload['temperature'] = $temperature; + } + + return $payload; + } +} diff --git a/commands/GreetCommand.php b/commands/GreetCommand.php new file mode 100644 index 0000000..372a54c --- /dev/null +++ b/commands/GreetCommand.php @@ -0,0 +1,55 @@ +setName($this->commandName) + ->setDescription($this->commandDescription) + ->addArgument( + $this->commandArgumentName, + InputArgument::OPTIONAL, + $this->commandArgumentDescription + ) + ->addOption( + $this->commandOptionName, + null, + InputOption::VALUE_NONE, + $this->commandOptionDescription + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $name = $input->getArgument($this->commandArgumentName); + + if ($name) { + $text = 'Hello '.$name; + } else { + $text = 'Hello'; + } + + if ($input->getOption($this->commandOptionName)) { + $text = strtoupper($text); + } + + $output->writeln($text); + return Command::SUCCESS; + } +} \ No newline at end of file diff --git a/commands/SeedMenuCommand.php b/commands/SeedMenuCommand.php new file mode 100644 index 0000000..bde1de1 --- /dev/null +++ b/commands/SeedMenuCommand.php @@ -0,0 +1,134 @@ +setName($this->commandName) + ->setDescription($this->commandDescription); + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $output->writeln('Creating tables...'); + + Db::execute(" + CREATE TABLE IF NOT EXISTS sp_menus ( + menu_id int unsigned NOT NULL AUTO_INCREMENT, + slug varchar(50) NOT NULL, + name varchar(100) NOT NULL, + active tinyint DEFAULT 1, + PRIMARY KEY (menu_id), + UNIQUE KEY (slug) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 + ", []); + + Db::execute(" + CREATE TABLE IF NOT EXISTS sp_menu_items ( + item_id int unsigned NOT NULL AUTO_INCREMENT, + menu_id int unsigned NOT NULL, + parent_id int unsigned DEFAULT NULL, + label varchar(100) NOT NULL, + icon varchar(100) DEFAULT NULL, + type enum('link','header','divider') DEFAULT 'link', + url varchar(255) DEFAULT NULL, + perm_id int unsigned DEFAULT NULL, + match_prefix tinyint DEFAULT 0, + sort_order int DEFAULT 0, + active tinyint DEFAULT 1, + PRIMARY KEY (item_id), + KEY (menu_id), + KEY (parent_id), + CONSTRAINT FOREIGN KEY (perm_id) REFERENCES sp_permissions(perm_id) ON DELETE SET NULL + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 + ", []); + + $output->writeln('Tables ready. Seeding data...'); + + // Idempotent: skip if already seeded + $existing = Db::getRow("SELECT menu_id FROM sp_menus WHERE slug = 'admin_sidebar' LIMIT 1"); + if ($existing) { + $output->writeln('admin_sidebar already exists — skipping seed. Run with --force to re-seed (delete rows manually first).'); + return Command::SUCCESS; + } + + // ── Insert menu ────────────────────────────────────────────────────────── + $menuResult = json_decode(Db::insert('sp_menus', [ + 'slug' => 'admin_sidebar', + 'name' => 'Admin Sidebar', + 'active' => 1, + ]), true); + + $menuId = (int)$menuResult['ID']; + $output->writeln("Menu ID: {$menuId}"); + + // Helper: fetch perm_id by controller.action, return null if not found + $perm = function(string $controller, string $action) use ($output): ?int { + $row = Db::getRow( + "SELECT perm_id FROM sp_permissions WHERE perm_controller = ? AND perm_action = ? LIMIT 1", + [$controller, $action] + ); + if (!$row) { + $output->writeln(" Permission not found: {$controller}.{$action} — using NULL"); + return null; + } + return (int)$row['perm_id']; + }; + + // Helper: insert item, return item_id + $insertItem = function(array $data) use ($menuId): int { + $data['menu_id'] = $menuId; + $result = json_decode(Db::insert('sp_menu_items', $data), true); + return (int)$result['ID']; + }; + + $sort = 10; + + // ── Parent: Manage Account ─────────────────────────────────────────────── + $acctId = $insertItem([ + 'parent_id' => null, + 'label' => 'Manage Account', + 'icon' => 'ki-duotone ki-address-book', + 'type' => 'link', + 'url' => null, + 'perm_id' => null, + 'sort_order' => $sort += 10, + ]); + + $subSort = 0; + $insertItem(['parent_id' => $acctId, 'label' => 'Account Settings', 'type' => 'link', 'url' => '/account', 'perm_id' => null, 'sort_order' => $subSort += 10]); + $insertItem(['parent_id' => $acctId, 'label' => 'Billing', 'type' => 'link', 'url' => '/account/billing', 'perm_id' => null, 'sort_order' => $subSort += 10]); + $insertItem(['parent_id' => $acctId, 'label' => 'Invoices', 'type' => 'link', 'url' => '/account/invoices', 'perm_id' => null, 'sort_order' => $subSort += 10]); + $insertItem(['parent_id' => $acctId, 'label' => 'Integrations & API', 'type' => 'link', 'url' => '/account/integrations', 'perm_id' => null, 'sort_order' => $subSort += 10]); + $insertItem(['parent_id' => $acctId, 'label' => 'Activity Logs', 'type' => 'link', 'url' => '/account/activity', 'perm_id' => null, 'sort_order' => $subSort += 10]); + + // ── Parent: Administrator ──────────────────────────────────────────────── + $adminId = $insertItem([ + 'parent_id' => null, + 'label' => 'Administrator', + 'icon' => 'ki-duotone ki-shield-tick', + 'type' => 'link', + 'url' => null, + 'perm_id' => null, + 'sort_order' => $sort += 10, + ]); + + $subSort = 0; + $insertItem(['parent_id' => $adminId, 'label' => 'Overview', 'type' => 'link', 'url' => '/admin', 'perm_id' => $perm('admin', 'index'), 'sort_order' => $subSort += 10, 'match_prefix' => 0]); + $insertItem(['parent_id' => $adminId, 'label' => 'Manage Orgs', 'type' => 'link', 'url' => '/admin/orgs', 'perm_id' => $perm('admin', 'orgs'), 'sort_order' => $subSort += 10, 'match_prefix' => 1]); + $insertItem(['parent_id' => $adminId, 'label' => 'Manage Users', 'type' => 'link', 'url' => '/admin/users', 'perm_id' => $perm('admin', 'users'), 'sort_order' => $subSort += 10, 'match_prefix' => 1]); + $insertItem(['parent_id' => $adminId, 'label' => 'Integrations', 'type' => 'link', 'url' => '/admin/integrations', 'perm_id' => $perm('admin', 'integrations'), 'sort_order' => $subSort += 10]); + $insertItem(['parent_id' => $adminId, 'label' => 'User Roles', 'type' => 'link', 'url' => '/admin/roles', 'perm_id' => $perm('admin', 'roles'), 'sort_order' => $subSort += 10, 'match_prefix' => 1]); + $insertItem(['parent_id' => $adminId, 'label' => 'Activity Logs', 'type' => 'link', 'url' => '/admin/activity', 'perm_id' => $perm('admin', 'activity'), 'sort_order' => $subSort += 10]); + $insertItem(['parent_id' => $adminId, 'label' => 'Menu Builder', 'type' => 'link', 'url' => '/admin/menu', 'perm_id' => $perm('admin', 'menu'), 'sort_order' => $subSort += 10, 'match_prefix' => 1]); + + $output->writeln('Seeded successfully.'); + return Command::SUCCESS; + } +} diff --git a/commands/commands.md b/commands/commands.md new file mode 100644 index 0000000..0f75817 --- /dev/null +++ b/commands/commands.md @@ -0,0 +1,119 @@ +# CLI Commands — `commands/` + +Scripts run via `php console `. No HTTP context — output goes to stdout. + +## File Naming + +`PascalCaseCommand.php` — e.g. `SyncDataCommand.php` + +## Running Commands + +```bash +php console SyncDataCommand +php console SyncDataCommand --date="2024-10-28" --force +php console CleanupDatabaseCommand --days=30 --dry-run +php console ProcessEmailQueueCommand --limit=50 +``` + +## Command Structure + +```php + $record['external_id']] + ); + if ($exists) { echo "Skipping {$record['external_id']}\n"; continue; } + } + + Db::insert('sp_data', [ + 'external_id' => $record['external_id'], + 'payload' => $record['payload'], + 'synced_at' => date('Y-m-d H:i:s') + ]); + echo "Inserted {$record['external_id']}\n"; + } + + echo "Done.\n"; + +} catch (Exception $e) { + echo "ERROR: " . $e->getMessage() . "\n"; + exit(1); +} +``` + +## Error Handling Pattern + +Log individual failures and keep processing — exit with code 1 only on fatal errors: + +```php +$errors = []; +$processed = 0; + +foreach ($items as $item) { + try { + processItem($item); + $processed++; + echo "."; + } catch (Exception $e) { + $errors[] = ['id' => $item['id'], 'error' => $e->getMessage()]; + echo "E"; + } +} + +echo "\n\nProcessed: $processed | Errors: " . count($errors) . "\n"; + +if (!empty($errors)) { + foreach ($errors as $err) { + echo "Item #{$err['id']}: {$err['error']}\n"; + } + exit(1); +} +``` + +## Dry-Run Pattern + +```php +$dryRun = isset($options['dry-run']); +if ($dryRun) { echo "DRY RUN — no changes will be made\n"; } + +$count = Db::getRow("SELECT COUNT(*) as n FROM sp_logs WHERE created_at < :date", + [':date' => $cutoffDate] +); +echo "Rows to delete: {$count['n']}\n"; + +if (!$dryRun) { + Db::delete('sp_logs', 'created_at < :date', [':date' => $cutoffDate]); +} +``` + +## Cron Setup + +```bash +crontab -e +``` + +```cron +# Every 5 minutes +*/5 * * * * cd /www/wwwroot/appSeedProject && php console ProcessEmailQueueCommand >> /var/log/email-queue.log 2>&1 + +# Every hour +0 * * * * cd /www/wwwroot/appSeedProject && php console SyncDataCommand >> /var/log/sync.log 2>&1 + +# Nightly cleanup at 2 AM +0 2 * * * cd /www/wwwroot/appSeedProject && php console CleanupDatabaseCommand --days=30 >> /var/log/cleanup.log 2>&1 +``` diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..cc94fe8 --- /dev/null +++ b/composer.json @@ -0,0 +1,49 @@ +{ + "name": "seedproject/seedproject", + "require": { + "anthropic-ai/sdk": "^0.3.0", + "carbonphp/carbon-doctrine-types": "^2.1.0", + "getbrevo/brevo-php": "^1.0.2", + "guzzlehttp/promises": "^2.3.0", + "guzzlehttp/psr7": "^2.8.0", + "mashape/unirest-php": "^3.0.4", + "nyholm/psr7": "^1.8.2", + "php-http/discovery": "^1.20.0", + "php-http/multipart-stream-builder": "^1.4.2", + "phpmailer/phpmailer": "^6.12.0", + "psr/container": "^2.0.2", + "psr/http-client": "^1.0.3", + "psr/http-factory": "^1.1.0", + "psr/http-message": "^2.0", + "symfony/console": "^5.4.47", + "symfony/deprecation-contracts": "^3.6.0", + "symfony/polyfill-ctype": "^1.33.0", + "symfony/polyfill-intl-grapheme": "^1.33.0", + "symfony/polyfill-intl-normalizer": "^1.33.0", + "symfony/polyfill-mbstring": "^1.33.0", + "symfony/polyfill-php73": "^1.33.0", + "symfony/polyfill-php80": "^1.33.0", + "symfony/service-contracts": "^3.6.1", + "symfony/string": "^6.4.30" + }, + "autoload": { + "classmap": [ + "core/", + "app/Helpers", + "app/Controllers", + "app/Components", + "app/Gateways", + "system/" + ], + "psr-4": { + "App\\": "app", + "Models\\": "models" + } + }, + "config": { + "platform-check": false, + "allow-plugins": { + "php-http/discovery": true + } + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..10643f8 --- /dev/null +++ b/composer.lock @@ -0,0 +1,1970 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "74a4cde47eddbeff4dbeda5cea0ae363", + "packages": [ + { + "name": "anthropic-ai/sdk", + "version": "v0.3.0", + "source": { + "type": "git", + "url": "https://github.com/anthropics/anthropic-sdk-php.git", + "reference": "e41ffa21bc156e795b79abca7bfcc88686a12e3c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/anthropics/anthropic-sdk-php/zipball/e41ffa21bc156e795b79abca7bfcc88686a12e3c", + "reference": "e41ffa21bc156e795b79abca7bfcc88686a12e3c", + "shasum": "" + }, + "require": { + "php": "^8.1", + "php-http/discovery": "^1", + "psr/http-client": "^1", + "psr/http-client-implementation": "^1", + "psr/http-factory-implementation": "^1", + "psr/http-message": "^1|^2" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3", + "nyholm/psr7": "^1", + "pestphp/pest": "^3", + "phpstan/extension-installer": "^1", + "phpstan/phpstan": "^2", + "phpstan/phpstan-phpunit": "^2", + "phpunit/phpunit": "^11", + "symfony/http-client": "^7" + }, + "type": "library", + "autoload": { + "files": [ + "src/Core.php", + "src/Client.php" + ], + "psr-4": { + "Anthropic\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "description": "Anthropic PHP SDK", + "support": { + "issues": "https://github.com/anthropics/anthropic-sdk-php/issues", + "source": "https://github.com/anthropics/anthropic-sdk-php/tree/v0.3.0" + }, + "time": "2025-09-02T16:25:58+00:00" + }, + { + "name": "carbonphp/carbon-doctrine-types", + "version": "2.1.0", + "source": { + "type": "git", + "url": "https://github.com/CarbonPHP/carbon-doctrine-types.git", + "reference": "99f76ffa36cce3b70a4a6abce41dba15ca2e84cb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/CarbonPHP/carbon-doctrine-types/zipball/99f76ffa36cce3b70a4a6abce41dba15ca2e84cb", + "reference": "99f76ffa36cce3b70a4a6abce41dba15ca2e84cb", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "conflict": { + "doctrine/dbal": "<3.7.0 || >=4.0.0" + }, + "require-dev": { + "doctrine/dbal": "^3.7.0", + "nesbot/carbon": "^2.71.0 || ^3.0.0", + "phpunit/phpunit": "^10.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Carbon\\Doctrine\\": "src/Carbon/Doctrine/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "KyleKatarn", + "email": "kylekatarnls@gmail.com" + } + ], + "description": "Types to use Carbon in Doctrine", + "keywords": [ + "carbon", + "date", + "datetime", + "doctrine", + "time" + ], + "support": { + "issues": "https://github.com/CarbonPHP/carbon-doctrine-types/issues", + "source": "https://github.com/CarbonPHP/carbon-doctrine-types/tree/2.1.0" + }, + "funding": [ + { + "url": "https://github.com/kylekatarnls", + "type": "github" + }, + { + "url": "https://opencollective.com/Carbon", + "type": "open_collective" + }, + { + "url": "https://tidelift.com/funding/github/packagist/nesbot/carbon", + "type": "tidelift" + } + ], + "time": "2023-12-11T17:09:12+00:00" + }, + { + "name": "getbrevo/brevo-php", + "version": "v1.0.2", + "source": { + "type": "git", + "url": "https://github.com/getbrevo/brevo-php.git", + "reference": "6c3286e62327277fd8445cddb057d44e850722c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/getbrevo/brevo-php/zipball/6c3286e62327277fd8445cddb057d44e850722c0", + "reference": "6c3286e62327277fd8445cddb057d44e850722c0", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "ext-json": "*", + "ext-mbstring": "*", + "guzzlehttp/guzzle": "^7.4.0", + "php": ">=5.6" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "~1.12", + "phpunit/phpunit": "^4.8", + "squizlabs/php_codesniffer": "~2.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x.x-dev" + } + }, + "autoload": { + "psr-4": { + "Brevo\\Client\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Brevo Developers", + "email": "contact@brevo.com", + "homepage": "https://www.brevo.com/" + } + ], + "description": "Official Brevo provided RESTFul API V3 php library", + "homepage": "https://github.com/getbrevo/brevo-php", + "keywords": [ + "api", + "brevo", + "php", + "sdk", + "swagger" + ], + "support": { + "issues": "https://github.com/getbrevo/brevo-php/issues", + "source": "https://github.com/getbrevo/brevo-php/tree/v1.0.2" + }, + "time": "2023-07-14T10:00:50+00:00" + }, + { + "name": "guzzlehttp/guzzle", + "version": "7.10.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/b51ac707cfa420b7bfd4e4d5e510ba8008e822b4", + "reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/promises": "^2.3", + "guzzlehttp/psr7": "^2.8", + "php": "^7.2.5 || ^8.0", + "psr/http-client": "^1.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" + }, + "provide": { + "psr/http-client-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "ext-curl": "*", + "guzzle/client-integration-tests": "3.0.2", + "php-http/message-factory": "^1.1", + "phpunit/phpunit": "^8.5.39 || ^9.6.20", + "psr/log": "^1.1 || ^2.0 || ^3.0" + }, + "suggest": { + "ext-curl": "Required for CURL handler support", + "ext-intl": "Required for Internationalized Domain Name (IDN) support", + "psr/log": "Required for using the Log middleware" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Jeremy Lindblom", + "email": "jeremeamia@gmail.com", + "homepage": "https://github.com/jeremeamia" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "psr-18", + "psr-7", + "rest", + "web service" + ], + "support": { + "issues": "https://github.com/guzzle/guzzle/issues", + "source": "https://github.com/guzzle/guzzle/tree/7.10.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", + "type": "tidelift" + } + ], + "time": "2025-08-23T22:36:01+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "2.3.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "481557b130ef3790cf82b713667b43030dc9c957" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/481557b130ef3790cf82b713667b43030dc9c957", + "reference": "481557b130ef3790cf82b713667b43030dc9c957", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.44 || ^9.6.25" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "support": { + "issues": "https://github.com/guzzle/promises/issues", + "source": "https://github.com/guzzle/promises/tree/2.3.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises", + "type": "tidelift" + } + ], + "time": "2025-08-22T14:34:08+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "2.9.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "7d0ed42f28e42d61352a7a79de682e5e67fec884" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/7d0ed42f28e42d61352a7a79de682e5e67fec884", + "reference": "7d0ed42f28e42d61352a7a79de682e5e67fec884", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.1 || ^2.0", + "ralouphie/getallheaders": "^3.0" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "http-interop/http-factory-tests": "0.9.0", + "jshttp/mime-db": "1.54.0.1", + "phpunit/phpunit": "^8.5.44 || ^9.6.25" + }, + "suggest": { + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "psr-7", + "request", + "response", + "stream", + "uri", + "url" + ], + "support": { + "issues": "https://github.com/guzzle/psr7/issues", + "source": "https://github.com/guzzle/psr7/tree/2.9.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", + "type": "tidelift" + } + ], + "time": "2026-03-10T16:41:02+00:00" + }, + { + "name": "mashape/unirest-php", + "version": "v3.0.4", + "source": { + "type": "git", + "url": "https://github.com/Mashape/unirest-php.git", + "reference": "842c0f242dfaaf85f16b72e217bf7f7c19ab12cb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Mashape/unirest-php/zipball/842c0f242dfaaf85f16b72e217bf7f7c19ab12cb", + "reference": "842c0f242dfaaf85f16b72e217bf7f7c19ab12cb", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "codeclimate/php-test-reporter": "0.1.*", + "phpunit/phpunit": "~4.4" + }, + "suggest": { + "ext-json": "Allows using JSON Bodies for sending and parsing requests" + }, + "type": "library", + "autoload": { + "psr-0": { + "Unirest\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Unirest PHP", + "homepage": "https://github.com/Mashape/unirest-php", + "keywords": [ + "client", + "curl", + "http", + "https", + "rest" + ], + "support": { + "email": "opensource@mashape.com", + "issues": "https://github.com/Mashape/unirest-php/issues", + "source": "https://github.com/Mashape/unirest-php/tree/master" + }, + "time": "2016-08-11T17:49:21+00:00" + }, + { + "name": "nyholm/psr7", + "version": "1.8.2", + "source": { + "type": "git", + "url": "https://github.com/Nyholm/psr7.git", + "reference": "a71f2b11690f4b24d099d6b16690a90ae14fc6f3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Nyholm/psr7/zipball/a71f2b11690f4b24d099d6b16690a90ae14fc6f3", + "reference": "a71f2b11690f4b24d099d6b16690a90ae14fc6f3", + "shasum": "" + }, + "require": { + "php": ">=7.2", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.1 || ^2.0" + }, + "provide": { + "php-http/message-factory-implementation": "1.0", + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "http-interop/http-factory-tests": "^0.9", + "php-http/message-factory": "^1.0", + "php-http/psr7-integration-tests": "^1.0", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.4", + "symfony/error-handler": "^4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.8-dev" + } + }, + "autoload": { + "psr-4": { + "Nyholm\\Psr7\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com" + }, + { + "name": "Martijn van der Ven", + "email": "martijn@vanderven.se" + } + ], + "description": "A fast PHP7 implementation of PSR-7", + "homepage": "https://tnyholm.se", + "keywords": [ + "psr-17", + "psr-7" + ], + "support": { + "issues": "https://github.com/Nyholm/psr7/issues", + "source": "https://github.com/Nyholm/psr7/tree/1.8.2" + }, + "funding": [ + { + "url": "https://github.com/Zegnat", + "type": "github" + }, + { + "url": "https://github.com/nyholm", + "type": "github" + } + ], + "time": "2024-09-09T07:06:30+00:00" + }, + { + "name": "php-http/discovery", + "version": "1.20.0", + "source": { + "type": "git", + "url": "https://github.com/php-http/discovery.git", + "reference": "82fe4c73ef3363caed49ff8dd1539ba06044910d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/discovery/zipball/82fe4c73ef3363caed49ff8dd1539ba06044910d", + "reference": "82fe4c73ef3363caed49ff8dd1539ba06044910d", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0|^2.0", + "php": "^7.1 || ^8.0" + }, + "conflict": { + "nyholm/psr7": "<1.0", + "zendframework/zend-diactoros": "*" + }, + "provide": { + "php-http/async-client-implementation": "*", + "php-http/client-implementation": "*", + "psr/http-client-implementation": "*", + "psr/http-factory-implementation": "*", + "psr/http-message-implementation": "*" + }, + "require-dev": { + "composer/composer": "^1.0.2|^2.0", + "graham-campbell/phpspec-skip-example-extension": "^5.0", + "php-http/httplug": "^1.0 || ^2.0", + "php-http/message-factory": "^1.0", + "phpspec/phpspec": "^5.1 || ^6.1 || ^7.3", + "sebastian/comparator": "^3.0.5 || ^4.0.8", + "symfony/phpunit-bridge": "^6.4.4 || ^7.0.1" + }, + "type": "composer-plugin", + "extra": { + "class": "Http\\Discovery\\Composer\\Plugin", + "plugin-optional": true + }, + "autoload": { + "psr-4": { + "Http\\Discovery\\": "src/" + }, + "exclude-from-classmap": [ + "src/Composer/Plugin.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + } + ], + "description": "Finds and installs PSR-7, PSR-17, PSR-18 and HTTPlug implementations", + "homepage": "http://php-http.org", + "keywords": [ + "adapter", + "client", + "discovery", + "factory", + "http", + "message", + "psr17", + "psr7" + ], + "support": { + "issues": "https://github.com/php-http/discovery/issues", + "source": "https://github.com/php-http/discovery/tree/1.20.0" + }, + "time": "2024-10-02T11:20:13+00:00" + }, + { + "name": "php-http/multipart-stream-builder", + "version": "1.4.2", + "source": { + "type": "git", + "url": "https://github.com/php-http/multipart-stream-builder.git", + "reference": "10086e6de6f53489cca5ecc45b6f468604d3460e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/multipart-stream-builder/zipball/10086e6de6f53489cca5ecc45b6f468604d3460e", + "reference": "10086e6de6f53489cca5ecc45b6f468604d3460e", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0", + "php-http/discovery": "^1.15", + "psr/http-factory-implementation": "^1.0" + }, + "require-dev": { + "nyholm/psr7": "^1.0", + "php-http/message": "^1.5", + "php-http/message-factory": "^1.0.2", + "phpunit/phpunit": "^7.5.15 || ^8.5 || ^9.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Http\\Message\\MultipartStream\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com" + } + ], + "description": "A builder class that help you create a multipart stream", + "homepage": "http://php-http.org", + "keywords": [ + "factory", + "http", + "message", + "multipart stream", + "stream" + ], + "support": { + "issues": "https://github.com/php-http/multipart-stream-builder/issues", + "source": "https://github.com/php-http/multipart-stream-builder/tree/1.4.2" + }, + "time": "2024-09-04T13:22:54+00:00" + }, + { + "name": "phpmailer/phpmailer", + "version": "v6.12.0", + "source": { + "type": "git", + "url": "https://github.com/PHPMailer/PHPMailer.git", + "reference": "d1ac35d784bf9f5e61b424901d5a014967f15b12" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/d1ac35d784bf9f5e61b424901d5a014967f15b12", + "reference": "d1ac35d784bf9f5e61b424901d5a014967f15b12", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-filter": "*", + "ext-hash": "*", + "php": ">=5.5.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "doctrine/annotations": "^1.2.6 || ^1.13.3", + "php-parallel-lint/php-console-highlighter": "^1.0.0", + "php-parallel-lint/php-parallel-lint": "^1.3.2", + "phpcompatibility/php-compatibility": "^9.3.5", + "roave/security-advisories": "dev-latest", + "squizlabs/php_codesniffer": "^3.7.2", + "yoast/phpunit-polyfills": "^1.0.4" + }, + "suggest": { + "decomplexity/SendOauth2": "Adapter for using XOAUTH2 authentication", + "ext-mbstring": "Needed to send email in multibyte encoding charset or decode encoded addresses", + "ext-openssl": "Needed for secure SMTP sending and DKIM signing", + "greew/oauth2-azure-provider": "Needed for Microsoft Azure XOAUTH2 authentication", + "hayageek/oauth2-yahoo": "Needed for Yahoo XOAUTH2 authentication", + "league/oauth2-google": "Needed for Google XOAUTH2 authentication", + "psr/log": "For optional PSR-3 debug logging", + "symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)", + "thenetworg/oauth2-azure": "Needed for Microsoft XOAUTH2 authentication" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPMailer\\PHPMailer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1-only" + ], + "authors": [ + { + "name": "Marcus Bointon", + "email": "phpmailer@synchromedia.co.uk" + }, + { + "name": "Jim Jagielski", + "email": "jimjag@gmail.com" + }, + { + "name": "Andy Prevost", + "email": "codeworxtech@users.sourceforge.net" + }, + { + "name": "Brent R. Matzelle" + } + ], + "description": "PHPMailer is a full-featured email creation and transfer class for PHP", + "support": { + "issues": "https://github.com/PHPMailer/PHPMailer/issues", + "source": "https://github.com/PHPMailer/PHPMailer/tree/v6.12.0" + }, + "funding": [ + { + "url": "https://github.com/Synchro", + "type": "github" + } + ], + "time": "2025-10-15T16:49:08+00:00" + }, + { + "name": "psr/container", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "time": "2021-11-05T16:47:00+00:00" + }, + { + "name": "psr/http-client", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-client.git", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ], + "support": { + "source": "https://github.com/php-fig/http-client" + }, + "time": "2023-09-23T14:17:50+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory" + }, + "time": "2024-04-15T12:06:14+00:00" + }, + { + "name": "psr/http-message", + "version": "2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/2.0" + }, + "time": "2023-04-04T09:54:51+00:00" + }, + { + "name": "ralouphie/getallheaders", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "support": { + "issues": "https://github.com/ralouphie/getallheaders/issues", + "source": "https://github.com/ralouphie/getallheaders/tree/develop" + }, + "time": "2019-03-08T08:55:37+00:00" + }, + { + "name": "symfony/console", + "version": "v5.4.47", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "c4ba980ca61a9eb18ee6bcc73f28e475852bb1ed" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/c4ba980ca61a9eb18ee6bcc73f28e475852bb1ed", + "reference": "c4ba980ca61a9eb18ee6bcc73f28e475852bb1ed", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php73": "^1.9", + "symfony/polyfill-php80": "^1.16", + "symfony/service-contracts": "^1.1|^2|^3", + "symfony/string": "^5.1|^6.0" + }, + "conflict": { + "psr/log": ">=3", + "symfony/dependency-injection": "<4.4", + "symfony/dotenv": "<5.1", + "symfony/event-dispatcher": "<4.4", + "symfony/lock": "<4.4", + "symfony/process": "<4.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0" + }, + "require-dev": { + "psr/log": "^1|^2", + "symfony/config": "^4.4|^5.0|^6.0", + "symfony/dependency-injection": "^4.4|^5.0|^6.0", + "symfony/event-dispatcher": "^4.4|^5.0|^6.0", + "symfony/lock": "^4.4|^5.0|^6.0", + "symfony/process": "^4.4|^5.0|^6.0", + "symfony/var-dumper": "^4.4|^5.0|^6.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/lock": "", + "symfony/process": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command-line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v5.4.47" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-11-06T11:30:55+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:21:43+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.37.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "141046a8f9477948ff284fa65be2095baafb94f2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/141046a8f9477948ff284fa65be2095baafb94f2", + "reference": "141046a8f9477948ff284fa65be2095baafb94f2", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.37.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-04-10T16:19:22+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.37.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "4864388bfbd3001ce88e234fab652acd91fdc57e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/4864388bfbd3001ce88e234fab652acd91fdc57e", + "reference": "4864388bfbd3001ce88e234fab652acd91fdc57e", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.37.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-04-26T13:13:48+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.37.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "3833d7255cc303546435cb650316bff708a1c75c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", + "reference": "3833d7255cc303546435cb650316bff708a1c75c", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.37.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.37.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "6a21eb99c6973357967f6ce3708cd55a6bec6315" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6a21eb99c6973357967f6ce3708cd55a6bec6315", + "reference": "6a21eb99c6973357967f6ce3708cd55a6bec6315", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "php": ">=7.2" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.37.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-04-10T17:25:58+00:00" + }, + { + "name": "symfony/polyfill-php73", + "version": "v1.37.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/0f68c03565dcaaf25a890667542e8bd75fe7e5bb", + "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php73\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php73/tree/v1.37.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.37.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/dfb55726c3a76ea3b6459fcfda1ec2d80a682411", + "reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.37.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-04-10T16:19:22+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v3.6.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/45112560a3ba2d715666a509a0bc9521d10b6c43", + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v3.6.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-07-15T11:30:57+00:00" + }, + { + "name": "symfony/string", + "version": "v6.4.34", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "2adaf4106f2ef4c67271971bde6d3fe0a6936432" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/2adaf4106f2ef4c67271971bde6d3fe0a6936432", + "reference": "2adaf4106f2ef4c67271971bde6d3fe0a6936432", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/translation-contracts": "<2.5" + }, + "require-dev": { + "symfony/http-client": "^5.4|^6.0|^7.0", + "symfony/intl": "^6.2|^7.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^5.4|^6.0|^7.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v6.4.34" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-02-08T20:44:54+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": {}, + "prefer-stable": false, + "prefer-lowest": false, + "platform": {}, + "platform-dev": {}, + "plugin-api-version": "2.9.0" +} diff --git a/config.php b/config.php new file mode 100644 index 0000000..35b8f46 --- /dev/null +++ b/config.php @@ -0,0 +1,41 @@ +nRFl@t,'); +define( 'HASH_API_KEY', ':-?7>?@[TP=oor}~BY 6UaJ!Vp$6Bw6tD'); +define( 'TIMESTAMP', date('Y-m-d H:i:s')); +date_default_timezone_set('America/New_York'); + +define('DB_TYPE', 'mysql'); +define('DB_HOST', 'LOCALHOST'); +define('DB_NAME', 'DBNAME'); +define('DB_USER', 'DBUSER'); +define('DB_PASS', 'DBPASSWORD'); + +\Db::setConnectionInfo( + DB_TYPE, + DB_NAME, + DB_USER, + DB_PASS, + DB_HOST, + 'snoopi2024' +); + +\Db::setConnectionInfo(DB_TYPE, DB_NAME, DB_USER, DB_PASS); \ No newline at end of file diff --git a/console b/console new file mode 100644 index 0000000..ee7f910 --- /dev/null +++ b/console @@ -0,0 +1,17 @@ +#!/usr/bin/env php +add(new GreetCommand()); +//$application->add(new Sentinel()); +//$application->add(new Engine()); +$application->run(); \ No newline at end of file diff --git a/core/AltoRouter.php b/core/AltoRouter.php new file mode 100644 index 0000000..7f7c944 --- /dev/null +++ b/core/AltoRouter.php @@ -0,0 +1,253 @@ + '[0-9]++', + 'a' => '[0-9A-Za-z]++', + 'h' => '[0-9A-Fa-f]++', + '*' => '.+?', + '**' => '.++', + '' => '[^/\.]++' + ); + + /** + * Create router in one call from config. + * + * @param array $routes + * @param string $basePath + * @param array $matchTypes + */ + public function __construct($routes = array(), $basePath = '', $matchTypes = array()) { + $this->setBasePath($basePath); + $this->addMatchTypes($matchTypes); + + foreach ($routes as $route) { + call_user_func_array(array($this, 'map'), $route); + } + } + + /** + * Set the base path. + * Useful if you are running your application from a subdirectory. + */ + public function setBasePath($basePath) { + $this->basePath = $basePath; + } + + /** + * Add named match types. It uses array_merge so keys can be overwritten. + * + * @param array $matchTypes The key is the name and the value is the regex. + */ + public function addMatchTypes($matchTypes) { + $this->matchTypes = array_merge($this->matchTypes, $matchTypes); + } + + /** + * Map a route to a target + * + * @param string $method One of 4 HTTP Methods, or a pipe-separated list of multiple HTTP Methods (GET|POST|PUT|DELETE) + * @param string $route The route regex, custom regex must start with an @. You can use multiple pre-set regex filters, like [i:id] + * @param mixed $target The target where this route should point to. Can be anything. + * @param string $name Optional name of this route. Supply if you want to reverse route this url in your application. + * + */ + public function map($method, $route, $target, $name = null) { + + $this->routes[] = array($method, $route, $target, $name); + + if ($name) { + if (isset($this->namedRoutes[$name])) { + throw new \Exception("Can not redeclare route '{$name}'"); + } else { + $this->namedRoutes[$name] = $route; + } + } + + return; + } + + /** + * Reversed routing + * + * Generate the URL for a named route. Replace regexes with supplied parameters + * + * @param string $routeName The name of the route. + * @param array @params Associative array of parameters to replace placeholders with. + * @return string The URL of the route with named parameters in place. + */ + public function generate($routeName, array $params = array()) { + + // Check if named route exists + if (!isset($this->namedRoutes[$routeName])) { + throw new \Exception("Route '{$routeName}' does not exist."); + } + + // Replace named parameters + $route = $this->namedRoutes[$routeName]; + + // prepend base path to route url again + $url = $this->basePath . $route; + + if (preg_match_all('`(/|\.|)\[([^:\]]*+)(?::([^:\]]*+))?\](\?|)`', $route, $matches, PREG_SET_ORDER)) { + + foreach ($matches as $match) { + list($block, $pre, $type, $param, $optional) = $match; + + if ($pre) { + $block = substr($block, 1); + } + + if (isset($params[$param])) { + $url = str_replace($block, $params[$param], $url); + } elseif ($optional) { + $url = str_replace($pre . $block, '', $url); + } + } + } + + return $url; + } + + /** + * Match a given Request Url against stored routes + * @param string $requestUrl + * @param string $requestMethod + * @return array|boolean Array with route information on success, false on failure (no match). + */ + public function match($requestUrl = null, $requestMethod = null) { + + $params = array(); + $match = false; + + // set Request Url if it isn't passed as parameter + if ($requestUrl === null) { + $requestUrl = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '/'; + } + + // strip base path from request url + $requestUrl = substr($requestUrl, strlen($this->basePath)); + + // Strip query string (?a=b) from Request Url + if (($strpos = strpos($requestUrl, '?')) !== false) { + $requestUrl = substr($requestUrl, 0, $strpos); + } + + // set Request Method if it isn't passed as a parameter + if ($requestMethod === null) { + $requestMethod = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : 'GET'; + } + + // Force request_order to be GP + // http://www.mail-archive.com/internals@lists.php.net/msg33119.html + $_REQUEST = array_merge($_GET, $_POST); + + foreach ($this->routes as $handler) { + list($method, $_route, $target, $name) = $handler; + + $methods = explode('|', $method); + $method_match = false; + + // Check if request method matches. If not, abandon early. (CHEAP) + foreach ($methods as $method) { + if (strcasecmp($requestMethod, $method) === 0) { + $method_match = true; + break; + } + } + + // Method did not match, continue to next route. + if (!$method_match) + continue; + + // Check for a wildcard (matches all) + if ($_route === '*') { + $match = true; + } elseif (isset($_route[0]) && $_route[0] === '@') { + $match = preg_match('`' . substr($_route, 1) . '`', $requestUrl, $params); + } else { + $route = null; + $regex = false; + $j = 0; + $n = isset($_route[0]) ? $_route[0] : null; + $i = 0; + + // Find the longest non-regex substring and match it against the URI + while (true) { + if (!isset($_route[$i])) { + break; + } elseif (false === $regex) { + $c = $n; + $regex = $c === '[' || $c === '(' || $c === '.'; + if (false === $regex && false !== isset($_route[$i + 1])) { + $n = $_route[$i + 1]; + $regex = $n === '?' || $n === '+' || $n === '*' || $n === '{'; + } + if (false === $regex && $c !== '/' && (!isset($requestUrl[$j]) || $c !== $requestUrl[$j])) { + continue 2; + } + $j++; + } + $route .= $_route[$i++]; + } + + $regex = $this->compileRoute($route); + $match = preg_match($regex, $requestUrl, $params); + } + + if (($match == true || $match > 0)) { + + if ($params) { + foreach ($params as $key => $value) { + if (is_numeric($key)) + unset($params[$key]); + } + } + + return array( + 'target' => $target, + 'params' => $params, + 'name' => $name + ); + } + } + return false; + } + + /** + * Compile the regex for a given route (EXPENSIVE) + */ + private function compileRoute($route) { + if (preg_match_all('`(/|\.|)\[([^:\]]*+)(?::([^:\]]*+))?\](\?|)`', $route, $matches, PREG_SET_ORDER)) { + + $matchTypes = $this->matchTypes; + foreach ($matches as $match) { + list($block, $pre, $type, $param, $optional) = $match; + + if (isset($matchTypes[$type])) { + $type = $matchTypes[$type]; + } + if ($pre === '.') { + $pre = '\.'; + } + + //Older versions of PCRE require the 'P' in (?P) + $pattern = '(?:' + . ($pre !== '' ? $pre : null) + . '(' + . ($param !== '' ? "?P<$param>" : null) + . $type + . '))' + . ($optional !== '' ? '?' : null); + + $route = str_replace($block, $pattern, $route); + } + } + return "`^$route$`"; + } + +} diff --git a/core/Bootstrap.php b/core/Bootstrap.php new file mode 100644 index 0000000..221d480 --- /dev/null +++ b/core/Bootstrap.php @@ -0,0 +1,248 @@ +_getUrl(); + + + + // Load the default controller if no URL is set + // eg: Visit http://localhost it loads Default Controller + if (empty($this->_url[0])) { + $this->_loadDefaultController(); + return false; + } + + //Router + $this->match = \Router::Routing(); + + + + if(DEBUG == true) { + register_shutdown_function(function () { + $err = error_get_last(); + if (! is_null($err)) { + print 'Error#'.$err['message'].'
'; + print 'Line#'.$err['line'].'
'; + print 'File#'.$err['file'].'
'; + } + }); + } + + + // This check whether there is a match + if (empty($this->match)) { + $this->_loadExistingController(); + $this->_callControllerMethod(); + } else { + $this->_loadRouter(); + } + + //$this->_loadExistingController(); + //$this->_callControllerMethod(); + } + + /** + * (Optional) Set a custom path to controllers + * @param string $path + */ + public function setControllerPath($path ='') { + \Helper::print_array($path); + $this->_controllerPath = trim($path, '') . ''; + } + + /** + * (Optional) Set a custom path to models + * @param string $path + */ + public function setModelPath($path ='') { + $this->_modelPath = trim($path, '') . ''; + } + + /** + * (Optional) Set a custom path to the error file + * @param string $path Use the file name of your controller, eg: error.php + */ + public function setErrorFile($path ='') { + $this->_errorFile = trim($path, '/'); + } + + /** + * (Optional) Set a custom path to the error file + * @param string $path Use the file name of your controller, eg: index.php + */ + public function setDefaultFile($path = '') { + $this->_defaultFile = trim($path, '/'); + } + + + /** + * (Optional) Set a custom path to the error file + * @param string $path Use the file name of your controller, eg: index.php + */ + public function setDefaultPath($path = '') { + $this->_defaultPath = trim($path, ''); // Removed the trim for / + } + + + + /** + * Fetches the $_GET from 'url' + */ + private function _getUrl() { + $url = isset($_GET['url']) ? $_GET['url'] : null; + $url = rtrim($url, '/'); + $url = filter_var($url, FILTER_SANITIZE_URL); + $this->_url = explode('/', $url); + } + + /** + * This loads if there is no GET parameter passed + */ + private function _loadDefaultController() { + require $this->_defaultPath . '/' . $this->_controllerPath . $this->_defaultFile; + $this->_controller = new Index(); + $this->_controller->index(); + } + + /** + * Load an existing controller if there IS a GET parameter passed + * + * @return boolean|string + */ + private function _loadExistingController() { + $file = $this->_defaultPath . '/' . $this->_controllerPath . $this->_url[0] . '.php'; + + + if (file_exists($file)) { + require $file; + if($this->_url[1]) { $method = $this->_url[1]; } else { $method = "index"; } + $this->_controller = new $this->_url[0]($method); + $this->_controller->loadModel($this->_url[0], $this->_modelPath); + } else { + $this->_error(); + return false; + } + } + + /** + * Loads Router if there's a rule set for specific URL combinate + * @return boolean + */ + private function _loadRouter() { + /// Run the Router + + + $this->ControllerName = $this->match['target']['c']; + $this->MethodName = $this->match['target']['a']; + $this->URIParameters = $this->match['params']; + + + $file = $this->_defaultPath . '/' . $this->_controllerPath . $this->ControllerName . '.php'; + + if (file_exists($file)) { + require $file; + + $this->_controller = new $this->ControllerName($this->MethodName); + $this->_controller->loadModel($this->ControllerName, $this->_modelPath); + } else { + $this->_error(); + return false; + } + + // Load Controller + $this->_controller->{$this->MethodName}($this->URIParameters); + } + + /** + * If a method is passed in the GET url parameter + * + * http://localhost/controller/method/(param)/(param)/(param) + * url[0] = Controller + * url[1] = Method (falls back to 'index' if not a valid method) + * url[2] = Param + * url[3] = Param + * url[4] = Param + * + * Fallback: /controller/param → controller->index(param) + */ + private function _callControllerMethod() { + $length = count($this->_url); + + // If url[1] is not a valid method, treat it as a param to index() + if ($length > 1) { + if (!method_exists($this->_controller, $this->_url[1])) { + array_splice($this->_url, 1, 0, ['index']); + $length = count($this->_url); + } + } + + // Determine what to load + switch ($length) { + case 5: + //Controller->Method(Param1, Param2, Param3) + $this->_controller->{$this->_url[1]}($this->_url[2], $this->_url[3], $this->_url[4]); + break; + + case 4: + //Controller->Method(Param1, Param2) + $this->_controller->{$this->_url[1]}($this->_url[2], $this->_url[3]); + break; + + case 3: + //Controller->Method(Param1, Param2) + $this->_controller->{$this->_url[1]}($this->_url[2]); + break; + + case 2: + //Controller->Method(Param1, Param2) + $this->_controller->{$this->_url[1]}(); + break; + + default: + $this->_controller->index(); + break; + } + } + + /** + * Display an error page if nothing exists + * + * @return boolean + */ + private function _error() { + require dirname(__DIR__) . '/' . $this->_defaultPath . '/' . $this->_controllerPath . $this->_errorFile; + $this->_controller = new _Error(); + $this->_controller->index(); + exit; + } + +} \ No newline at end of file diff --git a/core/Controller.php b/core/Controller.php new file mode 100644 index 0000000..31affca --- /dev/null +++ b/core/Controller.php @@ -0,0 +1,29 @@ +view = new View(); + $this->view->_c = $this; + } + + /** + * + * @param string $name Name of the model + * @param string $path Location of the models + */ + public function loadModel($name, $modelPath = 'models/') { + + $path = $modelPath . $name.'_model.php'; + + if (file_exists($path)) { + require $modelPath .$name.'_model.php'; + + $modelName = $name . '_Model'; + $this->model = new $modelName(); + $this->view->_m = $this->model; // extends [model] access to views + + } + } + +} \ No newline at end of file diff --git a/core/Database.php b/core/Database.php new file mode 100644 index 0000000..0a5ceff --- /dev/null +++ b/core/Database.php @@ -0,0 +1,321 @@ +prepare($sql); + foreach ($array as $key => $value) { + $sth->bindValue("$key", $value); + } + + $sth->execute(); + return $sth->fetchAll($fetchMode); + + } catch (PDOException $e) { + $Response['Code']='0'; + $Response['Message']= 'Error Select Entry: ' . $e->getMessage(); + + + echo json_encode($Response); + return false; + } + + + } + + /** + * insert + * @param string $table A name of table to insert into + * @param string $data An associative array + */ + public function insert($table, $data) + { + ksort($data); + + $fieldNames = implode('`, `', array_keys($data)); + $fieldValues = ':' . implode(', :', array_keys($data)); + + + try { + $sth = $this->prepare("INSERT INTO $table (`$fieldNames`) VALUES ($fieldValues)"); + + + foreach ($data as $key => $value) { + $sth->bindValue(":$key", $value); + } + + $sth->execute(); + $msg = $sth->errorInfo(); + + + if(!$msg[1]) { $msgCode = 1; } else { $msgCode = $msg[1]; } + if(!$msg[2]) { $msgMessage = 'Created'; } else { $msgMessage = $msg[2]; } + + $Response['Code']=$msgCode; + $Response['Message']=$msgMessage; + $Response['ID']= $this->lastInsertId(); + + } catch (PDOException $e) { + $Response['Code']='0'; + $Response['Message']= 'Error Creating Entry: ' . $e->getMessage(); + + + } + + return json_encode($Response); + + + + } + + /** + * update + * @param string $table A name of table to insert into + * @param string $data An associative array + * @param string $where the WHERE query part + */ + public function update($table, $data, $where) + { + ksort($data); + + $fieldDetails = NULL; + foreach($data as $key=> $value) { + $fieldDetails .= "`$key`=:$key,"; + } + $fieldDetails = rtrim($fieldDetails, ','); + + try { + + $sth = $this->prepare("UPDATE $table SET $fieldDetails WHERE $where"); + + foreach ($data as $key => $value) { + $sth->bindValue(":$key", $value); + } + + + $sth->execute(); + $count = $sth->rowCount(); + if($count){ + $Response['Code']='1'; + $Response['Rows']= $count; + $Response['Message']='Updated'; + } else { + $Response['Code']='0'; + $Response['Rows']= $count; + $Response['Message']='No Records Updated'; + } + + + } catch (PDOException $e) { + $Response['Code']='0'; + $Response['Message']= 'Error Updating Database: ' . $e->getMessage(); + + + } + + return json_encode($Response); + } + + + /** + * delete + * + * @param string $table + * @param string $where + * @param integer $limit + * @return integer Affected Rows + */ + public function delete($table, $where, $limit = 1) + { + $sth = $this->prepare("DELETE FROM $table WHERE $where LIMIT $limit"); + return $sth->execute(); + } + + + /** + * Query + * + * @param string $sql + * @return returns array results + */ + public function execQuery($sql, $limit='') { +// echo $sql; +// print_array($limit); + if($limit) { + $LimitStart = $limit['start']; + $LimitEnd = $limit['end']; + } + + $sth = $this->prepare($sql); + if($LimitStart) $sth->bindParam(1, $LimitStart,PDO::PARAM_INT); + if($LimitEnd) $sth->bindParam(2, $LimitEnd,PDO::PARAM_INT); + + $sth->execute(); + return $sth->fetchAll(PDO::FETCH_ASSOC); + + } + + + /** + * Query + * + * @param string $table + * @param array $arrQuery + * @return returns array results + * $ + */ + public function wherein($table, $column, $arrQuery, $CustomSQL='') { + $SQLWhereIn = implode(',', $arrQuery); + if($CustomSQL) { + $CustomSQL = " AND " . $CustomSQL; + } + $sql = "SELECT SQL_CALC_FOUND_ROWS * FROM {$table} WHERE {$column} IN ({$SQLWhereIn}) $CustomSQL"; + // print_array($sql); + $sth = $this->prepare($sql); + $sth->execute(); + + $count = $this->prepare('SELECT FOUND_ROWS() as Rows'); + $count->execute(); + $Counted = $count->fetchAll(PDO::FETCH_ASSOC); + $Counted = $Counted[0]['Rows']; + + $Result['results'] = $sth->fetchAll(PDO::FETCH_ASSOC); + $Result['rowsfound'] = $Counted; + return $Result; + + } + + + + + + /** + * select + * @param string $sql An SQL string + * @param array $array Paramters to bind + * @param constant $fetchMode A PDO Fetch mode + * @return mixed + + public function retrieve($table, $sql, $fetchMode = PDO::FETCH_ASSOC) + { + + $sql = "SELECT * FROM business_profile WHERE 1 LIMIT 0, 100"; + print_array($sql); + $sth = $this->prepare($sql); + $sth->execute(); + + $statement = $this->query('SELECT FOUND_ROWS() AS CountedRows'); + print_array($statement); + $Counted = $Counted[0]['CountedRows']; + + $Result['results'] = $sth->fetchAll(PDO::FETCH_ASSOC); + $Result['rowsfound'] = $Counted; + return $Result; + // return $sth->fetchAll($fetchMode); + + } + */ + + + + + + /** + * select + * @param string $sql An SQL string + * @param array $array Paramters to bind + * @param constant $fetchMode A PDO Fetch mode + * @return mixed + */ + public function retrieve($sql, $array = array(), $pagination = '') + { + + try { + $sth = $this->prepare($sql); + foreach ($array as $key => $value) { + $sth->bindValue("$key", $value); + } + + $sth->execute(); + + $statement = $this->execQuery('SELECT FOUND_ROWS() AS CountedRows'); + $Counted = $statement[0]['CountedRows']; + + $Result['results'] = $sth->fetchAll(PDO::FETCH_ASSOC); + $Result['rowsfound'] = $Counted; + + return $Result; +// return $sth->fetchAll($fetchMode); + + } catch (PDOException $e) { + $Result['Code']='0'; + $Result['Message']= 'Error Select Entry: ' . $e->getMessage(); + + return json_encode($Result); + } + } + + + public function pagination($sql, $page='0', $limit='10') + { + try { + $find = array("select * from", "LIMIT ?,?"); + $NewSQL = str_ireplace($find, '', $sql); + $totalSQL = "SELECT count(*) as Counted FROM {$NewSQL}"; + $statement = $this->execQuery($totalSQL); + $Counted = $statement[0]['Counted']; + + if(!$page) { $page = '0'; } + if(!$limit) { $limit = 10; } + $TotalPages = floor($Counted / $limit); + if($page) { $page_first_result = ($page) * $limit; } else { $page_first_result = '0'; } + + + $sth = $this->prepare($sql); + $sth->execute([$page_first_result, $limit]); + // echo "{$page_first_result}, $limit"; + + + + $Result['results'] = $sth->fetchAll(PDO::FETCH_ASSOC); + $Result['total'] = $Counted; + $Result['limit'] = $limit; + $Result['page'] = $page; + $Result['pages'] = $TotalPages; + + return $Result; + + } catch (PDOException $e) { + $Result['Code']='0'; + $Result['Message']= 'Error Select Entry: ' . $e->getMessage(); + + return json_encode($Result); + } + } + + + +} \ No newline at end of file diff --git a/core/ErrorHandler.php b/core/ErrorHandler.php new file mode 100644 index 0000000..c155bd4 --- /dev/null +++ b/core/ErrorHandler.php @@ -0,0 +1,76 @@ +getMessage(), $exception->getFile(), $exception->getLine()); + self::displayError(E_ERROR, $exception->getMessage(), $exception->getFile(), $exception->getLine(), $exception->getTraceAsString()); + } + + public static function handleShutdown() { + $error = error_get_last(); + if ($error !== null && in_array($error['type'], [E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE])) { + self::logError($error['type'], $error['message'], $error['file'], $error['line']); + self::displayError($error['type'], $error['message'], $error['file'], $error['line']); + } + } + + private static function logError($errno, $errstr, $errfile, $errline) { + $message = date('[Y-m-d H:i:s]') . " Error: [$errno] $errstr in $errfile on line $errline\n"; + error_log($message, 3, 'app_errors.log'); + } + + private static function displayError($errno, $errstr, $errfile, $errline, $trace = null) { + $errorTypes = [ + E_ERROR => 'Fatal Error', + E_WARNING => 'Warning', + E_PARSE => 'Parse Error', + E_NOTICE => 'Notice', + E_CORE_ERROR => 'Core Error', + E_CORE_WARNING => 'Core Warning', + E_COMPILE_ERROR => 'Compile Error', + E_COMPILE_WARNING => 'Compile Warning', + E_USER_ERROR => 'User Error', + E_USER_WARNING => 'User Warning', + E_USER_NOTICE => 'User Notice', + E_STRICT => 'Strict Standards', + E_RECOVERABLE_ERROR => 'Recoverable Error', + E_DEPRECATED => 'Deprecated', + E_USER_DEPRECATED => 'User Deprecated', + ]; + + $errorType = isset($errorTypes[$errno]) ? $errorTypes[$errno] : 'Unknown Error'; + + if (DEBUG) { + echo "
"; + echo "

$errorType Occurred

"; + echo "

Message: $errstr

"; + echo "

File: $errfile

"; + echo "

Line: $errline

"; + if ($trace) { + echo "

Stack Trace:

"; + echo "
$trace
"; + } + echo "

Request Details:

"; + echo "
";
+            echo "URL: " . $_SERVER['REQUEST_URI'] . "\n";
+            echo "Method: " . $_SERVER['REQUEST_METHOD'] . "\n";
+            echo "Time: " . date('Y-m-d H:i:s') . "\n";
+            echo "IP: " . $_SERVER['REMOTE_ADDR'] . "\n";
+            echo "
"; + echo "
"; + } + } +} diff --git a/core/Model.php b/core/Model.php new file mode 100644 index 0000000..da3fe2c --- /dev/null +++ b/core/Model.php @@ -0,0 +1,14 @@ +db = new Database(DB_TYPE, DB_HOST, DB_NAME, DB_USER, DB_PASS); + $this->db->setAttribute( PDO::ATTR_EMULATE_PREPARES, false ); + + + //$this->remotedb = new Database(DB_TYPE_REMOTE, DB_HOST_REMOTE, DB_NAME_REMOTE, DB_USER_REMOTE, DB_PASS_REMOTE); + //$this->remotedb->setAttribute( PDO::ATTR_EMULATE_PREPARES, false ); + + } +} \ No newline at end of file diff --git a/core/Session.php b/core/Session.php new file mode 100644 index 0000000..5bd92c8 --- /dev/null +++ b/core/Session.php @@ -0,0 +1,65 @@ +path = VIEWS_PATH . '/views/'; + } + + public function render($name, $type='site', $noInclude = true) { + if(empty($type)) { $type = 'site'; } + $name = strtolower($name); + extract((array) $this); + + if ($noInclude == false) { + require $this->path . "" . $name . ".php"; + } else { + include $this->path . "wrapper/{$type}/header.php"; + require $this->path . $name . ".php"; + include $this->path . "wrapper/{$type}/footer.php"; + } + } + + + /** + * generates partial view. Good for straight JSON or XML responses for internal views. + * @param $name + * @return string + */ + function PartialView($name, $param=false) + { + extract((array) $this); + ob_start(); + include ($this->path . "partial/" . $name . ".php"); + $name = ob_get_clean(); + + return ($name); + ob_end_flush(); + } + + function js($data){ + print_array($data); + } + + /* + public function render($name) { + require 'views/' . $name . '.php'; + } + */ + +} \ No newline at end of file diff --git a/core/core.md b/core/core.md new file mode 100644 index 0000000..988e594 --- /dev/null +++ b/core/core.md @@ -0,0 +1,128 @@ +# Core Framework — `core/` + +Framework internals. **Rarely modified.** Contains the request lifecycle engine. + +## Files + +| File | Purpose | +|------|---------| +| `Bootstrap.php` | Bootstraps environment, loads config, starts routing | +| `Controller.php` | Base controller class — all UI controllers extend this (via `AppController`) | +| `View.php` | View renderer — handles `render()`, `PartialView()`, asset injection | +| `AltoRouter.php` | URL router used for complex custom routes | + +--- + +## Request Lifecycle + +``` +Request + → public/index.php + → config.php (loads .env, defines constants) + → core/Bootstrap.php (environment setup, session, autoload) + → AltoRouter / auto-routing + → ControllerClass::method($param) + → $this->view->render(...) + → Response (HTML or JSON) +``` + +Auto-routing maps `/controller/method/param` directly to `ControllerClass::method($param)`. + +--- + +## `Controller` (`core/Controller.php`) + +Base class. Provides: + +- `$this->view` — View instance +- `$this->JavaScript[]` — array of JS paths to inject +- `$this->Styles[]` — array of CSS paths to inject +- `redirect($url)` — HTTP redirect helper + +**Do not modify this file.** App-wide customisation belongs in `app/Controllers/AppController.php`. + +--- + +## `AppController` (`app/Controllers/AppController.php`) + +Sits between `core/Controller` and all UI page controllers. +UI controllers extend `AppController`, not `Controller` directly. + +**Current responsibilities:** +- Injects `$UserProfile` (logged-in user's full profile via `User::profile()`) into every view automatically. + +**`use` statement scoping rule:** +- Namespaces needed on every page → `AppController` +- Namespaces needed by one controller → that controller only +- Namespaces needed by one method → inline as fully-qualified class name + +```php +// app/Controllers/AppController.php +use App\Components\User; + +class AppController extends Controller { + function __construct() { + parent::__construct(); + if (!empty($_SESSION['login']['userid'])) { + $this->view->UserProfile = User::profile($_SESSION['login']['userid']); + } + } +} +``` + +--- + +## `View` (`core/View.php`) + +**Properties:** +- Any property set on `$this->view` becomes a variable in the rendered template. + +**Methods:** +- `render($viewPath, $wrapper = '', $useWelcome = true)` — render a view +- `PartialView($partialName)` — load and return a partial as a string + +**Render signatures used in practice:** +```php +$this->view->render(__CLASS__ . '/' . __FUNCTION__); // default admin wrapper +$this->view->render(__CLASS__ . '/' . __FUNCTION__, 'login'); // login wrapper +$this->view->render('clients/index', true, 'site'); // legacy style +``` + +--- + +## Routing + +### Auto-Routing (default) + +| URL | Maps to | +|-----|---------| +| `/clients` | `Clients::index()` | +| `/clients/show/123` | `Clients::show(123)` | +| `/products/edit/456` | `Products::edit(456)` | + +### AltoRouter (complex routes) + +Define in `public/routes.php` or `api/routes.php`: + +```php +// Named route with typed parameter +$router->map('GET', '/users/[i:id]', 'Users#show', 'user_show'); + +// Multiple parameters +$router->map('GET', '/blog/[i:year]/[i:month]/[*:slug]', 'Blog#show', 'blog_post'); + +// POST +$router->map('POST', '/api/users/create', 'UsersAPI#create', 'api_user_create'); +``` + +**Match type tokens:** +- `[i:id]` — integer +- `[a:action]` — alphanumeric (A-Z, a-z, 0-9, -) +- `[h:key]` — hex +- `[*:trailing]` — catch-all (no slashes) +- `[**:path]` — catch-all including slashes + +```php +// Generate URL from named route +echo $router->generate('user_show', ['id' => 5]); // → /users/5 +``` diff --git a/index.php b/index.php new file mode 100644 index 0000000..a4bde58 --- /dev/null +++ b/index.php @@ -0,0 +1,6 @@ + code { + color: inherit; } + +kbd { + padding: 0.2rem 0.4rem; + font-size: 87.5%; + color: #fff; + background-color: #212529; + border-radius: 0.2rem; } + kbd kbd { + padding: 0; + font-size: 1em; + font-weight: 700; } + +figure { + margin: 0 0 1rem; } + +img, +svg { + vertical-align: middle; } + +table { + caption-side: bottom; + border-collapse: collapse; } + +caption { + padding-top: 0.75rem; + padding-bottom: 0.75rem; + color: #74788d; + text-align: left; } + +th { + font-weight: 500; + text-align: inherit; + text-align: -webkit-match-parent; } + +thead, +tbody, +tfoot, +tr, +td, +th { + border-color: inherit; + border-style: solid; + border-width: 0; } + +label { + display: inline-block; } + +button { + border-radius: 0; } + +button:focus:not(:focus-visible) { + outline: 0; } + +input, +button, +select, +optgroup, +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]::-webkit-calendar-picker-indicator { + display: none; } + +button, +[type="button"], +[type="reset"], +[type="submit"] { + -webkit-appearance: button; } + button:not(:disabled), + [type="button"]:not(:disabled), + [type="reset"]:not(:disabled), + [type="submit"]: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: 0.5rem; + font-size: calc(1.275rem + 0.3vw); + line-height: inherit; } + @media (min-width: 1200px) { + legend { + font-size: 1.5rem; } } + legend + * { + clear: left; } + +::-webkit-datetime-edit-fields-wrapper, +::-webkit-datetime-edit-text, +::-webkit-datetime-edit-minute, +::-webkit-datetime-edit-hour-field, +::-webkit-datetime-edit-day-field, +::-webkit-datetime-edit-month-field, +::-webkit-datetime-edit-year-field { + padding: 0; } + +::-webkit-inner-spin-button { + height: auto; } + +[type="search"] { + outline-offset: -2px; + -webkit-appearance: textfield; } + +/* rtl:raw: +[type="tel"], +[type="url"], +[type="email"], +[type="number"] { + direction: ltr; +} +*/ +::-webkit-search-decoration { + -webkit-appearance: none; } + +::-webkit-color-swatch-wrapper { + padding: 0; } + +::file-selector-button { + font: inherit; } + +::-webkit-file-upload-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.125rem; + font-weight: 300; } + +.display-1 { + font-size: calc(1.725rem + 5.7vw); + font-weight: 300; + line-height: 1.2; } + @media (min-width: 1200px) { + .display-1 { + font-size: 6rem; } } + +.display-2 { + font-size: calc(1.675rem + 5.1vw); + font-weight: 300; + line-height: 1.2; } + @media (min-width: 1200px) { + .display-2 { + font-size: 5.5rem; } } + +.display-3 { + font-size: calc(1.575rem + 3.9vw); + font-weight: 300; + line-height: 1.2; } + @media (min-width: 1200px) { + .display-3 { + font-size: 4.5rem; } } + +.display-4 { + font-size: calc(1.475rem + 2.7vw); + font-weight: 300; + line-height: 1.2; } + @media (min-width: 1200px) { + .display-4 { + font-size: 3.5rem; } } + +.display-5 { + font-size: calc(1.425rem + 2.1vw); + font-weight: 300; + line-height: 1.2; } + @media (min-width: 1200px) { + .display-5 { + font-size: 3rem; } } + +.display-6 { + font-size: calc(1.375rem + 1.5vw); + font-weight: 300; + line-height: 1.2; } + @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: 0.5rem; } + +.initialism { + font-size: 87.5%; + text-transform: uppercase; } + +.blockquote { + margin-bottom: 1rem; + font-size: 1.125rem; } + .blockquote > :last-child { + margin-bottom: 0; } + +.blockquote-footer { + margin-top: -1rem; + margin-bottom: 1rem; + font-size: 87.5%; + color: #74788d; } + .blockquote-footer::before { + content: "\2014\00A0"; } + +.img-fluid { + max-width: 100%; + height: auto; } + +.img-thumbnail { + padding: 0.25rem; + background-color: #f7f8fa; + border: 1px solid #eff0f2; + border-radius: 0.25rem; + max-width: 100%; + height: auto; } + +.figure { + display: inline-block; } + +.figure-img { + margin-bottom: 0.5rem; + line-height: 1; } + +.figure-caption { + font-size: 87.5%; + color: #74788d; } + +.container, +.container-fluid, +.container-sm, +.container-md, +.container-lg, +.container-xl, +.container-xxl { + width: 100%; + padding-right: var(--bs-gutter-x, 10px); + padding-left: var(--bs-gutter-x, 10px); + margin-right: auto; + margin-left: auto; } + +@media (min-width: 576px) { + .container, .container-sm { + max-width: 540px; } } + +@media (min-width: 768px) { + .container, .container-sm, .container-md { + max-width: 720px; } } + +@media (min-width: 992px) { + .container, .container-sm, .container-md, .container-lg { + max-width: 960px; } } + +@media (min-width: 1200px) { + .container, .container-sm, .container-md, .container-lg, .container-xl { + max-width: 1140px; } } + +@media (min-width: 1400px) { + .container, .container-sm, .container-md, .container-lg, .container-xl, .container-xxl { + max-width: 1320px; } } + +.row { + --bs-gutter-x: 20px; + --bs-gutter-y: 0; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + 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 > * { + -ms-flex-negative: 0; + 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 { + -webkit-box-flex: 1; + -ms-flex: 1 0 0%; + flex: 1 0 0%; } + +.row-cols-auto > * { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: auto; } + +.row-cols-1 > * { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 100%; } + +.row-cols-2 > * { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 50%; } + +.row-cols-3 > * { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 33.33333%; } + +.row-cols-4 > * { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 25%; } + +.row-cols-5 > * { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 20%; } + +.row-cols-6 > * { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 16.66667%; } + +.col-auto { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: auto; } + +.col-1 { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 8.33333%; } + +.col-2 { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 16.66667%; } + +.col-3 { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 25%; } + +.col-4 { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 33.33333%; } + +.col-5 { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 41.66667%; } + +.col-6 { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 50%; } + +.col-7 { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 58.33333%; } + +.col-8 { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 66.66667%; } + +.col-9 { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 75%; } + +.col-10 { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 83.33333%; } + +.col-11 { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 91.66667%; } + +.col-12 { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 100%; } + +.offset-1 { + margin-left: 8.33333%; } + +.offset-2 { + margin-left: 16.66667%; } + +.offset-3 { + margin-left: 25%; } + +.offset-4 { + margin-left: 33.33333%; } + +.offset-5 { + margin-left: 41.66667%; } + +.offset-6 { + margin-left: 50%; } + +.offset-7 { + margin-left: 58.33333%; } + +.offset-8 { + margin-left: 66.66667%; } + +.offset-9 { + margin-left: 75%; } + +.offset-10 { + margin-left: 83.33333%; } + +.offset-11 { + margin-left: 91.66667%; } + +.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 { + -webkit-box-flex: 1; + -ms-flex: 1 0 0%; + flex: 1 0 0%; } + .row-cols-sm-auto > * { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: auto; } + .row-cols-sm-1 > * { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 100%; } + .row-cols-sm-2 > * { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 50%; } + .row-cols-sm-3 > * { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 33.33333%; } + .row-cols-sm-4 > * { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 25%; } + .row-cols-sm-5 > * { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 20%; } + .row-cols-sm-6 > * { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 16.66667%; } + .col-sm-auto { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: auto; } + .col-sm-1 { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 8.33333%; } + .col-sm-2 { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 16.66667%; } + .col-sm-3 { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 25%; } + .col-sm-4 { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 33.33333%; } + .col-sm-5 { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 41.66667%; } + .col-sm-6 { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 50%; } + .col-sm-7 { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 58.33333%; } + .col-sm-8 { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 66.66667%; } + .col-sm-9 { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 75%; } + .col-sm-10 { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 83.33333%; } + .col-sm-11 { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 91.66667%; } + .col-sm-12 { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 100%; } + .offset-sm-0 { + margin-left: 0; } + .offset-sm-1 { + margin-left: 8.33333%; } + .offset-sm-2 { + margin-left: 16.66667%; } + .offset-sm-3 { + margin-left: 25%; } + .offset-sm-4 { + margin-left: 33.33333%; } + .offset-sm-5 { + margin-left: 41.66667%; } + .offset-sm-6 { + margin-left: 50%; } + .offset-sm-7 { + margin-left: 58.33333%; } + .offset-sm-8 { + margin-left: 66.66667%; } + .offset-sm-9 { + margin-left: 75%; } + .offset-sm-10 { + margin-left: 83.33333%; } + .offset-sm-11 { + margin-left: 91.66667%; } + .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 { + -webkit-box-flex: 1; + -ms-flex: 1 0 0%; + flex: 1 0 0%; } + .row-cols-md-auto > * { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: auto; } + .row-cols-md-1 > * { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 100%; } + .row-cols-md-2 > * { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 50%; } + .row-cols-md-3 > * { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 33.33333%; } + .row-cols-md-4 > * { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 25%; } + .row-cols-md-5 > * { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 20%; } + .row-cols-md-6 > * { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 16.66667%; } + .col-md-auto { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: auto; } + .col-md-1 { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 8.33333%; } + .col-md-2 { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 16.66667%; } + .col-md-3 { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 25%; } + .col-md-4 { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 33.33333%; } + .col-md-5 { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 41.66667%; } + .col-md-6 { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 50%; } + .col-md-7 { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 58.33333%; } + .col-md-8 { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 66.66667%; } + .col-md-9 { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 75%; } + .col-md-10 { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 83.33333%; } + .col-md-11 { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 91.66667%; } + .col-md-12 { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 100%; } + .offset-md-0 { + margin-left: 0; } + .offset-md-1 { + margin-left: 8.33333%; } + .offset-md-2 { + margin-left: 16.66667%; } + .offset-md-3 { + margin-left: 25%; } + .offset-md-4 { + margin-left: 33.33333%; } + .offset-md-5 { + margin-left: 41.66667%; } + .offset-md-6 { + margin-left: 50%; } + .offset-md-7 { + margin-left: 58.33333%; } + .offset-md-8 { + margin-left: 66.66667%; } + .offset-md-9 { + margin-left: 75%; } + .offset-md-10 { + margin-left: 83.33333%; } + .offset-md-11 { + margin-left: 91.66667%; } + .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 { + -webkit-box-flex: 1; + -ms-flex: 1 0 0%; + flex: 1 0 0%; } + .row-cols-lg-auto > * { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: auto; } + .row-cols-lg-1 > * { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 100%; } + .row-cols-lg-2 > * { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 50%; } + .row-cols-lg-3 > * { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 33.33333%; } + .row-cols-lg-4 > * { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 25%; } + .row-cols-lg-5 > * { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 20%; } + .row-cols-lg-6 > * { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 16.66667%; } + .col-lg-auto { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: auto; } + .col-lg-1 { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 8.33333%; } + .col-lg-2 { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 16.66667%; } + .col-lg-3 { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 25%; } + .col-lg-4 { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 33.33333%; } + .col-lg-5 { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 41.66667%; } + .col-lg-6 { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 50%; } + .col-lg-7 { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 58.33333%; } + .col-lg-8 { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 66.66667%; } + .col-lg-9 { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 75%; } + .col-lg-10 { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 83.33333%; } + .col-lg-11 { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 91.66667%; } + .col-lg-12 { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 100%; } + .offset-lg-0 { + margin-left: 0; } + .offset-lg-1 { + margin-left: 8.33333%; } + .offset-lg-2 { + margin-left: 16.66667%; } + .offset-lg-3 { + margin-left: 25%; } + .offset-lg-4 { + margin-left: 33.33333%; } + .offset-lg-5 { + margin-left: 41.66667%; } + .offset-lg-6 { + margin-left: 50%; } + .offset-lg-7 { + margin-left: 58.33333%; } + .offset-lg-8 { + margin-left: 66.66667%; } + .offset-lg-9 { + margin-left: 75%; } + .offset-lg-10 { + margin-left: 83.33333%; } + .offset-lg-11 { + margin-left: 91.66667%; } + .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 { + -webkit-box-flex: 1; + -ms-flex: 1 0 0%; + flex: 1 0 0%; } + .row-cols-xl-auto > * { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: auto; } + .row-cols-xl-1 > * { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 100%; } + .row-cols-xl-2 > * { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 50%; } + .row-cols-xl-3 > * { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 33.33333%; } + .row-cols-xl-4 > * { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 25%; } + .row-cols-xl-5 > * { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 20%; } + .row-cols-xl-6 > * { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 16.66667%; } + .col-xl-auto { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: auto; } + .col-xl-1 { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 8.33333%; } + .col-xl-2 { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 16.66667%; } + .col-xl-3 { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 25%; } + .col-xl-4 { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 33.33333%; } + .col-xl-5 { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 41.66667%; } + .col-xl-6 { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 50%; } + .col-xl-7 { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 58.33333%; } + .col-xl-8 { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 66.66667%; } + .col-xl-9 { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 75%; } + .col-xl-10 { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 83.33333%; } + .col-xl-11 { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 91.66667%; } + .col-xl-12 { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 100%; } + .offset-xl-0 { + margin-left: 0; } + .offset-xl-1 { + margin-left: 8.33333%; } + .offset-xl-2 { + margin-left: 16.66667%; } + .offset-xl-3 { + margin-left: 25%; } + .offset-xl-4 { + margin-left: 33.33333%; } + .offset-xl-5 { + margin-left: 41.66667%; } + .offset-xl-6 { + margin-left: 50%; } + .offset-xl-7 { + margin-left: 58.33333%; } + .offset-xl-8 { + margin-left: 66.66667%; } + .offset-xl-9 { + margin-left: 75%; } + .offset-xl-10 { + margin-left: 83.33333%; } + .offset-xl-11 { + margin-left: 91.66667%; } + .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 { + -webkit-box-flex: 1; + -ms-flex: 1 0 0%; + flex: 1 0 0%; } + .row-cols-xxl-auto > * { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: auto; } + .row-cols-xxl-1 > * { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 100%; } + .row-cols-xxl-2 > * { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 50%; } + .row-cols-xxl-3 > * { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 33.33333%; } + .row-cols-xxl-4 > * { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 25%; } + .row-cols-xxl-5 > * { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 20%; } + .row-cols-xxl-6 > * { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 16.66667%; } + .col-xxl-auto { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: auto; } + .col-xxl-1 { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 8.33333%; } + .col-xxl-2 { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 16.66667%; } + .col-xxl-3 { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 25%; } + .col-xxl-4 { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 33.33333%; } + .col-xxl-5 { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 41.66667%; } + .col-xxl-6 { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 50%; } + .col-xxl-7 { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 58.33333%; } + .col-xxl-8 { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 66.66667%; } + .col-xxl-9 { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 75%; } + .col-xxl-10 { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 83.33333%; } + .col-xxl-11 { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 91.66667%; } + .col-xxl-12 { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: 100%; } + .offset-xxl-0 { + margin-left: 0; } + .offset-xxl-1 { + margin-left: 8.33333%; } + .offset-xxl-2 { + margin-left: 16.66667%; } + .offset-xxl-3 { + margin-left: 25%; } + .offset-xxl-4 { + margin-left: 33.33333%; } + .offset-xxl-5 { + margin-left: 41.66667%; } + .offset-xxl-6 { + margin-left: 50%; } + .offset-xxl-7 { + margin-left: 58.33333%; } + .offset-xxl-8 { + margin-left: 66.66667%; } + .offset-xxl-9 { + margin-left: 75%; } + .offset-xxl-10 { + margin-left: 83.33333%; } + .offset-xxl-11 { + margin-left: 91.66667%; } + .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-bg: transparent; + --bs-table-accent-bg: transparent; + --bs-table-striped-color: #495057; + --bs-table-striped-bg: #f8f9fa; + --bs-table-active-color: #495057; + --bs-table-active-bg: rgba(0, 0, 0, 0.1); + --bs-table-hover-color: #495057; + --bs-table-hover-bg: #f8f9fa; + width: 100%; + margin-bottom: 1rem; + color: #495057; + vertical-align: top; + border-color: #eff0f2; } + .table > :not(caption) > * > * { + padding: 0.75rem 0.75rem; + background-color: var(--bs-table-bg); + border-bottom-width: 1px; + -webkit-box-shadow: inset 0 0 0 9999px var(--bs-table-accent-bg); + box-shadow: inset 0 0 0 9999px var(--bs-table-accent-bg); } + .table > tbody { + vertical-align: inherit; } + .table > thead { + vertical-align: bottom; } + .table > :not(:first-child) { + border-top: 2px solid #eff0f2; } + +.caption-top { + caption-side: top; } + +.table-sm > :not(caption) > * > * { + padding: 0.25rem 0.25rem; } + +.table-bordered > :not(caption) > * { + border-width: 1px 0; } + .table-bordered > :not(caption) > * > * { + border-width: 0 1px; } + +.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-accent-bg: var(--bs-table-striped-bg); + color: var(--bs-table-striped-color); } + +.table-active { + --bs-table-accent-bg: var(--bs-table-active-bg); + color: var(--bs-table-active-color); } + +.table-hover > tbody > tr:hover > * { + --bs-table-accent-bg: var(--bs-table-hover-bg); + color: var(--bs-table-hover-color); } + +.table-primary { + --bs-table-bg: #cde8f8; + --bs-table-striped-bg: #c3dcec; + --bs-table-striped-color: #000; + --bs-table-active-bg: #b9d1df; + --bs-table-active-color: #000; + --bs-table-hover-bg: #bed7e5; + --bs-table-hover-color: #000; + color: #000; + border-color: #b9d1df; } + +.table-secondary { + --bs-table-bg: #e3e4e8; + --bs-table-striped-bg: #d8d9dc; + --bs-table-striped-color: #000; + --bs-table-active-bg: #cccdd1; + --bs-table-active-color: #000; + --bs-table-hover-bg: #d2d3d7; + --bs-table-hover-color: #000; + color: #000; + border-color: #cccdd1; } + +.table-success { + --bs-table-bg: #dcf6e8; + --bs-table-striped-bg: #d1eadc; + --bs-table-striped-color: #000; + --bs-table-active-bg: #c6ddd1; + --bs-table-active-color: #000; + --bs-table-hover-bg: #cce4d7; + --bs-table-hover-color: #000; + color: #000; + border-color: #c6ddd1; } + +.table-info { + --bs-table-bg: #dff6fd; + --bs-table-striped-bg: #d4eaf0; + --bs-table-striped-color: #000; + --bs-table-active-bg: #c9dde4; + --bs-table-active-color: #000; + --bs-table-hover-bg: #cee4ea; + --bs-table-hover-color: #000; + color: #000; + border-color: #c9dde4; } + +.table-warning { + --bs-table-bg: #fdf5dd; + --bs-table-striped-bg: #f0e9d2; + --bs-table-striped-color: #000; + --bs-table-active-bg: #e4ddc7; + --bs-table-active-color: #000; + --bs-table-hover-bg: #eae3cc; + --bs-table-hover-color: #000; + color: #000; + border-color: #e4ddc7; } + +.table-danger { + --bs-table-bg: #fddcdc; + --bs-table-striped-bg: #f0d1d1; + --bs-table-striped-color: #000; + --bs-table-active-bg: #e4c6c6; + --bs-table-active-color: #000; + --bs-table-hover-bg: #eacccc; + --bs-table-hover-color: #000; + color: #000; + border-color: #e4c6c6; } + +.table-light { + --bs-table-bg: #f5f6f8; + --bs-table-striped-bg: #e9eaec; + --bs-table-striped-color: #000; + --bs-table-active-bg: #dddddf; + --bs-table-active-color: #000; + --bs-table-hover-bg: #e3e4e5; + --bs-table-hover-color: #000; + color: #000; + border-color: #dddddf; } + +.table-dark { + --bs-table-bg: #343a40; + --bs-table-striped-bg: #3e444a; + --bs-table-striped-color: #fff; + --bs-table-active-bg: #484e53; + --bs-table-active-color: #fff; + --bs-table-hover-bg: #43494e; + --bs-table-hover-color: #fff; + color: #fff; + border-color: #484e53; } + +.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: 0.5rem; } + +.col-form-label { + padding-top: calc(0.47rem + 1px); + padding-bottom: calc(0.47rem + 1px); + margin-bottom: 0; + font-size: inherit; + line-height: 1.5; } + +.col-form-label-lg { + padding-top: calc(0.5rem + 1px); + padding-bottom: calc(0.5rem + 1px); + font-size: 1.125rem; } + +.col-form-label-sm { + padding-top: calc(0.25rem + 1px); + padding-bottom: calc(0.25rem + 1px); + font-size: 0.7875rem; } + +.form-text { + margin-top: 0.25rem; + font-size: 87.5%; + color: #74788d; } + +.form-control { + display: block; + width: 100%; + padding: 0.47rem 0.75rem; + font-size: 0.9rem; + font-weight: 400; + line-height: 1.5; + color: #495057; + background-color: #fff; + background-clip: padding-box; + border: 1px solid #e2e5e8; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + border-radius: 0.25rem; + -webkit-transition: border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out; + transition: border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out; + transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; + transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out; } + @media (prefers-reduced-motion: reduce) { + .form-control { + -webkit-transition: none; + transition: none; } } + .form-control[type="file"] { + overflow: hidden; } + .form-control[type="file"]:not(:disabled):not([readonly]) { + cursor: pointer; } + .form-control:focus { + color: #495057; + background-color: #fff; + border-color: #cbced1; + outline: 0; + -webkit-box-shadow: none; + box-shadow: none; } + .form-control::-webkit-date-and-time-value { + height: 1.5em; } + .form-control::-webkit-input-placeholder { + color: #74788d; + opacity: 1; } + .form-control::-moz-placeholder { + color: #74788d; + opacity: 1; } + .form-control:-ms-input-placeholder { + color: #74788d; + opacity: 1; } + .form-control::-ms-input-placeholder { + color: #74788d; + opacity: 1; } + .form-control::placeholder { + color: #74788d; + opacity: 1; } + .form-control:disabled, .form-control[readonly] { + background-color: #f5f6f8; + opacity: 1; } + .form-control::file-selector-button { + padding: 0.47rem 0.75rem; + margin: -0.47rem -0.75rem; + -webkit-margin-end: 0.75rem; + margin-inline-end: 0.75rem; + color: #495057; + background-color: #f5f6f8; + pointer-events: none; + border-color: inherit; + border-style: solid; + border-width: 0; + border-inline-end-width: 1px; + border-radius: 0; + -webkit-transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out; + transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out; + 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; + 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, -webkit-box-shadow 0.15s ease-in-out; } + @media (prefers-reduced-motion: reduce) { + .form-control::file-selector-button { + -webkit-transition: none; + transition: none; } } + .form-control:hover:not(:disabled):not([readonly])::file-selector-button { + background-color: #e9eaec; } + .form-control::-webkit-file-upload-button { + padding: 0.47rem 0.75rem; + margin: -0.47rem -0.75rem; + -webkit-margin-end: 0.75rem; + margin-inline-end: 0.75rem; + color: #495057; + background-color: #f5f6f8; + pointer-events: none; + border-color: inherit; + border-style: solid; + border-width: 0; + border-inline-end-width: 1px; + border-radius: 0; + -webkit-transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out; + transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out; + 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; + 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, -webkit-box-shadow 0.15s ease-in-out; } + @media (prefers-reduced-motion: reduce) { + .form-control::-webkit-file-upload-button { + -webkit-transition: none; + transition: none; } } + .form-control:hover:not(:disabled):not([readonly])::-webkit-file-upload-button { + background-color: #e9eaec; } + +.form-control-plaintext { + display: block; + width: 100%; + padding: 0.47rem 0; + margin-bottom: 0; + line-height: 1.5; + color: #495057; + background-color: transparent; + border: solid transparent; + border-width: 1px 0; } + .form-control-plaintext.form-control-sm, .form-control-plaintext.form-control-lg { + padding-right: 0; + padding-left: 0; } + +.form-control-sm { + min-height: calc(1.5em + 0.5rem + 2px); + padding: 0.25rem 0.5rem; + font-size: 0.7875rem; + border-radius: 0.2rem; } + .form-control-sm::file-selector-button { + padding: 0.25rem 0.5rem; + margin: -0.25rem -0.5rem; + -webkit-margin-end: 0.5rem; + margin-inline-end: 0.5rem; } + .form-control-sm::-webkit-file-upload-button { + padding: 0.25rem 0.5rem; + margin: -0.25rem -0.5rem; + -webkit-margin-end: 0.5rem; + margin-inline-end: 0.5rem; } + +.form-control-lg { + min-height: calc(1.5em + 1rem + 2px); + padding: 0.5rem 1rem; + font-size: 1.125rem; + border-radius: 0.4rem; } + .form-control-lg::file-selector-button { + padding: 0.5rem 1rem; + margin: -0.5rem -1rem; + -webkit-margin-end: 1rem; + margin-inline-end: 1rem; } + .form-control-lg::-webkit-file-upload-button { + padding: 0.5rem 1rem; + margin: -0.5rem -1rem; + -webkit-margin-end: 1rem; + margin-inline-end: 1rem; } + +textarea.form-control { + min-height: calc(1.5em + 0.94rem + 2px); } + +textarea.form-control-sm { + min-height: calc(1.5em + 0.5rem + 2px); } + +textarea.form-control-lg { + min-height: calc(1.5em + 1rem + 2px); } + +.form-control-color { + width: 3rem; + height: auto; + padding: 0.47rem; } + .form-control-color:not(:disabled):not([readonly]) { + cursor: pointer; } + .form-control-color::-moz-color-swatch { + height: 1.5em; + border-radius: 0.25rem; } + .form-control-color::-webkit-color-swatch { + height: 1.5em; + border-radius: 0.25rem; } + +.form-select { + display: block; + width: 100%; + padding: 0.47rem 1.75rem 0.47rem 0.75rem; + -moz-padding-start: calc(0.75rem - 3px); + font-size: 0.9rem; + font-weight: 400; + line-height: 1.5; + color: #495057; + background-color: #fff; + 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.75rem center; + background-size: 16px 12px; + border: 1px solid #e2e5e8; + border-radius: 0.25rem; + -webkit-transition: border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out; + transition: border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out; + transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; + transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; } + @media (prefers-reduced-motion: reduce) { + .form-select { + -webkit-transition: none; + transition: none; } } + .form-select:focus { + border-color: #cbced1; + outline: 0; + -webkit-box-shadow: 0 0 0 0.15rem rgba(3, 142, 220, 0.25); + box-shadow: 0 0 0 0.15rem rgba(3, 142, 220, 0.25); } + .form-select[multiple], .form-select[size]:not([size="1"]) { + padding-right: 0.75rem; + background-image: none; } + .form-select:disabled { + color: #74788d; + background-color: #f5f6f8; } + .form-select:-moz-focusring { + color: transparent; + text-shadow: 0 0 0 #495057; } + +.form-select-sm { + padding-top: 0.25rem; + padding-bottom: 0.25rem; + padding-left: 0.5rem; + font-size: 0.7875rem; + border-radius: 0.2rem; } + +.form-select-lg { + padding-top: 0.5rem; + padding-bottom: 0.5rem; + padding-left: 1rem; + font-size: 1.125rem; + border-radius: 0.3rem; } + +.form-check { + display: block; + min-height: 1.35rem; + padding-left: 1.5em; + margin-bottom: 0rem; } + .form-check .form-check-input { + float: left; + margin-left: -1.5em; } + +.form-check-input { + width: 1em; + height: 1em; + margin-top: 0.25em; + vertical-align: top; + background-color: #fff; + background-repeat: no-repeat; + background-position: center; + background-size: contain; + border: 1px solid #adb5bd; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + -webkit-print-color-adjust: exact; + color-adjust: exact; + -webkit-transition: background-color 0.15s ease-in-out, background-position 0.15s ease-in-out, border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out; + transition: background-color 0.15s ease-in-out, background-position 0.15s ease-in-out, border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out; + transition: background-color 0.15s ease-in-out, background-position 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; + transition: background-color 0.15s ease-in-out, background-position 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out; } + @media (prefers-reduced-motion: reduce) { + .form-check-input { + -webkit-transition: none; + transition: none; } } + .form-check-input[type="checkbox"] { + border-radius: 0.25em; } + .form-check-input[type="radio"] { + border-radius: 50%; } + .form-check-input:active { + -webkit-filter: brightness(90%); + filter: brightness(90%); } + .form-check-input:focus { + border-color: #cbced1; + outline: 0; + -webkit-box-shadow: none; + box-shadow: none; } + .form-check-input:checked { + background-color: #2e86de; + border-color: #2e86de; } + .form-check-input:checked[type="checkbox"] { + background-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 10l3 3l6-6'/%3e%3c/svg%3e"); } + .form-check-input:checked[type="radio"] { + background-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: #2e86de; + border-color: #2e86de; + background-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; + -webkit-filter: none; + filter: none; + opacity: 0.5; } + .form-check-input[disabled] ~ .form-check-label, .form-check-input:disabled ~ .form-check-label { + opacity: 0.5; } + +.form-switch { + padding-left: 2.5em; } + .form-switch .form-check-input { + width: 2em; + margin-left: -2.5em; + background-image: 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"); + background-position: left center; + border-radius: 2em; + -webkit-transition: background-position 0.15s ease-in-out; + transition: background-position 0.15s ease-in-out; } + @media (prefers-reduced-motion: reduce) { + .form-switch .form-check-input { + -webkit-transition: none; + transition: none; } } + .form-switch .form-check-input:focus { + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23cbced1'/%3e%3c/svg%3e"); } + .form-switch .form-check-input:checked { + background-position: right center; + background-image: 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-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; + -webkit-filter: none; + filter: none; + opacity: 0.65; } + +.form-range { + width: 100%; + height: 1.3rem; + padding: 0; + background-color: transparent; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; } + .form-range:focus { + outline: 0; } + .form-range:focus::-webkit-slider-thumb { + -webkit-box-shadow: 0 0 0 1px #f7f8fa, none; + box-shadow: 0 0 0 1px #f7f8fa, none; } + .form-range:focus::-moz-range-thumb { + box-shadow: 0 0 0 1px #f7f8fa, none; } + .form-range::-moz-focus-outer { + border: 0; } + .form-range::-webkit-slider-thumb { + width: 1rem; + height: 1rem; + margin-top: -0.25rem; + background-color: #2e86de; + border: 0; + border-radius: 1rem; + -webkit-transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out; + transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out; + transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; + transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out; + -webkit-appearance: none; + appearance: none; } + @media (prefers-reduced-motion: reduce) { + .form-range::-webkit-slider-thumb { + -webkit-transition: none; + transition: none; } } + .form-range::-webkit-slider-thumb:active { + background-color: #b3ddf5; } + .form-range::-webkit-slider-runnable-track { + width: 100%; + height: 0.5rem; + color: transparent; + cursor: pointer; + background-color: #eff0f2; + border-color: transparent; + border-radius: 1rem; } + .form-range::-moz-range-thumb { + width: 1rem; + height: 1rem; + background-color: #2e86de; + border: 0; + border-radius: 1rem; + -moz-transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; + transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; + -moz-appearance: none; + appearance: none; } + @media (prefers-reduced-motion: reduce) { + .form-range::-moz-range-thumb { + -moz-transition: none; + transition: none; } } + .form-range::-moz-range-thumb:active { + background-color: #b3ddf5; } + .form-range::-moz-range-track { + width: 100%; + height: 0.5rem; + color: transparent; + cursor: pointer; + background-color: #eff0f2; + border-color: transparent; + border-radius: 1rem; } + .form-range:disabled { + pointer-events: none; } + .form-range:disabled::-webkit-slider-thumb { + background-color: #adb5bd; } + .form-range:disabled::-moz-range-thumb { + background-color: #adb5bd; } + +.form-floating { + position: relative; } + .form-floating > .form-control, + .form-floating > .form-select { + height: calc(3.5rem + 2px); + line-height: 1.25; } + .form-floating > label { + position: absolute; + top: 0; + left: 0; + height: 100%; + padding: 1rem 0.75rem; + pointer-events: none; + border: 1px solid transparent; + -webkit-transform-origin: 0 0; + transform-origin: 0 0; + -webkit-transition: opacity 0.1s ease-in-out, -webkit-transform 0.1s ease-in-out; + transition: opacity 0.1s ease-in-out, -webkit-transform 0.1s ease-in-out; + transition: opacity 0.1s ease-in-out, transform 0.1s ease-in-out; + transition: opacity 0.1s ease-in-out, transform 0.1s ease-in-out, -webkit-transform 0.1s ease-in-out; } + @media (prefers-reduced-motion: reduce) { + .form-floating > label { + -webkit-transition: none; + transition: none; } } + .form-floating > .form-control { + padding: 1rem 0.75rem; } + .form-floating > .form-control::-webkit-input-placeholder { + color: transparent; } + .form-floating > .form-control::-moz-placeholder { + color: transparent; } + .form-floating > .form-control:-ms-input-placeholder { + color: transparent; } + .form-floating > .form-control::-ms-input-placeholder { + color: transparent; } + .form-floating > .form-control::placeholder { + color: transparent; } + .form-floating > .form-control:not(:-moz-placeholder-shown) { + padding-top: 1.625rem; + padding-bottom: 0.625rem; } + .form-floating > .form-control:not(:-ms-input-placeholder) { + padding-top: 1.625rem; + padding-bottom: 0.625rem; } + .form-floating > .form-control:focus, .form-floating > .form-control:not(:placeholder-shown) { + padding-top: 1.625rem; + padding-bottom: 0.625rem; } + .form-floating > .form-control:-webkit-autofill { + padding-top: 1.625rem; + padding-bottom: 0.625rem; } + .form-floating > .form-select { + padding-top: 1.625rem; + padding-bottom: 0.625rem; } + .form-floating > .form-control:not(:-moz-placeholder-shown) ~ label { + opacity: 0.65; + transform: scale(0.85) translateY(-0.5rem) translateX(0.15rem); } + .form-floating > .form-control:not(:-ms-input-placeholder) ~ label { + opacity: 0.65; + transform: scale(0.85) translateY(-0.5rem) translateX(0.15rem); } + .form-floating > .form-control:focus ~ label, + .form-floating > .form-control:not(:placeholder-shown) ~ label, + .form-floating > .form-select ~ label { + opacity: 0.65; + -webkit-transform: scale(0.85) translateY(-0.5rem) translateX(0.15rem); + transform: scale(0.85) translateY(-0.5rem) translateX(0.15rem); } + .form-floating > .form-control:-webkit-autofill ~ label { + opacity: 0.65; + -webkit-transform: scale(0.85) translateY(-0.5rem) translateX(0.15rem); + transform: scale(0.85) translateY(-0.5rem) translateX(0.15rem); } + +.input-group { + position: relative; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + -webkit-box-align: stretch; + -ms-flex-align: stretch; + align-items: stretch; + width: 100%; } + .input-group > .form-control, + .input-group > .form-select { + position: relative; + -webkit-box-flex: 1; + -ms-flex: 1 1 auto; + flex: 1 1 auto; + width: 1%; + min-width: 0; } + .input-group > .form-control:focus, + .input-group > .form-select:focus { + z-index: 3; } + .input-group .btn { + position: relative; + z-index: 2; } + .input-group .btn:focus { + z-index: 3; } + +.input-group-text { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + padding: 0.47rem 0.75rem; + font-size: 0.9rem; + font-weight: 400; + line-height: 1.5; + color: #495057; + text-align: center; + white-space: nowrap; + background-color: #f5f6f8; + border: 1px solid #e2e5e8; + border-radius: 0.25rem; } + +.input-group-lg > .form-control, +.input-group-lg > .form-select, +.input-group-lg > .input-group-text, +.input-group-lg > .btn { + padding: 0.5rem 1rem; + font-size: 1.125rem; + border-radius: 0.4rem; } + +.input-group-sm > .form-control, +.input-group-sm > .form-select, +.input-group-sm > .input-group-text, +.input-group-sm > .btn { + padding: 0.25rem 0.5rem; + font-size: 0.7875rem; + border-radius: 0.2rem; } + +.input-group-lg > .form-select, +.input-group-sm > .form-select { + padding-right: 2.5rem; } + +.input-group:not(.has-validation) > :not(:last-child):not(.dropdown-toggle):not(.dropdown-menu), +.input-group:not(.has-validation) > .dropdown-toggle:nth-last-child(n + 3) { + border-top-right-radius: 0; + border-bottom-right-radius: 0; } + +.input-group.has-validation > :nth-last-child(n + 3):not(.dropdown-toggle):not(.dropdown-menu), +.input-group.has-validation > .dropdown-toggle:nth-last-child(n + 4) { + 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: -1px; + border-top-left-radius: 0; + border-bottom-left-radius: 0; } + +.valid-feedback { + display: none; + width: 100%; + margin-top: 0.25rem; + font-size: 87.5%; + color: #51d28c; } + +.valid-tooltip { + position: absolute; + top: 100%; + z-index: 5; + display: none; + max-width: 100%; + padding: 0.4rem 0.7rem; + margin-top: .1rem; + font-size: 0.7875rem; + color: #fff; + background-color: rgba(81, 210, 140, 0.9); + border-radius: 0.25rem; } + +.was-validated :valid ~ .valid-feedback, +.was-validated :valid ~ .valid-tooltip, +.is-valid ~ .valid-feedback, +.is-valid ~ .valid-tooltip { + display: block; } + +.was-validated .form-control:valid, .form-control.is-valid { + border-color: #51d28c; + padding-right: calc(1.5em + 0.94rem); + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%2351d28c' d='M2.3 6.73L.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.1z'/%3e%3c/svg%3e"); + background-repeat: no-repeat; + background-position: right calc(0.375em + 0.235rem) center; + background-size: calc(0.75em + 0.47rem) calc(0.75em + 0.47rem); } + .was-validated .form-control:valid:focus, .form-control.is-valid:focus { + border-color: #51d28c; + -webkit-box-shadow: 0 0 0 0.15rem rgba(81, 210, 140, 0.25); + box-shadow: 0 0 0 0.15rem rgba(81, 210, 140, 0.25); } + +.was-validated textarea.form-control:valid, textarea.form-control.is-valid { + padding-right: calc(1.5em + 0.94rem); + background-position: top calc(0.375em + 0.235rem) right calc(0.375em + 0.235rem); } + +.was-validated .form-select:valid, .form-select.is-valid { + border-color: #51d28c; } + .was-validated .form-select:valid:not([multiple]):not([size]), .was-validated .form-select:valid:not([multiple])[size="1"], .form-select.is-valid:not([multiple]):not([size]), .form-select.is-valid:not([multiple])[size="1"] { + padding-right: calc(0.75em + 3.205rem); + 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"), url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%2351d28c' d='M2.3 6.73L.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.1z'/%3e%3c/svg%3e"); + background-position: right 0.75rem center, center right 2.5rem; + background-size: 16px 12px, calc(0.75em + 0.47rem) calc(0.75em + 0.47rem); } + .was-validated .form-select:valid:focus, .form-select.is-valid:focus { + border-color: #51d28c; + -webkit-box-shadow: 0 0 0 0.15rem rgba(81, 210, 140, 0.25); + box-shadow: 0 0 0 0.15rem rgba(81, 210, 140, 0.25); } + +.was-validated .form-check-input:valid, .form-check-input.is-valid { + border-color: #51d28c; } + .was-validated .form-check-input:valid:checked, .form-check-input.is-valid:checked { + background-color: #51d28c; } + .was-validated .form-check-input:valid:focus, .form-check-input.is-valid:focus { + -webkit-box-shadow: 0 0 0 0.15rem rgba(81, 210, 140, 0.25); + box-shadow: 0 0 0 0.15rem rgba(81, 210, 140, 0.25); } + .was-validated .form-check-input:valid ~ .form-check-label, .form-check-input.is-valid ~ .form-check-label { + color: #51d28c; } + +.form-check-inline .form-check-input ~ .valid-feedback { + margin-left: .5em; } + +.was-validated .input-group .form-control:valid, .input-group .form-control.is-valid, .was-validated +.input-group .form-select:valid, +.input-group .form-select.is-valid { + z-index: 1; } + .was-validated .input-group .form-control:valid:focus, .input-group .form-control.is-valid:focus, .was-validated + .input-group .form-select:valid:focus, + .input-group .form-select.is-valid:focus { + z-index: 3; } + +.invalid-feedback { + display: none; + width: 100%; + margin-top: 0.25rem; + font-size: 87.5%; + color: #f34e4e; } + +.invalid-tooltip { + position: absolute; + top: 100%; + z-index: 5; + display: none; + max-width: 100%; + padding: 0.4rem 0.7rem; + margin-top: .1rem; + font-size: 0.7875rem; + color: #fff; + background-color: rgba(243, 78, 78, 0.9); + border-radius: 0.25rem; } + +.was-validated :invalid ~ .invalid-feedback, +.was-validated :invalid ~ .invalid-tooltip, +.is-invalid ~ .invalid-feedback, +.is-invalid ~ .invalid-tooltip { + display: block; } + +.was-validated .form-control:invalid, .form-control.is-invalid { + border-color: #f34e4e; + padding-right: calc(1.5em + 0.94rem); + 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='%23f34e4e'%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='%23f34e4e' stroke='none'/%3e%3c/svg%3e"); + background-repeat: no-repeat; + background-position: right calc(0.375em + 0.235rem) center; + background-size: calc(0.75em + 0.47rem) calc(0.75em + 0.47rem); } + .was-validated .form-control:invalid:focus, .form-control.is-invalid:focus { + border-color: #f34e4e; + -webkit-box-shadow: 0 0 0 0.15rem rgba(243, 78, 78, 0.25); + box-shadow: 0 0 0 0.15rem rgba(243, 78, 78, 0.25); } + +.was-validated textarea.form-control:invalid, textarea.form-control.is-invalid { + padding-right: calc(1.5em + 0.94rem); + background-position: top calc(0.375em + 0.235rem) right calc(0.375em + 0.235rem); } + +.was-validated .form-select:invalid, .form-select.is-invalid { + border-color: #f34e4e; } + .was-validated .form-select:invalid:not([multiple]):not([size]), .was-validated .form-select:invalid:not([multiple])[size="1"], .form-select.is-invalid:not([multiple]):not([size]), .form-select.is-invalid:not([multiple])[size="1"] { + padding-right: calc(0.75em + 3.205rem); + 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"), 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='%23f34e4e'%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='%23f34e4e' stroke='none'/%3e%3c/svg%3e"); + background-position: right 0.75rem center, center right 2.5rem; + background-size: 16px 12px, calc(0.75em + 0.47rem) calc(0.75em + 0.47rem); } + .was-validated .form-select:invalid:focus, .form-select.is-invalid:focus { + border-color: #f34e4e; + -webkit-box-shadow: 0 0 0 0.15rem rgba(243, 78, 78, 0.25); + box-shadow: 0 0 0 0.15rem rgba(243, 78, 78, 0.25); } + +.was-validated .form-check-input:invalid, .form-check-input.is-invalid { + border-color: #f34e4e; } + .was-validated .form-check-input:invalid:checked, .form-check-input.is-invalid:checked { + background-color: #f34e4e; } + .was-validated .form-check-input:invalid:focus, .form-check-input.is-invalid:focus { + -webkit-box-shadow: 0 0 0 0.15rem rgba(243, 78, 78, 0.25); + box-shadow: 0 0 0 0.15rem rgba(243, 78, 78, 0.25); } + .was-validated .form-check-input:invalid ~ .form-check-label, .form-check-input.is-invalid ~ .form-check-label { + color: #f34e4e; } + +.form-check-inline .form-check-input ~ .invalid-feedback { + margin-left: .5em; } + +.was-validated .input-group .form-control:invalid, .input-group .form-control.is-invalid, .was-validated +.input-group .form-select:invalid, +.input-group .form-select.is-invalid { + z-index: 2; } + .was-validated .input-group .form-control:invalid:focus, .input-group .form-control.is-invalid:focus, .was-validated + .input-group .form-select:invalid:focus, + .input-group .form-select.is-invalid:focus { + z-index: 3; } + +.btn { + display: inline-block; + font-weight: 400; + line-height: 1.5; + color: #495057; + text-align: center; + vertical-align: middle; + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + background-color: transparent; + border: 1px solid transparent; + padding: 0.47rem 0.75rem; + font-size: 0.9rem; + border-radius: 0.25rem; + -webkit-transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out; + transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out; + 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; + 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, -webkit-box-shadow 0.15s ease-in-out; } + @media (prefers-reduced-motion: reduce) { + .btn { + -webkit-transition: none; + transition: none; } } + .btn:hover { + color: #495057; } + .btn-check:focus + .btn, .btn:focus { + outline: 0; + -webkit-box-shadow: 0 0 0 0.15rem rgba(3, 142, 220, 0.25); + box-shadow: 0 0 0 0.15rem rgba(3, 142, 220, 0.25); } + .btn:disabled, .btn.disabled, + fieldset:disabled .btn { + pointer-events: none; + opacity: 0.65; } + +.btn-primary { + color: #fff; + background-color: #2e86de; + border-color: #2e86de; } + .btn-primary:hover { + color: #fff; + background-color: #0379bb; + border-color: #0272b0; } + .btn-check:focus + .btn-primary, .btn-primary:focus { + color: #fff; + background-color: #0379bb; + border-color: #0272b0; + -webkit-box-shadow: 0 0 0 0.15rem rgba(41, 159, 225, 0.5); + box-shadow: 0 0 0 0.15rem rgba(41, 159, 225, 0.5); } + .btn-check:checked + .btn-primary, + .btn-check:active + .btn-primary, .btn-primary:active, .btn-primary.active, + .show > .btn-primary.dropdown-toggle { + color: #fff; + background-color: #0272b0; + border-color: #026ba5; } + .btn-check:checked + .btn-primary:focus, + .btn-check:active + .btn-primary:focus, .btn-primary:active:focus, .btn-primary.active:focus, + .show > .btn-primary.dropdown-toggle:focus { + -webkit-box-shadow: 0 0 0 0.15rem rgba(41, 159, 225, 0.5); + box-shadow: 0 0 0 0.15rem rgba(41, 159, 225, 0.5); } + .btn-primary:disabled, .btn-primary.disabled { + color: #fff; + background-color: #2e86de; + border-color: #2e86de; } + +.btn-secondary { + color: #fff; + background-color: #74788d; + border-color: #74788d; } + .btn-secondary:hover { + color: #fff; + background-color: #636678; + border-color: #5d6071; } + .btn-check:focus + .btn-secondary, .btn-secondary:focus { + color: #fff; + background-color: #636678; + border-color: #5d6071; + -webkit-box-shadow: 0 0 0 0.15rem rgba(137, 140, 158, 0.5); + box-shadow: 0 0 0 0.15rem rgba(137, 140, 158, 0.5); } + .btn-check:checked + .btn-secondary, + .btn-check:active + .btn-secondary, .btn-secondary:active, .btn-secondary.active, + .show > .btn-secondary.dropdown-toggle { + color: #fff; + background-color: #5d6071; + border-color: #575a6a; } + .btn-check:checked + .btn-secondary:focus, + .btn-check:active + .btn-secondary:focus, .btn-secondary:active:focus, .btn-secondary.active:focus, + .show > .btn-secondary.dropdown-toggle:focus { + -webkit-box-shadow: 0 0 0 0.15rem rgba(137, 140, 158, 0.5); + box-shadow: 0 0 0 0.15rem rgba(137, 140, 158, 0.5); } + .btn-secondary:disabled, .btn-secondary.disabled { + color: #fff; + background-color: #74788d; + border-color: #74788d; } + +.btn-success { + color: #fff; + background-color: #51d28c; + border-color: #51d28c; } + .btn-success:hover { + color: #fff; + background-color: #45b377; + border-color: #41a870; } + .btn-check:focus + .btn-success, .btn-success:focus { + color: #fff; + background-color: #45b377; + border-color: #41a870; + -webkit-box-shadow: 0 0 0 0.15rem rgba(107, 217, 157, 0.5); + box-shadow: 0 0 0 0.15rem rgba(107, 217, 157, 0.5); } + .btn-check:checked + .btn-success, + .btn-check:active + .btn-success, .btn-success:active, .btn-success.active, + .show > .btn-success.dropdown-toggle { + color: #fff; + background-color: #41a870; + border-color: #3d9e69; } + .btn-check:checked + .btn-success:focus, + .btn-check:active + .btn-success:focus, .btn-success:active:focus, .btn-success.active:focus, + .show > .btn-success.dropdown-toggle:focus { + -webkit-box-shadow: 0 0 0 0.15rem rgba(107, 217, 157, 0.5); + box-shadow: 0 0 0 0.15rem rgba(107, 217, 157, 0.5); } + .btn-success:disabled, .btn-success.disabled { + color: #fff; + background-color: #51d28c; + border-color: #51d28c; } + +.btn-info { + color: #fff; + background-color: #5fd0f3; + border-color: #5fd0f3; } + .btn-info:hover { + color: #fff; + background-color: #51b1cf; + border-color: #4ca6c2; } + .btn-check:focus + .btn-info, .btn-info:focus { + color: #fff; + background-color: #51b1cf; + border-color: #4ca6c2; + -webkit-box-shadow: 0 0 0 0.15rem rgba(119, 215, 245, 0.5); + box-shadow: 0 0 0 0.15rem rgba(119, 215, 245, 0.5); } + .btn-check:checked + .btn-info, + .btn-check:active + .btn-info, .btn-info:active, .btn-info.active, + .show > .btn-info.dropdown-toggle { + color: #fff; + background-color: #4ca6c2; + border-color: #479cb6; } + .btn-check:checked + .btn-info:focus, + .btn-check:active + .btn-info:focus, .btn-info:active:focus, .btn-info.active:focus, + .show > .btn-info.dropdown-toggle:focus { + -webkit-box-shadow: 0 0 0 0.15rem rgba(119, 215, 245, 0.5); + box-shadow: 0 0 0 0.15rem rgba(119, 215, 245, 0.5); } + .btn-info:disabled, .btn-info.disabled { + color: #fff; + background-color: #5fd0f3; + border-color: #5fd0f3; } + +.btn-warning { + color: #000; + background-color: #f7cc53; + border-color: #f7cc53; } + .btn-warning:hover { + color: #000; + background-color: #f8d46d; + border-color: #f8d164; } + .btn-check:focus + .btn-warning, .btn-warning:focus { + color: #000; + background-color: #f8d46d; + border-color: #f8d164; + -webkit-box-shadow: 0 0 0 0.15rem rgba(210, 173, 71, 0.5); + box-shadow: 0 0 0 0.15rem rgba(210, 173, 71, 0.5); } + .btn-check:checked + .btn-warning, + .btn-check:active + .btn-warning, .btn-warning:active, .btn-warning.active, + .show > .btn-warning.dropdown-toggle { + color: #000; + background-color: #f9d675; + border-color: #f8d164; } + .btn-check:checked + .btn-warning:focus, + .btn-check:active + .btn-warning:focus, .btn-warning:active:focus, .btn-warning.active:focus, + .show > .btn-warning.dropdown-toggle:focus { + -webkit-box-shadow: 0 0 0 0.15rem rgba(210, 173, 71, 0.5); + box-shadow: 0 0 0 0.15rem rgba(210, 173, 71, 0.5); } + .btn-warning:disabled, .btn-warning.disabled { + color: #000; + background-color: #f7cc53; + border-color: #f7cc53; } + +.btn-danger { + color: #fff; + background-color: #f34e4e; + border-color: #f34e4e; } + .btn-danger:hover { + color: #fff; + background-color: #cf4242; + border-color: #c23e3e; } + .btn-check:focus + .btn-danger, .btn-danger:focus { + color: #fff; + background-color: #cf4242; + border-color: #c23e3e; + -webkit-box-shadow: 0 0 0 0.15rem rgba(245, 105, 105, 0.5); + box-shadow: 0 0 0 0.15rem rgba(245, 105, 105, 0.5); } + .btn-check:checked + .btn-danger, + .btn-check:active + .btn-danger, .btn-danger:active, .btn-danger.active, + .show > .btn-danger.dropdown-toggle { + color: #fff; + background-color: #c23e3e; + border-color: #b63b3b; } + .btn-check:checked + .btn-danger:focus, + .btn-check:active + .btn-danger:focus, .btn-danger:active:focus, .btn-danger.active:focus, + .show > .btn-danger.dropdown-toggle:focus { + -webkit-box-shadow: 0 0 0 0.15rem rgba(245, 105, 105, 0.5); + box-shadow: 0 0 0 0.15rem rgba(245, 105, 105, 0.5); } + .btn-danger:disabled, .btn-danger.disabled { + color: #fff; + background-color: #f34e4e; + border-color: #f34e4e; } + +.btn-pink { + color: #fff; + background-color: #e83e8c; + border-color: #e83e8c; } + .btn-pink:hover { + color: #fff; + background-color: #c53577; + border-color: #ba3270; } + .btn-check:focus + .btn-pink, .btn-pink:focus { + color: #fff; + background-color: #c53577; + border-color: #ba3270; + -webkit-box-shadow: 0 0 0 0.15rem rgba(235, 91, 157, 0.5); + box-shadow: 0 0 0 0.15rem rgba(235, 91, 157, 0.5); } + .btn-check:checked + .btn-pink, + .btn-check:active + .btn-pink, .btn-pink:active, .btn-pink.active, + .show > .btn-pink.dropdown-toggle { + color: #fff; + background-color: #ba3270; + border-color: #ae2f69; } + .btn-check:checked + .btn-pink:focus, + .btn-check:active + .btn-pink:focus, .btn-pink:active:focus, .btn-pink.active:focus, + .show > .btn-pink.dropdown-toggle:focus { + -webkit-box-shadow: 0 0 0 0.15rem rgba(235, 91, 157, 0.5); + box-shadow: 0 0 0 0.15rem rgba(235, 91, 157, 0.5); } + .btn-pink:disabled, .btn-pink.disabled { + color: #fff; + background-color: #e83e8c; + border-color: #e83e8c; } + +.btn-light { + color: #000; + background-color: #f5f6f8; + border-color: #f5f6f8; } + .btn-light:hover { + color: #000; + background-color: #f7f7f9; + border-color: #f6f7f9; } + .btn-check:focus + .btn-light, .btn-light:focus { + color: #000; + background-color: #f7f7f9; + border-color: #f6f7f9; + -webkit-box-shadow: 0 0 0 0.15rem rgba(208, 209, 211, 0.5); + box-shadow: 0 0 0 0.15rem rgba(208, 209, 211, 0.5); } + .btn-check:checked + .btn-light, + .btn-check:active + .btn-light, .btn-light:active, .btn-light.active, + .show > .btn-light.dropdown-toggle { + color: #000; + background-color: #f7f8f9; + border-color: #f6f7f9; } + .btn-check:checked + .btn-light:focus, + .btn-check:active + .btn-light:focus, .btn-light:active:focus, .btn-light.active:focus, + .show > .btn-light.dropdown-toggle:focus { + -webkit-box-shadow: 0 0 0 0.15rem rgba(208, 209, 211, 0.5); + box-shadow: 0 0 0 0.15rem rgba(208, 209, 211, 0.5); } + .btn-light:disabled, .btn-light.disabled { + color: #000; + background-color: #f5f6f8; + border-color: #f5f6f8; } + +.btn-dark { + color: #fff; + background-color: #343a40; + border-color: #343a40; } + .btn-dark:hover { + color: #fff; + background-color: #2c3136; + border-color: #2a2e33; } + .btn-check:focus + .btn-dark, .btn-dark:focus { + color: #fff; + background-color: #2c3136; + border-color: #2a2e33; + -webkit-box-shadow: 0 0 0 0.15rem rgba(82, 88, 93, 0.5); + box-shadow: 0 0 0 0.15rem rgba(82, 88, 93, 0.5); } + .btn-check:checked + .btn-dark, + .btn-check:active + .btn-dark, .btn-dark:active, .btn-dark.active, + .show > .btn-dark.dropdown-toggle { + color: #fff; + background-color: #2a2e33; + border-color: #272c30; } + .btn-check:checked + .btn-dark:focus, + .btn-check:active + .btn-dark:focus, .btn-dark:active:focus, .btn-dark.active:focus, + .show > .btn-dark.dropdown-toggle:focus { + -webkit-box-shadow: 0 0 0 0.15rem rgba(82, 88, 93, 0.5); + box-shadow: 0 0 0 0.15rem rgba(82, 88, 93, 0.5); } + .btn-dark:disabled, .btn-dark.disabled { + color: #fff; + background-color: #343a40; + border-color: #343a40; } + +.btn-purple { + color: #fff; + background-color: #564ab1; + border-color: #564ab1; } + .btn-purple:hover { + color: #fff; + background-color: #493f96; + border-color: #453b8e; } + .btn-check:focus + .btn-purple, .btn-purple:focus { + color: #fff; + background-color: #493f96; + border-color: #453b8e; + -webkit-box-shadow: 0 0 0 0.15rem rgba(111, 101, 189, 0.5); + box-shadow: 0 0 0 0.15rem rgba(111, 101, 189, 0.5); } + .btn-check:checked + .btn-purple, + .btn-check:active + .btn-purple, .btn-purple:active, .btn-purple.active, + .show > .btn-purple.dropdown-toggle { + color: #fff; + background-color: #453b8e; + border-color: #413885; } + .btn-check:checked + .btn-purple:focus, + .btn-check:active + .btn-purple:focus, .btn-purple:active:focus, .btn-purple.active:focus, + .show > .btn-purple.dropdown-toggle:focus { + -webkit-box-shadow: 0 0 0 0.15rem rgba(111, 101, 189, 0.5); + box-shadow: 0 0 0 0.15rem rgba(111, 101, 189, 0.5); } + .btn-purple:disabled, .btn-purple.disabled { + color: #fff; + background-color: #564ab1; + border-color: #564ab1; } + +.btn-outline-primary { + color: #2e86de; + border-color: #2e86de; } + .btn-outline-primary:hover { + color: #fff; + background-color: #2e86de; + border-color: #2e86de; } + .btn-check:focus + .btn-outline-primary, .btn-outline-primary:focus { + -webkit-box-shadow: 0 0 0 0.15rem rgba(3, 142, 220, 0.5); + box-shadow: 0 0 0 0.15rem rgba(3, 142, 220, 0.5); } + .btn-check:checked + .btn-outline-primary, + .btn-check:active + .btn-outline-primary, .btn-outline-primary:active, .btn-outline-primary.active, .btn-outline-primary.dropdown-toggle.show { + color: #fff; + background-color: #2e86de; + border-color: #2e86de; } + .btn-check:checked + .btn-outline-primary:focus, + .btn-check:active + .btn-outline-primary:focus, .btn-outline-primary:active:focus, .btn-outline-primary.active:focus, .btn-outline-primary.dropdown-toggle.show:focus { + -webkit-box-shadow: 0 0 0 0.15rem rgba(3, 142, 220, 0.5); + box-shadow: 0 0 0 0.15rem rgba(3, 142, 220, 0.5); } + .btn-outline-primary:disabled, .btn-outline-primary.disabled { + color: #2e86de; + background-color: transparent; } + +.btn-outline-secondary { + color: #74788d; + border-color: #74788d; } + .btn-outline-secondary:hover { + color: #fff; + background-color: #74788d; + border-color: #74788d; } + .btn-check:focus + .btn-outline-secondary, .btn-outline-secondary:focus { + -webkit-box-shadow: 0 0 0 0.15rem rgba(116, 120, 141, 0.5); + box-shadow: 0 0 0 0.15rem rgba(116, 120, 141, 0.5); } + .btn-check:checked + .btn-outline-secondary, + .btn-check:active + .btn-outline-secondary, .btn-outline-secondary:active, .btn-outline-secondary.active, .btn-outline-secondary.dropdown-toggle.show { + color: #fff; + background-color: #74788d; + border-color: #74788d; } + .btn-check:checked + .btn-outline-secondary:focus, + .btn-check:active + .btn-outline-secondary:focus, .btn-outline-secondary:active:focus, .btn-outline-secondary.active:focus, .btn-outline-secondary.dropdown-toggle.show:focus { + -webkit-box-shadow: 0 0 0 0.15rem rgba(116, 120, 141, 0.5); + box-shadow: 0 0 0 0.15rem rgba(116, 120, 141, 0.5); } + .btn-outline-secondary:disabled, .btn-outline-secondary.disabled { + color: #74788d; + background-color: transparent; } + +.btn-outline-success { + color: #51d28c; + border-color: #51d28c; } + .btn-outline-success:hover { + color: #fff; + background-color: #51d28c; + border-color: #51d28c; } + .btn-check:focus + .btn-outline-success, .btn-outline-success:focus { + -webkit-box-shadow: 0 0 0 0.15rem rgba(81, 210, 140, 0.5); + box-shadow: 0 0 0 0.15rem rgba(81, 210, 140, 0.5); } + .btn-check:checked + .btn-outline-success, + .btn-check:active + .btn-outline-success, .btn-outline-success:active, .btn-outline-success.active, .btn-outline-success.dropdown-toggle.show { + color: #fff; + background-color: #51d28c; + border-color: #51d28c; } + .btn-check:checked + .btn-outline-success:focus, + .btn-check:active + .btn-outline-success:focus, .btn-outline-success:active:focus, .btn-outline-success.active:focus, .btn-outline-success.dropdown-toggle.show:focus { + -webkit-box-shadow: 0 0 0 0.15rem rgba(81, 210, 140, 0.5); + box-shadow: 0 0 0 0.15rem rgba(81, 210, 140, 0.5); } + .btn-outline-success:disabled, .btn-outline-success.disabled { + color: #51d28c; + background-color: transparent; } + +.btn-outline-info { + color: #5fd0f3; + border-color: #5fd0f3; } + .btn-outline-info:hover { + color: #fff; + background-color: #5fd0f3; + border-color: #5fd0f3; } + .btn-check:focus + .btn-outline-info, .btn-outline-info:focus { + -webkit-box-shadow: 0 0 0 0.15rem rgba(95, 208, 243, 0.5); + box-shadow: 0 0 0 0.15rem rgba(95, 208, 243, 0.5); } + .btn-check:checked + .btn-outline-info, + .btn-check:active + .btn-outline-info, .btn-outline-info:active, .btn-outline-info.active, .btn-outline-info.dropdown-toggle.show { + color: #fff; + background-color: #5fd0f3; + border-color: #5fd0f3; } + .btn-check:checked + .btn-outline-info:focus, + .btn-check:active + .btn-outline-info:focus, .btn-outline-info:active:focus, .btn-outline-info.active:focus, .btn-outline-info.dropdown-toggle.show:focus { + -webkit-box-shadow: 0 0 0 0.15rem rgba(95, 208, 243, 0.5); + box-shadow: 0 0 0 0.15rem rgba(95, 208, 243, 0.5); } + .btn-outline-info:disabled, .btn-outline-info.disabled { + color: #5fd0f3; + background-color: transparent; } + +.btn-outline-warning { + color: #f7cc53; + border-color: #f7cc53; } + .btn-outline-warning:hover { + color: #000; + background-color: #f7cc53; + border-color: #f7cc53; } + .btn-check:focus + .btn-outline-warning, .btn-outline-warning:focus { + -webkit-box-shadow: 0 0 0 0.15rem rgba(247, 204, 83, 0.5); + box-shadow: 0 0 0 0.15rem rgba(247, 204, 83, 0.5); } + .btn-check:checked + .btn-outline-warning, + .btn-check:active + .btn-outline-warning, .btn-outline-warning:active, .btn-outline-warning.active, .btn-outline-warning.dropdown-toggle.show { + color: #000; + background-color: #f7cc53; + border-color: #f7cc53; } + .btn-check:checked + .btn-outline-warning:focus, + .btn-check:active + .btn-outline-warning:focus, .btn-outline-warning:active:focus, .btn-outline-warning.active:focus, .btn-outline-warning.dropdown-toggle.show:focus { + -webkit-box-shadow: 0 0 0 0.15rem rgba(247, 204, 83, 0.5); + box-shadow: 0 0 0 0.15rem rgba(247, 204, 83, 0.5); } + .btn-outline-warning:disabled, .btn-outline-warning.disabled { + color: #f7cc53; + background-color: transparent; } + +.btn-outline-danger { + color: #f34e4e; + border-color: #f34e4e; } + .btn-outline-danger:hover { + color: #fff; + background-color: #f34e4e; + border-color: #f34e4e; } + .btn-check:focus + .btn-outline-danger, .btn-outline-danger:focus { + -webkit-box-shadow: 0 0 0 0.15rem rgba(243, 78, 78, 0.5); + box-shadow: 0 0 0 0.15rem rgba(243, 78, 78, 0.5); } + .btn-check:checked + .btn-outline-danger, + .btn-check:active + .btn-outline-danger, .btn-outline-danger:active, .btn-outline-danger.active, .btn-outline-danger.dropdown-toggle.show { + color: #fff; + background-color: #f34e4e; + border-color: #f34e4e; } + .btn-check:checked + .btn-outline-danger:focus, + .btn-check:active + .btn-outline-danger:focus, .btn-outline-danger:active:focus, .btn-outline-danger.active:focus, .btn-outline-danger.dropdown-toggle.show:focus { + -webkit-box-shadow: 0 0 0 0.15rem rgba(243, 78, 78, 0.5); + box-shadow: 0 0 0 0.15rem rgba(243, 78, 78, 0.5); } + .btn-outline-danger:disabled, .btn-outline-danger.disabled { + color: #f34e4e; + background-color: transparent; } + +.btn-outline-pink { + color: #e83e8c; + border-color: #e83e8c; } + .btn-outline-pink:hover { + color: #fff; + background-color: #e83e8c; + border-color: #e83e8c; } + .btn-check:focus + .btn-outline-pink, .btn-outline-pink:focus { + -webkit-box-shadow: 0 0 0 0.15rem rgba(232, 62, 140, 0.5); + box-shadow: 0 0 0 0.15rem rgba(232, 62, 140, 0.5); } + .btn-check:checked + .btn-outline-pink, + .btn-check:active + .btn-outline-pink, .btn-outline-pink:active, .btn-outline-pink.active, .btn-outline-pink.dropdown-toggle.show { + color: #fff; + background-color: #e83e8c; + border-color: #e83e8c; } + .btn-check:checked + .btn-outline-pink:focus, + .btn-check:active + .btn-outline-pink:focus, .btn-outline-pink:active:focus, .btn-outline-pink.active:focus, .btn-outline-pink.dropdown-toggle.show:focus { + -webkit-box-shadow: 0 0 0 0.15rem rgba(232, 62, 140, 0.5); + box-shadow: 0 0 0 0.15rem rgba(232, 62, 140, 0.5); } + .btn-outline-pink:disabled, .btn-outline-pink.disabled { + color: #e83e8c; + background-color: transparent; } + +.btn-outline-light { + color: #f5f6f8; + border-color: #f5f6f8; } + .btn-outline-light:hover { + color: #000; + background-color: #f5f6f8; + border-color: #f5f6f8; } + .btn-check:focus + .btn-outline-light, .btn-outline-light:focus { + -webkit-box-shadow: 0 0 0 0.15rem rgba(245, 246, 248, 0.5); + box-shadow: 0 0 0 0.15rem rgba(245, 246, 248, 0.5); } + .btn-check:checked + .btn-outline-light, + .btn-check:active + .btn-outline-light, .btn-outline-light:active, .btn-outline-light.active, .btn-outline-light.dropdown-toggle.show { + color: #000; + background-color: #f5f6f8; + border-color: #f5f6f8; } + .btn-check:checked + .btn-outline-light:focus, + .btn-check:active + .btn-outline-light:focus, .btn-outline-light:active:focus, .btn-outline-light.active:focus, .btn-outline-light.dropdown-toggle.show:focus { + -webkit-box-shadow: 0 0 0 0.15rem rgba(245, 246, 248, 0.5); + box-shadow: 0 0 0 0.15rem rgba(245, 246, 248, 0.5); } + .btn-outline-light:disabled, .btn-outline-light.disabled { + color: #f5f6f8; + background-color: transparent; } + +.btn-outline-dark { + color: #343a40; + border-color: #343a40; } + .btn-outline-dark:hover { + color: #fff; + background-color: #343a40; + border-color: #343a40; } + .btn-check:focus + .btn-outline-dark, .btn-outline-dark:focus { + -webkit-box-shadow: 0 0 0 0.15rem rgba(52, 58, 64, 0.5); + box-shadow: 0 0 0 0.15rem rgba(52, 58, 64, 0.5); } + .btn-check:checked + .btn-outline-dark, + .btn-check:active + .btn-outline-dark, .btn-outline-dark:active, .btn-outline-dark.active, .btn-outline-dark.dropdown-toggle.show { + color: #fff; + background-color: #343a40; + border-color: #343a40; } + .btn-check:checked + .btn-outline-dark:focus, + .btn-check:active + .btn-outline-dark:focus, .btn-outline-dark:active:focus, .btn-outline-dark.active:focus, .btn-outline-dark.dropdown-toggle.show:focus { + -webkit-box-shadow: 0 0 0 0.15rem rgba(52, 58, 64, 0.5); + box-shadow: 0 0 0 0.15rem rgba(52, 58, 64, 0.5); } + .btn-outline-dark:disabled, .btn-outline-dark.disabled { + color: #343a40; + background-color: transparent; } + +.btn-outline-purple { + color: #564ab1; + border-color: #564ab1; } + .btn-outline-purple:hover { + color: #fff; + background-color: #564ab1; + border-color: #564ab1; } + .btn-check:focus + .btn-outline-purple, .btn-outline-purple:focus { + -webkit-box-shadow: 0 0 0 0.15rem rgba(86, 74, 177, 0.5); + box-shadow: 0 0 0 0.15rem rgba(86, 74, 177, 0.5); } + .btn-check:checked + .btn-outline-purple, + .btn-check:active + .btn-outline-purple, .btn-outline-purple:active, .btn-outline-purple.active, .btn-outline-purple.dropdown-toggle.show { + color: #fff; + background-color: #564ab1; + border-color: #564ab1; } + .btn-check:checked + .btn-outline-purple:focus, + .btn-check:active + .btn-outline-purple:focus, .btn-outline-purple:active:focus, .btn-outline-purple.active:focus, .btn-outline-purple.dropdown-toggle.show:focus { + -webkit-box-shadow: 0 0 0 0.15rem rgba(86, 74, 177, 0.5); + box-shadow: 0 0 0 0.15rem rgba(86, 74, 177, 0.5); } + .btn-outline-purple:disabled, .btn-outline-purple.disabled { + color: #564ab1; + background-color: transparent; } + +.btn-link { + font-weight: 400; + color: #2e86de; + text-decoration: none; } + .btn-link:hover { + color: #025d91; } + .btn-link:disabled, .btn-link.disabled { + color: #74788d; } + +.btn-lg, .btn-group-lg > .btn { + padding: 0.5rem 1rem; + font-size: 1.125rem; + border-radius: 0.4rem; } + +.btn-sm, .btn-group-sm > .btn { + padding: 0.25rem 0.5rem; + font-size: 0.7875rem; + border-radius: 0.2rem; } + +.fade { + -webkit-transition: opacity 0.15s linear; + transition: opacity 0.15s linear; } + @media (prefers-reduced-motion: reduce) { + .fade { + -webkit-transition: none; + transition: none; } } + .fade:not(.show) { + opacity: 0; } + +.collapse:not(.show) { + display: none; } + +.collapsing { + height: 0; + overflow: hidden; + -webkit-transition: height 0.35s ease; + transition: height 0.35s ease; } + @media (prefers-reduced-motion: reduce) { + .collapsing { + -webkit-transition: none; + transition: none; } } + .collapsing.collapse-horizontal { + width: 0; + height: auto; + -webkit-transition: width 0.35s ease; + transition: width 0.35s ease; } + @media (prefers-reduced-motion: reduce) { + .collapsing.collapse-horizontal { + -webkit-transition: none; + transition: none; } } + +.dropup, +.dropend, +.dropdown, +.dropstart { + position: relative; } + +.dropdown-toggle { + white-space: nowrap; } + +.dropdown-menu { + position: absolute; + z-index: 1000; + display: none; + min-width: 10rem; + padding: 0.5rem 0; + margin: 0; + font-size: 0.9rem; + color: #495057; + text-align: left; + list-style: none; + background-color: #fff; + background-clip: padding-box; + border: 1px solid #e2e5e8; + border-radius: 0.25rem; } + .dropdown-menu[data-bs-popper] { + top: 100%; + left: 0; + margin-top: 0.125rem; } + +.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: 0.125rem; } + +.dropend .dropdown-menu[data-bs-popper] { + top: 0; + right: auto; + left: 100%; + margin-top: 0; + margin-left: 0.125rem; } + +.dropend .dropdown-toggle::after { + vertical-align: 0; } + +.dropstart .dropdown-menu[data-bs-popper] { + top: 0; + right: 100%; + left: auto; + margin-top: 0; + margin-right: 0.125rem; } + +.dropstart .dropdown-toggle::before { + vertical-align: 0; } + +.dropdown-divider { + height: 0; + margin: 0.5rem 0; + overflow: hidden; + border-top: 1px solid #e2e5e8; } + +.dropdown-item { + display: block; + width: 100%; + padding: 0.35rem 1.5rem; + clear: both; + font-weight: 400; + color: #495057; + text-align: inherit; + white-space: nowrap; + background-color: transparent; + border: 0; } + .dropdown-item:hover, .dropdown-item:focus { + color: #2c3034; + background-color: #f8f9fa; } + .dropdown-item.active, .dropdown-item:active { + color: #2c3034; + text-decoration: none; + background-color: #f8f9fa; } + .dropdown-item.disabled, .dropdown-item:disabled { + color: #74788d; + pointer-events: none; + background-color: transparent; } + +.dropdown-menu.show { + display: block; } + +.dropdown-header { + display: block; + padding: 0.5rem 1.5rem; + margin-bottom: 0; + font-size: 0.7875rem; + color: #74788d; + white-space: nowrap; } + +.dropdown-item-text { + display: block; + padding: 0.35rem 1.5rem; + color: #495057; } + +.dropdown-menu-dark { + color: #eff0f2; + background-color: #495057; + border-color: #e2e5e8; } + .dropdown-menu-dark .dropdown-item { + color: #eff0f2; } + .dropdown-menu-dark .dropdown-item:hover, .dropdown-menu-dark .dropdown-item:focus { + color: #fff; + background-color: #555d65; } + .dropdown-menu-dark .dropdown-item.active, .dropdown-menu-dark .dropdown-item:active { + color: #e2e5e8; + background-color: #555d65; } + .dropdown-menu-dark .dropdown-item.disabled, .dropdown-menu-dark .dropdown-item:disabled { + color: #eff0f2; } + .dropdown-menu-dark .dropdown-divider { + border-color: #e2e5e8; } + .dropdown-menu-dark .dropdown-item-text { + color: #eff0f2; } + .dropdown-menu-dark .dropdown-header { + color: #adb5bd; } + +.btn-group, +.btn-group-vertical { + position: relative; + display: -webkit-inline-box; + display: -ms-inline-flexbox; + display: inline-flex; + vertical-align: middle; } + .btn-group > .btn, + .btn-group-vertical > .btn { + position: relative; + -webkit-box-flex: 1; + -ms-flex: 1 1 auto; + flex: 1 1 auto; } + .btn-group > .btn-check:checked + .btn, + .btn-group > .btn-check:focus + .btn, + .btn-group > .btn:hover, + .btn-group > .btn:focus, + .btn-group > .btn:active, + .btn-group > .btn.active, + .btn-group-vertical > .btn-check:checked + .btn, + .btn-group-vertical > .btn-check:focus + .btn, + .btn-group-vertical > .btn:hover, + .btn-group-vertical > .btn:focus, + .btn-group-vertical > .btn:active, + .btn-group-vertical > .btn.active { + z-index: 1; } + +.btn-toolbar { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + -webkit-box-pack: start; + -ms-flex-pack: start; + justify-content: flex-start; } + .btn-toolbar .input-group { + width: auto; } + +.btn-group > .btn:not(:first-child), +.btn-group > .btn-group:not(:first-child) { + margin-left: -1px; } + +.btn-group > .btn:not(:last-child):not(.dropdown-toggle), +.btn-group > .btn-group:not(:last-child) > .btn { + border-top-right-radius: 0; + border-bottom-right-radius: 0; } + +.btn-group > .btn:nth-child(n + 3), +.btn-group > :not(.btn-check) + .btn, +.btn-group > .btn-group:not(:first-child) > .btn { + border-top-left-radius: 0; + border-bottom-left-radius: 0; } + +.dropdown-toggle-split { + padding-right: 0.5625rem; + padding-left: 0.5625rem; } + .dropdown-toggle-split::after, + .dropup .dropdown-toggle-split::after, + .dropend .dropdown-toggle-split::after { + margin-left: 0; } + .dropstart .dropdown-toggle-split::before { + margin-right: 0; } + +.btn-sm + .dropdown-toggle-split, .btn-group-sm > .btn + .dropdown-toggle-split { + padding-right: 0.375rem; + padding-left: 0.375rem; } + +.btn-lg + .dropdown-toggle-split, .btn-group-lg > .btn + .dropdown-toggle-split { + padding-right: 0.75rem; + padding-left: 0.75rem; } + +.btn-group-vertical { + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; + -webkit-box-align: start; + -ms-flex-align: start; + align-items: flex-start; + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; } + .btn-group-vertical > .btn, + .btn-group-vertical > .btn-group { + width: 100%; } + .btn-group-vertical > .btn:not(:first-child), + .btn-group-vertical > .btn-group:not(:first-child) { + margin-top: -1px; } + .btn-group-vertical > .btn:not(:last-child):not(.dropdown-toggle), + .btn-group-vertical > .btn-group:not(:last-child) > .btn { + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; } + .btn-group-vertical > .btn ~ .btn, + .btn-group-vertical > .btn-group:not(:first-child) > .btn { + border-top-left-radius: 0; + border-top-right-radius: 0; } + +.nav { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + padding-left: 0; + margin-bottom: 0; + list-style: none; } + +.nav-link { + display: block; + padding: 0.5rem 1rem; + color: #2e86de; + -webkit-transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out; + transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out; } + @media (prefers-reduced-motion: reduce) { + .nav-link { + -webkit-transition: none; + transition: none; } } + .nav-link:hover, .nav-link:focus { + color: #025d91; } + .nav-link.disabled { + color: #74788d; + pointer-events: none; + cursor: default; } + +.nav-tabs { + border-bottom: 1px solid #e2e5e8; } + .nav-tabs .nav-link { + margin-bottom: -1px; + background: none; + border: 1px solid transparent; + border-top-left-radius: 0.25rem; + border-top-right-radius: 0.25rem; } + .nav-tabs .nav-link:hover, .nav-tabs .nav-link:focus { + border-color: #f5f6f8 #f5f6f8 #e2e5e8; + isolation: isolate; } + .nav-tabs .nav-link.disabled { + color: #74788d; + background-color: transparent; + border-color: transparent; } + .nav-tabs .nav-link.active, + .nav-tabs .nav-item.show .nav-link { + color: #495057; + background-color: #fff; + border-color: #e2e5e8 #e2e5e8 #fff; } + .nav-tabs .dropdown-menu { + margin-top: -1px; + border-top-left-radius: 0; + border-top-right-radius: 0; } + +.nav-pills .nav-link { + background: none; + border: 0; + border-radius: 0.25rem; } + +.nav-pills .nav-link.active, +.nav-pills .show > .nav-link { + color: #fff; + background-color: #2e86de; } + +.nav-fill > .nav-link, +.nav-fill .nav-item { + -webkit-box-flex: 1; + -ms-flex: 1 1 auto; + flex: 1 1 auto; + text-align: center; } + +.nav-justified > .nav-link, +.nav-justified .nav-item { + -ms-flex-preferred-size: 0; + flex-basis: 0; + -webkit-box-flex: 1; + -ms-flex-positive: 1; + flex-grow: 1; + 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 { + position: relative; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: justify; + -ms-flex-pack: justify; + justify-content: space-between; + padding-top: 0.5rem; + padding-bottom: 0.5rem; } + .navbar > .container, + .navbar > .container-fluid, .navbar > .container-sm, .navbar > .container-md, .navbar > .container-lg, .navbar > .container-xl, .navbar > .container-xxl { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: inherit; + flex-wrap: inherit; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: justify; + -ms-flex-pack: justify; + justify-content: space-between; } + +.navbar-brand { + padding-top: 0.33125rem; + padding-bottom: 0.33125rem; + margin-right: 1rem; + font-size: 1.125rem; + white-space: nowrap; } + +.navbar-nav { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; + padding-left: 0; + margin-bottom: 0; + list-style: none; } + .navbar-nav .nav-link { + padding-right: 0; + padding-left: 0; } + .navbar-nav .dropdown-menu { + position: static; } + +.navbar-text { + padding-top: 0.5rem; + padding-bottom: 0.5rem; } + +.navbar-collapse { + -ms-flex-preferred-size: 100%; + flex-basis: 100%; + -webkit-box-flex: 1; + -ms-flex-positive: 1; + flex-grow: 1; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; } + +.navbar-toggler { + padding: 0.25rem 0.75rem; + font-size: 1.125rem; + line-height: 1; + background-color: transparent; + border: 1px solid transparent; + border-radius: 0.25rem; + -webkit-transition: -webkit-box-shadow 0.15s ease-in-out; + transition: -webkit-box-shadow 0.15s ease-in-out; + transition: box-shadow 0.15s ease-in-out; + transition: box-shadow 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out; } + @media (prefers-reduced-motion: reduce) { + .navbar-toggler { + -webkit-transition: none; + transition: none; } } + .navbar-toggler:hover { + text-decoration: none; } + .navbar-toggler:focus { + text-decoration: none; + outline: 0; + -webkit-box-shadow: 0 0 0 0.15rem; + box-shadow: 0 0 0 0.15rem; } + +.navbar-toggler-icon { + display: inline-block; + width: 1.5em; + height: 1.5em; + vertical-align: middle; + 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 { + -ms-flex-wrap: nowrap; + flex-wrap: nowrap; + -webkit-box-pack: start; + -ms-flex-pack: start; + justify-content: flex-start; } + .navbar-expand-sm .navbar-nav { + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-direction: row; + flex-direction: row; } + .navbar-expand-sm .navbar-nav .dropdown-menu { + position: absolute; } + .navbar-expand-sm .navbar-nav .nav-link { + padding-right: 0.5rem; + padding-left: 0.5rem; } + .navbar-expand-sm .navbar-nav-scroll { + overflow: visible; } + .navbar-expand-sm .navbar-collapse { + display: -webkit-box !important; + display: -ms-flexbox !important; + display: flex !important; + -ms-flex-preferred-size: auto; + flex-basis: auto; } + .navbar-expand-sm .navbar-toggler { + display: none; } + .navbar-expand-sm .offcanvas-header { + display: none; } + .navbar-expand-sm .offcanvas { + position: inherit; + bottom: 0; + z-index: 1000; + -webkit-box-flex: 1; + -ms-flex-positive: 1; + flex-grow: 1; + visibility: visible !important; + background-color: transparent; + border-right: 0; + border-left: 0; + -webkit-transition: none; + transition: none; + -webkit-transform: none; + transform: none; } + .navbar-expand-sm .offcanvas-top, + .navbar-expand-sm .offcanvas-bottom { + height: auto; + border-top: 0; + border-bottom: 0; } + .navbar-expand-sm .offcanvas-body { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-flex: 0; + -ms-flex-positive: 0; + flex-grow: 0; + padding: 0; + overflow-y: visible; } } + +@media (min-width: 768px) { + .navbar-expand-md { + -ms-flex-wrap: nowrap; + flex-wrap: nowrap; + -webkit-box-pack: start; + -ms-flex-pack: start; + justify-content: flex-start; } + .navbar-expand-md .navbar-nav { + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-direction: row; + flex-direction: row; } + .navbar-expand-md .navbar-nav .dropdown-menu { + position: absolute; } + .navbar-expand-md .navbar-nav .nav-link { + padding-right: 0.5rem; + padding-left: 0.5rem; } + .navbar-expand-md .navbar-nav-scroll { + overflow: visible; } + .navbar-expand-md .navbar-collapse { + display: -webkit-box !important; + display: -ms-flexbox !important; + display: flex !important; + -ms-flex-preferred-size: auto; + flex-basis: auto; } + .navbar-expand-md .navbar-toggler { + display: none; } + .navbar-expand-md .offcanvas-header { + display: none; } + .navbar-expand-md .offcanvas { + position: inherit; + bottom: 0; + z-index: 1000; + -webkit-box-flex: 1; + -ms-flex-positive: 1; + flex-grow: 1; + visibility: visible !important; + background-color: transparent; + border-right: 0; + border-left: 0; + -webkit-transition: none; + transition: none; + -webkit-transform: none; + transform: none; } + .navbar-expand-md .offcanvas-top, + .navbar-expand-md .offcanvas-bottom { + height: auto; + border-top: 0; + border-bottom: 0; } + .navbar-expand-md .offcanvas-body { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-flex: 0; + -ms-flex-positive: 0; + flex-grow: 0; + padding: 0; + overflow-y: visible; } } + +@media (min-width: 992px) { + .navbar-expand-lg { + -ms-flex-wrap: nowrap; + flex-wrap: nowrap; + -webkit-box-pack: start; + -ms-flex-pack: start; + justify-content: flex-start; } + .navbar-expand-lg .navbar-nav { + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-direction: row; + flex-direction: row; } + .navbar-expand-lg .navbar-nav .dropdown-menu { + position: absolute; } + .navbar-expand-lg .navbar-nav .nav-link { + padding-right: 0.5rem; + padding-left: 0.5rem; } + .navbar-expand-lg .navbar-nav-scroll { + overflow: visible; } + .navbar-expand-lg .navbar-collapse { + display: -webkit-box !important; + display: -ms-flexbox !important; + display: flex !important; + -ms-flex-preferred-size: auto; + flex-basis: auto; } + .navbar-expand-lg .navbar-toggler { + display: none; } + .navbar-expand-lg .offcanvas-header { + display: none; } + .navbar-expand-lg .offcanvas { + position: inherit; + bottom: 0; + z-index: 1000; + -webkit-box-flex: 1; + -ms-flex-positive: 1; + flex-grow: 1; + visibility: visible !important; + background-color: transparent; + border-right: 0; + border-left: 0; + -webkit-transition: none; + transition: none; + -webkit-transform: none; + transform: none; } + .navbar-expand-lg .offcanvas-top, + .navbar-expand-lg .offcanvas-bottom { + height: auto; + border-top: 0; + border-bottom: 0; } + .navbar-expand-lg .offcanvas-body { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-flex: 0; + -ms-flex-positive: 0; + flex-grow: 0; + padding: 0; + overflow-y: visible; } } + +@media (min-width: 1200px) { + .navbar-expand-xl { + -ms-flex-wrap: nowrap; + flex-wrap: nowrap; + -webkit-box-pack: start; + -ms-flex-pack: start; + justify-content: flex-start; } + .navbar-expand-xl .navbar-nav { + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-direction: row; + flex-direction: row; } + .navbar-expand-xl .navbar-nav .dropdown-menu { + position: absolute; } + .navbar-expand-xl .navbar-nav .nav-link { + padding-right: 0.5rem; + padding-left: 0.5rem; } + .navbar-expand-xl .navbar-nav-scroll { + overflow: visible; } + .navbar-expand-xl .navbar-collapse { + display: -webkit-box !important; + display: -ms-flexbox !important; + display: flex !important; + -ms-flex-preferred-size: auto; + flex-basis: auto; } + .navbar-expand-xl .navbar-toggler { + display: none; } + .navbar-expand-xl .offcanvas-header { + display: none; } + .navbar-expand-xl .offcanvas { + position: inherit; + bottom: 0; + z-index: 1000; + -webkit-box-flex: 1; + -ms-flex-positive: 1; + flex-grow: 1; + visibility: visible !important; + background-color: transparent; + border-right: 0; + border-left: 0; + -webkit-transition: none; + transition: none; + -webkit-transform: none; + transform: none; } + .navbar-expand-xl .offcanvas-top, + .navbar-expand-xl .offcanvas-bottom { + height: auto; + border-top: 0; + border-bottom: 0; } + .navbar-expand-xl .offcanvas-body { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-flex: 0; + -ms-flex-positive: 0; + flex-grow: 0; + padding: 0; + overflow-y: visible; } } + +@media (min-width: 1400px) { + .navbar-expand-xxl { + -ms-flex-wrap: nowrap; + flex-wrap: nowrap; + -webkit-box-pack: start; + -ms-flex-pack: start; + justify-content: flex-start; } + .navbar-expand-xxl .navbar-nav { + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-direction: row; + flex-direction: row; } + .navbar-expand-xxl .navbar-nav .dropdown-menu { + position: absolute; } + .navbar-expand-xxl .navbar-nav .nav-link { + padding-right: 0.5rem; + padding-left: 0.5rem; } + .navbar-expand-xxl .navbar-nav-scroll { + overflow: visible; } + .navbar-expand-xxl .navbar-collapse { + display: -webkit-box !important; + display: -ms-flexbox !important; + display: flex !important; + -ms-flex-preferred-size: auto; + flex-basis: auto; } + .navbar-expand-xxl .navbar-toggler { + display: none; } + .navbar-expand-xxl .offcanvas-header { + display: none; } + .navbar-expand-xxl .offcanvas { + position: inherit; + bottom: 0; + z-index: 1000; + -webkit-box-flex: 1; + -ms-flex-positive: 1; + flex-grow: 1; + visibility: visible !important; + background-color: transparent; + border-right: 0; + border-left: 0; + -webkit-transition: none; + transition: none; + -webkit-transform: none; + transform: none; } + .navbar-expand-xxl .offcanvas-top, + .navbar-expand-xxl .offcanvas-bottom { + height: auto; + border-top: 0; + border-bottom: 0; } + .navbar-expand-xxl .offcanvas-body { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-flex: 0; + -ms-flex-positive: 0; + flex-grow: 0; + padding: 0; + overflow-y: visible; } } + +.navbar-expand { + -ms-flex-wrap: nowrap; + flex-wrap: nowrap; + -webkit-box-pack: start; + -ms-flex-pack: start; + justify-content: flex-start; } + .navbar-expand .navbar-nav { + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-direction: row; + flex-direction: row; } + .navbar-expand .navbar-nav .dropdown-menu { + position: absolute; } + .navbar-expand .navbar-nav .nav-link { + padding-right: 0.5rem; + padding-left: 0.5rem; } + .navbar-expand .navbar-nav-scroll { + overflow: visible; } + .navbar-expand .navbar-collapse { + display: -webkit-box !important; + display: -ms-flexbox !important; + display: flex !important; + -ms-flex-preferred-size: auto; + flex-basis: auto; } + .navbar-expand .navbar-toggler { + display: none; } + .navbar-expand .offcanvas-header { + display: none; } + .navbar-expand .offcanvas { + position: inherit; + bottom: 0; + z-index: 1000; + -webkit-box-flex: 1; + -ms-flex-positive: 1; + flex-grow: 1; + visibility: visible !important; + background-color: transparent; + border-right: 0; + border-left: 0; + -webkit-transition: none; + transition: none; + -webkit-transform: none; + transform: none; } + .navbar-expand .offcanvas-top, + .navbar-expand .offcanvas-bottom { + height: auto; + border-top: 0; + border-bottom: 0; } + .navbar-expand .offcanvas-body { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-flex: 0; + -ms-flex-positive: 0; + flex-grow: 0; + padding: 0; + overflow-y: visible; } + +.navbar-light .navbar-brand { + color: rgba(0, 0, 0, 0.9); } + .navbar-light .navbar-brand:hover, .navbar-light .navbar-brand:focus { + color: rgba(0, 0, 0, 0.9); } + +.navbar-light .navbar-nav .nav-link { + color: rgba(0, 0, 0, 0.55); } + .navbar-light .navbar-nav .nav-link:hover, .navbar-light .navbar-nav .nav-link:focus { + color: rgba(0, 0, 0, 0.7); } + .navbar-light .navbar-nav .nav-link.disabled { + color: rgba(0, 0, 0, 0.3); } + +.navbar-light .navbar-nav .show > .nav-link, +.navbar-light .navbar-nav .nav-link.active { + color: rgba(0, 0, 0, 0.9); } + +.navbar-light .navbar-toggler { + color: rgba(0, 0, 0, 0.55); + border-color: rgba(0, 0, 0, 0.1); } + +.navbar-light .navbar-toggler-icon { + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%280, 0, 0, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e"); } + +.navbar-light .navbar-text { + color: rgba(0, 0, 0, 0.55); } + .navbar-light .navbar-text a, + .navbar-light .navbar-text a:hover, + .navbar-light .navbar-text a:focus { + color: rgba(0, 0, 0, 0.9); } + +.navbar-dark .navbar-brand { + color: #fff; } + .navbar-dark .navbar-brand:hover, .navbar-dark .navbar-brand:focus { + color: #fff; } + +.navbar-dark .navbar-nav .nav-link { + color: rgba(255, 255, 255, 0.55); } + .navbar-dark .navbar-nav .nav-link:hover, .navbar-dark .navbar-nav .nav-link:focus { + color: rgba(255, 255, 255, 0.75); } + .navbar-dark .navbar-nav .nav-link.disabled { + color: rgba(255, 255, 255, 0.25); } + +.navbar-dark .navbar-nav .show > .nav-link, +.navbar-dark .navbar-nav .nav-link.active { + color: #fff; } + +.navbar-dark .navbar-toggler { + color: rgba(255, 255, 255, 0.55); + border-color: rgba(255, 255, 255, 0.1); } + +.navbar-dark .navbar-toggler-icon { + background-image: 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"); } + +.navbar-dark .navbar-text { + color: rgba(255, 255, 255, 0.55); } + .navbar-dark .navbar-text a, + .navbar-dark .navbar-text a:hover, + .navbar-dark .navbar-text a:focus { + color: #fff; } + +.card { + position: relative; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; + min-width: 0; + word-wrap: break-word; + background-color: #fff; + background-clip: border-box; + border: 1px solid #eff0f2; + border-radius: 0.25rem; } + .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: calc(0.25rem - 1px); + border-top-right-radius: calc(0.25rem - 1px); } + .card > .list-group:last-child { + border-bottom-width: 0; + border-bottom-right-radius: calc(0.25rem - 1px); + border-bottom-left-radius: calc(0.25rem - 1px); } + .card > .card-header + .list-group, + .card > .list-group + .card-footer { + border-top: 0; } + +.card-body { + -webkit-box-flex: 1; + -ms-flex: 1 1 auto; + flex: 1 1 auto; + padding: 1.25rem 1.25rem; } + +.card-title { + margin-bottom: 0.5rem; } + +.card-subtitle { + margin-top: -0.25rem; + margin-bottom: 0; } + +.card-text:last-child { + margin-bottom: 0; } + +.card-link + .card-link { + margin-left: 1.25rem; } + +.card-header { + padding: 1rem 1.25rem; + margin-bottom: 0; + background-color: #fff; + border-bottom: 1px solid #eff0f2; } + .card-header:first-child { + border-radius: calc(0.25rem - 1px) calc(0.25rem - 1px) 0 0; } + +.card-footer { + padding: 1rem 1.25rem; + background-color: #fff; + border-top: 1px solid #eff0f2; } + .card-footer:last-child { + border-radius: 0 0 calc(0.25rem - 1px) calc(0.25rem - 1px); } + +.card-header-tabs { + margin-right: -0.625rem; + margin-bottom: -1rem; + margin-left: -0.625rem; + border-bottom: 0; } + +.card-header-pills { + margin-right: -0.625rem; + margin-left: -0.625rem; } + +.card-img-overlay { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + padding: 1rem; + border-radius: calc(0.25rem - 1px); } + +.card-img, +.card-img-top, +.card-img-bottom { + width: 100%; } + +.card-img, +.card-img-top { + border-top-left-radius: calc(0.25rem - 1px); + border-top-right-radius: calc(0.25rem - 1px); } + +.card-img, +.card-img-bottom { + border-bottom-right-radius: calc(0.25rem - 1px); + border-bottom-left-radius: calc(0.25rem - 1px); } + +.card-group > .card { + margin-bottom: 10px; } + +@media (min-width: 576px) { + .card-group { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-flow: row wrap; + flex-flow: row wrap; } + .card-group > .card { + -webkit-box-flex: 1; + -ms-flex: 1 0 0%; + 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-img-top, + .card-group > .card:not(:last-child) .card-header { + border-top-right-radius: 0; } + .card-group > .card:not(:last-child) .card-img-bottom, + .card-group > .card:not(:last-child) .card-footer { + 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-img-top, + .card-group > .card:not(:first-child) .card-header { + border-top-left-radius: 0; } + .card-group > .card:not(:first-child) .card-img-bottom, + .card-group > .card:not(:first-child) .card-footer { + border-bottom-left-radius: 0; } } + +.accordion-button { + position: relative; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + width: 100%; + padding: 1rem 1.25rem; + font-size: 0.9rem; + color: #495057; + text-align: left; + background-color: transparent; + border: 0; + border-radius: 0; + overflow-anchor: none; + -webkit-transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, border-radius 0.15s ease, -webkit-box-shadow 0.15s ease-in-out; + transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, border-radius 0.15s ease, -webkit-box-shadow 0.15s ease-in-out; + 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; + 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, -webkit-box-shadow 0.15s ease-in-out; } + @media (prefers-reduced-motion: reduce) { + .accordion-button { + -webkit-transition: none; + transition: none; } } + .accordion-button:not(.collapsed) { + color: #0380c6; + background-color: #e6f4fc; + -webkit-box-shadow: inset 0 -1px 0 #eff0f2; + box-shadow: inset 0 -1px 0 #eff0f2; } + .accordion-button:not(.collapsed)::after { + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%230380c6'%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-.708z'/%3e%3c/svg%3e"); + -webkit-transform: rotate(180deg); + transform: rotate(180deg); } + .accordion-button::after { + -ms-flex-negative: 0; + flex-shrink: 0; + width: 14px; + height: 14px; + margin-left: auto; + content: ""; + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23495057'%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-.708z'/%3e%3c/svg%3e"); + background-repeat: no-repeat; + background-size: 14px; + -webkit-transition: -webkit-transform 0.2s ease-in-out; + transition: -webkit-transform 0.2s ease-in-out; + transition: transform 0.2s ease-in-out; + transition: transform 0.2s ease-in-out, -webkit-transform 0.2s ease-in-out; } + @media (prefers-reduced-motion: reduce) { + .accordion-button::after { + -webkit-transition: none; + transition: none; } } + .accordion-button:hover { + z-index: 2; } + .accordion-button:focus { + z-index: 3; + border-color: #cbced1; + outline: 0; + -webkit-box-shadow: none; + box-shadow: none; } + +.accordion-header { + margin-bottom: 0; } + +.accordion-item { + background-color: transparent; + border: 1px solid #eff0f2; } + .accordion-item:first-of-type { + border-top-left-radius: 0.25rem; + border-top-right-radius: 0.25rem; } + .accordion-item:first-of-type .accordion-button { + border-top-left-radius: calc(0.25rem - 1px); + border-top-right-radius: calc(0.25rem - 1px); } + .accordion-item:not(:first-of-type) { + border-top: 0; } + .accordion-item:last-of-type { + border-bottom-right-radius: 0.25rem; + border-bottom-left-radius: 0.25rem; } + .accordion-item:last-of-type .accordion-button.collapsed { + border-bottom-right-radius: calc(0.25rem - 1px); + border-bottom-left-radius: calc(0.25rem - 1px); } + .accordion-item:last-of-type .accordion-collapse { + border-bottom-right-radius: 0.25rem; + border-bottom-left-radius: 0.25rem; } + +.accordion-body { + padding: 1rem 1.25rem; } + +.accordion-flush .accordion-collapse { + border-width: 0; } + +.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-button { + border-radius: 0; } + +.breadcrumb { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + padding: 0 0; + margin-bottom: 1rem; + list-style: none; } + +.breadcrumb-item + .breadcrumb-item { + padding-left: 0.5rem; } + .breadcrumb-item + .breadcrumb-item::before { + float: left; + padding-right: 0.5rem; + color: #74788d; + content: var(--bs-breadcrumb-divider, "󰅂") /* rtl: var(--bs-breadcrumb-divider, "󰅂") */; } + +.breadcrumb-item.active { + color: #74788d; } + +.pagination { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + padding-left: 0; + list-style: none; } + +.page-link { + position: relative; + display: block; + color: #74788d; + background-color: #fff; + border: 1px solid #e2e5e8; + -webkit-transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out; + transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out; + 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; + 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, -webkit-box-shadow 0.15s ease-in-out; } + @media (prefers-reduced-motion: reduce) { + .page-link { + -webkit-transition: none; + transition: none; } } + .page-link:hover { + z-index: 2; + color: #025d91; + background-color: #f5f6f8; + border-color: #e2e5e8; } + .page-link:focus { + z-index: 3; + color: #025d91; + background-color: #f5f6f8; + outline: 0; + -webkit-box-shadow: none; + box-shadow: none; } + +.page-item:not(:first-child) .page-link { + margin-left: -1px; } + +.page-item.active .page-link { + z-index: 3; + color: #fff; + background-color: #2e86de; + border-color: #2e86de; } + +.page-item.disabled .page-link { + color: #adb5bd; + pointer-events: none; + background-color: #f8f9fa; + border-color: #e2e5e8; } + +.page-link { + padding: 0.375rem 0.75rem; } + +.page-item:first-child .page-link { + border-top-left-radius: 0.25rem; + border-bottom-left-radius: 0.25rem; } + +.page-item:last-child .page-link { + border-top-right-radius: 0.25rem; + border-bottom-right-radius: 0.25rem; } + +.pagination-lg .page-link { + padding: 0.75rem 1.5rem; + font-size: 1.125rem; } + +.pagination-lg .page-item:first-child .page-link { + border-top-left-radius: 0.4rem; + border-bottom-left-radius: 0.4rem; } + +.pagination-lg .page-item:last-child .page-link { + border-top-right-radius: 0.4rem; + border-bottom-right-radius: 0.4rem; } + +.pagination-sm .page-link { + padding: 0.25rem 0.5rem; + font-size: 0.7875rem; } + +.pagination-sm .page-item:first-child .page-link { + border-top-left-radius: 0.2rem; + border-bottom-left-radius: 0.2rem; } + +.pagination-sm .page-item:last-child .page-link { + border-top-right-radius: 0.2rem; + border-bottom-right-radius: 0.2rem; } + +.badge { + display: inline-block; + padding: 0.25em 0.6em; + font-size: 75%; + font-weight: 500; + line-height: 1; + color: #fff; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + border-radius: 0.25rem; } + .badge:empty { + display: none; } + +.btn .badge { + position: relative; + top: -1px; } + +.alert { + position: relative; + padding: 0.75rem 1.25rem; + margin-bottom: 1rem; + border: 1px solid transparent; + border-radius: 0.25rem; } + +.alert-heading { + color: inherit; } + +.alert-link { + font-weight: 700; } + +.alert-dismissible { + padding-right: 3.75rem; } + .alert-dismissible .btn-close { + position: absolute; + top: 0; + right: 0; + z-index: 2; + padding: 0.9375rem 1.25rem; } + +.alert-primary { + color: #025584; + background-color: #cde8f8; + border-color: #b3ddf5; } + .alert-primary .alert-link { + color: #02446a; } + +.alert-secondary { + color: #464855; + background-color: #e3e4e8; + border-color: #d5d7dd; } + .alert-secondary .alert-link { + color: #383a44; } + +.alert-success { + color: #317e54; + background-color: #dcf6e8; + border-color: #cbf2dd; } + .alert-success .alert-link { + color: #276543; } + +.alert-info { + color: #397d92; + background-color: #dff6fd; + border-color: #cff1fb; } + .alert-info .alert-link { + color: #2e6475; } + +.alert-warning { + color: #947a32; + background-color: #fdf5dd; + border-color: #fdf0cb; } + .alert-warning .alert-link { + color: #766228; } + +.alert-danger { + color: #922f2f; + background-color: #fddcdc; + border-color: #fbcaca; } + .alert-danger .alert-link { + color: #752626; } + +.alert-pink { + color: #8b2554; + background-color: #fad8e8; + border-color: #f8c5dd; } + .alert-pink .alert-link { + color: #6f1e43; } + +.alert-light { + color: #939495; + background-color: #fdfdfe; + border-color: #fcfcfd; } + .alert-light .alert-link { + color: #767677; } + +.alert-dark { + color: #1f2326; + background-color: #d6d8d9; + border-color: #c2c4c6; } + .alert-dark .alert-link { + color: #191c1e; } + +.alert-purple { + color: #342c6a; + background-color: #dddbef; + border-color: #ccc9e8; } + .alert-purple .alert-link { + color: #2a2355; } + +@-webkit-keyframes progress-bar-stripes { + 0% { + background-position-x: 0.625rem; } } + +@keyframes progress-bar-stripes { + 0% { + background-position-x: 0.625rem; } } + +.progress { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + height: 0.625rem; + overflow: hidden; + font-size: 0.675rem; + background-color: #f5f6f8; + border-radius: 0.25rem; } + +.progress-bar { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; + overflow: hidden; + color: #fff; + text-align: center; + white-space: nowrap; + background-color: #2e86de; + -webkit-transition: width 0.6s ease; + transition: width 0.6s ease; } + @media (prefers-reduced-motion: reduce) { + .progress-bar { + -webkit-transition: none; + transition: none; } } + +.progress-bar-striped { + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-size: 0.625rem 0.625rem; } + +.progress-bar-animated { + -webkit-animation: 1s linear infinite progress-bar-stripes; + animation: 1s linear infinite progress-bar-stripes; } + @media (prefers-reduced-motion: reduce) { + .progress-bar-animated { + -webkit-animation: none; + animation: none; } } + +.list-group { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; + padding-left: 0; + margin-bottom: 0; + border-radius: 0.25rem; } + +.list-group-numbered { + list-style-type: none; + counter-reset: section; } + .list-group-numbered > li::before { + content: counters(section, ".") ". "; + counter-increment: section; } + +.list-group-item-action { + width: 100%; + color: #495057; + text-align: inherit; } + .list-group-item-action:hover, .list-group-item-action:focus { + z-index: 1; + color: #495057; + text-decoration: none; + background-color: #f8f9fa; } + .list-group-item-action:active { + color: #495057; + background-color: #f5f6f8; } + +.list-group-item { + position: relative; + display: block; + padding: 0.75rem 1.25rem; + color: #212529; + background-color: #fff; + border: 1px solid #eff0f2; } + .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: #74788d; + pointer-events: none; + background-color: #fff; } + .list-group-item.active { + z-index: 2; + color: #fff; + background-color: #2e86de; + border-color: #2e86de; } + .list-group-item + .list-group-item { + border-top-width: 0; } + .list-group-item + .list-group-item.active { + margin-top: -1px; + border-top-width: 1px; } + +.list-group-horizontal { + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-direction: row; + flex-direction: row; } + .list-group-horizontal > .list-group-item:first-child { + border-bottom-left-radius: 0.25rem; + border-top-right-radius: 0; } + .list-group-horizontal > .list-group-item:last-child { + border-top-right-radius: 0.25rem; + 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: 1px; + border-left-width: 0; } + .list-group-horizontal > .list-group-item + .list-group-item.active { + margin-left: -1px; + border-left-width: 1px; } + +@media (min-width: 576px) { + .list-group-horizontal-sm { + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-direction: row; + flex-direction: row; } + .list-group-horizontal-sm > .list-group-item:first-child { + border-bottom-left-radius: 0.25rem; + border-top-right-radius: 0; } + .list-group-horizontal-sm > .list-group-item:last-child { + border-top-right-radius: 0.25rem; + 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: 1px; + border-left-width: 0; } + .list-group-horizontal-sm > .list-group-item + .list-group-item.active { + margin-left: -1px; + border-left-width: 1px; } } + +@media (min-width: 768px) { + .list-group-horizontal-md { + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-direction: row; + flex-direction: row; } + .list-group-horizontal-md > .list-group-item:first-child { + border-bottom-left-radius: 0.25rem; + border-top-right-radius: 0; } + .list-group-horizontal-md > .list-group-item:last-child { + border-top-right-radius: 0.25rem; + 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: 1px; + border-left-width: 0; } + .list-group-horizontal-md > .list-group-item + .list-group-item.active { + margin-left: -1px; + border-left-width: 1px; } } + +@media (min-width: 992px) { + .list-group-horizontal-lg { + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-direction: row; + flex-direction: row; } + .list-group-horizontal-lg > .list-group-item:first-child { + border-bottom-left-radius: 0.25rem; + border-top-right-radius: 0; } + .list-group-horizontal-lg > .list-group-item:last-child { + border-top-right-radius: 0.25rem; + 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: 1px; + border-left-width: 0; } + .list-group-horizontal-lg > .list-group-item + .list-group-item.active { + margin-left: -1px; + border-left-width: 1px; } } + +@media (min-width: 1200px) { + .list-group-horizontal-xl { + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-direction: row; + flex-direction: row; } + .list-group-horizontal-xl > .list-group-item:first-child { + border-bottom-left-radius: 0.25rem; + border-top-right-radius: 0; } + .list-group-horizontal-xl > .list-group-item:last-child { + border-top-right-radius: 0.25rem; + 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: 1px; + border-left-width: 0; } + .list-group-horizontal-xl > .list-group-item + .list-group-item.active { + margin-left: -1px; + border-left-width: 1px; } } + +@media (min-width: 1400px) { + .list-group-horizontal-xxl { + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-direction: row; + flex-direction: row; } + .list-group-horizontal-xxl > .list-group-item:first-child { + border-bottom-left-radius: 0.25rem; + border-top-right-radius: 0; } + .list-group-horizontal-xxl > .list-group-item:last-child { + border-top-right-radius: 0.25rem; + 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: 1px; + border-left-width: 0; } + .list-group-horizontal-xxl > .list-group-item + .list-group-item.active { + margin-left: -1px; + border-left-width: 1px; } } + +.list-group-flush { + border-radius: 0; } + .list-group-flush > .list-group-item { + border-width: 0 0 1px; } + .list-group-flush > .list-group-item:last-child { + border-bottom-width: 0; } + +.list-group-item-primary { + color: #025584; + background-color: #cde8f8; } + .list-group-item-primary.list-group-item-action:hover, .list-group-item-primary.list-group-item-action:focus { + color: #025584; + background-color: #b9d1df; } + .list-group-item-primary.list-group-item-action.active { + color: #fff; + background-color: #025584; + border-color: #025584; } + +.list-group-item-secondary { + color: #464855; + background-color: #e3e4e8; } + .list-group-item-secondary.list-group-item-action:hover, .list-group-item-secondary.list-group-item-action:focus { + color: #464855; + background-color: #cccdd1; } + .list-group-item-secondary.list-group-item-action.active { + color: #fff; + background-color: #464855; + border-color: #464855; } + +.list-group-item-success { + color: #317e54; + background-color: #dcf6e8; } + .list-group-item-success.list-group-item-action:hover, .list-group-item-success.list-group-item-action:focus { + color: #317e54; + background-color: #c6ddd1; } + .list-group-item-success.list-group-item-action.active { + color: #fff; + background-color: #317e54; + border-color: #317e54; } + +.list-group-item-info { + color: #397d92; + background-color: #dff6fd; } + .list-group-item-info.list-group-item-action:hover, .list-group-item-info.list-group-item-action:focus { + color: #397d92; + background-color: #c9dde4; } + .list-group-item-info.list-group-item-action.active { + color: #fff; + background-color: #397d92; + border-color: #397d92; } + +.list-group-item-warning { + color: #947a32; + background-color: #fdf5dd; } + .list-group-item-warning.list-group-item-action:hover, .list-group-item-warning.list-group-item-action:focus { + color: #947a32; + background-color: #e4ddc7; } + .list-group-item-warning.list-group-item-action.active { + color: #fff; + background-color: #947a32; + border-color: #947a32; } + +.list-group-item-danger { + color: #922f2f; + background-color: #fddcdc; } + .list-group-item-danger.list-group-item-action:hover, .list-group-item-danger.list-group-item-action:focus { + color: #922f2f; + background-color: #e4c6c6; } + .list-group-item-danger.list-group-item-action.active { + color: #fff; + background-color: #922f2f; + border-color: #922f2f; } + +.list-group-item-pink { + color: #8b2554; + background-color: #fad8e8; } + .list-group-item-pink.list-group-item-action:hover, .list-group-item-pink.list-group-item-action:focus { + color: #8b2554; + background-color: #e1c2d1; } + .list-group-item-pink.list-group-item-action.active { + color: #fff; + background-color: #8b2554; + border-color: #8b2554; } + +.list-group-item-light { + color: #939495; + background-color: #fdfdfe; } + .list-group-item-light.list-group-item-action:hover, .list-group-item-light.list-group-item-action:focus { + color: #939495; + background-color: #e4e4e5; } + .list-group-item-light.list-group-item-action.active { + color: #fff; + background-color: #939495; + border-color: #939495; } + +.list-group-item-dark { + color: #1f2326; + background-color: #d6d8d9; } + .list-group-item-dark.list-group-item-action:hover, .list-group-item-dark.list-group-item-action:focus { + color: #1f2326; + background-color: #c1c2c3; } + .list-group-item-dark.list-group-item-action.active { + color: #fff; + background-color: #1f2326; + border-color: #1f2326; } + +.list-group-item-purple { + color: #342c6a; + background-color: #dddbef; } + .list-group-item-purple.list-group-item-action:hover, .list-group-item-purple.list-group-item-action:focus { + color: #342c6a; + background-color: #c7c5d7; } + .list-group-item-purple.list-group-item-action.active { + color: #fff; + background-color: #342c6a; + border-color: #342c6a; } + +.btn-close { + -webkit-box-sizing: content-box; + box-sizing: content-box; + width: 1em; + height: 1em; + padding: 0.25em 0.25em; + color: #000; + background: transparent 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 011.414 0L8 6.586 14.293.293a1 1 0 111.414 1.414L9.414 8l6.293 6.293a1 1 0 01-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 01-1.414-1.414L6.586 8 .293 1.707a1 1 0 010-1.414z'/%3e%3c/svg%3e") center/1em auto no-repeat; + border: 0; + border-radius: 0.25rem; + opacity: 0.5; } + .btn-close:hover { + color: #000; + text-decoration: none; + opacity: 0.75; } + .btn-close:focus { + outline: 0; + -webkit-box-shadow: none; + box-shadow: none; + opacity: 1; } + .btn-close:disabled, .btn-close.disabled { + pointer-events: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + opacity: 0.25; } + +.btn-close-white { + -webkit-filter: invert(1) grayscale(100%) brightness(200%); + filter: invert(1) grayscale(100%) brightness(200%); } + +.toast { + width: 350px; + max-width: 100%; + font-size: 0.875rem; + pointer-events: auto; + background-color: rgba(255, 255, 255, 0.85); + background-clip: padding-box; + border: 1px solid rgba(0, 0, 0, 0.1); + -webkit-box-shadow: 0 2px 3px rgba(52, 58, 64, 0.06); + box-shadow: 0 2px 3px rgba(52, 58, 64, 0.06); + border-radius: 0.25rem; } + .toast.showing { + opacity: 0; } + .toast:not(.show) { + display: none; } + +.toast-container { + width: -webkit-max-content; + width: -moz-max-content; + width: max-content; + max-width: 100%; + pointer-events: none; } + .toast-container > :not(:last-child) { + margin-bottom: 10px; } + +.toast-header { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + padding: 0.5rem 0.75rem; + color: #74788d; + background-color: rgba(255, 255, 255, 0.85); + background-clip: padding-box; + border-bottom: 1px solid rgba(0, 0, 0, 0.05); + border-top-left-radius: calc(0.25rem - 1px); + border-top-right-radius: calc(0.25rem - 1px); } + .toast-header .btn-close { + margin-right: -0.375rem; + margin-left: 0.75rem; } + +.toast-body { + padding: 0.75rem; + word-wrap: break-word; } + +.modal { + position: fixed; + top: 0; + left: 0; + z-index: 1055; + display: none; + width: 100%; + height: 100%; + overflow-x: hidden; + overflow-y: auto; + outline: 0; } + +.modal-dialog { + position: relative; + width: auto; + margin: 0.5rem; + pointer-events: none; } + .modal.fade .modal-dialog { + -webkit-transition: -webkit-transform 0.3s ease-out; + transition: -webkit-transform 0.3s ease-out; + transition: transform 0.3s ease-out; + transition: transform 0.3s ease-out, -webkit-transform 0.3s ease-out; + -webkit-transform: translate(0, -50px); + transform: translate(0, -50px); } + @media (prefers-reduced-motion: reduce) { + .modal.fade .modal-dialog { + -webkit-transition: none; + transition: none; } } + .modal.show .modal-dialog { + -webkit-transform: none; + transform: none; } + .modal.modal-static .modal-dialog { + -webkit-transform: scale(1.02); + transform: scale(1.02); } + +.modal-dialog-scrollable { + height: calc(100% - 1rem); } + .modal-dialog-scrollable .modal-content { + max-height: 100%; + overflow: hidden; } + .modal-dialog-scrollable .modal-body { + overflow-y: auto; } + +.modal-dialog-centered { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + min-height: calc(100% - 1rem); } + +.modal-content { + position: relative; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; + width: 100%; + pointer-events: auto; + background-color: #fff; + background-clip: padding-box; + border: 1px solid #eff0f2; + border-radius: 0.4rem; + outline: 0; } + +.modal-backdrop { + position: fixed; + top: 0; + left: 0; + z-index: 1050; + width: 100vw; + height: 100vh; + background-color: #000; } + .modal-backdrop.fade { + opacity: 0; } + .modal-backdrop.show { + opacity: 0.5; } + +.modal-header { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -ms-flex-negative: 0; + flex-shrink: 0; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: justify; + -ms-flex-pack: justify; + justify-content: space-between; + padding: 1rem 1rem; + border-bottom: 1px solid #eff0f2; + border-top-left-radius: calc(0.4rem - 1px); + border-top-right-radius: calc(0.4rem - 1px); } + .modal-header .btn-close { + padding: 0.5rem 0.5rem; + margin: -0.5rem -0.5rem -0.5rem auto; } + +.modal-title { + margin-bottom: 0; + line-height: 1.5; } + +.modal-body { + position: relative; + -webkit-box-flex: 1; + -ms-flex: 1 1 auto; + flex: 1 1 auto; + padding: 1rem; } + +.modal-footer { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + -ms-flex-negative: 0; + flex-shrink: 0; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: end; + -ms-flex-pack: end; + justify-content: flex-end; + padding: 0.75rem; + border-top: 1px solid #eff0f2; + border-bottom-right-radius: calc(0.4rem - 1px); + border-bottom-left-radius: calc(0.4rem - 1px); } + .modal-footer > * { + margin: 0.25rem; } + +@media (min-width: 576px) { + .modal-dialog { + max-width: 500px; + margin: 1.75rem auto; } + .modal-dialog-scrollable { + height: calc(100% - 3.5rem); } + .modal-dialog-centered { + min-height: calc(100% - 3.5rem); } + .modal-sm { + max-width: 300px; } } + +@media (min-width: 992px) { + .modal-lg, + .modal-xl { + max-width: 800px; } } + +@media (min-width: 1200px) { + .modal-xl { + max-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-header { + border-radius: 0; } + .modal-fullscreen .modal-body { + overflow-y: auto; } + .modal-fullscreen .modal-footer { + border-radius: 0; } + +@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-header { + border-radius: 0; } + .modal-fullscreen-sm-down .modal-body { + overflow-y: auto; } + .modal-fullscreen-sm-down .modal-footer { + border-radius: 0; } } + +@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-header { + border-radius: 0; } + .modal-fullscreen-md-down .modal-body { + overflow-y: auto; } + .modal-fullscreen-md-down .modal-footer { + border-radius: 0; } } + +@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-header { + border-radius: 0; } + .modal-fullscreen-lg-down .modal-body { + overflow-y: auto; } + .modal-fullscreen-lg-down .modal-footer { + border-radius: 0; } } + +@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-header { + border-radius: 0; } + .modal-fullscreen-xl-down .modal-body { + overflow-y: auto; } + .modal-fullscreen-xl-down .modal-footer { + border-radius: 0; } } + +@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-header { + border-radius: 0; } + .modal-fullscreen-xxl-down .modal-body { + overflow-y: auto; } + .modal-fullscreen-xxl-down .modal-footer { + border-radius: 0; } } + +.tooltip { + position: absolute; + z-index: 999; + display: block; + margin: 0; + font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + font-style: normal; + font-weight: 400; + line-height: 1.5; + text-align: left; + text-align: start; + text-decoration: none; + text-shadow: none; + text-transform: none; + letter-spacing: normal; + word-break: normal; + word-spacing: normal; + white-space: normal; + line-break: auto; + font-size: 0.7875rem; + word-wrap: break-word; + opacity: 0; } + .tooltip.show { + opacity: 0.9; } + .tooltip .tooltip-arrow { + position: absolute; + display: block; + width: 0.8rem; + height: 0.4rem; } + .tooltip .tooltip-arrow::before { + position: absolute; + content: ""; + border-color: transparent; + border-style: solid; } + +.bs-tooltip-top, .bs-tooltip-auto[data-popper-placement^="top"] { + padding: 0.4rem 0; } + .bs-tooltip-top .tooltip-arrow, .bs-tooltip-auto[data-popper-placement^="top"] .tooltip-arrow { + bottom: 0; } + .bs-tooltip-top .tooltip-arrow::before, .bs-tooltip-auto[data-popper-placement^="top"] .tooltip-arrow::before { + top: -1px; + border-width: 0.4rem 0.4rem 0; + border-top-color: #000; } + +.bs-tooltip-end, .bs-tooltip-auto[data-popper-placement^="right"] { + padding: 0 0.4rem; } + .bs-tooltip-end .tooltip-arrow, .bs-tooltip-auto[data-popper-placement^="right"] .tooltip-arrow { + left: 0; + width: 0.4rem; + height: 0.8rem; } + .bs-tooltip-end .tooltip-arrow::before, .bs-tooltip-auto[data-popper-placement^="right"] .tooltip-arrow::before { + right: -1px; + border-width: 0.4rem 0.4rem 0.4rem 0; + border-right-color: #000; } + +.bs-tooltip-bottom, .bs-tooltip-auto[data-popper-placement^="bottom"] { + padding: 0.4rem 0; } + .bs-tooltip-bottom .tooltip-arrow, .bs-tooltip-auto[data-popper-placement^="bottom"] .tooltip-arrow { + top: 0; } + .bs-tooltip-bottom .tooltip-arrow::before, .bs-tooltip-auto[data-popper-placement^="bottom"] .tooltip-arrow::before { + bottom: -1px; + border-width: 0 0.4rem 0.4rem; + border-bottom-color: #000; } + +.bs-tooltip-start, .bs-tooltip-auto[data-popper-placement^="left"] { + padding: 0 0.4rem; } + .bs-tooltip-start .tooltip-arrow, .bs-tooltip-auto[data-popper-placement^="left"] .tooltip-arrow { + right: 0; + width: 0.4rem; + height: 0.8rem; } + .bs-tooltip-start .tooltip-arrow::before, .bs-tooltip-auto[data-popper-placement^="left"] .tooltip-arrow::before { + left: -1px; + border-width: 0.4rem 0 0.4rem 0.4rem; + border-left-color: #000; } + +.tooltip-inner { + max-width: 200px; + padding: 0.4rem 0.7rem; + color: #fff; + text-align: center; + background-color: #000; + border-radius: 0.25rem; } + +.popover { + position: absolute; + top: 0; + left: 0 /* rtl:ignore */; + z-index: 999; + display: block; + max-width: 276px; + font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + font-style: normal; + font-weight: 400; + line-height: 1.5; + text-align: left; + text-align: start; + text-decoration: none; + text-shadow: none; + text-transform: none; + letter-spacing: normal; + word-break: normal; + word-spacing: normal; + white-space: normal; + line-break: auto; + font-size: 0.7875rem; + word-wrap: break-word; + background-color: #fff; + background-clip: padding-box; + border: 1px solid #e2e5e8; + border-radius: 0.4rem; } + .popover .popover-arrow { + position: absolute; + display: block; + width: 1rem; + height: 0.5rem; } + .popover .popover-arrow::before, .popover .popover-arrow::after { + position: absolute; + display: block; + content: ""; + border-color: transparent; + border-style: solid; } + +.bs-popover-top > .popover-arrow, .bs-popover-auto[data-popper-placement^="top"] > .popover-arrow { + bottom: calc(-0.5rem - 1px); } + .bs-popover-top > .popover-arrow::before, .bs-popover-auto[data-popper-placement^="top"] > .popover-arrow::before { + bottom: 0; + border-width: 0.5rem 0.5rem 0; + border-top-color: #e2e5e8; } + .bs-popover-top > .popover-arrow::after, .bs-popover-auto[data-popper-placement^="top"] > .popover-arrow::after { + bottom: 1px; + border-width: 0.5rem 0.5rem 0; + border-top-color: #fff; } + +.bs-popover-end > .popover-arrow, .bs-popover-auto[data-popper-placement^="right"] > .popover-arrow { + left: calc(-0.5rem - 1px); + width: 0.5rem; + height: 1rem; } + .bs-popover-end > .popover-arrow::before, .bs-popover-auto[data-popper-placement^="right"] > .popover-arrow::before { + left: 0; + border-width: 0.5rem 0.5rem 0.5rem 0; + border-right-color: #e2e5e8; } + .bs-popover-end > .popover-arrow::after, .bs-popover-auto[data-popper-placement^="right"] > .popover-arrow::after { + left: 1px; + border-width: 0.5rem 0.5rem 0.5rem 0; + border-right-color: #fff; } + +.bs-popover-bottom > .popover-arrow, .bs-popover-auto[data-popper-placement^="bottom"] > .popover-arrow { + top: calc(-0.5rem - 1px); } + .bs-popover-bottom > .popover-arrow::before, .bs-popover-auto[data-popper-placement^="bottom"] > .popover-arrow::before { + top: 0; + border-width: 0 0.5rem 0.5rem 0.5rem; + border-bottom-color: #e2e5e8; } + .bs-popover-bottom > .popover-arrow::after, .bs-popover-auto[data-popper-placement^="bottom"] > .popover-arrow::after { + top: 1px; + border-width: 0 0.5rem 0.5rem 0.5rem; + border-bottom-color: #fff; } + +.bs-popover-bottom .popover-header::before, .bs-popover-auto[data-popper-placement^="bottom"] .popover-header::before { + position: absolute; + top: 0; + left: 50%; + display: block; + width: 1rem; + margin-left: -0.5rem; + content: ""; + border-bottom: 1px solid #f0f0f0; } + +.bs-popover-start > .popover-arrow, .bs-popover-auto[data-popper-placement^="left"] > .popover-arrow { + right: calc(-0.5rem - 1px); + width: 0.5rem; + height: 1rem; } + .bs-popover-start > .popover-arrow::before, .bs-popover-auto[data-popper-placement^="left"] > .popover-arrow::before { + right: 0; + border-width: 0.5rem 0 0.5rem 0.5rem; + border-left-color: #e2e5e8; } + .bs-popover-start > .popover-arrow::after, .bs-popover-auto[data-popper-placement^="left"] > .popover-arrow::after { + right: 1px; + border-width: 0.5rem 0 0.5rem 0.5rem; + border-left-color: #fff; } + +.popover-header { + padding: 0.5rem 0.75rem; + margin-bottom: 0; + font-size: 0.9rem; + background-color: #f0f0f0; + border-bottom: 1px solid #e2e5e8; + border-top-left-radius: calc(0.4rem - 1px); + border-top-right-radius: calc(0.4rem - 1px); } + .popover-header:empty { + display: none; } + +.popover-body { + padding: 0.5rem 0.75rem; + color: #495057; } + +.carousel { + position: relative; } + +.carousel.pointer-event { + -ms-touch-action: pan-y; + 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; + -webkit-transition: -webkit-transform 0.6s ease-in-out; + transition: -webkit-transform 0.6s ease-in-out; + transition: transform 0.6s ease-in-out; + transition: transform 0.6s ease-in-out, -webkit-transform 0.6s ease-in-out; } + @media (prefers-reduced-motion: reduce) { + .carousel-item { + -webkit-transition: none; + transition: none; } } + +.carousel-item.active, +.carousel-item-next, +.carousel-item-prev { + display: block; } + +/* rtl:begin:ignore */ +.carousel-item-next:not(.carousel-item-start), +.active.carousel-item-end { + -webkit-transform: translateX(100%); + transform: translateX(100%); } + +.carousel-item-prev:not(.carousel-item-end), +.active.carousel-item-start { + -webkit-transform: translateX(-100%); + transform: translateX(-100%); } + +/* rtl:end:ignore */ +.carousel-fade .carousel-item { + opacity: 0; + -webkit-transition-property: opacity; + transition-property: opacity; + -webkit-transform: none; + transform: none; } + +.carousel-fade .carousel-item.active, +.carousel-fade .carousel-item-next.carousel-item-start, +.carousel-fade .carousel-item-prev.carousel-item-end { + z-index: 1; + opacity: 1; } + +.carousel-fade .active.carousel-item-start, +.carousel-fade .active.carousel-item-end { + z-index: 0; + opacity: 0; + -webkit-transition: opacity 0s 0.6s; + transition: opacity 0s 0.6s; } + @media (prefers-reduced-motion: reduce) { + .carousel-fade .active.carousel-item-start, + .carousel-fade .active.carousel-item-end { + -webkit-transition: none; + transition: none; } } + +.carousel-control-prev, +.carousel-control-next { + position: absolute; + top: 0; + bottom: 0; + z-index: 1; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; + width: 15%; + padding: 0; + color: #fff; + text-align: center; + background: none; + border: 0; + opacity: 0.5; + -webkit-transition: opacity 0.15s ease; + transition: opacity 0.15s ease; } + @media (prefers-reduced-motion: reduce) { + .carousel-control-prev, + .carousel-control-next { + -webkit-transition: none; + transition: none; } } + .carousel-control-prev:hover, .carousel-control-prev:focus, + .carousel-control-next:hover, + .carousel-control-next:focus { + color: #fff; + text-decoration: none; + outline: 0; + opacity: 0.9; } + +.carousel-control-prev { + left: 0; } + +.carousel-control-next { + right: 0; } + +.carousel-control-prev-icon, +.carousel-control-next-icon { + display: inline-block; + width: 1rem; + height: 1rem; + background-repeat: no-repeat; + background-position: 50%; + background-size: 100% 100%; } + +/* rtl:options: { + "autoRename": true, + "stringMap":[ { + "name" : "prev-next", + "search" : "prev", + "replace" : "next" + } ] +} */ +.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 0z'/%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-.708z'/%3e%3c/svg%3e"); } + +.carousel-indicators { + position: absolute; + right: 0; + bottom: 0; + left: 0; + z-index: 2; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; + padding: 0; + margin-right: 15%; + margin-bottom: 1rem; + margin-left: 15%; + list-style: none; } + .carousel-indicators [data-bs-target] { + -webkit-box-sizing: content-box; + box-sizing: content-box; + -webkit-box-flex: 0; + -ms-flex: 0 1 auto; + flex: 0 1 auto; + width: 30px; + height: 3px; + padding: 0; + margin-right: 3px; + margin-left: 3px; + text-indent: -999px; + cursor: pointer; + background-color: #fff; + background-clip: padding-box; + border: 0; + border-top: 10px solid transparent; + border-bottom: 10px solid transparent; + opacity: 0.5; + -webkit-transition: opacity 0.6s ease; + transition: opacity 0.6s ease; } + @media (prefers-reduced-motion: reduce) { + .carousel-indicators [data-bs-target] { + -webkit-transition: none; + 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: #fff; + text-align: center; } + +.carousel-dark .carousel-control-prev-icon, +.carousel-dark .carousel-control-next-icon { + -webkit-filter: invert(1) grayscale(100); + filter: invert(1) grayscale(100); } + +.carousel-dark .carousel-indicators [data-bs-target] { + background-color: #000; } + +.carousel-dark .carousel-caption { + color: #000; } + +@-webkit-keyframes spinner-border { + to { + -webkit-transform: rotate(360deg) /* rtl:ignore */; + transform: rotate(360deg) /* rtl:ignore */; } } + +@keyframes spinner-border { + to { + -webkit-transform: rotate(360deg) /* rtl:ignore */; + transform: rotate(360deg) /* rtl:ignore */; } } + +.spinner-border { + display: inline-block; + width: 2rem; + height: 2rem; + vertical-align: -0.125em; + border: 0.25em solid currentColor; + border-right-color: transparent; + border-radius: 50%; + -webkit-animation: 0.75s linear infinite spinner-border; + animation: 0.75s linear infinite spinner-border; } + +.spinner-border-sm { + width: 1rem; + height: 1rem; + border-width: 0.2em; } + +@-webkit-keyframes spinner-grow { + 0% { + -webkit-transform: scale(0); + transform: scale(0); } + 50% { + opacity: 1; + -webkit-transform: none; + transform: none; } } + +@keyframes spinner-grow { + 0% { + -webkit-transform: scale(0); + transform: scale(0); } + 50% { + opacity: 1; + -webkit-transform: none; + transform: none; } } + +.spinner-grow { + display: inline-block; + width: 2rem; + height: 2rem; + vertical-align: -0.125em; + background-color: currentColor; + border-radius: 50%; + opacity: 0; + -webkit-animation: 0.75s linear infinite spinner-grow; + animation: 0.75s linear infinite spinner-grow; } + +.spinner-grow-sm { + width: 1rem; + height: 1rem; } + +@media (prefers-reduced-motion: reduce) { + .spinner-border, + .spinner-grow { + -webkit-animation-duration: 1.5s; + animation-duration: 1.5s; } } + +.offcanvas { + position: fixed; + bottom: 0; + z-index: 1045; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; + max-width: 100%; + visibility: hidden; + background-color: #fff; + background-clip: padding-box; + outline: 0; + -webkit-transition: -webkit-transform 0.3s ease-in-out; + transition: -webkit-transform 0.3s ease-in-out; + transition: transform 0.3s ease-in-out; + transition: transform 0.3s ease-in-out, -webkit-transform 0.3s ease-in-out; } + @media (prefers-reduced-motion: reduce) { + .offcanvas { + -webkit-transition: none; + transition: none; } } + +.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: 0.5; } + +.offcanvas-header { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: justify; + -ms-flex-pack: justify; + justify-content: space-between; + padding: 1rem 1rem; } + .offcanvas-header .btn-close { + padding: 0.5rem 0.5rem; + margin-top: -0.5rem; + margin-right: -0.5rem; + margin-bottom: -0.5rem; } + +.offcanvas-title { + margin-bottom: 0; + line-height: 1.5; } + +.offcanvas-body { + -webkit-box-flex: 1; + -ms-flex-positive: 1; + flex-grow: 1; + padding: 1rem 1rem; + overflow-y: auto; } + +.offcanvas-start { + top: 0; + left: 0; + width: 400px; + border-right: 1px solid #eff0f2; + -webkit-transform: translateX(-100%); + transform: translateX(-100%); } + +.offcanvas-end { + top: 0; + right: 0; + width: 400px; + border-left: 1px solid #eff0f2; + -webkit-transform: translateX(100%); + transform: translateX(100%); } + +.offcanvas-top { + top: 0; + right: 0; + left: 0; + height: 30vh; + max-height: 100%; + border-bottom: 1px solid #eff0f2; + -webkit-transform: translateY(-100%); + transform: translateY(-100%); } + +.offcanvas-bottom { + right: 0; + left: 0; + height: 30vh; + max-height: 100%; + border-top: 1px solid #eff0f2; + -webkit-transform: translateY(100%); + transform: translateY(100%); +} + +.fullPageH { + height: 100% !important; +} + +.fullPageW { + width: 100% !important; +} +.offcanvas.show { + -webkit-transform: none; + transform: none; } + +.placeholder { + display: inline-block; + min-height: 1em; + vertical-align: middle; + cursor: wait; + background-color: currentColor; + opacity: 0.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 { + -webkit-animation: placeholder-glow 2s ease-in-out infinite; + animation: placeholder-glow 2s ease-in-out infinite; } + +@-webkit-keyframes placeholder-glow { + 50% { + opacity: 0.2; } } + +@keyframes placeholder-glow { + 50% { + opacity: 0.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%; + -webkit-animation: placeholder-wave 2s linear infinite; + animation: placeholder-wave 2s linear infinite; } + +@-webkit-keyframes placeholder-wave { + 100% { + -webkit-mask-position: -200% 0%; + mask-position: -200% 0%; } } + +@keyframes placeholder-wave { + 100% { + -webkit-mask-position: -200% 0%; + mask-position: -200% 0%; } } + +.clearfix::after { + display: block; + clear: both; + content: ""; } + +.link-primary { + color: #2e86de; } + .link-primary:hover, .link-primary:focus { + color: #0272b0; } + +.link-secondary { + color: #74788d; } + .link-secondary:hover, .link-secondary:focus { + color: #5d6071; } + +.link-success { + color: #51d28c; } + .link-success:hover, .link-success:focus { + color: #41a870; } + +.link-info { + color: #5fd0f3; } + .link-info:hover, .link-info:focus { + color: #4ca6c2; } + +.link-warning { + color: #f7cc53; } + .link-warning:hover, .link-warning:focus { + color: #f9d675; } + +.link-danger { + color: #f34e4e; } + .link-danger:hover, .link-danger:focus { + color: #c23e3e; } + +.link-pink { + color: #e83e8c; } + .link-pink:hover, .link-pink:focus { + color: #ba3270; } + +.link-light { + color: #f5f6f8; } + .link-light:hover, .link-light:focus { + color: #f7f8f9; } + +.link-dark { + color: #343a40; } + .link-dark:hover, .link-dark:focus { + color: #2a2e33; } + +.link-purple { + color: #564ab1; } + .link-purple:hover, .link-purple:focus { + color: #453b8e; } + +.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: calc(3 / 4 * 100%); } + +.ratio-16x9 { + --bs-aspect-ratio: calc(9 / 16 * 100%); } + +.ratio-21x9 { + --bs-aspect-ratio: calc(9 / 21 * 100%); } + +.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: sticky; + top: 0; + z-index: 1020; } + +@media (min-width: 576px) { + .sticky-sm-top { + position: sticky; + top: 0; + z-index: 1020; } } + +@media (min-width: 768px) { + .sticky-md-top { + position: sticky; + top: 0; + z-index: 1020; } } + +@media (min-width: 992px) { + .sticky-lg-top { + position: sticky; + top: 0; + z-index: 1020; } } + +@media (min-width: 1200px) { + .sticky-xl-top { + position: sticky; + top: 0; + z-index: 1020; } } + +@media (min-width: 1400px) { + .sticky-xxl-top { + position: sticky; + top: 0; + z-index: 1020; } } + +.hstack { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-direction: row; + flex-direction: row; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -ms-flex-item-align: stretch; + align-self: stretch; } + +.vstack { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-flex: 1; + -ms-flex: 1 1 auto; + flex: 1 1 auto; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; + -ms-flex-item-align: stretch; + align-self: stretch; } + +.visually-hidden, +.visually-hidden-focusable:not(:focus):not(:focus-within) { + position: absolute !important; + 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; } + +.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; + -ms-flex-item-align: stretch; + align-self: stretch; + width: 1px; + min-height: 1em; + background-color: currentColor; + opacity: 0.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; } + +.opacity-0 { + opacity: 0 !important; } + +.opacity-25 { + opacity: 0.25 !important; } + +.opacity-50 { + opacity: 0.5 !important; } + +.opacity-75 { + opacity: 0.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; } + +.d-inline { + display: inline !important; } + +.d-inline-block { + display: inline-block !important; } + +.d-block { + display: block !important; } + +.d-grid { + display: grid !important; } + +.d-table { + display: table !important; } + +.d-table-row { + display: table-row !important; } + +.d-table-cell { + display: table-cell !important; } + +.d-flex { + display: -webkit-box !important; + display: -ms-flexbox !important; + display: flex !important; } + +.d-inline-flex { + display: -webkit-inline-box !important; + display: -ms-inline-flexbox !important; + display: inline-flex !important; } + +.d-none { + display: none !important; } + +.shadow { + -webkit-box-shadow: 0 2px 3px rgba(52, 58, 64, 0.06) !important; + box-shadow: 0 2px 3px rgba(52, 58, 64, 0.06) !important; } + +.shadow-sm { + -webkit-box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075) !important; + box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075) !important; } + +.shadow-lg { + -webkit-box-shadow: 0 5px 6px rgba(52, 58, 64, 0.1) !important; + box-shadow: 0 5px 6px rgba(52, 58, 64, 0.1) !important; } + +.shadow-none { + -webkit-box-shadow: none !important; + box-shadow: none !important; } + +.position-static { + position: static !important; } + +.position-relative { + position: relative !important; } + +.position-absolute { + position: absolute !important; } + +.position-fixed { + position: fixed !important; } + +.position-sticky { + 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 { + -webkit-transform: translate(-50%, -50%) !important; + transform: translate(-50%, -50%) !important; } + +.translate-middle-x { + -webkit-transform: translateX(-50%) !important; + transform: translateX(-50%) !important; } + +.translate-middle-y { + -webkit-transform: translateY(-50%) !important; + transform: translateY(-50%) !important; } + +.border { + border: 1px solid #eff0f2 !important; } + +.border-0 { + border: 0 !important; } + +.border-top { + border-top: 1px solid #eff0f2 !important; } + +.border-top-0 { + border-top: 0 !important; } + +.border-end { + border-right: 1px solid #eff0f2 !important; } + +.border-end-0 { + border-right: 0 !important; } + +.border-bottom { + border-bottom: 1px solid #eff0f2 !important; } + +.border-bottom-0 { + border-bottom: 0 !important; } + +.border-start { + border-left: 1px solid #eff0f2 !important; } + +.border-start-0 { + border-left: 0 !important; } + +.border-primary { + border-color: #2e86de !important; } + +.border-secondary { + border-color: #74788d !important; } + +.border-success { + border-color: #51d28c !important; } + +.border-info { + border-color: #5fd0f3 !important; } + +.border-warning { + border-color: #f7cc53 !important; } + +.border-danger { + border-color: #f34e4e !important; } + +.border-pink { + border-color: #e83e8c !important; } + +.border-light { + border-color: #f5f6f8 !important; } + +.border-dark { + border-color: #343a40 !important; } + +.border-purple { + border-color: #564ab1 !important; } + +.border-white { + border-color: #fff !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; } + +.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 { + -webkit-box-flex: 1 !important; + -ms-flex: 1 1 auto !important; + flex: 1 1 auto !important; } + +.flex-row { + -webkit-box-orient: horizontal !important; + -webkit-box-direction: normal !important; + -ms-flex-direction: row !important; + flex-direction: row !important; } + +.flex-column { + -webkit-box-orient: vertical !important; + -webkit-box-direction: normal !important; + -ms-flex-direction: column !important; + flex-direction: column !important; } + +.flex-row-reverse { + -webkit-box-orient: horizontal !important; + -webkit-box-direction: reverse !important; + -ms-flex-direction: row-reverse !important; + flex-direction: row-reverse !important; } + +.flex-column-reverse { + -webkit-box-orient: vertical !important; + -webkit-box-direction: reverse !important; + -ms-flex-direction: column-reverse !important; + flex-direction: column-reverse !important; } + +.flex-grow-0 { + -webkit-box-flex: 0 !important; + -ms-flex-positive: 0 !important; + flex-grow: 0 !important; } + +.flex-grow-1 { + -webkit-box-flex: 1 !important; + -ms-flex-positive: 1 !important; + flex-grow: 1 !important; } + +.flex-shrink-0 { + -ms-flex-negative: 0 !important; + flex-shrink: 0 !important; } + +.flex-shrink-1 { + -ms-flex-negative: 1 !important; + flex-shrink: 1 !important; } + +.flex-wrap { + -ms-flex-wrap: wrap !important; + flex-wrap: wrap !important; } + +.flex-nowrap { + -ms-flex-wrap: nowrap !important; + flex-wrap: nowrap !important; } + +.flex-wrap-reverse { + -ms-flex-wrap: wrap-reverse !important; + flex-wrap: wrap-reverse !important; } + +.gap-0 { + gap: 0 !important; } + +.gap-1 { + gap: 0.25rem !important; } + +.gap-2 { + gap: 0.5rem !important; } + +.gap-3 { + gap: 1rem !important; } + +.gap-4 { + gap: 1.5rem !important; } + +.gap-5 { + gap: 3rem !important; } + +.justify-content-start { + -webkit-box-pack: start !important; + -ms-flex-pack: start !important; + justify-content: flex-start !important; } + +.justify-content-end { + -webkit-box-pack: end !important; + -ms-flex-pack: end !important; + justify-content: flex-end !important; } + +.justify-content-center { + -webkit-box-pack: center !important; + -ms-flex-pack: center !important; + justify-content: center !important; } + +.justify-content-between { + -webkit-box-pack: justify !important; + -ms-flex-pack: justify !important; + justify-content: space-between !important; } + +.justify-content-around { + -ms-flex-pack: distribute !important; + justify-content: space-around !important; } + +.justify-content-evenly { + -webkit-box-pack: space-evenly !important; + -ms-flex-pack: space-evenly !important; + justify-content: space-evenly !important; } + +.align-items-start { + -webkit-box-align: start !important; + -ms-flex-align: start !important; + align-items: flex-start !important; } + +.align-items-end { + -webkit-box-align: end !important; + -ms-flex-align: end !important; + align-items: flex-end !important; } + +.align-items-center { + -webkit-box-align: center !important; + -ms-flex-align: center !important; + align-items: center !important; } + +.align-items-baseline { + -webkit-box-align: baseline !important; + -ms-flex-align: baseline !important; + align-items: baseline !important; } + +.align-items-stretch { + -webkit-box-align: stretch !important; + -ms-flex-align: stretch !important; + align-items: stretch !important; } + +.align-content-start { + -ms-flex-line-pack: start !important; + align-content: flex-start !important; } + +.align-content-end { + -ms-flex-line-pack: end !important; + align-content: flex-end !important; } + +.align-content-center { + -ms-flex-line-pack: center !important; + align-content: center !important; } + +.align-content-between { + -ms-flex-line-pack: justify !important; + align-content: space-between !important; } + +.align-content-around { + -ms-flex-line-pack: distribute !important; + align-content: space-around !important; } + +.align-content-stretch { + -ms-flex-line-pack: stretch !important; + align-content: stretch !important; } + +.align-self-auto { + -ms-flex-item-align: auto !important; + align-self: auto !important; } + +.align-self-start { + -ms-flex-item-align: start !important; + align-self: flex-start !important; } + +.align-self-end { + -ms-flex-item-align: end !important; + align-self: flex-end !important; } + +.align-self-center { + -ms-flex-item-align: center !important; + align-self: center !important; } + +.align-self-baseline { + -ms-flex-item-align: baseline !important; + align-self: baseline !important; } + +.align-self-stretch { + -ms-flex-item-align: stretch !important; + align-self: stretch !important; } + +.order-first { + -webkit-box-ordinal-group: 0 !important; + -ms-flex-order: -1 !important; + order: -1 !important; } + +.order-0 { + -webkit-box-ordinal-group: 1 !important; + -ms-flex-order: 0 !important; + order: 0 !important; } + +.order-1 { + -webkit-box-ordinal-group: 2 !important; + -ms-flex-order: 1 !important; + order: 1 !important; } + +.order-2 { + -webkit-box-ordinal-group: 3 !important; + -ms-flex-order: 2 !important; + order: 2 !important; } + +.order-3 { + -webkit-box-ordinal-group: 4 !important; + -ms-flex-order: 3 !important; + order: 3 !important; } + +.order-4 { + -webkit-box-ordinal-group: 5 !important; + -ms-flex-order: 4 !important; + order: 4 !important; } + +.order-5 { + -webkit-box-ordinal-group: 6 !important; + -ms-flex-order: 5 !important; + order: 5 !important; } + +.order-last { + -webkit-box-ordinal-group: 7 !important; + -ms-flex-order: 6 !important; + order: 6 !important; } + +.m-0 { + margin: 0 !important; } + +.m-1 { + margin: 0.25rem !important; } + +.m-2 { + margin: 0.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: 0.25rem !important; + margin-left: 0.25rem !important; } + +.mx-2 { + margin-right: 0.5rem !important; + margin-left: 0.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: 0.25rem !important; + margin-bottom: 0.25rem !important; } + +.my-2 { + margin-top: 0.5rem !important; + margin-bottom: 0.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: 0.25rem !important; } + +.mt-2 { + margin-top: 0.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: 0.25rem !important; } + +.me-2 { + margin-right: 0.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: 0.25rem !important; } + +.mb-2 { + margin-bottom: 0.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: 0.25rem !important; } + +.ms-2 { + margin-left: 0.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; } + +.m-n1 { + margin: -0.25rem !important; } + +.m-n2 { + margin: -0.5rem !important; } + +.m-n3 { + margin: -1rem !important; } + +.m-n4 { + margin: -1.5rem !important; } + +.m-n5 { + margin: -3rem !important; } + +.mx-n1 { + margin-right: -0.25rem !important; + margin-left: -0.25rem !important; } + +.mx-n2 { + margin-right: -0.5rem !important; + margin-left: -0.5rem !important; } + +.mx-n3 { + margin-right: -1rem !important; + margin-left: -1rem !important; } + +.mx-n4 { + margin-right: -1.5rem !important; + margin-left: -1.5rem !important; } + +.mx-n5 { + margin-right: -3rem !important; + margin-left: -3rem !important; } + +.my-n1 { + margin-top: -0.25rem !important; + margin-bottom: -0.25rem !important; } + +.my-n2 { + margin-top: -0.5rem !important; + margin-bottom: -0.5rem !important; } + +.my-n3 { + margin-top: -1rem !important; + margin-bottom: -1rem !important; } + +.my-n4 { + margin-top: -1.5rem !important; + margin-bottom: -1.5rem !important; } + +.my-n5 { + margin-top: -3rem !important; + margin-bottom: -3rem !important; } + +.mt-n1 { + margin-top: -0.25rem !important; } + +.mt-n2 { + margin-top: -0.5rem !important; } + +.mt-n3 { + margin-top: -1rem !important; } + +.mt-n4 { + margin-top: -1.5rem !important; } + +.mt-n5 { + margin-top: -3rem !important; } + +.me-n1 { + margin-right: -0.25rem !important; } + +.me-n2 { + margin-right: -0.5rem !important; } + +.me-n3 { + margin-right: -1rem !important; } + +.me-n4 { + margin-right: -1.5rem !important; } + +.me-n5 { + margin-right: -3rem !important; } + +.mb-n1 { + margin-bottom: -0.25rem !important; } + +.mb-n2 { + margin-bottom: -0.5rem !important; } + +.mb-n3 { + margin-bottom: -1rem !important; } + +.mb-n4 { + margin-bottom: -1.5rem !important; } + +.mb-n5 { + margin-bottom: -3rem !important; } + +.ms-n1 { + margin-left: -0.25rem !important; } + +.ms-n2 { + margin-left: -0.5rem !important; } + +.ms-n3 { + margin-left: -1rem !important; } + +.ms-n4 { + margin-left: -1.5rem !important; } + +.ms-n5 { + margin-left: -3rem !important; } + +.p-0 { + padding: 0 !important; } + +.p-1 { + padding: 0.25rem !important; } + +.p-2 { + padding: 0.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: 0.25rem !important; + padding-left: 0.25rem !important; } + +.px-2 { + padding-right: 0.5rem !important; + padding-left: 0.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: 0.25rem !important; + padding-bottom: 0.25rem !important; } + +.py-2 { + padding-top: 0.5rem !important; + padding-bottom: 0.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: 0.25rem !important; } + +.pt-2 { + padding-top: 0.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: 0.25rem !important; } + +.pe-2 { + padding-right: 0.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: 0.25rem !important; } + +.pb-2 { + padding-bottom: 0.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: 0.25rem !important; } + +.ps-2 { + padding-left: 0.5rem !important; } + +.ps-3 { + padding-left: 1rem !important; } + +.ps-4 { + padding-left: 1.5rem !important; } + +.ps-5 { + padding-left: 3rem !important; } + +.font-monospace { + font-family: var(--bs-font-monospace) !important; } + +.fs-1 { + font-size: calc(1.35rem + 1.2vw) !important; } + +.fs-2 { + font-size: calc(1.305rem + 0.66vw) !important; } + +.fs-3 { + font-size: calc(1.2825rem + 0.39vw) !important; } + +.fs-4 { + font-size: calc(1.26rem + 0.12vw) !important; } + +.fs-5 { + font-size: 1.125rem !important; } + +.fs-6 { + font-size: 0.9rem !important; } + +.fst-italic { + font-style: italic !important; } + +.fst-normal { + font-style: normal !important; } + +.fw-light { + font-weight: 300 !important; } + +.fw-lighter { + font-weight: lighter !important; } + +.fw-normal { + font-weight: 400 !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; } + +/* rtl:begin:remove */ +.text-break { + word-wrap: break-word !important; + word-break: break-word !important; } + +/* rtl:end:remove */ +.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-pink { + --bs-text-opacity: 1; + color: rgba(var(--bs-pink-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-purple { + --bs-text-opacity: 1; + color: rgba(var(--bs-purple-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: #74788d !important; } + +.text-black-50 { + --bs-text-opacity: 1; + color: rgba(0, 0, 0, 0.5) !important; } + +.text-white-50 { + --bs-text-opacity: 1; + color: rgba(255, 255, 255, 0.5) !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; } + +.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-pink { + --bs-bg-opacity: 1; + background-color: rgba(var(--bs-pink-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-purple { + --bs-bg-opacity: 1; + background-color: rgba(var(--bs-purple-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-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-gradient { + background-image: var(--bs-gradient) !important; } + +.user-select-all { + -webkit-user-select: all !important; + -moz-user-select: all !important; + -ms-user-select: all !important; + user-select: all !important; } + +.user-select-auto { + -webkit-user-select: auto !important; + -moz-user-select: auto !important; + -ms-user-select: auto !important; + user-select: auto !important; } + +.user-select-none { + -webkit-user-select: none !important; + -moz-user-select: none !important; + -ms-user-select: none !important; + user-select: none !important; } + +.pe-none { + pointer-events: none !important; } + +.pe-auto { + pointer-events: auto !important; } + +.rounded { + border-radius: 0.25rem !important; } + +.rounded-0 { + border-radius: 0 !important; } + +.rounded-1 { + border-radius: 0.2rem !important; } + +.rounded-2 { + border-radius: 0.25rem !important; } + +.rounded-3 { + border-radius: 0.4rem !important; } + +.rounded-circle { + border-radius: 50% !important; } + +.rounded-pill { + border-radius: 50rem !important; } + +.rounded-top { + border-top-left-radius: 0.25rem !important; + border-top-right-radius: 0.25rem !important; } + +.rounded-end { + border-top-right-radius: 0.25rem !important; + border-bottom-right-radius: 0.25rem !important; } + +.rounded-bottom { + border-bottom-right-radius: 0.25rem !important; + border-bottom-left-radius: 0.25rem !important; } + +.rounded-start { + border-bottom-left-radius: 0.25rem !important; + border-top-left-radius: 0.25rem !important; } + +.visible { + visibility: visible !important; } + +.invisible { + visibility: hidden !important; } + +@media (min-width: 576px) { + .float-sm-start { + float: left !important; } + .float-sm-end { + float: right !important; } + .float-sm-none { + float: 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-table { + display: table !important; } + .d-sm-table-row { + display: table-row !important; } + .d-sm-table-cell { + display: table-cell !important; } + .d-sm-flex { + display: -webkit-box !important; + display: -ms-flexbox !important; + display: flex !important; } + .d-sm-inline-flex { + display: -webkit-inline-box !important; + display: -ms-inline-flexbox !important; + display: inline-flex !important; } + .d-sm-none { + display: none !important; } + .flex-sm-fill { + -webkit-box-flex: 1 !important; + -ms-flex: 1 1 auto !important; + flex: 1 1 auto !important; } + .flex-sm-row { + -webkit-box-orient: horizontal !important; + -webkit-box-direction: normal !important; + -ms-flex-direction: row !important; + flex-direction: row !important; } + .flex-sm-column { + -webkit-box-orient: vertical !important; + -webkit-box-direction: normal !important; + -ms-flex-direction: column !important; + flex-direction: column !important; } + .flex-sm-row-reverse { + -webkit-box-orient: horizontal !important; + -webkit-box-direction: reverse !important; + -ms-flex-direction: row-reverse !important; + flex-direction: row-reverse !important; } + .flex-sm-column-reverse { + -webkit-box-orient: vertical !important; + -webkit-box-direction: reverse !important; + -ms-flex-direction: column-reverse !important; + flex-direction: column-reverse !important; } + .flex-sm-grow-0 { + -webkit-box-flex: 0 !important; + -ms-flex-positive: 0 !important; + flex-grow: 0 !important; } + .flex-sm-grow-1 { + -webkit-box-flex: 1 !important; + -ms-flex-positive: 1 !important; + flex-grow: 1 !important; } + .flex-sm-shrink-0 { + -ms-flex-negative: 0 !important; + flex-shrink: 0 !important; } + .flex-sm-shrink-1 { + -ms-flex-negative: 1 !important; + flex-shrink: 1 !important; } + .flex-sm-wrap { + -ms-flex-wrap: wrap !important; + flex-wrap: wrap !important; } + .flex-sm-nowrap { + -ms-flex-wrap: nowrap !important; + flex-wrap: nowrap !important; } + .flex-sm-wrap-reverse { + -ms-flex-wrap: wrap-reverse !important; + flex-wrap: wrap-reverse !important; } + .gap-sm-0 { + gap: 0 !important; } + .gap-sm-1 { + gap: 0.25rem !important; } + .gap-sm-2 { + gap: 0.5rem !important; } + .gap-sm-3 { + gap: 1rem !important; } + .gap-sm-4 { + gap: 1.5rem !important; } + .gap-sm-5 { + gap: 3rem !important; } + .justify-content-sm-start { + -webkit-box-pack: start !important; + -ms-flex-pack: start !important; + justify-content: flex-start !important; } + .justify-content-sm-end { + -webkit-box-pack: end !important; + -ms-flex-pack: end !important; + justify-content: flex-end !important; } + .justify-content-sm-center { + -webkit-box-pack: center !important; + -ms-flex-pack: center !important; + justify-content: center !important; } + .justify-content-sm-between { + -webkit-box-pack: justify !important; + -ms-flex-pack: justify !important; + justify-content: space-between !important; } + .justify-content-sm-around { + -ms-flex-pack: distribute !important; + justify-content: space-around !important; } + .justify-content-sm-evenly { + -webkit-box-pack: space-evenly !important; + -ms-flex-pack: space-evenly !important; + justify-content: space-evenly !important; } + .align-items-sm-start { + -webkit-box-align: start !important; + -ms-flex-align: start !important; + align-items: flex-start !important; } + .align-items-sm-end { + -webkit-box-align: end !important; + -ms-flex-align: end !important; + align-items: flex-end !important; } + .align-items-sm-center { + -webkit-box-align: center !important; + -ms-flex-align: center !important; + align-items: center !important; } + .align-items-sm-baseline { + -webkit-box-align: baseline !important; + -ms-flex-align: baseline !important; + align-items: baseline !important; } + .align-items-sm-stretch { + -webkit-box-align: stretch !important; + -ms-flex-align: stretch !important; + align-items: stretch !important; } + .align-content-sm-start { + -ms-flex-line-pack: start !important; + align-content: flex-start !important; } + .align-content-sm-end { + -ms-flex-line-pack: end !important; + align-content: flex-end !important; } + .align-content-sm-center { + -ms-flex-line-pack: center !important; + align-content: center !important; } + .align-content-sm-between { + -ms-flex-line-pack: justify !important; + align-content: space-between !important; } + .align-content-sm-around { + -ms-flex-line-pack: distribute !important; + align-content: space-around !important; } + .align-content-sm-stretch { + -ms-flex-line-pack: stretch !important; + align-content: stretch !important; } + .align-self-sm-auto { + -ms-flex-item-align: auto !important; + align-self: auto !important; } + .align-self-sm-start { + -ms-flex-item-align: start !important; + align-self: flex-start !important; } + .align-self-sm-end { + -ms-flex-item-align: end !important; + align-self: flex-end !important; } + .align-self-sm-center { + -ms-flex-item-align: center !important; + align-self: center !important; } + .align-self-sm-baseline { + -ms-flex-item-align: baseline !important; + align-self: baseline !important; } + .align-self-sm-stretch { + -ms-flex-item-align: stretch !important; + align-self: stretch !important; } + .order-sm-first { + -webkit-box-ordinal-group: 0 !important; + -ms-flex-order: -1 !important; + order: -1 !important; } + .order-sm-0 { + -webkit-box-ordinal-group: 1 !important; + -ms-flex-order: 0 !important; + order: 0 !important; } + .order-sm-1 { + -webkit-box-ordinal-group: 2 !important; + -ms-flex-order: 1 !important; + order: 1 !important; } + .order-sm-2 { + -webkit-box-ordinal-group: 3 !important; + -ms-flex-order: 2 !important; + order: 2 !important; } + .order-sm-3 { + -webkit-box-ordinal-group: 4 !important; + -ms-flex-order: 3 !important; + order: 3 !important; } + .order-sm-4 { + -webkit-box-ordinal-group: 5 !important; + -ms-flex-order: 4 !important; + order: 4 !important; } + .order-sm-5 { + -webkit-box-ordinal-group: 6 !important; + -ms-flex-order: 5 !important; + order: 5 !important; } + .order-sm-last { + -webkit-box-ordinal-group: 7 !important; + -ms-flex-order: 6 !important; + order: 6 !important; } + .m-sm-0 { + margin: 0 !important; } + .m-sm-1 { + margin: 0.25rem !important; } + .m-sm-2 { + margin: 0.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: 0.25rem !important; + margin-left: 0.25rem !important; } + .mx-sm-2 { + margin-right: 0.5rem !important; + margin-left: 0.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: 0.25rem !important; + margin-bottom: 0.25rem !important; } + .my-sm-2 { + margin-top: 0.5rem !important; + margin-bottom: 0.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: 0.25rem !important; } + .mt-sm-2 { + margin-top: 0.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: 0.25rem !important; } + .me-sm-2 { + margin-right: 0.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: 0.25rem !important; } + .mb-sm-2 { + margin-bottom: 0.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: 0.25rem !important; } + .ms-sm-2 { + margin-left: 0.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; } + .m-sm-n1 { + margin: -0.25rem !important; } + .m-sm-n2 { + margin: -0.5rem !important; } + .m-sm-n3 { + margin: -1rem !important; } + .m-sm-n4 { + margin: -1.5rem !important; } + .m-sm-n5 { + margin: -3rem !important; } + .mx-sm-n1 { + margin-right: -0.25rem !important; + margin-left: -0.25rem !important; } + .mx-sm-n2 { + margin-right: -0.5rem !important; + margin-left: -0.5rem !important; } + .mx-sm-n3 { + margin-right: -1rem !important; + margin-left: -1rem !important; } + .mx-sm-n4 { + margin-right: -1.5rem !important; + margin-left: -1.5rem !important; } + .mx-sm-n5 { + margin-right: -3rem !important; + margin-left: -3rem !important; } + .my-sm-n1 { + margin-top: -0.25rem !important; + margin-bottom: -0.25rem !important; } + .my-sm-n2 { + margin-top: -0.5rem !important; + margin-bottom: -0.5rem !important; } + .my-sm-n3 { + margin-top: -1rem !important; + margin-bottom: -1rem !important; } + .my-sm-n4 { + margin-top: -1.5rem !important; + margin-bottom: -1.5rem !important; } + .my-sm-n5 { + margin-top: -3rem !important; + margin-bottom: -3rem !important; } + .mt-sm-n1 { + margin-top: -0.25rem !important; } + .mt-sm-n2 { + margin-top: -0.5rem !important; } + .mt-sm-n3 { + margin-top: -1rem !important; } + .mt-sm-n4 { + margin-top: -1.5rem !important; } + .mt-sm-n5 { + margin-top: -3rem !important; } + .me-sm-n1 { + margin-right: -0.25rem !important; } + .me-sm-n2 { + margin-right: -0.5rem !important; } + .me-sm-n3 { + margin-right: -1rem !important; } + .me-sm-n4 { + margin-right: -1.5rem !important; } + .me-sm-n5 { + margin-right: -3rem !important; } + .mb-sm-n1 { + margin-bottom: -0.25rem !important; } + .mb-sm-n2 { + margin-bottom: -0.5rem !important; } + .mb-sm-n3 { + margin-bottom: -1rem !important; } + .mb-sm-n4 { + margin-bottom: -1.5rem !important; } + .mb-sm-n5 { + margin-bottom: -3rem !important; } + .ms-sm-n1 { + margin-left: -0.25rem !important; } + .ms-sm-n2 { + margin-left: -0.5rem !important; } + .ms-sm-n3 { + margin-left: -1rem !important; } + .ms-sm-n4 { + margin-left: -1.5rem !important; } + .ms-sm-n5 { + margin-left: -3rem !important; } + .p-sm-0 { + padding: 0 !important; } + .p-sm-1 { + padding: 0.25rem !important; } + .p-sm-2 { + padding: 0.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: 0.25rem !important; + padding-left: 0.25rem !important; } + .px-sm-2 { + padding-right: 0.5rem !important; + padding-left: 0.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: 0.25rem !important; + padding-bottom: 0.25rem !important; } + .py-sm-2 { + padding-top: 0.5rem !important; + padding-bottom: 0.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: 0.25rem !important; } + .pt-sm-2 { + padding-top: 0.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: 0.25rem !important; } + .pe-sm-2 { + padding-right: 0.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: 0.25rem !important; } + .pb-sm-2 { + padding-bottom: 0.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: 0.25rem !important; } + .ps-sm-2 { + padding-left: 0.5rem !important; } + .ps-sm-3 { + padding-left: 1rem !important; } + .ps-sm-4 { + padding-left: 1.5rem !important; } + .ps-sm-5 { + padding-left: 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; } + .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-table { + display: table !important; } + .d-md-table-row { + display: table-row !important; } + .d-md-table-cell { + display: table-cell !important; } + .d-md-flex { + display: -webkit-box !important; + display: -ms-flexbox !important; + display: flex !important; } + .d-md-inline-flex { + display: -webkit-inline-box !important; + display: -ms-inline-flexbox !important; + display: inline-flex !important; } + .d-md-none { + display: none !important; } + .flex-md-fill { + -webkit-box-flex: 1 !important; + -ms-flex: 1 1 auto !important; + flex: 1 1 auto !important; } + .flex-md-row { + -webkit-box-orient: horizontal !important; + -webkit-box-direction: normal !important; + -ms-flex-direction: row !important; + flex-direction: row !important; } + .flex-md-column { + -webkit-box-orient: vertical !important; + -webkit-box-direction: normal !important; + -ms-flex-direction: column !important; + flex-direction: column !important; } + .flex-md-row-reverse { + -webkit-box-orient: horizontal !important; + -webkit-box-direction: reverse !important; + -ms-flex-direction: row-reverse !important; + flex-direction: row-reverse !important; } + .flex-md-column-reverse { + -webkit-box-orient: vertical !important; + -webkit-box-direction: reverse !important; + -ms-flex-direction: column-reverse !important; + flex-direction: column-reverse !important; } + .flex-md-grow-0 { + -webkit-box-flex: 0 !important; + -ms-flex-positive: 0 !important; + flex-grow: 0 !important; } + .flex-md-grow-1 { + -webkit-box-flex: 1 !important; + -ms-flex-positive: 1 !important; + flex-grow: 1 !important; } + .flex-md-shrink-0 { + -ms-flex-negative: 0 !important; + flex-shrink: 0 !important; } + .flex-md-shrink-1 { + -ms-flex-negative: 1 !important; + flex-shrink: 1 !important; } + .flex-md-wrap { + -ms-flex-wrap: wrap !important; + flex-wrap: wrap !important; } + .flex-md-nowrap { + -ms-flex-wrap: nowrap !important; + flex-wrap: nowrap !important; } + .flex-md-wrap-reverse { + -ms-flex-wrap: wrap-reverse !important; + flex-wrap: wrap-reverse !important; } + .gap-md-0 { + gap: 0 !important; } + .gap-md-1 { + gap: 0.25rem !important; } + .gap-md-2 { + gap: 0.5rem !important; } + .gap-md-3 { + gap: 1rem !important; } + .gap-md-4 { + gap: 1.5rem !important; } + .gap-md-5 { + gap: 3rem !important; } + .justify-content-md-start { + -webkit-box-pack: start !important; + -ms-flex-pack: start !important; + justify-content: flex-start !important; } + .justify-content-md-end { + -webkit-box-pack: end !important; + -ms-flex-pack: end !important; + justify-content: flex-end !important; } + .justify-content-md-center { + -webkit-box-pack: center !important; + -ms-flex-pack: center !important; + justify-content: center !important; } + .justify-content-md-between { + -webkit-box-pack: justify !important; + -ms-flex-pack: justify !important; + justify-content: space-between !important; } + .justify-content-md-around { + -ms-flex-pack: distribute !important; + justify-content: space-around !important; } + .justify-content-md-evenly { + -webkit-box-pack: space-evenly !important; + -ms-flex-pack: space-evenly !important; + justify-content: space-evenly !important; } + .align-items-md-start { + -webkit-box-align: start !important; + -ms-flex-align: start !important; + align-items: flex-start !important; } + .align-items-md-end { + -webkit-box-align: end !important; + -ms-flex-align: end !important; + align-items: flex-end !important; } + .align-items-md-center { + -webkit-box-align: center !important; + -ms-flex-align: center !important; + align-items: center !important; } + .align-items-md-baseline { + -webkit-box-align: baseline !important; + -ms-flex-align: baseline !important; + align-items: baseline !important; } + .align-items-md-stretch { + -webkit-box-align: stretch !important; + -ms-flex-align: stretch !important; + align-items: stretch !important; } + .align-content-md-start { + -ms-flex-line-pack: start !important; + align-content: flex-start !important; } + .align-content-md-end { + -ms-flex-line-pack: end !important; + align-content: flex-end !important; } + .align-content-md-center { + -ms-flex-line-pack: center !important; + align-content: center !important; } + .align-content-md-between { + -ms-flex-line-pack: justify !important; + align-content: space-between !important; } + .align-content-md-around { + -ms-flex-line-pack: distribute !important; + align-content: space-around !important; } + .align-content-md-stretch { + -ms-flex-line-pack: stretch !important; + align-content: stretch !important; } + .align-self-md-auto { + -ms-flex-item-align: auto !important; + align-self: auto !important; } + .align-self-md-start { + -ms-flex-item-align: start !important; + align-self: flex-start !important; } + .align-self-md-end { + -ms-flex-item-align: end !important; + align-self: flex-end !important; } + .align-self-md-center { + -ms-flex-item-align: center !important; + align-self: center !important; } + .align-self-md-baseline { + -ms-flex-item-align: baseline !important; + align-self: baseline !important; } + .align-self-md-stretch { + -ms-flex-item-align: stretch !important; + align-self: stretch !important; } + .order-md-first { + -webkit-box-ordinal-group: 0 !important; + -ms-flex-order: -1 !important; + order: -1 !important; } + .order-md-0 { + -webkit-box-ordinal-group: 1 !important; + -ms-flex-order: 0 !important; + order: 0 !important; } + .order-md-1 { + -webkit-box-ordinal-group: 2 !important; + -ms-flex-order: 1 !important; + order: 1 !important; } + .order-md-2 { + -webkit-box-ordinal-group: 3 !important; + -ms-flex-order: 2 !important; + order: 2 !important; } + .order-md-3 { + -webkit-box-ordinal-group: 4 !important; + -ms-flex-order: 3 !important; + order: 3 !important; } + .order-md-4 { + -webkit-box-ordinal-group: 5 !important; + -ms-flex-order: 4 !important; + order: 4 !important; } + .order-md-5 { + -webkit-box-ordinal-group: 6 !important; + -ms-flex-order: 5 !important; + order: 5 !important; } + .order-md-last { + -webkit-box-ordinal-group: 7 !important; + -ms-flex-order: 6 !important; + order: 6 !important; } + .m-md-0 { + margin: 0 !important; } + .m-md-1 { + margin: 0.25rem !important; } + .m-md-2 { + margin: 0.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: 0.25rem !important; + margin-left: 0.25rem !important; } + .mx-md-2 { + margin-right: 0.5rem !important; + margin-left: 0.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: 0.25rem !important; + margin-bottom: 0.25rem !important; } + .my-md-2 { + margin-top: 0.5rem !important; + margin-bottom: 0.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: 0.25rem !important; } + .mt-md-2 { + margin-top: 0.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: 0.25rem !important; } + .me-md-2 { + margin-right: 0.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: 0.25rem !important; } + .mb-md-2 { + margin-bottom: 0.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: 0.25rem !important; } + .ms-md-2 { + margin-left: 0.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; } + .m-md-n1 { + margin: -0.25rem !important; } + .m-md-n2 { + margin: -0.5rem !important; } + .m-md-n3 { + margin: -1rem !important; } + .m-md-n4 { + margin: -1.5rem !important; } + .m-md-n5 { + margin: -3rem !important; } + .mx-md-n1 { + margin-right: -0.25rem !important; + margin-left: -0.25rem !important; } + .mx-md-n2 { + margin-right: -0.5rem !important; + margin-left: -0.5rem !important; } + .mx-md-n3 { + margin-right: -1rem !important; + margin-left: -1rem !important; } + .mx-md-n4 { + margin-right: -1.5rem !important; + margin-left: -1.5rem !important; } + .mx-md-n5 { + margin-right: -3rem !important; + margin-left: -3rem !important; } + .my-md-n1 { + margin-top: -0.25rem !important; + margin-bottom: -0.25rem !important; } + .my-md-n2 { + margin-top: -0.5rem !important; + margin-bottom: -0.5rem !important; } + .my-md-n3 { + margin-top: -1rem !important; + margin-bottom: -1rem !important; } + .my-md-n4 { + margin-top: -1.5rem !important; + margin-bottom: -1.5rem !important; } + .my-md-n5 { + margin-top: -3rem !important; + margin-bottom: -3rem !important; } + .mt-md-n1 { + margin-top: -0.25rem !important; } + .mt-md-n2 { + margin-top: -0.5rem !important; } + .mt-md-n3 { + margin-top: -1rem !important; } + .mt-md-n4 { + margin-top: -1.5rem !important; } + .mt-md-n5 { + margin-top: -3rem !important; } + .me-md-n1 { + margin-right: -0.25rem !important; } + .me-md-n2 { + margin-right: -0.5rem !important; } + .me-md-n3 { + margin-right: -1rem !important; } + .me-md-n4 { + margin-right: -1.5rem !important; } + .me-md-n5 { + margin-right: -3rem !important; } + .mb-md-n1 { + margin-bottom: -0.25rem !important; } + .mb-md-n2 { + margin-bottom: -0.5rem !important; } + .mb-md-n3 { + margin-bottom: -1rem !important; } + .mb-md-n4 { + margin-bottom: -1.5rem !important; } + .mb-md-n5 { + margin-bottom: -3rem !important; } + .ms-md-n1 { + margin-left: -0.25rem !important; } + .ms-md-n2 { + margin-left: -0.5rem !important; } + .ms-md-n3 { + margin-left: -1rem !important; } + .ms-md-n4 { + margin-left: -1.5rem !important; } + .ms-md-n5 { + margin-left: -3rem !important; } + .p-md-0 { + padding: 0 !important; } + .p-md-1 { + padding: 0.25rem !important; } + .p-md-2 { + padding: 0.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: 0.25rem !important; + padding-left: 0.25rem !important; } + .px-md-2 { + padding-right: 0.5rem !important; + padding-left: 0.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: 0.25rem !important; + padding-bottom: 0.25rem !important; } + .py-md-2 { + padding-top: 0.5rem !important; + padding-bottom: 0.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: 0.25rem !important; } + .pt-md-2 { + padding-top: 0.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: 0.25rem !important; } + .pe-md-2 { + padding-right: 0.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: 0.25rem !important; } + .pb-md-2 { + padding-bottom: 0.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: 0.25rem !important; } + .ps-md-2 { + padding-left: 0.5rem !important; } + .ps-md-3 { + padding-left: 1rem !important; } + .ps-md-4 { + padding-left: 1.5rem !important; } + .ps-md-5 { + padding-left: 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; } + .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-table { + display: table !important; } + .d-lg-table-row { + display: table-row !important; } + .d-lg-table-cell { + display: table-cell !important; } + .d-lg-flex { + display: -webkit-box !important; + display: -ms-flexbox !important; + display: flex !important; } + .d-lg-inline-flex { + display: -webkit-inline-box !important; + display: -ms-inline-flexbox !important; + display: inline-flex !important; } + .d-lg-none { + display: none !important; } + .flex-lg-fill { + -webkit-box-flex: 1 !important; + -ms-flex: 1 1 auto !important; + flex: 1 1 auto !important; } + .flex-lg-row { + -webkit-box-orient: horizontal !important; + -webkit-box-direction: normal !important; + -ms-flex-direction: row !important; + flex-direction: row !important; } + .flex-lg-column { + -webkit-box-orient: vertical !important; + -webkit-box-direction: normal !important; + -ms-flex-direction: column !important; + flex-direction: column !important; } + .flex-lg-row-reverse { + -webkit-box-orient: horizontal !important; + -webkit-box-direction: reverse !important; + -ms-flex-direction: row-reverse !important; + flex-direction: row-reverse !important; } + .flex-lg-column-reverse { + -webkit-box-orient: vertical !important; + -webkit-box-direction: reverse !important; + -ms-flex-direction: column-reverse !important; + flex-direction: column-reverse !important; } + .flex-lg-grow-0 { + -webkit-box-flex: 0 !important; + -ms-flex-positive: 0 !important; + flex-grow: 0 !important; } + .flex-lg-grow-1 { + -webkit-box-flex: 1 !important; + -ms-flex-positive: 1 !important; + flex-grow: 1 !important; } + .flex-lg-shrink-0 { + -ms-flex-negative: 0 !important; + flex-shrink: 0 !important; } + .flex-lg-shrink-1 { + -ms-flex-negative: 1 !important; + flex-shrink: 1 !important; } + .flex-lg-wrap { + -ms-flex-wrap: wrap !important; + flex-wrap: wrap !important; } + .flex-lg-nowrap { + -ms-flex-wrap: nowrap !important; + flex-wrap: nowrap !important; } + .flex-lg-wrap-reverse { + -ms-flex-wrap: wrap-reverse !important; + flex-wrap: wrap-reverse !important; } + .gap-lg-0 { + gap: 0 !important; } + .gap-lg-1 { + gap: 0.25rem !important; } + .gap-lg-2 { + gap: 0.5rem !important; } + .gap-lg-3 { + gap: 1rem !important; } + .gap-lg-4 { + gap: 1.5rem !important; } + .gap-lg-5 { + gap: 3rem !important; } + .justify-content-lg-start { + -webkit-box-pack: start !important; + -ms-flex-pack: start !important; + justify-content: flex-start !important; } + .justify-content-lg-end { + -webkit-box-pack: end !important; + -ms-flex-pack: end !important; + justify-content: flex-end !important; } + .justify-content-lg-center { + -webkit-box-pack: center !important; + -ms-flex-pack: center !important; + justify-content: center !important; } + .justify-content-lg-between { + -webkit-box-pack: justify !important; + -ms-flex-pack: justify !important; + justify-content: space-between !important; } + .justify-content-lg-around { + -ms-flex-pack: distribute !important; + justify-content: space-around !important; } + .justify-content-lg-evenly { + -webkit-box-pack: space-evenly !important; + -ms-flex-pack: space-evenly !important; + justify-content: space-evenly !important; } + .align-items-lg-start { + -webkit-box-align: start !important; + -ms-flex-align: start !important; + align-items: flex-start !important; } + .align-items-lg-end { + -webkit-box-align: end !important; + -ms-flex-align: end !important; + align-items: flex-end !important; } + .align-items-lg-center { + -webkit-box-align: center !important; + -ms-flex-align: center !important; + align-items: center !important; } + .align-items-lg-baseline { + -webkit-box-align: baseline !important; + -ms-flex-align: baseline !important; + align-items: baseline !important; } + .align-items-lg-stretch { + -webkit-box-align: stretch !important; + -ms-flex-align: stretch !important; + align-items: stretch !important; } + .align-content-lg-start { + -ms-flex-line-pack: start !important; + align-content: flex-start !important; } + .align-content-lg-end { + -ms-flex-line-pack: end !important; + align-content: flex-end !important; } + .align-content-lg-center { + -ms-flex-line-pack: center !important; + align-content: center !important; } + .align-content-lg-between { + -ms-flex-line-pack: justify !important; + align-content: space-between !important; } + .align-content-lg-around { + -ms-flex-line-pack: distribute !important; + align-content: space-around !important; } + .align-content-lg-stretch { + -ms-flex-line-pack: stretch !important; + align-content: stretch !important; } + .align-self-lg-auto { + -ms-flex-item-align: auto !important; + align-self: auto !important; } + .align-self-lg-start { + -ms-flex-item-align: start !important; + align-self: flex-start !important; } + .align-self-lg-end { + -ms-flex-item-align: end !important; + align-self: flex-end !important; } + .align-self-lg-center { + -ms-flex-item-align: center !important; + align-self: center !important; } + .align-self-lg-baseline { + -ms-flex-item-align: baseline !important; + align-self: baseline !important; } + .align-self-lg-stretch { + -ms-flex-item-align: stretch !important; + align-self: stretch !important; } + .order-lg-first { + -webkit-box-ordinal-group: 0 !important; + -ms-flex-order: -1 !important; + order: -1 !important; } + .order-lg-0 { + -webkit-box-ordinal-group: 1 !important; + -ms-flex-order: 0 !important; + order: 0 !important; } + .order-lg-1 { + -webkit-box-ordinal-group: 2 !important; + -ms-flex-order: 1 !important; + order: 1 !important; } + .order-lg-2 { + -webkit-box-ordinal-group: 3 !important; + -ms-flex-order: 2 !important; + order: 2 !important; } + .order-lg-3 { + -webkit-box-ordinal-group: 4 !important; + -ms-flex-order: 3 !important; + order: 3 !important; } + .order-lg-4 { + -webkit-box-ordinal-group: 5 !important; + -ms-flex-order: 4 !important; + order: 4 !important; } + .order-lg-5 { + -webkit-box-ordinal-group: 6 !important; + -ms-flex-order: 5 !important; + order: 5 !important; } + .order-lg-last { + -webkit-box-ordinal-group: 7 !important; + -ms-flex-order: 6 !important; + order: 6 !important; } + .m-lg-0 { + margin: 0 !important; } + .m-lg-1 { + margin: 0.25rem !important; } + .m-lg-2 { + margin: 0.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: 0.25rem !important; + margin-left: 0.25rem !important; } + .mx-lg-2 { + margin-right: 0.5rem !important; + margin-left: 0.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: 0.25rem !important; + margin-bottom: 0.25rem !important; } + .my-lg-2 { + margin-top: 0.5rem !important; + margin-bottom: 0.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: 0.25rem !important; } + .mt-lg-2 { + margin-top: 0.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: 0.25rem !important; } + .me-lg-2 { + margin-right: 0.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: 0.25rem !important; } + .mb-lg-2 { + margin-bottom: 0.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: 0.25rem !important; } + .ms-lg-2 { + margin-left: 0.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; } + .m-lg-n1 { + margin: -0.25rem !important; } + .m-lg-n2 { + margin: -0.5rem !important; } + .m-lg-n3 { + margin: -1rem !important; } + .m-lg-n4 { + margin: -1.5rem !important; } + .m-lg-n5 { + margin: -3rem !important; } + .mx-lg-n1 { + margin-right: -0.25rem !important; + margin-left: -0.25rem !important; } + .mx-lg-n2 { + margin-right: -0.5rem !important; + margin-left: -0.5rem !important; } + .mx-lg-n3 { + margin-right: -1rem !important; + margin-left: -1rem !important; } + .mx-lg-n4 { + margin-right: -1.5rem !important; + margin-left: -1.5rem !important; } + .mx-lg-n5 { + margin-right: -3rem !important; + margin-left: -3rem !important; } + .my-lg-n1 { + margin-top: -0.25rem !important; + margin-bottom: -0.25rem !important; } + .my-lg-n2 { + margin-top: -0.5rem !important; + margin-bottom: -0.5rem !important; } + .my-lg-n3 { + margin-top: -1rem !important; + margin-bottom: -1rem !important; } + .my-lg-n4 { + margin-top: -1.5rem !important; + margin-bottom: -1.5rem !important; } + .my-lg-n5 { + margin-top: -3rem !important; + margin-bottom: -3rem !important; } + .mt-lg-n1 { + margin-top: -0.25rem !important; } + .mt-lg-n2 { + margin-top: -0.5rem !important; } + .mt-lg-n3 { + margin-top: -1rem !important; } + .mt-lg-n4 { + margin-top: -1.5rem !important; } + .mt-lg-n5 { + margin-top: -3rem !important; } + .me-lg-n1 { + margin-right: -0.25rem !important; } + .me-lg-n2 { + margin-right: -0.5rem !important; } + .me-lg-n3 { + margin-right: -1rem !important; } + .me-lg-n4 { + margin-right: -1.5rem !important; } + .me-lg-n5 { + margin-right: -3rem !important; } + .mb-lg-n1 { + margin-bottom: -0.25rem !important; } + .mb-lg-n2 { + margin-bottom: -0.5rem !important; } + .mb-lg-n3 { + margin-bottom: -1rem !important; } + .mb-lg-n4 { + margin-bottom: -1.5rem !important; } + .mb-lg-n5 { + margin-bottom: -3rem !important; } + .ms-lg-n1 { + margin-left: -0.25rem !important; } + .ms-lg-n2 { + margin-left: -0.5rem !important; } + .ms-lg-n3 { + margin-left: -1rem !important; } + .ms-lg-n4 { + margin-left: -1.5rem !important; } + .ms-lg-n5 { + margin-left: -3rem !important; } + .p-lg-0 { + padding: 0 !important; } + .p-lg-1 { + padding: 0.25rem !important; } + .p-lg-2 { + padding: 0.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: 0.25rem !important; + padding-left: 0.25rem !important; } + .px-lg-2 { + padding-right: 0.5rem !important; + padding-left: 0.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: 0.25rem !important; + padding-bottom: 0.25rem !important; } + .py-lg-2 { + padding-top: 0.5rem !important; + padding-bottom: 0.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: 0.25rem !important; } + .pt-lg-2 { + padding-top: 0.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: 0.25rem !important; } + .pe-lg-2 { + padding-right: 0.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: 0.25rem !important; } + .pb-lg-2 { + padding-bottom: 0.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: 0.25rem !important; } + .ps-lg-2 { + padding-left: 0.5rem !important; } + .ps-lg-3 { + padding-left: 1rem !important; } + .ps-lg-4 { + padding-left: 1.5rem !important; } + .ps-lg-5 { + padding-left: 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; } + .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-table { + display: table !important; } + .d-xl-table-row { + display: table-row !important; } + .d-xl-table-cell { + display: table-cell !important; } + .d-xl-flex { + display: -webkit-box !important; + display: -ms-flexbox !important; + display: flex !important; } + .d-xl-inline-flex { + display: -webkit-inline-box !important; + display: -ms-inline-flexbox !important; + display: inline-flex !important; } + .d-xl-none { + display: none !important; } + .flex-xl-fill { + -webkit-box-flex: 1 !important; + -ms-flex: 1 1 auto !important; + flex: 1 1 auto !important; } + .flex-xl-row { + -webkit-box-orient: horizontal !important; + -webkit-box-direction: normal !important; + -ms-flex-direction: row !important; + flex-direction: row !important; } + .flex-xl-column { + -webkit-box-orient: vertical !important; + -webkit-box-direction: normal !important; + -ms-flex-direction: column !important; + flex-direction: column !important; } + .flex-xl-row-reverse { + -webkit-box-orient: horizontal !important; + -webkit-box-direction: reverse !important; + -ms-flex-direction: row-reverse !important; + flex-direction: row-reverse !important; } + .flex-xl-column-reverse { + -webkit-box-orient: vertical !important; + -webkit-box-direction: reverse !important; + -ms-flex-direction: column-reverse !important; + flex-direction: column-reverse !important; } + .flex-xl-grow-0 { + -webkit-box-flex: 0 !important; + -ms-flex-positive: 0 !important; + flex-grow: 0 !important; } + .flex-xl-grow-1 { + -webkit-box-flex: 1 !important; + -ms-flex-positive: 1 !important; + flex-grow: 1 !important; } + .flex-xl-shrink-0 { + -ms-flex-negative: 0 !important; + flex-shrink: 0 !important; } + .flex-xl-shrink-1 { + -ms-flex-negative: 1 !important; + flex-shrink: 1 !important; } + .flex-xl-wrap { + -ms-flex-wrap: wrap !important; + flex-wrap: wrap !important; } + .flex-xl-nowrap { + -ms-flex-wrap: nowrap !important; + flex-wrap: nowrap !important; } + .flex-xl-wrap-reverse { + -ms-flex-wrap: wrap-reverse !important; + flex-wrap: wrap-reverse !important; } + .gap-xl-0 { + gap: 0 !important; } + .gap-xl-1 { + gap: 0.25rem !important; } + .gap-xl-2 { + gap: 0.5rem !important; } + .gap-xl-3 { + gap: 1rem !important; } + .gap-xl-4 { + gap: 1.5rem !important; } + .gap-xl-5 { + gap: 3rem !important; } + .justify-content-xl-start { + -webkit-box-pack: start !important; + -ms-flex-pack: start !important; + justify-content: flex-start !important; } + .justify-content-xl-end { + -webkit-box-pack: end !important; + -ms-flex-pack: end !important; + justify-content: flex-end !important; } + .justify-content-xl-center { + -webkit-box-pack: center !important; + -ms-flex-pack: center !important; + justify-content: center !important; } + .justify-content-xl-between { + -webkit-box-pack: justify !important; + -ms-flex-pack: justify !important; + justify-content: space-between !important; } + .justify-content-xl-around { + -ms-flex-pack: distribute !important; + justify-content: space-around !important; } + .justify-content-xl-evenly { + -webkit-box-pack: space-evenly !important; + -ms-flex-pack: space-evenly !important; + justify-content: space-evenly !important; } + .align-items-xl-start { + -webkit-box-align: start !important; + -ms-flex-align: start !important; + align-items: flex-start !important; } + .align-items-xl-end { + -webkit-box-align: end !important; + -ms-flex-align: end !important; + align-items: flex-end !important; } + .align-items-xl-center { + -webkit-box-align: center !important; + -ms-flex-align: center !important; + align-items: center !important; } + .align-items-xl-baseline { + -webkit-box-align: baseline !important; + -ms-flex-align: baseline !important; + align-items: baseline !important; } + .align-items-xl-stretch { + -webkit-box-align: stretch !important; + -ms-flex-align: stretch !important; + align-items: stretch !important; } + .align-content-xl-start { + -ms-flex-line-pack: start !important; + align-content: flex-start !important; } + .align-content-xl-end { + -ms-flex-line-pack: end !important; + align-content: flex-end !important; } + .align-content-xl-center { + -ms-flex-line-pack: center !important; + align-content: center !important; } + .align-content-xl-between { + -ms-flex-line-pack: justify !important; + align-content: space-between !important; } + .align-content-xl-around { + -ms-flex-line-pack: distribute !important; + align-content: space-around !important; } + .align-content-xl-stretch { + -ms-flex-line-pack: stretch !important; + align-content: stretch !important; } + .align-self-xl-auto { + -ms-flex-item-align: auto !important; + align-self: auto !important; } + .align-self-xl-start { + -ms-flex-item-align: start !important; + align-self: flex-start !important; } + .align-self-xl-end { + -ms-flex-item-align: end !important; + align-self: flex-end !important; } + .align-self-xl-center { + -ms-flex-item-align: center !important; + align-self: center !important; } + .align-self-xl-baseline { + -ms-flex-item-align: baseline !important; + align-self: baseline !important; } + .align-self-xl-stretch { + -ms-flex-item-align: stretch !important; + align-self: stretch !important; } + .order-xl-first { + -webkit-box-ordinal-group: 0 !important; + -ms-flex-order: -1 !important; + order: -1 !important; } + .order-xl-0 { + -webkit-box-ordinal-group: 1 !important; + -ms-flex-order: 0 !important; + order: 0 !important; } + .order-xl-1 { + -webkit-box-ordinal-group: 2 !important; + -ms-flex-order: 1 !important; + order: 1 !important; } + .order-xl-2 { + -webkit-box-ordinal-group: 3 !important; + -ms-flex-order: 2 !important; + order: 2 !important; } + .order-xl-3 { + -webkit-box-ordinal-group: 4 !important; + -ms-flex-order: 3 !important; + order: 3 !important; } + .order-xl-4 { + -webkit-box-ordinal-group: 5 !important; + -ms-flex-order: 4 !important; + order: 4 !important; } + .order-xl-5 { + -webkit-box-ordinal-group: 6 !important; + -ms-flex-order: 5 !important; + order: 5 !important; } + .order-xl-last { + -webkit-box-ordinal-group: 7 !important; + -ms-flex-order: 6 !important; + order: 6 !important; } + .m-xl-0 { + margin: 0 !important; } + .m-xl-1 { + margin: 0.25rem !important; } + .m-xl-2 { + margin: 0.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: 0.25rem !important; + margin-left: 0.25rem !important; } + .mx-xl-2 { + margin-right: 0.5rem !important; + margin-left: 0.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: 0.25rem !important; + margin-bottom: 0.25rem !important; } + .my-xl-2 { + margin-top: 0.5rem !important; + margin-bottom: 0.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: 0.25rem !important; } + .mt-xl-2 { + margin-top: 0.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: 0.25rem !important; } + .me-xl-2 { + margin-right: 0.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: 0.25rem !important; } + .mb-xl-2 { + margin-bottom: 0.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: 0.25rem !important; } + .ms-xl-2 { + margin-left: 0.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; } + .m-xl-n1 { + margin: -0.25rem !important; } + .m-xl-n2 { + margin: -0.5rem !important; } + .m-xl-n3 { + margin: -1rem !important; } + .m-xl-n4 { + margin: -1.5rem !important; } + .m-xl-n5 { + margin: -3rem !important; } + .mx-xl-n1 { + margin-right: -0.25rem !important; + margin-left: -0.25rem !important; } + .mx-xl-n2 { + margin-right: -0.5rem !important; + margin-left: -0.5rem !important; } + .mx-xl-n3 { + margin-right: -1rem !important; + margin-left: -1rem !important; } + .mx-xl-n4 { + margin-right: -1.5rem !important; + margin-left: -1.5rem !important; } + .mx-xl-n5 { + margin-right: -3rem !important; + margin-left: -3rem !important; } + .my-xl-n1 { + margin-top: -0.25rem !important; + margin-bottom: -0.25rem !important; } + .my-xl-n2 { + margin-top: -0.5rem !important; + margin-bottom: -0.5rem !important; } + .my-xl-n3 { + margin-top: -1rem !important; + margin-bottom: -1rem !important; } + .my-xl-n4 { + margin-top: -1.5rem !important; + margin-bottom: -1.5rem !important; } + .my-xl-n5 { + margin-top: -3rem !important; + margin-bottom: -3rem !important; } + .mt-xl-n1 { + margin-top: -0.25rem !important; } + .mt-xl-n2 { + margin-top: -0.5rem !important; } + .mt-xl-n3 { + margin-top: -1rem !important; } + .mt-xl-n4 { + margin-top: -1.5rem !important; } + .mt-xl-n5 { + margin-top: -3rem !important; } + .me-xl-n1 { + margin-right: -0.25rem !important; } + .me-xl-n2 { + margin-right: -0.5rem !important; } + .me-xl-n3 { + margin-right: -1rem !important; } + .me-xl-n4 { + margin-right: -1.5rem !important; } + .me-xl-n5 { + margin-right: -3rem !important; } + .mb-xl-n1 { + margin-bottom: -0.25rem !important; } + .mb-xl-n2 { + margin-bottom: -0.5rem !important; } + .mb-xl-n3 { + margin-bottom: -1rem !important; } + .mb-xl-n4 { + margin-bottom: -1.5rem !important; } + .mb-xl-n5 { + margin-bottom: -3rem !important; } + .ms-xl-n1 { + margin-left: -0.25rem !important; } + .ms-xl-n2 { + margin-left: -0.5rem !important; } + .ms-xl-n3 { + margin-left: -1rem !important; } + .ms-xl-n4 { + margin-left: -1.5rem !important; } + .ms-xl-n5 { + margin-left: -3rem !important; } + .p-xl-0 { + padding: 0 !important; } + .p-xl-1 { + padding: 0.25rem !important; } + .p-xl-2 { + padding: 0.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: 0.25rem !important; + padding-left: 0.25rem !important; } + .px-xl-2 { + padding-right: 0.5rem !important; + padding-left: 0.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: 0.25rem !important; + padding-bottom: 0.25rem !important; } + .py-xl-2 { + padding-top: 0.5rem !important; + padding-bottom: 0.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: 0.25rem !important; } + .pt-xl-2 { + padding-top: 0.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: 0.25rem !important; } + .pe-xl-2 { + padding-right: 0.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: 0.25rem !important; } + .pb-xl-2 { + padding-bottom: 0.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: 0.25rem !important; } + .ps-xl-2 { + padding-left: 0.5rem !important; } + .ps-xl-3 { + padding-left: 1rem !important; } + .ps-xl-4 { + padding-left: 1.5rem !important; } + .ps-xl-5 { + padding-left: 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; } + .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-table { + display: table !important; } + .d-xxl-table-row { + display: table-row !important; } + .d-xxl-table-cell { + display: table-cell !important; } + .d-xxl-flex { + display: -webkit-box !important; + display: -ms-flexbox !important; + display: flex !important; } + .d-xxl-inline-flex { + display: -webkit-inline-box !important; + display: -ms-inline-flexbox !important; + display: inline-flex !important; } + .d-xxl-none { + display: none !important; } + .flex-xxl-fill { + -webkit-box-flex: 1 !important; + -ms-flex: 1 1 auto !important; + flex: 1 1 auto !important; } + .flex-xxl-row { + -webkit-box-orient: horizontal !important; + -webkit-box-direction: normal !important; + -ms-flex-direction: row !important; + flex-direction: row !important; } + .flex-xxl-column { + -webkit-box-orient: vertical !important; + -webkit-box-direction: normal !important; + -ms-flex-direction: column !important; + flex-direction: column !important; } + .flex-xxl-row-reverse { + -webkit-box-orient: horizontal !important; + -webkit-box-direction: reverse !important; + -ms-flex-direction: row-reverse !important; + flex-direction: row-reverse !important; } + .flex-xxl-column-reverse { + -webkit-box-orient: vertical !important; + -webkit-box-direction: reverse !important; + -ms-flex-direction: column-reverse !important; + flex-direction: column-reverse !important; } + .flex-xxl-grow-0 { + -webkit-box-flex: 0 !important; + -ms-flex-positive: 0 !important; + flex-grow: 0 !important; } + .flex-xxl-grow-1 { + -webkit-box-flex: 1 !important; + -ms-flex-positive: 1 !important; + flex-grow: 1 !important; } + .flex-xxl-shrink-0 { + -ms-flex-negative: 0 !important; + flex-shrink: 0 !important; } + .flex-xxl-shrink-1 { + -ms-flex-negative: 1 !important; + flex-shrink: 1 !important; } + .flex-xxl-wrap { + -ms-flex-wrap: wrap !important; + flex-wrap: wrap !important; } + .flex-xxl-nowrap { + -ms-flex-wrap: nowrap !important; + flex-wrap: nowrap !important; } + .flex-xxl-wrap-reverse { + -ms-flex-wrap: wrap-reverse !important; + flex-wrap: wrap-reverse !important; } + .gap-xxl-0 { + gap: 0 !important; } + .gap-xxl-1 { + gap: 0.25rem !important; } + .gap-xxl-2 { + gap: 0.5rem !important; } + .gap-xxl-3 { + gap: 1rem !important; } + .gap-xxl-4 { + gap: 1.5rem !important; } + .gap-xxl-5 { + gap: 3rem !important; } + .justify-content-xxl-start { + -webkit-box-pack: start !important; + -ms-flex-pack: start !important; + justify-content: flex-start !important; } + .justify-content-xxl-end { + -webkit-box-pack: end !important; + -ms-flex-pack: end !important; + justify-content: flex-end !important; } + .justify-content-xxl-center { + -webkit-box-pack: center !important; + -ms-flex-pack: center !important; + justify-content: center !important; } + .justify-content-xxl-between { + -webkit-box-pack: justify !important; + -ms-flex-pack: justify !important; + justify-content: space-between !important; } + .justify-content-xxl-around { + -ms-flex-pack: distribute !important; + justify-content: space-around !important; } + .justify-content-xxl-evenly { + -webkit-box-pack: space-evenly !important; + -ms-flex-pack: space-evenly !important; + justify-content: space-evenly !important; } + .align-items-xxl-start { + -webkit-box-align: start !important; + -ms-flex-align: start !important; + align-items: flex-start !important; } + .align-items-xxl-end { + -webkit-box-align: end !important; + -ms-flex-align: end !important; + align-items: flex-end !important; } + .align-items-xxl-center { + -webkit-box-align: center !important; + -ms-flex-align: center !important; + align-items: center !important; } + .align-items-xxl-baseline { + -webkit-box-align: baseline !important; + -ms-flex-align: baseline !important; + align-items: baseline !important; } + .align-items-xxl-stretch { + -webkit-box-align: stretch !important; + -ms-flex-align: stretch !important; + align-items: stretch !important; } + .align-content-xxl-start { + -ms-flex-line-pack: start !important; + align-content: flex-start !important; } + .align-content-xxl-end { + -ms-flex-line-pack: end !important; + align-content: flex-end !important; } + .align-content-xxl-center { + -ms-flex-line-pack: center !important; + align-content: center !important; } + .align-content-xxl-between { + -ms-flex-line-pack: justify !important; + align-content: space-between !important; } + .align-content-xxl-around { + -ms-flex-line-pack: distribute !important; + align-content: space-around !important; } + .align-content-xxl-stretch { + -ms-flex-line-pack: stretch !important; + align-content: stretch !important; } + .align-self-xxl-auto { + -ms-flex-item-align: auto !important; + align-self: auto !important; } + .align-self-xxl-start { + -ms-flex-item-align: start !important; + align-self: flex-start !important; } + .align-self-xxl-end { + -ms-flex-item-align: end !important; + align-self: flex-end !important; } + .align-self-xxl-center { + -ms-flex-item-align: center !important; + align-self: center !important; } + .align-self-xxl-baseline { + -ms-flex-item-align: baseline !important; + align-self: baseline !important; } + .align-self-xxl-stretch { + -ms-flex-item-align: stretch !important; + align-self: stretch !important; } + .order-xxl-first { + -webkit-box-ordinal-group: 0 !important; + -ms-flex-order: -1 !important; + order: -1 !important; } + .order-xxl-0 { + -webkit-box-ordinal-group: 1 !important; + -ms-flex-order: 0 !important; + order: 0 !important; } + .order-xxl-1 { + -webkit-box-ordinal-group: 2 !important; + -ms-flex-order: 1 !important; + order: 1 !important; } + .order-xxl-2 { + -webkit-box-ordinal-group: 3 !important; + -ms-flex-order: 2 !important; + order: 2 !important; } + .order-xxl-3 { + -webkit-box-ordinal-group: 4 !important; + -ms-flex-order: 3 !important; + order: 3 !important; } + .order-xxl-4 { + -webkit-box-ordinal-group: 5 !important; + -ms-flex-order: 4 !important; + order: 4 !important; } + .order-xxl-5 { + -webkit-box-ordinal-group: 6 !important; + -ms-flex-order: 5 !important; + order: 5 !important; } + .order-xxl-last { + -webkit-box-ordinal-group: 7 !important; + -ms-flex-order: 6 !important; + order: 6 !important; } + .m-xxl-0 { + margin: 0 !important; } + .m-xxl-1 { + margin: 0.25rem !important; } + .m-xxl-2 { + margin: 0.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: 0.25rem !important; + margin-left: 0.25rem !important; } + .mx-xxl-2 { + margin-right: 0.5rem !important; + margin-left: 0.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: 0.25rem !important; + margin-bottom: 0.25rem !important; } + .my-xxl-2 { + margin-top: 0.5rem !important; + margin-bottom: 0.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: 0.25rem !important; } + .mt-xxl-2 { + margin-top: 0.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: 0.25rem !important; } + .me-xxl-2 { + margin-right: 0.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: 0.25rem !important; } + .mb-xxl-2 { + margin-bottom: 0.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: 0.25rem !important; } + .ms-xxl-2 { + margin-left: 0.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; } + .m-xxl-n1 { + margin: -0.25rem !important; } + .m-xxl-n2 { + margin: -0.5rem !important; } + .m-xxl-n3 { + margin: -1rem !important; } + .m-xxl-n4 { + margin: -1.5rem !important; } + .m-xxl-n5 { + margin: -3rem !important; } + .mx-xxl-n1 { + margin-right: -0.25rem !important; + margin-left: -0.25rem !important; } + .mx-xxl-n2 { + margin-right: -0.5rem !important; + margin-left: -0.5rem !important; } + .mx-xxl-n3 { + margin-right: -1rem !important; + margin-left: -1rem !important; } + .mx-xxl-n4 { + margin-right: -1.5rem !important; + margin-left: -1.5rem !important; } + .mx-xxl-n5 { + margin-right: -3rem !important; + margin-left: -3rem !important; } + .my-xxl-n1 { + margin-top: -0.25rem !important; + margin-bottom: -0.25rem !important; } + .my-xxl-n2 { + margin-top: -0.5rem !important; + margin-bottom: -0.5rem !important; } + .my-xxl-n3 { + margin-top: -1rem !important; + margin-bottom: -1rem !important; } + .my-xxl-n4 { + margin-top: -1.5rem !important; + margin-bottom: -1.5rem !important; } + .my-xxl-n5 { + margin-top: -3rem !important; + margin-bottom: -3rem !important; } + .mt-xxl-n1 { + margin-top: -0.25rem !important; } + .mt-xxl-n2 { + margin-top: -0.5rem !important; } + .mt-xxl-n3 { + margin-top: -1rem !important; } + .mt-xxl-n4 { + margin-top: -1.5rem !important; } + .mt-xxl-n5 { + margin-top: -3rem !important; } + .me-xxl-n1 { + margin-right: -0.25rem !important; } + .me-xxl-n2 { + margin-right: -0.5rem !important; } + .me-xxl-n3 { + margin-right: -1rem !important; } + .me-xxl-n4 { + margin-right: -1.5rem !important; } + .me-xxl-n5 { + margin-right: -3rem !important; } + .mb-xxl-n1 { + margin-bottom: -0.25rem !important; } + .mb-xxl-n2 { + margin-bottom: -0.5rem !important; } + .mb-xxl-n3 { + margin-bottom: -1rem !important; } + .mb-xxl-n4 { + margin-bottom: -1.5rem !important; } + .mb-xxl-n5 { + margin-bottom: -3rem !important; } + .ms-xxl-n1 { + margin-left: -0.25rem !important; } + .ms-xxl-n2 { + margin-left: -0.5rem !important; } + .ms-xxl-n3 { + margin-left: -1rem !important; } + .ms-xxl-n4 { + margin-left: -1.5rem !important; } + .ms-xxl-n5 { + margin-left: -3rem !important; } + .p-xxl-0 { + padding: 0 !important; } + .p-xxl-1 { + padding: 0.25rem !important; } + .p-xxl-2 { + padding: 0.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: 0.25rem !important; + padding-left: 0.25rem !important; } + .px-xxl-2 { + padding-right: 0.5rem !important; + padding-left: 0.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: 0.25rem !important; + padding-bottom: 0.25rem !important; } + .py-xxl-2 { + padding-top: 0.5rem !important; + padding-bottom: 0.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: 0.25rem !important; } + .pt-xxl-2 { + padding-top: 0.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: 0.25rem !important; } + .pe-xxl-2 { + padding-right: 0.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: 0.25rem !important; } + .pb-xxl-2 { + padding-bottom: 0.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: 0.25rem !important; } + .ps-xxl-2 { + padding-left: 0.5rem !important; } + .ps-xxl-3 { + padding-left: 1rem !important; } + .ps-xxl-4 { + padding-left: 1.5rem !important; } + .ps-xxl-5 { + padding-left: 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.25rem !important; } + .fs-2 { + font-size: 1.8rem !important; } + .fs-3 { + font-size: 1.575rem !important; } + .fs-4 { + font-size: 1.35rem !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-table { + display: table !important; } + .d-print-table-row { + display: table-row !important; } + .d-print-table-cell { + display: table-cell !important; } + .d-print-flex { + display: -webkit-box !important; + display: -ms-flexbox !important; + display: flex !important; } + .d-print-inline-flex { + display: -webkit-inline-box !important; + display: -ms-inline-flexbox !important; + display: inline-flex !important; } + .d-print-none { + display: none !important; } } + +/* ============= + General +============= */ +html { + position: relative; + min-height: 100%; } + +.h1, +.h2, +.h3, +.h4, +.h5, +.h6, h1, .h1, h2, .h2, h3, .h3, h4, .h4, h5, .h5, h6, .h6 { + color: #495057; + font-weight: 500; + font-family: "Roboto", sans-serif; } + +a { + text-decoration: none !important; } + +label { + font-weight: 500; + margin-bottom: 0.5rem; } + +.ff-primary { + font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; } + +.ff-secondary { + font-family: "Roboto", sans-serif; } + +.small, small, .small { + font-weight: 400; } + +.blockquote { + padding: 10px 20px; + border-left: 4px solid #eff0f2; } + +.blockquote-reverse { + border-left: 0; + border-right: 4px solid #eff0f2; + text-align: right; } + +@media (min-width: 1200px) { + .container, + .container-lg, + .container-md, + .container-sm, + .container-xl, + .container-xxl { + max-width: 1140px; } } + +.row > * { + position: relative; } + +.icon { + position: relative; + top: -2px; } + +.icon-xxs { + height: 14px; + width: 14px; } + +.icon-xs { + height: 16px; + width: 16px; } + +.icon-sm { + height: 20px; + width: 20px; } + +.icon-lg { + height: 32px; + width: 32px; } + +.icon-xl { + height: 46px; + width: 46px; } + +.icon-xxl { + height: 62px; + width: 62px; } + +.sw-3 { + stroke-width: 3px; } + +.sw-1_5 { + stroke-width: 1.5px; } + +.sw-1 { + stroke-width: 1px; } + +.icon-dual-primary { + color: #2e86de; + fill: rgba(3, 142, 220, 0.16); } + +.icon-fill-primary { + fill: #2e86de !important; } + +.icon-dual-secondary { + color: #74788d; + fill: rgba(116, 120, 141, 0.16); } + +.icon-fill-secondary { + fill: #74788d !important; } + +.icon-dual-success { + color: #51d28c; + fill: rgba(81, 210, 140, 0.16); } + +.icon-fill-success { + fill: #51d28c !important; } + +.icon-dual-info { + color: #5fd0f3; + fill: rgba(95, 208, 243, 0.16); } + +.icon-fill-info { + fill: #5fd0f3 !important; } + +.icon-dual-warning { + color: #f7cc53; + fill: rgba(247, 204, 83, 0.16); } + +.icon-fill-warning { + fill: #f7cc53 !important; } + +.icon-dual-danger { + color: #f34e4e; + fill: rgba(243, 78, 78, 0.16); } + +.icon-fill-danger { + fill: #f34e4e !important; } + +.icon-dual-pink { + color: #e83e8c; + fill: rgba(232, 62, 140, 0.16); } + +.icon-fill-pink { + fill: #e83e8c !important; } + +.icon-dual-light { + color: #f5f6f8; + fill: rgba(245, 246, 248, 0.16); } + +.icon-fill-light { + fill: #f5f6f8 !important; } + +.icon-dual-dark { + color: #343a40; + fill: rgba(52, 58, 64, 0.16); } + +.icon-fill-dark { + fill: #343a40 !important; } + +.icon-dual-purple { + color: #564ab1; + fill: rgba(86, 74, 177, 0.16); } + +.icon-fill-purple { + fill: #564ab1 !important; } + +.custom-blockpuote.blockquote { + padding: 20px 20px 20px 30px; + border-left: 3px solid; + font-size: 16px; } + +.custom-blockpuote.blockquote.blockpuote-primary { + color: #2e86de; + border-color: #2e86de; + background-color: rgba(3, 142, 220, 0.15); } + .custom-blockpuote.blockquote.blockpuote-primary .blockquote-footer { + color: #2e86de; } + +.custom-blockpuote.blockquote.blockpuote-outline-primary { + border: 1px solid #2e86de; + border-left: 4px solid #2e86de; } + .custom-blockpuote.blockquote.blockpuote-outline-primary .blockquote-footer { + color: #2e86de; } + +.custom-blockpuote.blockquote.blockpuote-secondary { + color: #74788d; + border-color: #74788d; + background-color: rgba(116, 120, 141, 0.15); } + .custom-blockpuote.blockquote.blockpuote-secondary .blockquote-footer { + color: #74788d; } + +.custom-blockpuote.blockquote.blockpuote-outline-secondary { + border: 1px solid #74788d; + border-left: 4px solid #74788d; } + .custom-blockpuote.blockquote.blockpuote-outline-secondary .blockquote-footer { + color: #74788d; } + +.custom-blockpuote.blockquote.blockpuote-success { + color: #51d28c; + border-color: #51d28c; + background-color: rgba(81, 210, 140, 0.15); } + .custom-blockpuote.blockquote.blockpuote-success .blockquote-footer { + color: #51d28c; } + +.custom-blockpuote.blockquote.blockpuote-outline-success { + border: 1px solid #51d28c; + border-left: 4px solid #51d28c; } + .custom-blockpuote.blockquote.blockpuote-outline-success .blockquote-footer { + color: #51d28c; } + +.custom-blockpuote.blockquote.blockpuote-info { + color: #5fd0f3; + border-color: #5fd0f3; + background-color: rgba(95, 208, 243, 0.15); } + .custom-blockpuote.blockquote.blockpuote-info .blockquote-footer { + color: #5fd0f3; } + +.custom-blockpuote.blockquote.blockpuote-outline-info { + border: 1px solid #5fd0f3; + border-left: 4px solid #5fd0f3; } + .custom-blockpuote.blockquote.blockpuote-outline-info .blockquote-footer { + color: #5fd0f3; } + +.custom-blockpuote.blockquote.blockpuote-warning { + color: #f7cc53; + border-color: #f7cc53; + background-color: rgba(247, 204, 83, 0.15); } + .custom-blockpuote.blockquote.blockpuote-warning .blockquote-footer { + color: #f7cc53; } + +.custom-blockpuote.blockquote.blockpuote-outline-warning { + border: 1px solid #f7cc53; + border-left: 4px solid #f7cc53; } + .custom-blockpuote.blockquote.blockpuote-outline-warning .blockquote-footer { + color: #f7cc53; } + +.custom-blockpuote.blockquote.blockpuote-danger { + color: #f34e4e; + border-color: #f34e4e; + background-color: rgba(243, 78, 78, 0.15); } + .custom-blockpuote.blockquote.blockpuote-danger .blockquote-footer { + color: #f34e4e; } + +.custom-blockpuote.blockquote.blockpuote-outline-danger { + border: 1px solid #f34e4e; + border-left: 4px solid #f34e4e; } + .custom-blockpuote.blockquote.blockpuote-outline-danger .blockquote-footer { + color: #f34e4e; } + +.custom-blockpuote.blockquote.blockpuote-pink { + color: #e83e8c; + border-color: #e83e8c; + background-color: rgba(232, 62, 140, 0.15); } + .custom-blockpuote.blockquote.blockpuote-pink .blockquote-footer { + color: #e83e8c; } + +.custom-blockpuote.blockquote.blockpuote-outline-pink { + border: 1px solid #e83e8c; + border-left: 4px solid #e83e8c; } + .custom-blockpuote.blockquote.blockpuote-outline-pink .blockquote-footer { + color: #e83e8c; } + +.custom-blockpuote.blockquote.blockpuote-light { + color: #f5f6f8; + border-color: #f5f6f8; + background-color: rgba(245, 246, 248, 0.15); } + .custom-blockpuote.blockquote.blockpuote-light .blockquote-footer { + color: #f5f6f8; } + +.custom-blockpuote.blockquote.blockpuote-outline-light { + border: 1px solid #f5f6f8; + border-left: 4px solid #f5f6f8; } + .custom-blockpuote.blockquote.blockpuote-outline-light .blockquote-footer { + color: #f5f6f8; } + +.custom-blockpuote.blockquote.blockpuote-dark { + color: #343a40; + border-color: #343a40; + background-color: rgba(52, 58, 64, 0.15); } + .custom-blockpuote.blockquote.blockpuote-dark .blockquote-footer { + color: #343a40; } + +.custom-blockpuote.blockquote.blockpuote-outline-dark { + border: 1px solid #343a40; + border-left: 4px solid #343a40; } + .custom-blockpuote.blockquote.blockpuote-outline-dark .blockquote-footer { + color: #343a40; } + +.custom-blockpuote.blockquote.blockpuote-purple { + color: #564ab1; + border-color: #564ab1; + background-color: rgba(86, 74, 177, 0.15); } + .custom-blockpuote.blockquote.blockpuote-purple .blockquote-footer { + color: #564ab1; } + +.custom-blockpuote.blockquote.blockpuote-outline-purple { + border: 1px solid #564ab1; + border-left: 4px solid #564ab1; } + .custom-blockpuote.blockquote.blockpuote-outline-purple .blockquote-footer { + color: #564ab1; } + +.bg-soft-primary { + background-color: rgba(3, 142, 220, 0.25) !important; } + +.bg-soft-secondary { + background-color: rgba(116, 120, 141, 0.25) !important; } + +.bg-soft-success { + background-color: rgba(81, 210, 140, 0.25) !important; } + +.bg-soft-info { + background-color: rgba(95, 208, 243, 0.25) !important; } + +.bg-soft-warning { + background-color: rgba(247, 204, 83, 0.25) !important; } + +.bg-soft-danger { + background-color: rgba(243, 78, 78, 0.25) !important; } + +.bg-soft-pink { + background-color: rgba(232, 62, 140, 0.25) !important; } + +.bg-soft-light { + background-color: rgba(245, 246, 248, 0.25) !important; } + +.bg-soft-dark { + background-color: rgba(52, 58, 64, 0.25) !important; } + +.bg-soft-purple { + background-color: rgba(86, 74, 177, 0.25) !important; } + +body[data-layout-mode="dark"] .bg-body { + background-color: #03273c !important; } + +body[data-layout-mode="dark"] .bg-light { + background-color: #043a5a !important; } + +body[data-layout-mode="dark"] .bg-dark { + background-color: #033350 !important; } + +body[data-layout-mode="dark"] .bg-soft-light { + background-color: rgba(4, 58, 90, 0.25) !important; } + +.badge-soft-primary { + color: #2e86de; + background-color: rgba(3, 142, 220, 0.1); } + +.badge-soft-secondary { + color: #74788d; + background-color: rgba(116, 120, 141, 0.1); } + +.badge-soft-success { + color: #51d28c; + background-color: rgba(81, 210, 140, 0.1); } + +.badge-soft-info { + color: #5fd0f3; + background-color: rgba(95, 208, 243, 0.1); } + +.badge-soft-warning { + color: #f7cc53; + background-color: rgba(247, 204, 83, 0.1); } + +.badge-soft-danger { + color: #f34e4e; + background-color: rgba(243, 78, 78, 0.1); } + +.badge-soft-pink { + color: #e83e8c; + background-color: rgba(232, 62, 140, 0.1); } + +.badge-soft-light { + color: #f5f6f8; + background-color: rgba(245, 246, 248, 0.1); } + +.badge-soft-dark { + color: #343a40; + background-color: rgba(52, 58, 64, 0.1); } + +.badge-soft-purple { + color: #564ab1; + background-color: rgba(86, 74, 177, 0.1); } + +.badge-outline-primary { + color: #2e86de; + border: 1px solid #2e86de; + background-color: transparent; } + +.badge-outline-secondary { + color: #74788d; + border: 1px solid #74788d; + background-color: transparent; } + +.badge-outline-success { + color: #51d28c; + border: 1px solid #51d28c; + background-color: transparent; } + +.badge-outline-info { + color: #5fd0f3; + border: 1px solid #5fd0f3; + background-color: transparent; } + +.badge-outline-warning { + color: #f7cc53; + border: 1px solid #f7cc53; + background-color: transparent; } + +.badge-outline-danger { + color: #f34e4e; + border: 1px solid #f34e4e; + background-color: transparent; } + +.badge-outline-pink { + color: #e83e8c; + border: 1px solid #e83e8c; + background-color: transparent; } + +.badge-outline-light { + color: #f5f6f8; + border: 1px solid #f5f6f8; + background-color: transparent; } + +.badge-outline-dark { + color: #343a40; + border: 1px solid #343a40; + background-color: transparent; } + +.badge-outline-purple { + color: #564ab1; + border: 1px solid #564ab1; + background-color: transparent; } + +.badge.bg-light, .badge.badge-soft-light, .badge.badge-outline-light { + color: #343a40; } + +body[data-layout-mode=dark] .badge.bg-light, body[data-layout-mode=dark] .badge.badge-soft-light, body[data-layout-mode=dark] .badge.badge-outline-light { + color: #adb5bd; } + +body[data-layout-mode=dark] .badge-soft-dark { + color: #adb5bd; } + +button, +a { + outline: none !important; } + +.btn-warning { + color: #fff !important; } + +.btn-soft-primary { + color: #2e86de; + background-color: rgba(3, 142, 220, 0.1); + border-color: transparent; } + .btn-soft-primary:hover, .btn-soft-primary:focus, .btn-soft-primary:active { + color: #fff; + background-color: #2e86de; } + .btn-soft-primary:focus { + -webkit-box-shadow: 0 0 0 0.15rem rgba(3, 142, 220, 0.5); + box-shadow: 0 0 0 0.15rem rgba(3, 142, 220, 0.5); } + +.btn-soft-secondary { + color: #74788d; + background-color: rgba(116, 120, 141, 0.1); + border-color: transparent; } + .btn-soft-secondary:hover, .btn-soft-secondary:focus, .btn-soft-secondary:active { + color: #fff; + background-color: #74788d; } + .btn-soft-secondary:focus { + -webkit-box-shadow: 0 0 0 0.15rem rgba(116, 120, 141, 0.5); + box-shadow: 0 0 0 0.15rem rgba(116, 120, 141, 0.5); } + +.btn-soft-success { + color: #51d28c; + background-color: rgba(81, 210, 140, 0.1); + border-color: transparent; } + .btn-soft-success:hover, .btn-soft-success:focus, .btn-soft-success:active { + color: #fff; + background-color: #51d28c; } + .btn-soft-success:focus { + -webkit-box-shadow: 0 0 0 0.15rem rgba(81, 210, 140, 0.5); + box-shadow: 0 0 0 0.15rem rgba(81, 210, 140, 0.5); } + +.btn-soft-info { + color: #5fd0f3; + background-color: rgba(95, 208, 243, 0.1); + border-color: transparent; } + .btn-soft-info:hover, .btn-soft-info:focus, .btn-soft-info:active { + color: #fff; + background-color: #5fd0f3; } + .btn-soft-info:focus { + -webkit-box-shadow: 0 0 0 0.15rem rgba(95, 208, 243, 0.5); + box-shadow: 0 0 0 0.15rem rgba(95, 208, 243, 0.5); } + +.btn-soft-warning { + color: #f7cc53; + background-color: rgba(247, 204, 83, 0.1); + border-color: transparent; } + .btn-soft-warning:hover, .btn-soft-warning:focus, .btn-soft-warning:active { + color: #fff; + background-color: #f7cc53; } + .btn-soft-warning:focus { + -webkit-box-shadow: 0 0 0 0.15rem rgba(247, 204, 83, 0.5); + box-shadow: 0 0 0 0.15rem rgba(247, 204, 83, 0.5); } + +.btn-soft-danger { + color: #f34e4e; + background-color: rgba(243, 78, 78, 0.1); + border-color: transparent; } + .btn-soft-danger:hover, .btn-soft-danger:focus, .btn-soft-danger:active { + color: #fff; + background-color: #f34e4e; } + .btn-soft-danger:focus { + -webkit-box-shadow: 0 0 0 0.15rem rgba(243, 78, 78, 0.5); + box-shadow: 0 0 0 0.15rem rgba(243, 78, 78, 0.5); } + +.btn-soft-pink { + color: #e83e8c; + background-color: rgba(232, 62, 140, 0.1); + border-color: transparent; } + .btn-soft-pink:hover, .btn-soft-pink:focus, .btn-soft-pink:active { + color: #fff; + background-color: #e83e8c; } + .btn-soft-pink:focus { + -webkit-box-shadow: 0 0 0 0.15rem rgba(232, 62, 140, 0.5); + box-shadow: 0 0 0 0.15rem rgba(232, 62, 140, 0.5); } + +.btn-soft-light { + color: #f5f6f8; + background-color: rgba(245, 246, 248, 0.1); + border-color: transparent; } + .btn-soft-light:hover, .btn-soft-light:focus, .btn-soft-light:active { + color: #fff; + background-color: #f5f6f8; } + .btn-soft-light:focus { + -webkit-box-shadow: 0 0 0 0.15rem rgba(245, 246, 248, 0.5); + box-shadow: 0 0 0 0.15rem rgba(245, 246, 248, 0.5); } + +.btn-soft-dark { + color: #343a40; + background-color: rgba(52, 58, 64, 0.1); + border-color: transparent; } + .btn-soft-dark:hover, .btn-soft-dark:focus, .btn-soft-dark:active { + color: #fff; + background-color: #343a40; } + .btn-soft-dark:focus { + -webkit-box-shadow: 0 0 0 0.15rem rgba(52, 58, 64, 0.5); + box-shadow: 0 0 0 0.15rem rgba(52, 58, 64, 0.5); } + +.btn-soft-purple { + color: #564ab1; + background-color: rgba(86, 74, 177, 0.1); + border-color: transparent; } + .btn-soft-purple:hover, .btn-soft-purple:focus, .btn-soft-purple:active { + color: #fff; + background-color: #564ab1; } + .btn-soft-purple:focus { + -webkit-box-shadow: 0 0 0 0.15rem rgba(86, 74, 177, 0.5); + box-shadow: 0 0 0 0.15rem rgba(86, 74, 177, 0.5); } + +.btn-soft-light { + color: #74788d; } + .btn-soft-light:hover, .btn-soft-light:focus, .btn-soft-light:active { + color: #343a40; } + +.btn-outline-light { + color: #343a40; } + .btn-outline-light:hover, .btn-outline-light:focus, .btn-outline-light:active { + color: #343a40; } + +.btn-primary.dropdown-toggle-split { + background-color: #039ef5; + border: none; } + +.btn-secondary.dropdown-toggle-split { + background-color: #828698; + border: none; } + +.btn-success.dropdown-toggle-split { + background-color: #65d799; + border: none; } + +.btn-info.dropdown-toggle-split { + background-color: #77d7f5; + border: none; } + +.btn-warning.dropdown-toggle-split { + background-color: #f8d36b; + border: none; } + +.btn-danger.dropdown-toggle-split { + background-color: #f56666; + border: none; } + +.btn-pink.dropdown-toggle-split { + background-color: #eb559a; + border: none; } + +.btn-light.dropdown-toggle-split { + background-color: white; + border: none; } + +.btn-dark.dropdown-toggle-split { + background-color: #3f474e; + border: none; } + +.btn-purple.dropdown-toggle-split { + background-color: #665aba; + border: none; } + +.btn-light.dropdown-toggle-split { + background-color: #eceef2; } + +.btn-rounded { + border-radius: 30px; } + +body[data-layout-mode="dark"] .btn-light { + color: #eff0f2; + background-color: #043a5a; + border-color: #043a5a !important; } + +body[data-layout-mode="dark"] .btn-outline-light { + color: #adb5bd; + border-color: #043a5a; } + body[data-layout-mode="dark"] .btn-outline-light:hover { + color: #adb5bd; + background-color: #043a5a; + border-color: #043a5a; } + +body[data-layout-mode="dark"] .btn-check:focus + .btn-light, +body[data-layout-mode="dark"] .btn-light:focus, +body[data-layout-mode="dark"] .btn-check:focus + .btn-dark, +body[data-layout-mode="dark"] .btn-dark:focus, +body[data-layout-mode="dark"] .btn-check:focus + .btn-outline-light, +body[data-layout-mode="dark"] .btn-outline-light:focus { + -webkit-box-shadow: 0 0 0 0.15rem rgba(4, 58, 90, 0.5); + box-shadow: 0 0 0 0.15rem rgba(4, 58, 90, 0.5); } + +body[data-layout-mode="dark"] .btn-soft-dark, body[data-layout-mode="dark"] .btn-outline-dark { + color: #eff0f2; } + +body[data-layout-mode="dark"] .btn-dark { + background-color: #033350; + border-color: #033350; } + +body[data-layout-mode="dark"] .btn-outline-dark { + border-color: #033350; } + body[data-layout-mode="dark"] .btn-outline-dark:hover { + background-color: #033350; + border-color: #033350; } + +body[data-layout-mode="dark"] .btn-soft-dark { + background-color: rgba(3, 45, 70, 0.25); + border-color: rgba(3, 45, 70, 0.25); } + body[data-layout-mode="dark"] .btn-soft-dark:hover { + background-color: #033350; + border-color: #033350; } + +.breadcrumb-item > a { + color: #495057; } + +.breadcrumb-item + .breadcrumb-item::before { + font-family: "Material Design Icons"; } + +body[data-layout-mode="dark"] .breadcrumb-item > a { + color: #adb5bd; } + +body[data-layout-mode="dark"] .breadcrumb-item.active { + color: #7e93a0; } + +.card { + margin-bottom: 20px; + -webkit-box-shadow: 0 2px 3px #eaedf2; + box-shadow: 0 2px 3px #eaedf2; } + +.card-drop { + color: #495057; } + +.card-title { + font-size: 16px; + margin-bottom: 0; } + +.card-title-desc { + color: #74788d; + margin-bottom: 24px; } + +.card-h-100 { + height: calc(100% - 20px); } + +.card-header.bg-primary { + background-color: #039ef5 !important; + border-bottom: none; } + +.card-header.bg-secondary { + background-color: #828698 !important; + border-bottom: none; } + +.card-header.bg-success { + background-color: #65d799 !important; + border-bottom: none; } + +.card-header.bg-info { + background-color: #77d7f5 !important; + border-bottom: none; } + +.card-header.bg-warning { + background-color: #f8d36b !important; + border-bottom: none; } + +.card-header.bg-danger { + background-color: #f56666 !important; + border-bottom: none; } + +.card-header.bg-pink { + background-color: #eb559a !important; + border-bottom: none; } + +.card-header.bg-light { + background-color: white !important; + border-bottom: none; } + +.card-header.bg-dark { + background-color: #3f474e !important; + border-bottom: none; } + +.card-header.bg-purple { + background-color: #665aba !important; + border-bottom: none; } + +body[data-layout-mode="dark"] .card { + -webkit-box-shadow: 0 2px 3px #022032; + box-shadow: 0 2px 3px #022032; } + +body[data-layout-mode="dark"] .card, body[data-layout-mode="dark"] .card-header, body[data-layout-mode="dark"] .card-footer, +body[data-layout-mode="dark"] .modal-content, body[data-layout-mode="dark"] .offcanvas { + background-color: #032d46; + border-color: #043a5a; } + +body[data-layout-mode="dark"] .card-title-desc { + color: #7e93a0; } + +.carousel-control-prev, +.carousel-control-next { + height: 30px; + width: 30px; + margin: auto 0; + background-color: #2e86de; } + +.carousel-dark .carousel-caption { + color: rgba(0, 0, 0, 0.8); } + .carousel-dark .carousel-caption .h1, .carousel-dark .carousel-caption .h2, .carousel-dark .carousel-caption .h3, .carousel-dark .carousel-caption .h4, .carousel-dark .carousel-caption .h5, .carousel-dark .carousel-caption .h6, + .carousel-dark .carousel-caption h1, + .carousel-dark .carousel-caption .h1, .carousel-dark .carousel-caption h2, .carousel-dark .carousel-caption .h2, .carousel-dark .carousel-caption h3, .carousel-dark .carousel-caption .h3, .carousel-dark .carousel-caption h4, .carousel-dark .carousel-caption .h4, .carousel-dark .carousel-caption h5, .carousel-dark .carousel-caption .h5, .carousel-dark .carousel-caption h6, .carousel-dark .carousel-caption .h6 { + color: rgba(0, 0, 0, 0.8); } + +.dropdown-menu { + -webkit-box-shadow: 0 5px 6px rgba(52, 58, 64, 0.1); + box-shadow: 0 5px 6px rgba(52, 58, 64, 0.1); + -webkit-animation-name: DropDownSlide; + animation-name: DropDownSlide; + -webkit-animation-duration: .3s; + animation-duration: .3s; + -webkit-animation-fill-mode: both; + animation-fill-mode: both; + position: absolute; + z-index: 1000; } + .dropdown-menu.dropdown-megamenu { + padding: 20px; + left: 0 !important; + right: 0 !important; } + .dropdown-menu[data-popper-placement="top-start"] { + -webkit-animation-name: DropDownSlideDown; + animation-name: DropDownSlideDown; } + +@-webkit-keyframes DropDownSlide { + 100% { + margin-top: -1px; } + 0% { + margin-top: 8px; } } + +@keyframes DropDownSlide { + 100% { + margin-top: -1px; } + 0% { + margin-top: 8px; } } + +@-webkit-keyframes DropDownSlideDown { + 100% { + margin-bottom: 0; } + 0% { + margin-bottom: 8px; } } + +@keyframes DropDownSlideDown { + 100% { + margin-bottom: 0; } + 0% { + margin-bottom: 8px; } } + +@media (min-width: 600px) { + .dropdown-menu-xl { + width: 420px; } + .dropdown-menu-lg { + width: 320px; } + .dropdown-menu-md { + width: 240px; } } + +.dropdown-divider { + border-top-color: #eff0f2; } + +.dropdown-mega { + position: static !important; } + +.dropdown-mega-menu-xl { + width: 38rem; } + +.dropdown-mega-menu-lg { + width: 26rem; } + +[dir="ltr"] .dropdown-menu-start { + --bs-position: end; } + +[dir="ltr"] .dropdown-menu-end { + --bs-position: start; } + +body[data-layout-mode="dark"] .dropdown-menu { + background-color: #043552; + border-color: #043a5a; + color: #adb5bd; + -webkit-box-shadow: 0 5px 6px rgba(3, 45, 70, 0.1); + box-shadow: 0 5px 6px rgba(3, 45, 70, 0.1); } + +body[data-layout-mode="dark"] .dropdown-item { + color: #adb5bd; } + body[data-layout-mode="dark"] .dropdown-item:hover, body[data-layout-mode="dark"] .dropdown-item:active, body[data-layout-mode="dark"] .dropdown-item:focus { + background-color: #043d5e; } + body[data-layout-mode="dark"] .dropdown-item.active, body[data-layout-mode="dark"] .dropdown-item:active { + background-color: #043d5e; } + +body[data-layout-mode="dark"] .dropdown-divider { + border-top-color: #043a5a; } + +.nav-tabs > li > a, .nav-pills > li > a { + color: #495057; + font-weight: 500; } + +.nav-pills > a { + color: #495057; + font-weight: 500; } + +.nav-tabs-custom { + border-bottom: 2px solid #eff0f2; } + .nav-tabs-custom .nav-item { + position: relative; + color: #343a40; } + .nav-tabs-custom .nav-item .nav-link { + border: none; } + .nav-tabs-custom .nav-item .nav-link::after { + content: ""; + background: #2e86de; + height: 2px; + position: absolute; + width: 100%; + left: 0; + bottom: -2px; + -webkit-transition: all 250ms ease 0s; + transition: all 250ms ease 0s; + -webkit-transform: scale(0); + transform: scale(0); } + .nav-tabs-custom .nav-item .nav-link.active { + color: #2e86de; } + .nav-tabs-custom .nav-item .nav-link.active:after { + -webkit-transform: scale(1); + transform: scale(1); } + +body[data-layout-mode="dark"] .nav-link { + color: #adb5bd; } + +body[data-layout-mode="dark"] .nav-tabs { + border-color: #043a5a; } + body[data-layout-mode="dark"] .nav-tabs .nav-link { + color: #eff0f2; } + body[data-layout-mode="dark"] .nav-tabs .nav-link:focus, body[data-layout-mode="dark"] .nav-tabs .nav-link:hover { + border-color: #043a5a #043a5a #043a5a; } + body[data-layout-mode="dark"] .nav-tabs .nav-link.active { + background-color: #032d46; + border-color: #043a5a #043a5a #032d46; } + +body[data-layout-mode="dark"] .nav-pills .nav-link { + color: #eff0f2; } + body[data-layout-mode="dark"] .nav-pills .nav-link.active { + color: #fff; } + +.table th { + font-weight: 500; } + +.table .table-light { + color: #495057; + border-color: #eff0f2; + background-color: #f8f9fa; } + +.table-nowrap th, +.table-nowrap td { + white-space: nowrap; } + +.table-responsive::-webkit-scrollbar { + -webkit-appearance: none; } + +.table-responsive::-webkit-scrollbar:vertical { + width: 12px; } + +.table-responsive::-webkit-scrollbar:horizontal { + height: 9px; } + +.table-responsive::-webkit-scrollbar-thumb { + background-color: rgba(52, 58, 64, 0.2); + border-radius: 10px; + border: 2px solid #fff; } + +.table-responsive::-webkit-scrollbar-track { + border-radius: 10px; + background-color: #fff; } + +body[data-layout-mode="dark"] .table { + border-color: #043d5e; + color: #7e93a0; } + +body[data-layout-mode="dark"] .table-bordered { + border-color: #043a5a; } + body[data-layout-mode="dark"] .table-bordered th, + body[data-layout-mode="dark"] .table-bordered td { + border-color: #043a5a; } + +body[data-layout-mode="dark"] .table > :not(:last-child) > :last-child > * { + border-bottom-color: #043a5a; } + +body[data-layout-mode="dark"] .table-striped > tbody > tr:nth-of-type(odd), +body[data-layout-mode="dark"] .table-hover > tbody > tr:hover, +body[data-layout-mode="dark"] .table .table-light { + --bs-table-accent-bg: #03304b; + color: #7e93a0; } + +body[data-layout-mode="dark"] .table-dark { + background-color: #043a5a; } + body[data-layout-mode="dark"] .table-dark > :not(caption) > * > * { + background-color: #043a5a; } + +body[data-layout-mode="dark"] .table-active { + background-color: #033350 !important; + color: #7e93a0; } + body[data-layout-mode="dark"] .table-active th, body[data-layout-mode="dark"] .table-active td { + background-color: #033350; } + +body[data-layout-mode="dark"] .table-responsive::-webkit-scrollbar-thumb { + background-color: rgba(255, 255, 255, 0.075); + border: 2px solid #032d46; } + +body[data-layout-mode="dark"] .table-responsive::-webkit-scrollbar-track { + background-color: #032d46; } + +.pagination-rounded .page-link { + border-radius: 30px !important; + margin: 0 5px; + border: none; + width: 32px; + height: 32px; + padding: 0; + text-align: center; + line-height: 32px; } + +body[data-layout-mode="dark"] .page-link { + background-color: #032d46; + border-color: #043a5a; + color: #adb5bd; } + body[data-layout-mode="dark"] .page-link:hover { + background-color: #033350; + color: #2e86de; } + +body[data-layout-mode="dark"] .page-item.disabled .page-link { + color: #7e93a0; + background-color: #032d46; + border-color: #043a5a; } + +body[data-layout-mode="dark"] .page-item.active .page-link { + color: #fff; + background-color: #2e86de; + border-color: #2e86de; } + +.progress-sm { + height: 5px; } + +.progress-md { + height: 8px; } + +.progress-lg { + height: 12px; } + +.progress-xl { + height: 16px; } + +.animated-progess { + position: relative; } + .animated-progess .progress-bar { + position: relative; + border-radius: 30px; + -webkit-animation: animate-positive 2s; + animation: animate-positive 2s; } + +@-webkit-keyframes animate-positive { + 0% { + width: 0; } } + +@keyframes animate-positive { + 0% { + width: 0; } } + +.custom-progress { + height: 15px; + padding: 4px; + border-radius: 30px; } + .custom-progress .progress-bar { + position: relative; + border-radius: 30px; } + .custom-progress .progress-bar::before { + content: ""; + position: absolute; + width: 4px; + height: 4px; + background-color: #fff; + border-radius: 7px; + right: 2px; + top: 50%; + -webkit-transform: translateY(-50%); + transform: translateY(-50%); } + +body[data-layout-mode="dark"] .progress { + background-color: #043a5a; } + +.popover { + -webkit-box-shadow: 0 5px 6px rgba(52, 58, 64, 0.1); + box-shadow: 0 5px 6px rgba(52, 58, 64, 0.1); } diff --git a/public/assets/imgs/WelcomeSeedProject.jpg b/public/assets/imgs/WelcomeSeedProject.jpg new file mode 100644 index 0000000000000000000000000000000000000000..6ac3b973de656f959510622032de18cd25ac0fc1 GIT binary patch literal 88590 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 e=t.getAttribute("data-bs-target");if(!e||"#"===e){let i=t.getAttribute("href");if(!i||!i.includes("#")&&!i.startsWith("."))return null;i.includes("#")&&!i.startsWith("#")&&(i=`#${i.split("#")[1]}`),e=i&&"#"!==i?i.trim():null}return e},i=t=>{const i=e(t);return i&&document.querySelector(i)?i:null},n=t=>{const i=e(t);return i?document.querySelector(i):null},s=e=>{e.dispatchEvent(new Event(t))},o=t=>!(!t||"object"!=typeof t)&&(void 0!==t.jquery&&(t=t[0]),void 0!==t.nodeType),r=t=>o(t)?t.jquery?t[0]:t:"string"==typeof t&&t.length>0?document.querySelector(t):null,a=(t,e,i)=>{Object.keys(i).forEach((n=>{const s=i[n],r=e[n],a=r&&o(r)?"element":null==(l=r)?`${l}`:{}.toString.call(l).match(/\s([a-z]+)/i)[1].toLowerCase();var l;if(!new RegExp(s).test(a))throw new TypeError(`${t.toUpperCase()}: Option "${n}" provided type "${a}" but expected type "${s}".`)}))},l=t=>!(!o(t)||0===t.getClientRects().length)&&"visible"===getComputedStyle(t).getPropertyValue("visibility"),c=t=>!t||t.nodeType!==Node.ELEMENT_NODE||!!t.classList.contains("disabled")||(void 0!==t.disabled?t.disabled:t.hasAttribute("disabled")&&"false"!==t.getAttribute("disabled")),h=t=>{if(!document.documentElement.attachShadow)return null;if("function"==typeof t.getRootNode){const e=t.getRootNode();return e instanceof ShadowRoot?e:null}return t instanceof ShadowRoot?t:t.parentNode?h(t.parentNode):null},d=()=>{},u=t=>{t.offsetHeight},f=()=>{const{jQuery:t}=window;return t&&!document.body.hasAttribute("data-bs-no-jquery")?t:null},p=[],m=()=>"rtl"===document.documentElement.dir,g=t=>{var e;e=()=>{const e=f();if(e){const i=t.NAME,n=e.fn[i];e.fn[i]=t.jQueryInterface,e.fn[i].Constructor=t,e.fn[i].noConflict=()=>(e.fn[i]=n,t.jQueryInterface)}},"loading"===document.readyState?(p.length||document.addEventListener("DOMContentLoaded",(()=>{p.forEach((t=>t()))})),p.push(e)):e()},_=t=>{"function"==typeof t&&t()},b=(e,i,n=!0)=>{if(!n)return void _(e);const o=(t=>{if(!t)return 0;let{transitionDuration:e,transitionDelay:i}=window.getComputedStyle(t);const n=Number.parseFloat(e),s=Number.parseFloat(i);return n||s?(e=e.split(",")[0],i=i.split(",")[0],1e3*(Number.parseFloat(e)+Number.parseFloat(i))):0})(i)+5;let r=!1;const a=({target:n})=>{n===i&&(r=!0,i.removeEventListener(t,a),_(e))};i.addEventListener(t,a),setTimeout((()=>{r||s(i)}),o)},v=(t,e,i,n)=>{let s=t.indexOf(e);if(-1===s)return t[!i&&n?t.length-1:0];const o=t.length;return s+=i?1:-1,n&&(s=(s+o)%o),t[Math.max(0,Math.min(s,o-1))]},y=/[^.]*(?=\..*)\.|.*/,w=/\..*/,E=/::\d+$/,A={};let T=1;const O={mouseenter:"mouseover",mouseleave:"mouseout"},C=/^(mouseenter|mouseleave)/i,k=new Set(["click","dblclick","mouseup","mousedown","contextmenu","mousewheel","DOMMouseScroll","mouseover","mouseout","mousemove","selectstart","selectend","keydown","keypress","keyup","orientationchange","touchstart","touchmove","touchend","touchcancel","pointerdown","pointermove","pointerup","pointerleave","pointercancel","gesturestart","gesturechange","gestureend","focus","blur","change","reset","select","submit","focusin","focusout","load","unload","beforeunload","resize","move","DOMContentLoaded","readystatechange","error","abort","scroll"]);function L(t,e){return e&&`${e}::${T++}`||t.uidEvent||T++}function x(t){const e=L(t);return t.uidEvent=e,A[e]=A[e]||{},A[e]}function D(t,e,i=null){const n=Object.keys(t);for(let s=0,o=n.length;sfunction(e){if(!e.relatedTarget||e.relatedTarget!==e.delegateTarget&&!e.delegateTarget.contains(e.relatedTarget))return t.call(this,e)};n?n=t(n):i=t(i)}const[o,r,a]=S(e,i,n),l=x(t),c=l[a]||(l[a]={}),h=D(c,r,o?i:null);if(h)return void(h.oneOff=h.oneOff&&s);const d=L(r,e.replace(y,"")),u=o?function(t,e,i){return function n(s){const o=t.querySelectorAll(e);for(let{target:r}=s;r&&r!==this;r=r.parentNode)for(let a=o.length;a--;)if(o[a]===r)return s.delegateTarget=r,n.oneOff&&j.off(t,s.type,e,i),i.apply(r,[s]);return null}}(t,i,n):function(t,e){return function i(n){return n.delegateTarget=t,i.oneOff&&j.off(t,n.type,e),e.apply(t,[n])}}(t,i);u.delegationSelector=o?i:null,u.originalHandler=r,u.oneOff=s,u.uidEvent=d,c[d]=u,t.addEventListener(a,u,o)}function I(t,e,i,n,s){const o=D(e[i],n,s);o&&(t.removeEventListener(i,o,Boolean(s)),delete e[i][o.uidEvent])}function P(t){return t=t.replace(w,""),O[t]||t}const j={on(t,e,i,n){N(t,e,i,n,!1)},one(t,e,i,n){N(t,e,i,n,!0)},off(t,e,i,n){if("string"!=typeof e||!t)return;const[s,o,r]=S(e,i,n),a=r!==e,l=x(t),c=e.startsWith(".");if(void 0!==o){if(!l||!l[r])return;return void I(t,l,r,o,s?i:null)}c&&Object.keys(l).forEach((i=>{!function(t,e,i,n){const s=e[i]||{};Object.keys(s).forEach((o=>{if(o.includes(n)){const n=s[o];I(t,e,i,n.originalHandler,n.delegationSelector)}}))}(t,l,i,e.slice(1))}));const h=l[r]||{};Object.keys(h).forEach((i=>{const n=i.replace(E,"");if(!a||e.includes(n)){const e=h[i];I(t,l,r,e.originalHandler,e.delegationSelector)}}))},trigger(t,e,i){if("string"!=typeof e||!t)return null;const n=f(),s=P(e),o=e!==s,r=k.has(s);let a,l=!0,c=!0,h=!1,d=null;return o&&n&&(a=n.Event(e,i),n(t).trigger(a),l=!a.isPropagationStopped(),c=!a.isImmediatePropagationStopped(),h=a.isDefaultPrevented()),r?(d=document.createEvent("HTMLEvents"),d.initEvent(s,l,!0)):d=new CustomEvent(e,{bubbles:l,cancelable:!0}),void 0!==i&&Object.keys(i).forEach((t=>{Object.defineProperty(d,t,{get:()=>i[t]})})),h&&d.preventDefault(),c&&t.dispatchEvent(d),d.defaultPrevented&&void 0!==a&&a.preventDefault(),d}},M=new Map,H={set(t,e,i){M.has(t)||M.set(t,new Map);const n=M.get(t);n.has(e)||0===n.size?n.set(e,i):console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(n.keys())[0]}.`)},get:(t,e)=>M.has(t)&&M.get(t).get(e)||null,remove(t,e){if(!M.has(t))return;const i=M.get(t);i.delete(e),0===i.size&&M.delete(t)}};class B{constructor(t){(t=r(t))&&(this._element=t,H.set(this._element,this.constructor.DATA_KEY,this))}dispose(){H.remove(this._element,this.constructor.DATA_KEY),j.off(this._element,this.constructor.EVENT_KEY),Object.getOwnPropertyNames(this).forEach((t=>{this[t]=null}))}_queueCallback(t,e,i=!0){b(t,e,i)}static getInstance(t){return H.get(r(t),this.DATA_KEY)}static getOrCreateInstance(t,e={}){return this.getInstance(t)||new this(t,"object"==typeof e?e:null)}static get VERSION(){return"5.1.3"}static get NAME(){throw new Error('You have to implement the static method "NAME", for each component!')}static get DATA_KEY(){return`bs.${this.NAME}`}static get EVENT_KEY(){return`.${this.DATA_KEY}`}}const R=(t,e="hide")=>{const i=`click.dismiss${t.EVENT_KEY}`,s=t.NAME;j.on(document,i,`[data-bs-dismiss="${s}"]`,(function(i){if(["A","AREA"].includes(this.tagName)&&i.preventDefault(),c(this))return;const o=n(this)||this.closest(`.${s}`);t.getOrCreateInstance(o)[e]()}))};class W extends B{static get NAME(){return"alert"}close(){if(j.trigger(this._element,"close.bs.alert").defaultPrevented)return;this._element.classList.remove("show");const t=this._element.classList.contains("fade");this._queueCallback((()=>this._destroyElement()),this._element,t)}_destroyElement(){this._element.remove(),j.trigger(this._element,"closed.bs.alert"),this.dispose()}static jQueryInterface(t){return this.each((function(){const e=W.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}R(W,"close"),g(W);const $='[data-bs-toggle="button"]';class z extends B{static get NAME(){return"button"}toggle(){this._element.setAttribute("aria-pressed",this._element.classList.toggle("active"))}static jQueryInterface(t){return this.each((function(){const e=z.getOrCreateInstance(this);"toggle"===t&&e[t]()}))}}function q(t){return"true"===t||"false"!==t&&(t===Number(t).toString()?Number(t):""===t||"null"===t?null:t)}function F(t){return t.replace(/[A-Z]/g,(t=>`-${t.toLowerCase()}`))}j.on(document,"click.bs.button.data-api",$,(t=>{t.preventDefault();const e=t.target.closest($);z.getOrCreateInstance(e).toggle()})),g(z);const U={setDataAttribute(t,e,i){t.setAttribute(`data-bs-${F(e)}`,i)},removeDataAttribute(t,e){t.removeAttribute(`data-bs-${F(e)}`)},getDataAttributes(t){if(!t)return{};const e={};return Object.keys(t.dataset).filter((t=>t.startsWith("bs"))).forEach((i=>{let n=i.replace(/^bs/,"");n=n.charAt(0).toLowerCase()+n.slice(1,n.length),e[n]=q(t.dataset[i])})),e},getDataAttribute:(t,e)=>q(t.getAttribute(`data-bs-${F(e)}`)),offset(t){const e=t.getBoundingClientRect();return{top:e.top+window.pageYOffset,left:e.left+window.pageXOffset}},position:t=>({top:t.offsetTop,left:t.offsetLeft})},V={find:(t,e=document.documentElement)=>[].concat(...Element.prototype.querySelectorAll.call(e,t)),findOne:(t,e=document.documentElement)=>Element.prototype.querySelector.call(e,t),children:(t,e)=>[].concat(...t.children).filter((t=>t.matches(e))),parents(t,e){const i=[];let n=t.parentNode;for(;n&&n.nodeType===Node.ELEMENT_NODE&&3!==n.nodeType;)n.matches(e)&&i.push(n),n=n.parentNode;return i},prev(t,e){let i=t.previousElementSibling;for(;i;){if(i.matches(e))return[i];i=i.previousElementSibling}return[]},next(t,e){let i=t.nextElementSibling;for(;i;){if(i.matches(e))return[i];i=i.nextElementSibling}return[]},focusableChildren(t){const e=["a","button","input","textarea","select","details","[tabindex]",'[contenteditable="true"]'].map((t=>`${t}:not([tabindex^="-"])`)).join(", ");return this.find(e,t).filter((t=>!c(t)&&l(t)))}},K="carousel",X={interval:5e3,keyboard:!0,slide:!1,pause:"hover",wrap:!0,touch:!0},Y={interval:"(number|boolean)",keyboard:"boolean",slide:"(boolean|string)",pause:"(string|boolean)",wrap:"boolean",touch:"boolean"},Q="next",G="prev",Z="left",J="right",tt={ArrowLeft:J,ArrowRight:Z},et="slid.bs.carousel",it="active",nt=".active.carousel-item";class st extends B{constructor(t,e){super(t),this._items=null,this._interval=null,this._activeElement=null,this._isPaused=!1,this._isSliding=!1,this.touchTimeout=null,this.touchStartX=0,this.touchDeltaX=0,this._config=this._getConfig(e),this._indicatorsElement=V.findOne(".carousel-indicators",this._element),this._touchSupported="ontouchstart"in document.documentElement||navigator.maxTouchPoints>0,this._pointerEvent=Boolean(window.PointerEvent),this._addEventListeners()}static get Default(){return X}static get NAME(){return K}next(){this._slide(Q)}nextWhenVisible(){!document.hidden&&l(this._element)&&this.next()}prev(){this._slide(G)}pause(t){t||(this._isPaused=!0),V.findOne(".carousel-item-next, .carousel-item-prev",this._element)&&(s(this._element),this.cycle(!0)),clearInterval(this._interval),this._interval=null}cycle(t){t||(this._isPaused=!1),this._interval&&(clearInterval(this._interval),this._interval=null),this._config&&this._config.interval&&!this._isPaused&&(this._updateInterval(),this._interval=setInterval((document.visibilityState?this.nextWhenVisible:this.next).bind(this),this._config.interval))}to(t){this._activeElement=V.findOne(nt,this._element);const e=this._getItemIndex(this._activeElement);if(t>this._items.length-1||t<0)return;if(this._isSliding)return void j.one(this._element,et,(()=>this.to(t)));if(e===t)return this.pause(),void this.cycle();const i=t>e?Q:G;this._slide(i,this._items[t])}_getConfig(t){return t={...X,...U.getDataAttributes(this._element),..."object"==typeof t?t:{}},a(K,t,Y),t}_handleSwipe(){const t=Math.abs(this.touchDeltaX);if(t<=40)return;const e=t/this.touchDeltaX;this.touchDeltaX=0,e&&this._slide(e>0?J:Z)}_addEventListeners(){this._config.keyboard&&j.on(this._element,"keydown.bs.carousel",(t=>this._keydown(t))),"hover"===this._config.pause&&(j.on(this._element,"mouseenter.bs.carousel",(t=>this.pause(t))),j.on(this._element,"mouseleave.bs.carousel",(t=>this.cycle(t)))),this._config.touch&&this._touchSupported&&this._addTouchEventListeners()}_addTouchEventListeners(){const t=t=>this._pointerEvent&&("pen"===t.pointerType||"touch"===t.pointerType),e=e=>{t(e)?this.touchStartX=e.clientX:this._pointerEvent||(this.touchStartX=e.touches[0].clientX)},i=t=>{this.touchDeltaX=t.touches&&t.touches.length>1?0:t.touches[0].clientX-this.touchStartX},n=e=>{t(e)&&(this.touchDeltaX=e.clientX-this.touchStartX),this._handleSwipe(),"hover"===this._config.pause&&(this.pause(),this.touchTimeout&&clearTimeout(this.touchTimeout),this.touchTimeout=setTimeout((t=>this.cycle(t)),500+this._config.interval))};V.find(".carousel-item img",this._element).forEach((t=>{j.on(t,"dragstart.bs.carousel",(t=>t.preventDefault()))})),this._pointerEvent?(j.on(this._element,"pointerdown.bs.carousel",(t=>e(t))),j.on(this._element,"pointerup.bs.carousel",(t=>n(t))),this._element.classList.add("pointer-event")):(j.on(this._element,"touchstart.bs.carousel",(t=>e(t))),j.on(this._element,"touchmove.bs.carousel",(t=>i(t))),j.on(this._element,"touchend.bs.carousel",(t=>n(t))))}_keydown(t){if(/input|textarea/i.test(t.target.tagName))return;const e=tt[t.key];e&&(t.preventDefault(),this._slide(e))}_getItemIndex(t){return this._items=t&&t.parentNode?V.find(".carousel-item",t.parentNode):[],this._items.indexOf(t)}_getItemByOrder(t,e){const i=t===Q;return v(this._items,e,i,this._config.wrap)}_triggerSlideEvent(t,e){const i=this._getItemIndex(t),n=this._getItemIndex(V.findOne(nt,this._element));return j.trigger(this._element,"slide.bs.carousel",{relatedTarget:t,direction:e,from:n,to:i})}_setActiveIndicatorElement(t){if(this._indicatorsElement){const e=V.findOne(".active",this._indicatorsElement);e.classList.remove(it),e.removeAttribute("aria-current");const i=V.find("[data-bs-target]",this._indicatorsElement);for(let e=0;e{j.trigger(this._element,et,{relatedTarget:o,direction:d,from:s,to:r})};if(this._element.classList.contains("slide")){o.classList.add(h),u(o),n.classList.add(c),o.classList.add(c);const t=()=>{o.classList.remove(c,h),o.classList.add(it),n.classList.remove(it,h,c),this._isSliding=!1,setTimeout(f,0)};this._queueCallback(t,n,!0)}else n.classList.remove(it),o.classList.add(it),this._isSliding=!1,f();a&&this.cycle()}_directionToOrder(t){return[J,Z].includes(t)?m()?t===Z?G:Q:t===Z?Q:G:t}_orderToDirection(t){return[Q,G].includes(t)?m()?t===G?Z:J:t===G?J:Z:t}static carouselInterface(t,e){const i=st.getOrCreateInstance(t,e);let{_config:n}=i;"object"==typeof e&&(n={...n,...e});const s="string"==typeof e?e:n.slide;if("number"==typeof e)i.to(e);else if("string"==typeof s){if(void 0===i[s])throw new TypeError(`No method named "${s}"`);i[s]()}else n.interval&&n.ride&&(i.pause(),i.cycle())}static jQueryInterface(t){return this.each((function(){st.carouselInterface(this,t)}))}static dataApiClickHandler(t){const e=n(this);if(!e||!e.classList.contains("carousel"))return;const i={...U.getDataAttributes(e),...U.getDataAttributes(this)},s=this.getAttribute("data-bs-slide-to");s&&(i.interval=!1),st.carouselInterface(e,i),s&&st.getInstance(e).to(s),t.preventDefault()}}j.on(document,"click.bs.carousel.data-api","[data-bs-slide], [data-bs-slide-to]",st.dataApiClickHandler),j.on(window,"load.bs.carousel.data-api",(()=>{const t=V.find('[data-bs-ride="carousel"]');for(let e=0,i=t.length;et===this._element));null!==s&&o.length&&(this._selector=s,this._triggerArray.push(e))}this._initializeChildren(),this._config.parent||this._addAriaAndCollapsedClass(this._triggerArray,this._isShown()),this._config.toggle&&this.toggle()}static get Default(){return rt}static get NAME(){return ot}toggle(){this._isShown()?this.hide():this.show()}show(){if(this._isTransitioning||this._isShown())return;let t,e=[];if(this._config.parent){const t=V.find(ut,this._config.parent);e=V.find(".collapse.show, .collapse.collapsing",this._config.parent).filter((e=>!t.includes(e)))}const i=V.findOne(this._selector);if(e.length){const n=e.find((t=>i!==t));if(t=n?pt.getInstance(n):null,t&&t._isTransitioning)return}if(j.trigger(this._element,"show.bs.collapse").defaultPrevented)return;e.forEach((e=>{i!==e&&pt.getOrCreateInstance(e,{toggle:!1}).hide(),t||H.set(e,"bs.collapse",null)}));const n=this._getDimension();this._element.classList.remove(ct),this._element.classList.add(ht),this._element.style[n]=0,this._addAriaAndCollapsedClass(this._triggerArray,!0),this._isTransitioning=!0;const s=`scroll${n[0].toUpperCase()+n.slice(1)}`;this._queueCallback((()=>{this._isTransitioning=!1,this._element.classList.remove(ht),this._element.classList.add(ct,lt),this._element.style[n]="",j.trigger(this._element,"shown.bs.collapse")}),this._element,!0),this._element.style[n]=`${this._element[s]}px`}hide(){if(this._isTransitioning||!this._isShown())return;if(j.trigger(this._element,"hide.bs.collapse").defaultPrevented)return;const t=this._getDimension();this._element.style[t]=`${this._element.getBoundingClientRect()[t]}px`,u(this._element),this._element.classList.add(ht),this._element.classList.remove(ct,lt);const e=this._triggerArray.length;for(let t=0;t{this._isTransitioning=!1,this._element.classList.remove(ht),this._element.classList.add(ct),j.trigger(this._element,"hidden.bs.collapse")}),this._element,!0)}_isShown(t=this._element){return t.classList.contains(lt)}_getConfig(t){return(t={...rt,...U.getDataAttributes(this._element),...t}).toggle=Boolean(t.toggle),t.parent=r(t.parent),a(ot,t,at),t}_getDimension(){return this._element.classList.contains("collapse-horizontal")?"width":"height"}_initializeChildren(){if(!this._config.parent)return;const t=V.find(ut,this._config.parent);V.find(ft,this._config.parent).filter((e=>!t.includes(e))).forEach((t=>{const e=n(t);e&&this._addAriaAndCollapsedClass([t],this._isShown(e))}))}_addAriaAndCollapsedClass(t,e){t.length&&t.forEach((t=>{e?t.classList.remove(dt):t.classList.add(dt),t.setAttribute("aria-expanded",e)}))}static jQueryInterface(t){return this.each((function(){const e={};"string"==typeof t&&/show|hide/.test(t)&&(e.toggle=!1);const i=pt.getOrCreateInstance(this,e);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t]()}}))}}j.on(document,"click.bs.collapse.data-api",ft,(function(t){("A"===t.target.tagName||t.delegateTarget&&"A"===t.delegateTarget.tagName)&&t.preventDefault();const e=i(this);V.find(e).forEach((t=>{pt.getOrCreateInstance(t,{toggle:!1}).toggle()}))})),g(pt);var mt="top",gt="bottom",_t="right",bt="left",vt="auto",yt=[mt,gt,_t,bt],wt="start",Et="end",At="clippingParents",Tt="viewport",Ot="popper",Ct="reference",kt=yt.reduce((function(t,e){return t.concat([e+"-"+wt,e+"-"+Et])}),[]),Lt=[].concat(yt,[vt]).reduce((function(t,e){return t.concat([e,e+"-"+wt,e+"-"+Et])}),[]),xt="beforeRead",Dt="read",St="afterRead",Nt="beforeMain",It="main",Pt="afterMain",jt="beforeWrite",Mt="write",Ht="afterWrite",Bt=[xt,Dt,St,Nt,It,Pt,jt,Mt,Ht];function Rt(t){return t?(t.nodeName||"").toLowerCase():null}function Wt(t){if(null==t)return window;if("[object Window]"!==t.toString()){var e=t.ownerDocument;return e&&e.defaultView||window}return t}function $t(t){return t instanceof Wt(t).Element||t instanceof Element}function zt(t){return t instanceof Wt(t).HTMLElement||t instanceof HTMLElement}function qt(t){return"undefined"!=typeof ShadowRoot&&(t instanceof Wt(t).ShadowRoot||t instanceof ShadowRoot)}const Ft={name:"applyStyles",enabled:!0,phase:"write",fn:function(t){var e=t.state;Object.keys(e.elements).forEach((function(t){var i=e.styles[t]||{},n=e.attributes[t]||{},s=e.elements[t];zt(s)&&Rt(s)&&(Object.assign(s.style,i),Object.keys(n).forEach((function(t){var e=n[t];!1===e?s.removeAttribute(t):s.setAttribute(t,!0===e?"":e)})))}))},effect:function(t){var e=t.state,i={popper:{position:e.options.strategy,left:"0",top:"0",margin:"0"},arrow:{position:"absolute"},reference:{}};return Object.assign(e.elements.popper.style,i.popper),e.styles=i,e.elements.arrow&&Object.assign(e.elements.arrow.style,i.arrow),function(){Object.keys(e.elements).forEach((function(t){var n=e.elements[t],s=e.attributes[t]||{},o=Object.keys(e.styles.hasOwnProperty(t)?e.styles[t]:i[t]).reduce((function(t,e){return t[e]="",t}),{});zt(n)&&Rt(n)&&(Object.assign(n.style,o),Object.keys(s).forEach((function(t){n.removeAttribute(t)})))}))}},requires:["computeStyles"]};function Ut(t){return t.split("-")[0]}function Vt(t,e){var i=t.getBoundingClientRect();return{width:i.width/1,height:i.height/1,top:i.top/1,right:i.right/1,bottom:i.bottom/1,left:i.left/1,x:i.left/1,y:i.top/1}}function Kt(t){var e=Vt(t),i=t.offsetWidth,n=t.offsetHeight;return Math.abs(e.width-i)<=1&&(i=e.width),Math.abs(e.height-n)<=1&&(n=e.height),{x:t.offsetLeft,y:t.offsetTop,width:i,height:n}}function Xt(t,e){var i=e.getRootNode&&e.getRootNode();if(t.contains(e))return!0;if(i&&qt(i)){var n=e;do{if(n&&t.isSameNode(n))return!0;n=n.parentNode||n.host}while(n)}return!1}function Yt(t){return Wt(t).getComputedStyle(t)}function Qt(t){return["table","td","th"].indexOf(Rt(t))>=0}function Gt(t){return(($t(t)?t.ownerDocument:t.document)||window.document).documentElement}function Zt(t){return"html"===Rt(t)?t:t.assignedSlot||t.parentNode||(qt(t)?t.host:null)||Gt(t)}function Jt(t){return zt(t)&&"fixed"!==Yt(t).position?t.offsetParent:null}function te(t){for(var e=Wt(t),i=Jt(t);i&&Qt(i)&&"static"===Yt(i).position;)i=Jt(i);return i&&("html"===Rt(i)||"body"===Rt(i)&&"static"===Yt(i).position)?e:i||function(t){var e=-1!==navigator.userAgent.toLowerCase().indexOf("firefox");if(-1!==navigator.userAgent.indexOf("Trident")&&zt(t)&&"fixed"===Yt(t).position)return null;for(var i=Zt(t);zt(i)&&["html","body"].indexOf(Rt(i))<0;){var n=Yt(i);if("none"!==n.transform||"none"!==n.perspective||"paint"===n.contain||-1!==["transform","perspective"].indexOf(n.willChange)||e&&"filter"===n.willChange||e&&n.filter&&"none"!==n.filter)return i;i=i.parentNode}return null}(t)||e}function ee(t){return["top","bottom"].indexOf(t)>=0?"x":"y"}var ie=Math.max,ne=Math.min,se=Math.round;function oe(t,e,i){return ie(t,ne(e,i))}function re(t){return Object.assign({},{top:0,right:0,bottom:0,left:0},t)}function ae(t,e){return e.reduce((function(e,i){return e[i]=t,e}),{})}const le={name:"arrow",enabled:!0,phase:"main",fn:function(t){var e,i=t.state,n=t.name,s=t.options,o=i.elements.arrow,r=i.modifiersData.popperOffsets,a=Ut(i.placement),l=ee(a),c=[bt,_t].indexOf(a)>=0?"height":"width";if(o&&r){var h=function(t,e){return re("number"!=typeof(t="function"==typeof t?t(Object.assign({},e.rects,{placement:e.placement})):t)?t:ae(t,yt))}(s.padding,i),d=Kt(o),u="y"===l?mt:bt,f="y"===l?gt:_t,p=i.rects.reference[c]+i.rects.reference[l]-r[l]-i.rects.popper[c],m=r[l]-i.rects.reference[l],g=te(o),_=g?"y"===l?g.clientHeight||0:g.clientWidth||0:0,b=p/2-m/2,v=h[u],y=_-d[c]-h[f],w=_/2-d[c]/2+b,E=oe(v,w,y),A=l;i.modifiersData[n]=((e={})[A]=E,e.centerOffset=E-w,e)}},effect:function(t){var e=t.state,i=t.options.element,n=void 0===i?"[data-popper-arrow]":i;null!=n&&("string"!=typeof n||(n=e.elements.popper.querySelector(n)))&&Xt(e.elements.popper,n)&&(e.elements.arrow=n)},requires:["popperOffsets"],requiresIfExists:["preventOverflow"]};function ce(t){return t.split("-")[1]}var he={top:"auto",right:"auto",bottom:"auto",left:"auto"};function de(t){var e,i=t.popper,n=t.popperRect,s=t.placement,o=t.variation,r=t.offsets,a=t.position,l=t.gpuAcceleration,c=t.adaptive,h=t.roundOffsets,d=!0===h?function(t){var e=t.x,i=t.y,n=window.devicePixelRatio||1;return{x:se(se(e*n)/n)||0,y:se(se(i*n)/n)||0}}(r):"function"==typeof h?h(r):r,u=d.x,f=void 0===u?0:u,p=d.y,m=void 0===p?0:p,g=r.hasOwnProperty("x"),_=r.hasOwnProperty("y"),b=bt,v=mt,y=window;if(c){var w=te(i),E="clientHeight",A="clientWidth";w===Wt(i)&&"static"!==Yt(w=Gt(i)).position&&"absolute"===a&&(E="scrollHeight",A="scrollWidth"),w=w,s!==mt&&(s!==bt&&s!==_t||o!==Et)||(v=gt,m-=w[E]-n.height,m*=l?1:-1),s!==bt&&(s!==mt&&s!==gt||o!==Et)||(b=_t,f-=w[A]-n.width,f*=l?1:-1)}var T,O=Object.assign({position:a},c&&he);return l?Object.assign({},O,((T={})[v]=_?"0":"",T[b]=g?"0":"",T.transform=(y.devicePixelRatio||1)<=1?"translate("+f+"px, "+m+"px)":"translate3d("+f+"px, "+m+"px, 0)",T)):Object.assign({},O,((e={})[v]=_?m+"px":"",e[b]=g?f+"px":"",e.transform="",e))}const ue={name:"computeStyles",enabled:!0,phase:"beforeWrite",fn:function(t){var e=t.state,i=t.options,n=i.gpuAcceleration,s=void 0===n||n,o=i.adaptive,r=void 0===o||o,a=i.roundOffsets,l=void 0===a||a,c={placement:Ut(e.placement),variation:ce(e.placement),popper:e.elements.popper,popperRect:e.rects.popper,gpuAcceleration:s};null!=e.modifiersData.popperOffsets&&(e.styles.popper=Object.assign({},e.styles.popper,de(Object.assign({},c,{offsets:e.modifiersData.popperOffsets,position:e.options.strategy,adaptive:r,roundOffsets:l})))),null!=e.modifiersData.arrow&&(e.styles.arrow=Object.assign({},e.styles.arrow,de(Object.assign({},c,{offsets:e.modifiersData.arrow,position:"absolute",adaptive:!1,roundOffsets:l})))),e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-placement":e.placement})},data:{}};var fe={passive:!0};const pe={name:"eventListeners",enabled:!0,phase:"write",fn:function(){},effect:function(t){var e=t.state,i=t.instance,n=t.options,s=n.scroll,o=void 0===s||s,r=n.resize,a=void 0===r||r,l=Wt(e.elements.popper),c=[].concat(e.scrollParents.reference,e.scrollParents.popper);return o&&c.forEach((function(t){t.addEventListener("scroll",i.update,fe)})),a&&l.addEventListener("resize",i.update,fe),function(){o&&c.forEach((function(t){t.removeEventListener("scroll",i.update,fe)})),a&&l.removeEventListener("resize",i.update,fe)}},data:{}};var me={left:"right",right:"left",bottom:"top",top:"bottom"};function ge(t){return t.replace(/left|right|bottom|top/g,(function(t){return me[t]}))}var _e={start:"end",end:"start"};function be(t){return t.replace(/start|end/g,(function(t){return _e[t]}))}function ve(t){var e=Wt(t);return{scrollLeft:e.pageXOffset,scrollTop:e.pageYOffset}}function ye(t){return Vt(Gt(t)).left+ve(t).scrollLeft}function we(t){var e=Yt(t),i=e.overflow,n=e.overflowX,s=e.overflowY;return/auto|scroll|overlay|hidden/.test(i+s+n)}function Ee(t){return["html","body","#document"].indexOf(Rt(t))>=0?t.ownerDocument.body:zt(t)&&we(t)?t:Ee(Zt(t))}function Ae(t,e){var i;void 0===e&&(e=[]);var n=Ee(t),s=n===(null==(i=t.ownerDocument)?void 0:i.body),o=Wt(n),r=s?[o].concat(o.visualViewport||[],we(n)?n:[]):n,a=e.concat(r);return s?a:a.concat(Ae(Zt(r)))}function Te(t){return Object.assign({},t,{left:t.x,top:t.y,right:t.x+t.width,bottom:t.y+t.height})}function Oe(t,e){return e===Tt?Te(function(t){var e=Wt(t),i=Gt(t),n=e.visualViewport,s=i.clientWidth,o=i.clientHeight,r=0,a=0;return n&&(s=n.width,o=n.height,/^((?!chrome|android).)*safari/i.test(navigator.userAgent)||(r=n.offsetLeft,a=n.offsetTop)),{width:s,height:o,x:r+ye(t),y:a}}(t)):zt(e)?function(t){var e=Vt(t);return e.top=e.top+t.clientTop,e.left=e.left+t.clientLeft,e.bottom=e.top+t.clientHeight,e.right=e.left+t.clientWidth,e.width=t.clientWidth,e.height=t.clientHeight,e.x=e.left,e.y=e.top,e}(e):Te(function(t){var e,i=Gt(t),n=ve(t),s=null==(e=t.ownerDocument)?void 0:e.body,o=ie(i.scrollWidth,i.clientWidth,s?s.scrollWidth:0,s?s.clientWidth:0),r=ie(i.scrollHeight,i.clientHeight,s?s.scrollHeight:0,s?s.clientHeight:0),a=-n.scrollLeft+ye(t),l=-n.scrollTop;return"rtl"===Yt(s||i).direction&&(a+=ie(i.clientWidth,s?s.clientWidth:0)-o),{width:o,height:r,x:a,y:l}}(Gt(t)))}function Ce(t){var e,i=t.reference,n=t.element,s=t.placement,o=s?Ut(s):null,r=s?ce(s):null,a=i.x+i.width/2-n.width/2,l=i.y+i.height/2-n.height/2;switch(o){case mt:e={x:a,y:i.y-n.height};break;case gt:e={x:a,y:i.y+i.height};break;case _t:e={x:i.x+i.width,y:l};break;case bt:e={x:i.x-n.width,y:l};break;default:e={x:i.x,y:i.y}}var c=o?ee(o):null;if(null!=c){var h="y"===c?"height":"width";switch(r){case wt:e[c]=e[c]-(i[h]/2-n[h]/2);break;case Et:e[c]=e[c]+(i[h]/2-n[h]/2)}}return e}function ke(t,e){void 0===e&&(e={});var i=e,n=i.placement,s=void 0===n?t.placement:n,o=i.boundary,r=void 0===o?At:o,a=i.rootBoundary,l=void 0===a?Tt:a,c=i.elementContext,h=void 0===c?Ot:c,d=i.altBoundary,u=void 0!==d&&d,f=i.padding,p=void 0===f?0:f,m=re("number"!=typeof p?p:ae(p,yt)),g=h===Ot?Ct:Ot,_=t.rects.popper,b=t.elements[u?g:h],v=function(t,e,i){var n="clippingParents"===e?function(t){var e=Ae(Zt(t)),i=["absolute","fixed"].indexOf(Yt(t).position)>=0&&zt(t)?te(t):t;return $t(i)?e.filter((function(t){return $t(t)&&Xt(t,i)&&"body"!==Rt(t)})):[]}(t):[].concat(e),s=[].concat(n,[i]),o=s[0],r=s.reduce((function(e,i){var n=Oe(t,i);return e.top=ie(n.top,e.top),e.right=ne(n.right,e.right),e.bottom=ne(n.bottom,e.bottom),e.left=ie(n.left,e.left),e}),Oe(t,o));return r.width=r.right-r.left,r.height=r.bottom-r.top,r.x=r.left,r.y=r.top,r}($t(b)?b:b.contextElement||Gt(t.elements.popper),r,l),y=Vt(t.elements.reference),w=Ce({reference:y,element:_,strategy:"absolute",placement:s}),E=Te(Object.assign({},_,w)),A=h===Ot?E:y,T={top:v.top-A.top+m.top,bottom:A.bottom-v.bottom+m.bottom,left:v.left-A.left+m.left,right:A.right-v.right+m.right},O=t.modifiersData.offset;if(h===Ot&&O){var C=O[s];Object.keys(T).forEach((function(t){var e=[_t,gt].indexOf(t)>=0?1:-1,i=[mt,gt].indexOf(t)>=0?"y":"x";T[t]+=C[i]*e}))}return T}function Le(t,e){void 0===e&&(e={});var i=e,n=i.placement,s=i.boundary,o=i.rootBoundary,r=i.padding,a=i.flipVariations,l=i.allowedAutoPlacements,c=void 0===l?Lt:l,h=ce(n),d=h?a?kt:kt.filter((function(t){return ce(t)===h})):yt,u=d.filter((function(t){return c.indexOf(t)>=0}));0===u.length&&(u=d);var f=u.reduce((function(e,i){return e[i]=ke(t,{placement:i,boundary:s,rootBoundary:o,padding:r})[Ut(i)],e}),{});return Object.keys(f).sort((function(t,e){return f[t]-f[e]}))}const xe={name:"flip",enabled:!0,phase:"main",fn:function(t){var e=t.state,i=t.options,n=t.name;if(!e.modifiersData[n]._skip){for(var s=i.mainAxis,o=void 0===s||s,r=i.altAxis,a=void 0===r||r,l=i.fallbackPlacements,c=i.padding,h=i.boundary,d=i.rootBoundary,u=i.altBoundary,f=i.flipVariations,p=void 0===f||f,m=i.allowedAutoPlacements,g=e.options.placement,_=Ut(g),b=l||(_!==g&&p?function(t){if(Ut(t)===vt)return[];var e=ge(t);return[be(t),e,be(e)]}(g):[ge(g)]),v=[g].concat(b).reduce((function(t,i){return t.concat(Ut(i)===vt?Le(e,{placement:i,boundary:h,rootBoundary:d,padding:c,flipVariations:p,allowedAutoPlacements:m}):i)}),[]),y=e.rects.reference,w=e.rects.popper,E=new Map,A=!0,T=v[0],O=0;O=0,D=x?"width":"height",S=ke(e,{placement:C,boundary:h,rootBoundary:d,altBoundary:u,padding:c}),N=x?L?_t:bt:L?gt:mt;y[D]>w[D]&&(N=ge(N));var I=ge(N),P=[];if(o&&P.push(S[k]<=0),a&&P.push(S[N]<=0,S[I]<=0),P.every((function(t){return t}))){T=C,A=!1;break}E.set(C,P)}if(A)for(var j=function(t){var e=v.find((function(e){var i=E.get(e);if(i)return i.slice(0,t).every((function(t){return t}))}));if(e)return T=e,"break"},M=p?3:1;M>0&&"break"!==j(M);M--);e.placement!==T&&(e.modifiersData[n]._skip=!0,e.placement=T,e.reset=!0)}},requiresIfExists:["offset"],data:{_skip:!1}};function De(t,e,i){return void 0===i&&(i={x:0,y:0}),{top:t.top-e.height-i.y,right:t.right-e.width+i.x,bottom:t.bottom-e.height+i.y,left:t.left-e.width-i.x}}function Se(t){return[mt,_t,gt,bt].some((function(e){return t[e]>=0}))}const Ne={name:"hide",enabled:!0,phase:"main",requiresIfExists:["preventOverflow"],fn:function(t){var e=t.state,i=t.name,n=e.rects.reference,s=e.rects.popper,o=e.modifiersData.preventOverflow,r=ke(e,{elementContext:"reference"}),a=ke(e,{altBoundary:!0}),l=De(r,n),c=De(a,s,o),h=Se(l),d=Se(c);e.modifiersData[i]={referenceClippingOffsets:l,popperEscapeOffsets:c,isReferenceHidden:h,hasPopperEscaped:d},e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-reference-hidden":h,"data-popper-escaped":d})}},Ie={name:"offset",enabled:!0,phase:"main",requires:["popperOffsets"],fn:function(t){var e=t.state,i=t.options,n=t.name,s=i.offset,o=void 0===s?[0,0]:s,r=Lt.reduce((function(t,i){return t[i]=function(t,e,i){var n=Ut(t),s=[bt,mt].indexOf(n)>=0?-1:1,o="function"==typeof i?i(Object.assign({},e,{placement:t})):i,r=o[0],a=o[1];return r=r||0,a=(a||0)*s,[bt,_t].indexOf(n)>=0?{x:a,y:r}:{x:r,y:a}}(i,e.rects,o),t}),{}),a=r[e.placement],l=a.x,c=a.y;null!=e.modifiersData.popperOffsets&&(e.modifiersData.popperOffsets.x+=l,e.modifiersData.popperOffsets.y+=c),e.modifiersData[n]=r}},Pe={name:"popperOffsets",enabled:!0,phase:"read",fn:function(t){var e=t.state,i=t.name;e.modifiersData[i]=Ce({reference:e.rects.reference,element:e.rects.popper,strategy:"absolute",placement:e.placement})},data:{}},je={name:"preventOverflow",enabled:!0,phase:"main",fn:function(t){var e=t.state,i=t.options,n=t.name,s=i.mainAxis,o=void 0===s||s,r=i.altAxis,a=void 0!==r&&r,l=i.boundary,c=i.rootBoundary,h=i.altBoundary,d=i.padding,u=i.tether,f=void 0===u||u,p=i.tetherOffset,m=void 0===p?0:p,g=ke(e,{boundary:l,rootBoundary:c,padding:d,altBoundary:h}),_=Ut(e.placement),b=ce(e.placement),v=!b,y=ee(_),w="x"===y?"y":"x",E=e.modifiersData.popperOffsets,A=e.rects.reference,T=e.rects.popper,O="function"==typeof m?m(Object.assign({},e.rects,{placement:e.placement})):m,C={x:0,y:0};if(E){if(o||a){var k="y"===y?mt:bt,L="y"===y?gt:_t,x="y"===y?"height":"width",D=E[y],S=E[y]+g[k],N=E[y]-g[L],I=f?-T[x]/2:0,P=b===wt?A[x]:T[x],j=b===wt?-T[x]:-A[x],M=e.elements.arrow,H=f&&M?Kt(M):{width:0,height:0},B=e.modifiersData["arrow#persistent"]?e.modifiersData["arrow#persistent"].padding:{top:0,right:0,bottom:0,left:0},R=B[k],W=B[L],$=oe(0,A[x],H[x]),z=v?A[x]/2-I-$-R-O:P-$-R-O,q=v?-A[x]/2+I+$+W+O:j+$+W+O,F=e.elements.arrow&&te(e.elements.arrow),U=F?"y"===y?F.clientTop||0:F.clientLeft||0:0,V=e.modifiersData.offset?e.modifiersData.offset[e.placement][y]:0,K=E[y]+z-V-U,X=E[y]+q-V;if(o){var Y=oe(f?ne(S,K):S,D,f?ie(N,X):N);E[y]=Y,C[y]=Y-D}if(a){var Q="x"===y?mt:bt,G="x"===y?gt:_t,Z=E[w],J=Z+g[Q],tt=Z-g[G],et=oe(f?ne(J,K):J,Z,f?ie(tt,X):tt);E[w]=et,C[w]=et-Z}}e.modifiersData[n]=C}},requiresIfExists:["offset"]};function Me(t,e,i){void 0===i&&(i=!1);var n=zt(e);zt(e)&&function(t){var e=t.getBoundingClientRect();e.width,t.offsetWidth,e.height,t.offsetHeight}(e);var s,o,r=Gt(e),a=Vt(t),l={scrollLeft:0,scrollTop:0},c={x:0,y:0};return(n||!n&&!i)&&(("body"!==Rt(e)||we(r))&&(l=(s=e)!==Wt(s)&&zt(s)?{scrollLeft:(o=s).scrollLeft,scrollTop:o.scrollTop}:ve(s)),zt(e)?((c=Vt(e)).x+=e.clientLeft,c.y+=e.clientTop):r&&(c.x=ye(r))),{x:a.left+l.scrollLeft-c.x,y:a.top+l.scrollTop-c.y,width:a.width,height:a.height}}function He(t){var e=new Map,i=new Set,n=[];function s(t){i.add(t.name),[].concat(t.requires||[],t.requiresIfExists||[]).forEach((function(t){if(!i.has(t)){var n=e.get(t);n&&s(n)}})),n.push(t)}return t.forEach((function(t){e.set(t.name,t)})),t.forEach((function(t){i.has(t.name)||s(t)})),n}var Be={placement:"bottom",modifiers:[],strategy:"absolute"};function Re(){for(var t=arguments.length,e=new Array(t),i=0;ij.on(t,"mouseover",d))),this._element.focus(),this._element.setAttribute("aria-expanded",!0),this._menu.classList.add(Je),this._element.classList.add(Je),j.trigger(this._element,"shown.bs.dropdown",t)}hide(){if(c(this._element)||!this._isShown(this._menu))return;const t={relatedTarget:this._element};this._completeHide(t)}dispose(){this._popper&&this._popper.destroy(),super.dispose()}update(){this._inNavbar=this._detectNavbar(),this._popper&&this._popper.update()}_completeHide(t){j.trigger(this._element,"hide.bs.dropdown",t).defaultPrevented||("ontouchstart"in document.documentElement&&[].concat(...document.body.children).forEach((t=>j.off(t,"mouseover",d))),this._popper&&this._popper.destroy(),this._menu.classList.remove(Je),this._element.classList.remove(Je),this._element.setAttribute("aria-expanded","false"),U.removeDataAttribute(this._menu,"popper"),j.trigger(this._element,"hidden.bs.dropdown",t))}_getConfig(t){if(t={...this.constructor.Default,...U.getDataAttributes(this._element),...t},a(Ue,t,this.constructor.DefaultType),"object"==typeof t.reference&&!o(t.reference)&&"function"!=typeof t.reference.getBoundingClientRect)throw new TypeError(`${Ue.toUpperCase()}: Option "reference" provided type "object" without a required "getBoundingClientRect" method.`);return t}_createPopper(t){if(void 0===Fe)throw new TypeError("Bootstrap's dropdowns require Popper (https://popper.js.org)");let e=this._element;"parent"===this._config.reference?e=t:o(this._config.reference)?e=r(this._config.reference):"object"==typeof this._config.reference&&(e=this._config.reference);const i=this._getPopperConfig(),n=i.modifiers.find((t=>"applyStyles"===t.name&&!1===t.enabled));this._popper=qe(e,this._menu,i),n&&U.setDataAttribute(this._menu,"popper","static")}_isShown(t=this._element){return t.classList.contains(Je)}_getMenuElement(){return V.next(this._element,ei)[0]}_getPlacement(){const t=this._element.parentNode;if(t.classList.contains("dropend"))return ri;if(t.classList.contains("dropstart"))return ai;const e="end"===getComputedStyle(this._menu).getPropertyValue("--bs-position").trim();return t.classList.contains("dropup")?e?ni:ii:e?oi:si}_detectNavbar(){return null!==this._element.closest(".navbar")}_getOffset(){const{offset:t}=this._config;return"string"==typeof t?t.split(",").map((t=>Number.parseInt(t,10))):"function"==typeof t?e=>t(e,this._element):t}_getPopperConfig(){const t={placement:this._getPlacement(),modifiers:[{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"offset",options:{offset:this._getOffset()}}]};return"static"===this._config.display&&(t.modifiers=[{name:"applyStyles",enabled:!1}]),{...t,..."function"==typeof this._config.popperConfig?this._config.popperConfig(t):this._config.popperConfig}}_selectMenuItem({key:t,target:e}){const i=V.find(".dropdown-menu .dropdown-item:not(.disabled):not(:disabled)",this._menu).filter(l);i.length&&v(i,e,t===Ye,!i.includes(e)).focus()}static jQueryInterface(t){return this.each((function(){const e=hi.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}static clearMenus(t){if(t&&(2===t.button||"keyup"===t.type&&"Tab"!==t.key))return;const e=V.find(ti);for(let i=0,n=e.length;ie+t)),this._setElementAttributes(di,"paddingRight",(e=>e+t)),this._setElementAttributes(ui,"marginRight",(e=>e-t))}_disableOverFlow(){this._saveInitialAttribute(this._element,"overflow"),this._element.style.overflow="hidden"}_setElementAttributes(t,e,i){const n=this.getWidth();this._applyManipulationCallback(t,(t=>{if(t!==this._element&&window.innerWidth>t.clientWidth+n)return;this._saveInitialAttribute(t,e);const s=window.getComputedStyle(t)[e];t.style[e]=`${i(Number.parseFloat(s))}px`}))}reset(){this._resetElementAttributes(this._element,"overflow"),this._resetElementAttributes(this._element,"paddingRight"),this._resetElementAttributes(di,"paddingRight"),this._resetElementAttributes(ui,"marginRight")}_saveInitialAttribute(t,e){const i=t.style[e];i&&U.setDataAttribute(t,e,i)}_resetElementAttributes(t,e){this._applyManipulationCallback(t,(t=>{const i=U.getDataAttribute(t,e);void 0===i?t.style.removeProperty(e):(U.removeDataAttribute(t,e),t.style[e]=i)}))}_applyManipulationCallback(t,e){o(t)?e(t):V.find(t,this._element).forEach(e)}isOverflowing(){return this.getWidth()>0}}const pi={className:"modal-backdrop",isVisible:!0,isAnimated:!1,rootElement:"body",clickCallback:null},mi={className:"string",isVisible:"boolean",isAnimated:"boolean",rootElement:"(element|string)",clickCallback:"(function|null)"},gi="show",_i="mousedown.bs.backdrop";class bi{constructor(t){this._config=this._getConfig(t),this._isAppended=!1,this._element=null}show(t){this._config.isVisible?(this._append(),this._config.isAnimated&&u(this._getElement()),this._getElement().classList.add(gi),this._emulateAnimation((()=>{_(t)}))):_(t)}hide(t){this._config.isVisible?(this._getElement().classList.remove(gi),this._emulateAnimation((()=>{this.dispose(),_(t)}))):_(t)}_getElement(){if(!this._element){const t=document.createElement("div");t.className=this._config.className,this._config.isAnimated&&t.classList.add("fade"),this._element=t}return this._element}_getConfig(t){return(t={...pi,..."object"==typeof t?t:{}}).rootElement=r(t.rootElement),a("backdrop",t,mi),t}_append(){this._isAppended||(this._config.rootElement.append(this._getElement()),j.on(this._getElement(),_i,(()=>{_(this._config.clickCallback)})),this._isAppended=!0)}dispose(){this._isAppended&&(j.off(this._element,_i),this._element.remove(),this._isAppended=!1)}_emulateAnimation(t){b(t,this._getElement(),this._config.isAnimated)}}const vi={trapElement:null,autofocus:!0},yi={trapElement:"element",autofocus:"boolean"},wi=".bs.focustrap",Ei="backward";class Ai{constructor(t){this._config=this._getConfig(t),this._isActive=!1,this._lastTabNavDirection=null}activate(){const{trapElement:t,autofocus:e}=this._config;this._isActive||(e&&t.focus(),j.off(document,wi),j.on(document,"focusin.bs.focustrap",(t=>this._handleFocusin(t))),j.on(document,"keydown.tab.bs.focustrap",(t=>this._handleKeydown(t))),this._isActive=!0)}deactivate(){this._isActive&&(this._isActive=!1,j.off(document,wi))}_handleFocusin(t){const{target:e}=t,{trapElement:i}=this._config;if(e===document||e===i||i.contains(e))return;const n=V.focusableChildren(i);0===n.length?i.focus():this._lastTabNavDirection===Ei?n[n.length-1].focus():n[0].focus()}_handleKeydown(t){"Tab"===t.key&&(this._lastTabNavDirection=t.shiftKey?Ei:"forward")}_getConfig(t){return t={...vi,..."object"==typeof t?t:{}},a("focustrap",t,yi),t}}const Ti="modal",Oi="Escape",Ci={backdrop:!0,keyboard:!0,focus:!0},ki={backdrop:"(boolean|string)",keyboard:"boolean",focus:"boolean"},Li="hidden.bs.modal",xi="show.bs.modal",Di="resize.bs.modal",Si="click.dismiss.bs.modal",Ni="keydown.dismiss.bs.modal",Ii="mousedown.dismiss.bs.modal",Pi="modal-open",ji="show",Mi="modal-static";class Hi extends B{constructor(t,e){super(t),this._config=this._getConfig(e),this._dialog=V.findOne(".modal-dialog",this._element),this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._isShown=!1,this._ignoreBackdropClick=!1,this._isTransitioning=!1,this._scrollBar=new fi}static get Default(){return Ci}static get NAME(){return Ti}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||this._isTransitioning||j.trigger(this._element,xi,{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._isAnimated()&&(this._isTransitioning=!0),this._scrollBar.hide(),document.body.classList.add(Pi),this._adjustDialog(),this._setEscapeEvent(),this._setResizeEvent(),j.on(this._dialog,Ii,(()=>{j.one(this._element,"mouseup.dismiss.bs.modal",(t=>{t.target===this._element&&(this._ignoreBackdropClick=!0)}))})),this._showBackdrop((()=>this._showElement(t))))}hide(){if(!this._isShown||this._isTransitioning)return;if(j.trigger(this._element,"hide.bs.modal").defaultPrevented)return;this._isShown=!1;const t=this._isAnimated();t&&(this._isTransitioning=!0),this._setEscapeEvent(),this._setResizeEvent(),this._focustrap.deactivate(),this._element.classList.remove(ji),j.off(this._element,Si),j.off(this._dialog,Ii),this._queueCallback((()=>this._hideModal()),this._element,t)}dispose(){[window,this._dialog].forEach((t=>j.off(t,".bs.modal"))),this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}handleUpdate(){this._adjustDialog()}_initializeBackDrop(){return new bi({isVisible:Boolean(this._config.backdrop),isAnimated:this._isAnimated()})}_initializeFocusTrap(){return new Ai({trapElement:this._element})}_getConfig(t){return t={...Ci,...U.getDataAttributes(this._element),..."object"==typeof t?t:{}},a(Ti,t,ki),t}_showElement(t){const e=this._isAnimated(),i=V.findOne(".modal-body",this._dialog);this._element.parentNode&&this._element.parentNode.nodeType===Node.ELEMENT_NODE||document.body.append(this._element),this._element.style.display="block",this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.scrollTop=0,i&&(i.scrollTop=0),e&&u(this._element),this._element.classList.add(ji),this._queueCallback((()=>{this._config.focus&&this._focustrap.activate(),this._isTransitioning=!1,j.trigger(this._element,"shown.bs.modal",{relatedTarget:t})}),this._dialog,e)}_setEscapeEvent(){this._isShown?j.on(this._element,Ni,(t=>{this._config.keyboard&&t.key===Oi?(t.preventDefault(),this.hide()):this._config.keyboard||t.key!==Oi||this._triggerBackdropTransition()})):j.off(this._element,Ni)}_setResizeEvent(){this._isShown?j.on(window,Di,(()=>this._adjustDialog())):j.off(window,Di)}_hideModal(){this._element.style.display="none",this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._isTransitioning=!1,this._backdrop.hide((()=>{document.body.classList.remove(Pi),this._resetAdjustments(),this._scrollBar.reset(),j.trigger(this._element,Li)}))}_showBackdrop(t){j.on(this._element,Si,(t=>{this._ignoreBackdropClick?this._ignoreBackdropClick=!1:t.target===t.currentTarget&&(!0===this._config.backdrop?this.hide():"static"===this._config.backdrop&&this._triggerBackdropTransition())})),this._backdrop.show(t)}_isAnimated(){return this._element.classList.contains("fade")}_triggerBackdropTransition(){if(j.trigger(this._element,"hidePrevented.bs.modal").defaultPrevented)return;const{classList:t,scrollHeight:e,style:i}=this._element,n=e>document.documentElement.clientHeight;!n&&"hidden"===i.overflowY||t.contains(Mi)||(n||(i.overflowY="hidden"),t.add(Mi),this._queueCallback((()=>{t.remove(Mi),n||this._queueCallback((()=>{i.overflowY=""}),this._dialog)}),this._dialog),this._element.focus())}_adjustDialog(){const t=this._element.scrollHeight>document.documentElement.clientHeight,e=this._scrollBar.getWidth(),i=e>0;(!i&&t&&!m()||i&&!t&&m())&&(this._element.style.paddingLeft=`${e}px`),(i&&!t&&!m()||!i&&t&&m())&&(this._element.style.paddingRight=`${e}px`)}_resetAdjustments(){this._element.style.paddingLeft="",this._element.style.paddingRight=""}static jQueryInterface(t,e){return this.each((function(){const i=Hi.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t](e)}}))}}j.on(document,"click.bs.modal.data-api",'[data-bs-toggle="modal"]',(function(t){const e=n(this);["A","AREA"].includes(this.tagName)&&t.preventDefault(),j.one(e,xi,(t=>{t.defaultPrevented||j.one(e,Li,(()=>{l(this)&&this.focus()}))}));const i=V.findOne(".modal.show");i&&Hi.getInstance(i).hide(),Hi.getOrCreateInstance(e).toggle(this)})),R(Hi),g(Hi);const Bi="offcanvas",Ri={backdrop:!0,keyboard:!0,scroll:!1},Wi={backdrop:"boolean",keyboard:"boolean",scroll:"boolean"},$i="show",zi=".offcanvas.show",qi="hidden.bs.offcanvas";class Fi extends B{constructor(t,e){super(t),this._config=this._getConfig(e),this._isShown=!1,this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._addEventListeners()}static get NAME(){return Bi}static get Default(){return Ri}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||j.trigger(this._element,"show.bs.offcanvas",{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._element.style.visibility="visible",this._backdrop.show(),this._config.scroll||(new fi).hide(),this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.classList.add($i),this._queueCallback((()=>{this._config.scroll||this._focustrap.activate(),j.trigger(this._element,"shown.bs.offcanvas",{relatedTarget:t})}),this._element,!0))}hide(){this._isShown&&(j.trigger(this._element,"hide.bs.offcanvas").defaultPrevented||(this._focustrap.deactivate(),this._element.blur(),this._isShown=!1,this._element.classList.remove($i),this._backdrop.hide(),this._queueCallback((()=>{this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._element.style.visibility="hidden",this._config.scroll||(new fi).reset(),j.trigger(this._element,qi)}),this._element,!0)))}dispose(){this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}_getConfig(t){return t={...Ri,...U.getDataAttributes(this._element),..."object"==typeof t?t:{}},a(Bi,t,Wi),t}_initializeBackDrop(){return new bi({className:"offcanvas-backdrop",isVisible:this._config.backdrop,isAnimated:!0,rootElement:this._element.parentNode,clickCallback:()=>this.hide()})}_initializeFocusTrap(){return new Ai({trapElement:this._element})}_addEventListeners(){j.on(this._element,"keydown.dismiss.bs.offcanvas",(t=>{this._config.keyboard&&"Escape"===t.key&&this.hide()}))}static jQueryInterface(t){return this.each((function(){const e=Fi.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}j.on(document,"click.bs.offcanvas.data-api",'[data-bs-toggle="offcanvas"]',(function(t){const e=n(this);if(["A","AREA"].includes(this.tagName)&&t.preventDefault(),c(this))return;j.one(e,qi,(()=>{l(this)&&this.focus()}));const i=V.findOne(zi);i&&i!==e&&Fi.getInstance(i).hide(),Fi.getOrCreateInstance(e).toggle(this)})),j.on(window,"load.bs.offcanvas.data-api",(()=>V.find(zi).forEach((t=>Fi.getOrCreateInstance(t).show())))),R(Fi),g(Fi);const Ui=new Set(["background","cite","href","itemtype","longdesc","poster","src","xlink:href"]),Vi=/^(?:(?:https?|mailto|ftp|tel|file|sms):|[^#&/:?]*(?:[#/?]|$))/i,Ki=/^data:(?:image\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\/(?:mpeg|mp4|ogg|webm)|audio\/(?:mp3|oga|ogg|opus));base64,[\d+/a-z]+=*$/i,Xi=(t,e)=>{const i=t.nodeName.toLowerCase();if(e.includes(i))return!Ui.has(i)||Boolean(Vi.test(t.nodeValue)||Ki.test(t.nodeValue));const n=e.filter((t=>t instanceof RegExp));for(let t=0,e=n.length;t{Xi(t,r)||i.removeAttribute(t.nodeName)}))}return n.body.innerHTML}const Qi="tooltip",Gi=new Set(["sanitize","allowList","sanitizeFn"]),Zi={animation:"boolean",template:"string",title:"(string|element|function)",trigger:"string",delay:"(number|object)",html:"boolean",selector:"(string|boolean)",placement:"(string|function)",offset:"(array|string|function)",container:"(string|element|boolean)",fallbackPlacements:"array",boundary:"(string|element)",customClass:"(string|function)",sanitize:"boolean",sanitizeFn:"(null|function)",allowList:"object",popperConfig:"(null|object|function)"},Ji={AUTO:"auto",TOP:"top",RIGHT:m()?"left":"right",BOTTOM:"bottom",LEFT:m()?"right":"left"},tn={animation:!0,template:'

',trigger:"hover focus",title:"",delay:0,html:!1,selector:!1,placement:"top",offset:[0,0],container:!1,fallbackPlacements:["top","right","bottom","left"],boundary:"clippingParents",customClass:"",sanitize:!0,sanitizeFn:null,allowList:{"*":["class","dir","id","lang","role",/^aria-[\w-]*$/i],a:["target","href","title","rel"],area:[],b:[],br:[],col:[],code:[],div:[],em:[],hr:[],h1:[],h2:[],h3:[],h4:[],h5:[],h6:[],i:[],img:["src","srcset","alt","title","width","height"],li:[],ol:[],p:[],pre:[],s:[],small:[],span:[],sub:[],sup:[],strong:[],u:[],ul:[]},popperConfig:null},en={HIDE:"hide.bs.tooltip",HIDDEN:"hidden.bs.tooltip",SHOW:"show.bs.tooltip",SHOWN:"shown.bs.tooltip",INSERTED:"inserted.bs.tooltip",CLICK:"click.bs.tooltip",FOCUSIN:"focusin.bs.tooltip",FOCUSOUT:"focusout.bs.tooltip",MOUSEENTER:"mouseenter.bs.tooltip",MOUSELEAVE:"mouseleave.bs.tooltip"},nn="fade",sn="show",on="show",rn="out",an=".tooltip-inner",ln=".modal",cn="hide.bs.modal",hn="hover",dn="focus";class un extends B{constructor(t,e){if(void 0===Fe)throw new TypeError("Bootstrap's tooltips require Popper (https://popper.js.org)");super(t),this._isEnabled=!0,this._timeout=0,this._hoverState="",this._activeTrigger={},this._popper=null,this._config=this._getConfig(e),this.tip=null,this._setListeners()}static get Default(){return tn}static get NAME(){return Qi}static get Event(){return en}static get DefaultType(){return Zi}enable(){this._isEnabled=!0}disable(){this._isEnabled=!1}toggleEnabled(){this._isEnabled=!this._isEnabled}toggle(t){if(this._isEnabled)if(t){const e=this._initializeOnDelegatedTarget(t);e._activeTrigger.click=!e._activeTrigger.click,e._isWithActiveTrigger()?e._enter(null,e):e._leave(null,e)}else{if(this.getTipElement().classList.contains(sn))return void this._leave(null,this);this._enter(null,this)}}dispose(){clearTimeout(this._timeout),j.off(this._element.closest(ln),cn,this._hideModalHandler),this.tip&&this.tip.remove(),this._disposePopper(),super.dispose()}show(){if("none"===this._element.style.display)throw new Error("Please use show on visible elements");if(!this.isWithContent()||!this._isEnabled)return;const t=j.trigger(this._element,this.constructor.Event.SHOW),e=h(this._element),i=null===e?this._element.ownerDocument.documentElement.contains(this._element):e.contains(this._element);if(t.defaultPrevented||!i)return;"tooltip"===this.constructor.NAME&&this.tip&&this.getTitle()!==this.tip.querySelector(an).innerHTML&&(this._disposePopper(),this.tip.remove(),this.tip=null);const n=this.getTipElement(),s=(t=>{do{t+=Math.floor(1e6*Math.random())}while(document.getElementById(t));return t})(this.constructor.NAME);n.setAttribute("id",s),this._element.setAttribute("aria-describedby",s),this._config.animation&&n.classList.add(nn);const o="function"==typeof this._config.placement?this._config.placement.call(this,n,this._element):this._config.placement,r=this._getAttachment(o);this._addAttachmentClass(r);const{container:a}=this._config;H.set(n,this.constructor.DATA_KEY,this),this._element.ownerDocument.documentElement.contains(this.tip)||(a.append(n),j.trigger(this._element,this.constructor.Event.INSERTED)),this._popper?this._popper.update():this._popper=qe(this._element,n,this._getPopperConfig(r)),n.classList.add(sn);const l=this._resolvePossibleFunction(this._config.customClass);l&&n.classList.add(...l.split(" ")),"ontouchstart"in document.documentElement&&[].concat(...document.body.children).forEach((t=>{j.on(t,"mouseover",d)}));const c=this.tip.classList.contains(nn);this._queueCallback((()=>{const t=this._hoverState;this._hoverState=null,j.trigger(this._element,this.constructor.Event.SHOWN),t===rn&&this._leave(null,this)}),this.tip,c)}hide(){if(!this._popper)return;const t=this.getTipElement();if(j.trigger(this._element,this.constructor.Event.HIDE).defaultPrevented)return;t.classList.remove(sn),"ontouchstart"in document.documentElement&&[].concat(...document.body.children).forEach((t=>j.off(t,"mouseover",d))),this._activeTrigger.click=!1,this._activeTrigger.focus=!1,this._activeTrigger.hover=!1;const e=this.tip.classList.contains(nn);this._queueCallback((()=>{this._isWithActiveTrigger()||(this._hoverState!==on&&t.remove(),this._cleanTipClass(),this._element.removeAttribute("aria-describedby"),j.trigger(this._element,this.constructor.Event.HIDDEN),this._disposePopper())}),this.tip,e),this._hoverState=""}update(){null!==this._popper&&this._popper.update()}isWithContent(){return Boolean(this.getTitle())}getTipElement(){if(this.tip)return this.tip;const t=document.createElement("div");t.innerHTML=this._config.template;const e=t.children[0];return this.setContent(e),e.classList.remove(nn,sn),this.tip=e,this.tip}setContent(t){this._sanitizeAndSetContent(t,this.getTitle(),an)}_sanitizeAndSetContent(t,e,i){const n=V.findOne(i,t);e||!n?this.setElementContent(n,e):n.remove()}setElementContent(t,e){if(null!==t)return o(e)?(e=r(e),void(this._config.html?e.parentNode!==t&&(t.innerHTML="",t.append(e)):t.textContent=e.textContent)):void(this._config.html?(this._config.sanitize&&(e=Yi(e,this._config.allowList,this._config.sanitizeFn)),t.innerHTML=e):t.textContent=e)}getTitle(){const t=this._element.getAttribute("data-bs-original-title")||this._config.title;return this._resolvePossibleFunction(t)}updateAttachment(t){return"right"===t?"end":"left"===t?"start":t}_initializeOnDelegatedTarget(t,e){return e||this.constructor.getOrCreateInstance(t.delegateTarget,this._getDelegateConfig())}_getOffset(){const{offset:t}=this._config;return"string"==typeof t?t.split(",").map((t=>Number.parseInt(t,10))):"function"==typeof t?e=>t(e,this._element):t}_resolvePossibleFunction(t){return"function"==typeof t?t.call(this._element):t}_getPopperConfig(t){const e={placement:t,modifiers:[{name:"flip",options:{fallbackPlacements:this._config.fallbackPlacements}},{name:"offset",options:{offset:this._getOffset()}},{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"arrow",options:{element:`.${this.constructor.NAME}-arrow`}},{name:"onChange",enabled:!0,phase:"afterWrite",fn:t=>this._handlePopperPlacementChange(t)}],onFirstUpdate:t=>{t.options.placement!==t.placement&&this._handlePopperPlacementChange(t)}};return{...e,..."function"==typeof this._config.popperConfig?this._config.popperConfig(e):this._config.popperConfig}}_addAttachmentClass(t){this.getTipElement().classList.add(`${this._getBasicClassPrefix()}-${this.updateAttachment(t)}`)}_getAttachment(t){return Ji[t.toUpperCase()]}_setListeners(){this._config.trigger.split(" ").forEach((t=>{if("click"===t)j.on(this._element,this.constructor.Event.CLICK,this._config.selector,(t=>this.toggle(t)));else if("manual"!==t){const e=t===hn?this.constructor.Event.MOUSEENTER:this.constructor.Event.FOCUSIN,i=t===hn?this.constructor.Event.MOUSELEAVE:this.constructor.Event.FOCUSOUT;j.on(this._element,e,this._config.selector,(t=>this._enter(t))),j.on(this._element,i,this._config.selector,(t=>this._leave(t)))}})),this._hideModalHandler=()=>{this._element&&this.hide()},j.on(this._element.closest(ln),cn,this._hideModalHandler),this._config.selector?this._config={...this._config,trigger:"manual",selector:""}:this._fixTitle()}_fixTitle(){const t=this._element.getAttribute("title"),e=typeof this._element.getAttribute("data-bs-original-title");(t||"string"!==e)&&(this._element.setAttribute("data-bs-original-title",t||""),!t||this._element.getAttribute("aria-label")||this._element.textContent||this._element.setAttribute("aria-label",t),this._element.setAttribute("title",""))}_enter(t,e){e=this._initializeOnDelegatedTarget(t,e),t&&(e._activeTrigger["focusin"===t.type?dn:hn]=!0),e.getTipElement().classList.contains(sn)||e._hoverState===on?e._hoverState=on:(clearTimeout(e._timeout),e._hoverState=on,e._config.delay&&e._config.delay.show?e._timeout=setTimeout((()=>{e._hoverState===on&&e.show()}),e._config.delay.show):e.show())}_leave(t,e){e=this._initializeOnDelegatedTarget(t,e),t&&(e._activeTrigger["focusout"===t.type?dn:hn]=e._element.contains(t.relatedTarget)),e._isWithActiveTrigger()||(clearTimeout(e._timeout),e._hoverState=rn,e._config.delay&&e._config.delay.hide?e._timeout=setTimeout((()=>{e._hoverState===rn&&e.hide()}),e._config.delay.hide):e.hide())}_isWithActiveTrigger(){for(const t in this._activeTrigger)if(this._activeTrigger[t])return!0;return!1}_getConfig(t){const e=U.getDataAttributes(this._element);return Object.keys(e).forEach((t=>{Gi.has(t)&&delete e[t]})),(t={...this.constructor.Default,...e,..."object"==typeof t&&t?t:{}}).container=!1===t.container?document.body:r(t.container),"number"==typeof t.delay&&(t.delay={show:t.delay,hide:t.delay}),"number"==typeof t.title&&(t.title=t.title.toString()),"number"==typeof t.content&&(t.content=t.content.toString()),a(Qi,t,this.constructor.DefaultType),t.sanitize&&(t.template=Yi(t.template,t.allowList,t.sanitizeFn)),t}_getDelegateConfig(){const t={};for(const e in this._config)this.constructor.Default[e]!==this._config[e]&&(t[e]=this._config[e]);return t}_cleanTipClass(){const t=this.getTipElement(),e=new RegExp(`(^|\\s)${this._getBasicClassPrefix()}\\S+`,"g"),i=t.getAttribute("class").match(e);null!==i&&i.length>0&&i.map((t=>t.trim())).forEach((e=>t.classList.remove(e)))}_getBasicClassPrefix(){return"bs-tooltip"}_handlePopperPlacementChange(t){const{state:e}=t;e&&(this.tip=e.elements.popper,this._cleanTipClass(),this._addAttachmentClass(this._getAttachment(e.placement)))}_disposePopper(){this._popper&&(this._popper.destroy(),this._popper=null)}static jQueryInterface(t){return this.each((function(){const e=un.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}g(un);const fn={...un.Default,placement:"right",offset:[0,8],trigger:"click",content:"",template:''},pn={...un.DefaultType,content:"(string|element|function)"},mn={HIDE:"hide.bs.popover",HIDDEN:"hidden.bs.popover",SHOW:"show.bs.popover",SHOWN:"shown.bs.popover",INSERTED:"inserted.bs.popover",CLICK:"click.bs.popover",FOCUSIN:"focusin.bs.popover",FOCUSOUT:"focusout.bs.popover",MOUSEENTER:"mouseenter.bs.popover",MOUSELEAVE:"mouseleave.bs.popover"};class gn extends un{static get Default(){return fn}static get NAME(){return"popover"}static get Event(){return mn}static get DefaultType(){return pn}isWithContent(){return this.getTitle()||this._getContent()}setContent(t){this._sanitizeAndSetContent(t,this.getTitle(),".popover-header"),this._sanitizeAndSetContent(t,this._getContent(),".popover-body")}_getContent(){return this._resolvePossibleFunction(this._config.content)}_getBasicClassPrefix(){return"bs-popover"}static jQueryInterface(t){return this.each((function(){const e=gn.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}g(gn);const _n="scrollspy",bn={offset:10,method:"auto",target:""},vn={offset:"number",method:"string",target:"(string|element)"},yn="active",wn=".nav-link, .list-group-item, .dropdown-item",En="position";class An extends B{constructor(t,e){super(t),this._scrollElement="BODY"===this._element.tagName?window:this._element,this._config=this._getConfig(e),this._offsets=[],this._targets=[],this._activeTarget=null,this._scrollHeight=0,j.on(this._scrollElement,"scroll.bs.scrollspy",(()=>this._process())),this.refresh(),this._process()}static get Default(){return bn}static get NAME(){return _n}refresh(){const t=this._scrollElement===this._scrollElement.window?"offset":En,e="auto"===this._config.method?t:this._config.method,n=e===En?this._getScrollTop():0;this._offsets=[],this._targets=[],this._scrollHeight=this._getScrollHeight(),V.find(wn,this._config.target).map((t=>{const s=i(t),o=s?V.findOne(s):null;if(o){const t=o.getBoundingClientRect();if(t.width||t.height)return[U[e](o).top+n,s]}return null})).filter((t=>t)).sort(((t,e)=>t[0]-e[0])).forEach((t=>{this._offsets.push(t[0]),this._targets.push(t[1])}))}dispose(){j.off(this._scrollElement,".bs.scrollspy"),super.dispose()}_getConfig(t){return(t={...bn,...U.getDataAttributes(this._element),..."object"==typeof t&&t?t:{}}).target=r(t.target)||document.documentElement,a(_n,t,vn),t}_getScrollTop(){return this._scrollElement===window?this._scrollElement.pageYOffset:this._scrollElement.scrollTop}_getScrollHeight(){return this._scrollElement.scrollHeight||Math.max(document.body.scrollHeight,document.documentElement.scrollHeight)}_getOffsetHeight(){return this._scrollElement===window?window.innerHeight:this._scrollElement.getBoundingClientRect().height}_process(){const t=this._getScrollTop()+this._config.offset,e=this._getScrollHeight(),i=this._config.offset+e-this._getOffsetHeight();if(this._scrollHeight!==e&&this.refresh(),t>=i){const t=this._targets[this._targets.length-1];this._activeTarget!==t&&this._activate(t)}else{if(this._activeTarget&&t0)return this._activeTarget=null,void this._clear();for(let e=this._offsets.length;e--;)this._activeTarget!==this._targets[e]&&t>=this._offsets[e]&&(void 0===this._offsets[e+1]||t`${e}[data-bs-target="${t}"],${e}[href="${t}"]`)),i=V.findOne(e.join(","),this._config.target);i.classList.add(yn),i.classList.contains("dropdown-item")?V.findOne(".dropdown-toggle",i.closest(".dropdown")).classList.add(yn):V.parents(i,".nav, .list-group").forEach((t=>{V.prev(t,".nav-link, .list-group-item").forEach((t=>t.classList.add(yn))),V.prev(t,".nav-item").forEach((t=>{V.children(t,".nav-link").forEach((t=>t.classList.add(yn)))}))})),j.trigger(this._scrollElement,"activate.bs.scrollspy",{relatedTarget:t})}_clear(){V.find(wn,this._config.target).filter((t=>t.classList.contains(yn))).forEach((t=>t.classList.remove(yn)))}static jQueryInterface(t){return this.each((function(){const e=An.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}j.on(window,"load.bs.scrollspy.data-api",(()=>{V.find('[data-bs-spy="scroll"]').forEach((t=>new An(t)))})),g(An);const Tn="active",On="fade",Cn="show",kn=".active",Ln=":scope > li > .active";class xn extends B{static get NAME(){return"tab"}show(){if(this._element.parentNode&&this._element.parentNode.nodeType===Node.ELEMENT_NODE&&this._element.classList.contains(Tn))return;let t;const e=n(this._element),i=this._element.closest(".nav, .list-group");if(i){const e="UL"===i.nodeName||"OL"===i.nodeName?Ln:kn;t=V.find(e,i),t=t[t.length-1]}const s=t?j.trigger(t,"hide.bs.tab",{relatedTarget:this._element}):null;if(j.trigger(this._element,"show.bs.tab",{relatedTarget:t}).defaultPrevented||null!==s&&s.defaultPrevented)return;this._activate(this._element,i);const o=()=>{j.trigger(t,"hidden.bs.tab",{relatedTarget:this._element}),j.trigger(this._element,"shown.bs.tab",{relatedTarget:t})};e?this._activate(e,e.parentNode,o):o()}_activate(t,e,i){const n=(!e||"UL"!==e.nodeName&&"OL"!==e.nodeName?V.children(e,kn):V.find(Ln,e))[0],s=i&&n&&n.classList.contains(On),o=()=>this._transitionComplete(t,n,i);n&&s?(n.classList.remove(Cn),this._queueCallback(o,t,!0)):o()}_transitionComplete(t,e,i){if(e){e.classList.remove(Tn);const t=V.findOne(":scope > .dropdown-menu .active",e.parentNode);t&&t.classList.remove(Tn),"tab"===e.getAttribute("role")&&e.setAttribute("aria-selected",!1)}t.classList.add(Tn),"tab"===t.getAttribute("role")&&t.setAttribute("aria-selected",!0),u(t),t.classList.contains(On)&&t.classList.add(Cn);let n=t.parentNode;if(n&&"LI"===n.nodeName&&(n=n.parentNode),n&&n.classList.contains("dropdown-menu")){const e=t.closest(".dropdown");e&&V.find(".dropdown-toggle",e).forEach((t=>t.classList.add(Tn))),t.setAttribute("aria-expanded",!0)}i&&i()}static jQueryInterface(t){return this.each((function(){const e=xn.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}j.on(document,"click.bs.tab.data-api",'[data-bs-toggle="tab"], [data-bs-toggle="pill"], [data-bs-toggle="list"]',(function(t){["A","AREA"].includes(this.tagName)&&t.preventDefault(),c(this)||xn.getOrCreateInstance(this).show()})),g(xn);const Dn="toast",Sn="hide",Nn="show",In="showing",Pn={animation:"boolean",autohide:"boolean",delay:"number"},jn={animation:!0,autohide:!0,delay:5e3};class Mn extends B{constructor(t,e){super(t),this._config=this._getConfig(e),this._timeout=null,this._hasMouseInteraction=!1,this._hasKeyboardInteraction=!1,this._setListeners()}static get DefaultType(){return Pn}static get Default(){return jn}static get NAME(){return Dn}show(){j.trigger(this._element,"show.bs.toast").defaultPrevented||(this._clearTimeout(),this._config.animation&&this._element.classList.add("fade"),this._element.classList.remove(Sn),u(this._element),this._element.classList.add(Nn),this._element.classList.add(In),this._queueCallback((()=>{this._element.classList.remove(In),j.trigger(this._element,"shown.bs.toast"),this._maybeScheduleHide()}),this._element,this._config.animation))}hide(){this._element.classList.contains(Nn)&&(j.trigger(this._element,"hide.bs.toast").defaultPrevented||(this._element.classList.add(In),this._queueCallback((()=>{this._element.classList.add(Sn),this._element.classList.remove(In),this._element.classList.remove(Nn),j.trigger(this._element,"hidden.bs.toast")}),this._element,this._config.animation)))}dispose(){this._clearTimeout(),this._element.classList.contains(Nn)&&this._element.classList.remove(Nn),super.dispose()}_getConfig(t){return t={...jn,...U.getDataAttributes(this._element),..."object"==typeof t&&t?t:{}},a(Dn,t,this.constructor.DefaultType),t}_maybeScheduleHide(){this._config.autohide&&(this._hasMouseInteraction||this._hasKeyboardInteraction||(this._timeout=setTimeout((()=>{this.hide()}),this._config.delay)))}_onInteraction(t,e){switch(t.type){case"mouseover":case"mouseout":this._hasMouseInteraction=e;break;case"focusin":case"focusout":this._hasKeyboardInteraction=e}if(e)return void this._clearTimeout();const i=t.relatedTarget;this._element===i||this._element.contains(i)||this._maybeScheduleHide()}_setListeners(){j.on(this._element,"mouseover.bs.toast",(t=>this._onInteraction(t,!0))),j.on(this._element,"mouseout.bs.toast",(t=>this._onInteraction(t,!1))),j.on(this._element,"focusin.bs.toast",(t=>this._onInteraction(t,!0))),j.on(this._element,"focusout.bs.toast",(t=>this._onInteraction(t,!1)))}_clearTimeout(){clearTimeout(this._timeout),this._timeout=null}static jQueryInterface(t){return this.each((function(){const e=Mn.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}return R(Mn),g(Mn),{Alert:W,Button:z,Carousel:st,Collapse:pt,Dropdown:hi,Modal:Hi,Offcanvas:Fi,Popover:gn,ScrollSpy:An,Tab:xn,Toast:Mn,Tooltip:un}})); +//# sourceMappingURL=bootstrap.bundle.min.js.map \ No newline at end of file diff --git a/public/assets/js/custom.js b/public/assets/js/custom.js new file mode 100644 index 0000000..e69de29 diff --git a/public/assets/js/index.js b/public/assets/js/index.js new file mode 100644 index 0000000..e543de8 --- /dev/null +++ b/public/assets/js/index.js @@ -0,0 +1,177 @@ + +const SIGNAL_DATA = [ + { type: 'BUY', price: '2034.50', tp: '2042.00', sl: '2029.00', confidence: 89, time: '2m ago' }, + { type: 'SELL', price: '2045.10', tp: '2038.50', sl: '2048.00', confidence: 92, time: '15m ago' }, + { type: 'BUY', price: '2022.80', tp: '2030.00', sl: '2018.50', confidence: 76, time: '42m ago' }, + { type: 'SELL', price: '2051.00', tp: '2044.20', sl: '2055.00', confidence: 84, time: '1h ago' }, + { type: 'BUY', price: '2036.20', tp: '2041.50', sl: '2033.00', confidence: 65, time: '1h ago' }, + { type: 'SELL', price: '2048.90', tp: '2040.00', sl: '2052.00', confidence: 95, time: '2h ago' }, +]; + +const HISTORY_DATA = [ + { asset: 'XAUUSD', type: 'BUY', entry: '2,024.50', exit: '2,031.20', yield: '+670 pts', color: 'text-emerald-400', time: '2h ago' }, + { asset: 'XAUUSD', type: 'SELL', entry: '2,038.10', exit: '2,034.40', yield: '+370 pts', color: 'text-emerald-400', time: '4h ago' }, + { asset: 'XAUUSD', type: 'BUY', entry: '2,018.00', exit: '2,015.50', yield: '-250 pts', color: 'text-rose-400', time: '6h ago' }, + { asset: 'XAUUSD', type: 'BUY', entry: '2,012.20', exit: '2,020.80', yield: '+860 pts', color: 'text-emerald-400', time: '10h ago' }, +]; + +class Particle { + constructor(sphereRadius) { + const theta = Math.random() * 2 * Math.PI; + const phi = Math.acos(2 * Math.random() - 1); + this.sphereRadius = sphereRadius; + this.x = sphereRadius * Math.sin(phi) * Math.cos(theta); + this.y = sphereRadius * Math.sin(phi) * Math.sin(theta); + this.z = sphereRadius * Math.cos(phi); + this.size = Math.random() * 1.8 + 0.6; + this.baseOpacity = Math.random() * 0.6 + 0.3; + } + + rotate(angleX, angleY) { + let cosY = Math.cos(angleY), sinY = Math.sin(angleY); + let x = this.x * cosY - this.z * sinY; + let z = this.z * cosY + this.x * sinY; + this.x = x; this.z = z; + + let cosX = Math.cos(angleX), sinX = Math.sin(angleX); + let y = this.y * cosX - this.z * sinX; + z = this.z * cosX + this.y * sinX; + this.y = y; this.z = z; + } + + draw(ctx, centerX, centerY) { + const scale = 300 / (300 + this.z); + const x2d = this.x * scale + centerX; + const y2d = this.y * scale + centerY; + const opacity = Math.max(0, this.baseOpacity + (this.z / this.sphereRadius) * 0.4); + + ctx.beginPath(); + ctx.arc(x2d, y2d, this.size * scale, 0, Math.PI * 2); + if (this.x > 40) ctx.fillStyle = `rgba(34, 211, 238, ${opacity})`; + else if (this.x < -40) ctx.fillStyle = `rgba(167, 139, 250, ${opacity})`; + else ctx.fillStyle = `rgba(248, 250, 252, ${opacity})`; + ctx.fill(); + } +} + +function initParticleSphere() { + const canvas = document.getElementById('particle-canvas'); + const ctx = canvas.getContext('2d', { alpha: true }); + const sphereRadius = 105; + const particleCount = 400; + const rotationSpeed = 0.003; + let particles = []; + let width, height; + + function resize() { + const dpr = window.devicePixelRatio || 1; + const rect = canvas.parentElement.getBoundingClientRect(); + canvas.width = rect.width * dpr; + canvas.height = rect.height * dpr; + canvas.style.width = `${rect.width}px`; + canvas.style.height = `${rect.height}px`; + ctx.scale(dpr, dpr); + width = rect.width; + height = rect.height; + } + + function animate() { + ctx.clearRect(0, 0, width, height); + const centerX = width / 2; + const centerY = height / 2; + particles.sort((a, b) => b.z - a.z); + particles.forEach(p => { + p.rotate(rotationSpeed, rotationSpeed * 0.6); + p.draw(ctx, centerX, centerY); + }); + requestAnimationFrame(animate); + } + + for (let i = 0; i < particleCount; i++) particles.push(new Particle(sphereRadius)); + window.addEventListener('resize', resize); + resize(); + animate(); +} + +function renderSignals() { + const grid = document.getElementById('signals-grid'); + grid.innerHTML = SIGNAL_DATA.map(s => ` +
+
+
+
+ ${s.type === 'BUY' ? '' : ''} +
+
+
Vector Type
+
${s.type} ORDER
+
+
+
${s.time}
+
+
+
+ Trigger Entry + ${s.price} +
+
+ Target TP + ${s.tp} +
+
+ Risk SL + ${s.sl} +
+
+
+
+ Confidence + ${s.confidence}% +
+
+
+
+
+
+ `).join(''); +} + +function renderHistory() { + const tbody = document.getElementById('history-body'); + tbody.innerHTML = HISTORY_DATA.map(t => ` + + +
+
G
+ ${t.asset} +
+ + + + ${t.type} + + + ${t.entry} + ${t.exit} + ${t.yield} + ${t.time} + + `).join(''); +} + +document.addEventListener('DOMContentLoaded', () => { + initParticleSphere(); + renderSignals(); + renderHistory(); + + const initBtn = document.getElementById('init-btn'); + const signalsSection = document.getElementById('signals-section'); + + initBtn.addEventListener('click', () => { + signalsSection.classList.remove('hidden-section'); + signalsSection.classList.add('visible-section'); + setTimeout(() => { + signalsSection.scrollIntoView({ behavior: 'smooth', block: 'start' }); + }, 100); + }); +}); diff --git a/public/assets/js/jquery.min.js b/public/assets/js/jquery.min.js new file mode 100644 index 0000000..409c3f4 --- /dev/null +++ b/public/assets/js/jquery.min.js @@ -0,0 +1,2 @@ +/*! jQuery v3.4.1 | (c) JS Foundation and other contributors | jquery.org/license */ +!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],E=C.document,r=Object.getPrototypeOf,s=t.slice,g=t.concat,u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType},x=function(e){return null!=e&&e===e.window},c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.4.1",k=function(e,t){return new k.fn.init(e,t)},p=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;function d(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp($),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+$),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\([\\da-f]{1,6}"+M+"?|("+M+")|.)","ig"),ne=function(e,t,n){var r="0x"+t-65536;return r!=r||n?t:r<0?String.fromCharCode(r+65536):String.fromCharCode(r>>10|55296,1023&r|56320)},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(m.childNodes),m.childNodes),t[m.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&((e?e.ownerDocument||e:m)!==C&&T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!A[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&U.test(t)){(s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=k),o=(l=h(t)).length;while(o--)l[o]="#"+s+" "+xe(l[o]);c=l.join(","),f=ee.test(t)&&ye(e.parentNode)||e}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){A(t,!0)}finally{s===k&&e.removeAttribute("id")}}}return g(t.replace(B,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[k]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e.namespaceURI,n=(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:m;return r!==C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),m!==C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=k,!C.getElementsByName||!C.getElementsByName(k).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+k+"-]").length||v.push("~="),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+k+"+*").length||v.push(".#.+[+~]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",$)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},D=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)===(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e===C||e.ownerDocument===m&&y(m,e)?-1:t===C||t.ownerDocument===m&&y(m,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e===C?-1:t===C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]===m?-1:s[r]===m?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if((e.ownerDocument||e)!==C&&T(e),d.matchesSelector&&E&&!A[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){A(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=p[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&p(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?k.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?k.grep(e,function(e){return e===n!==r}):"string"!=typeof n?k.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(k.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||q,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:L.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof k?t[0]:t,k.merge(this,k.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),D.test(r[1])&&k.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(k):k.makeArray(e,this)}).prototype=k.fn,q=k(E);var H=/^(?:parents|prev(?:Until|All))/,O={children:!0,contents:!0,next:!0,prev:!0};function P(e,t){while((e=e[t])&&1!==e.nodeType);return e}k.fn.extend({has:function(e){var t=k(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i,ge={option:[1,""],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?k.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;nx",y.noCloneChecked=!!me.cloneNode(!0).lastChild.defaultValue;var Te=/^key/,Ce=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,Ee=/^([^.]*)(?:\.(.+)|)/;function ke(){return!0}function Se(){return!1}function Ne(e,t){return e===function(){try{return E.activeElement}catch(e){}}()==("focus"===t)}function Ae(e,t,n,r,i,o){var a,s;if("object"==typeof t){for(s in"string"!=typeof n&&(r=r||n,n=void 0),t)Ae(e,s,n,r,t[s],o);return e}if(null==r&&null==i?(i=n,r=n=void 0):null==i&&("string"==typeof n?(i=r,r=void 0):(i=r,r=n,n=void 0)),!1===i)i=Se;else if(!i)return e;return 1===o&&(a=i,(i=function(e){return k().off(e),a.apply(this,arguments)}).guid=a.guid||(a.guid=k.guid++)),e.each(function(){k.event.add(this,t,i,r,n)})}function De(e,i,o){o?(Q.set(e,i,!1),k.event.add(e,i,{namespace:!1,handler:function(e){var t,n,r=Q.get(this,i);if(1&e.isTrigger&&this[i]){if(r.length)(k.event.special[i]||{}).delegateType&&e.stopPropagation();else if(r=s.call(arguments),Q.set(this,i,r),t=o(this,i),this[i](),r!==(n=Q.get(this,i))||t?Q.set(this,i,!1):n={},r!==n)return e.stopImmediatePropagation(),e.preventDefault(),n.value}else r.length&&(Q.set(this,i,{value:k.event.trigger(k.extend(r[0],k.Event.prototype),r.slice(1),this)}),e.stopImmediatePropagation())}})):void 0===Q.get(e,i)&&k.event.add(e,i,ke)}k.event={global:{},add:function(t,e,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,v=Q.get(t);if(v){n.handler&&(n=(o=n).handler,i=o.selector),i&&k.find.matchesSelector(ie,i),n.guid||(n.guid=k.guid++),(u=v.events)||(u=v.events={}),(a=v.handle)||(a=v.handle=function(e){return"undefined"!=typeof k&&k.event.triggered!==e.type?k.event.dispatch.apply(t,arguments):void 0}),l=(e=(e||"").match(R)||[""]).length;while(l--)d=g=(s=Ee.exec(e[l])||[])[1],h=(s[2]||"").split(".").sort(),d&&(f=k.event.special[d]||{},d=(i?f.delegateType:f.bindType)||d,f=k.event.special[d]||{},c=k.extend({type:d,origType:g,data:r,handler:n,guid:n.guid,selector:i,needsContext:i&&k.expr.match.needsContext.test(i),namespace:h.join(".")},o),(p=u[d])||((p=u[d]=[]).delegateCount=0,f.setup&&!1!==f.setup.call(t,r,h,a)||t.addEventListener&&t.addEventListener(d,a)),f.add&&(f.add.call(t,c),c.handler.guid||(c.handler.guid=n.guid)),i?p.splice(p.delegateCount++,0,c):p.push(c),k.event.global[d]=!0)}},remove:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,v=Q.hasData(e)&&Q.get(e);if(v&&(u=v.events)){l=(t=(t||"").match(R)||[""]).length;while(l--)if(d=g=(s=Ee.exec(t[l])||[])[1],h=(s[2]||"").split(".").sort(),d){f=k.event.special[d]||{},p=u[d=(r?f.delegateType:f.bindType)||d]||[],s=s[2]&&new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),a=o=p.length;while(o--)c=p[o],!i&&g!==c.origType||n&&n.guid!==c.guid||s&&!s.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(p.splice(o,1),c.selector&&p.delegateCount--,f.remove&&f.remove.call(e,c));a&&!p.length&&(f.teardown&&!1!==f.teardown.call(e,h,v.handle)||k.removeEvent(e,d,v.handle),delete u[d])}else for(d in u)k.event.remove(e,d+t[l],n,r,!0);k.isEmptyObject(u)&&Q.remove(e,"handle events")}},dispatch:function(e){var t,n,r,i,o,a,s=k.event.fix(e),u=new Array(arguments.length),l=(Q.get(this,"events")||{})[s.type]||[],c=k.event.special[s.type]||{};for(u[0]=s,t=1;t\x20\t\r\n\f]*)[^>]*)\/>/gi,qe=/\s*$/g;function Oe(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&k(e).children("tbody")[0]||e}function Pe(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function Re(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Me(e,t){var n,r,i,o,a,s,u,l;if(1===t.nodeType){if(Q.hasData(e)&&(o=Q.access(e),a=Q.set(t,o),l=o.events))for(i in delete a.handle,a.events={},l)for(n=0,r=l[i].length;n")},clone:function(e,t,n){var r,i,o,a,s,u,l,c=e.cloneNode(!0),f=oe(e);if(!(y.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||k.isXMLDoc(e)))for(a=ve(c),r=0,i=(o=ve(e)).length;r").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var Vt,Gt=[],Yt=/(=)\?(?=&|$)|\?\?/;k.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Gt.pop()||k.expando+"_"+kt++;return this[e]=!0,e}}),k.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Yt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Yt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Yt,"$1"+r):!1!==e.jsonp&&(e.url+=(St.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||k.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?k(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Gt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((Vt=E.implementation.createHTMLDocument("").body).innerHTML="
",2===Vt.childNodes.length),k.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=D.exec(e))?[t.createElement(i[1])]:(i=we([e],t,o),o&&o.length&&k(o).remove(),k.merge([],i.childNodes)));var r,i,o},k.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(k.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},k.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){k.fn[t]=function(e){return this.on(t,e)}}),k.expr.pseudos.animated=function(t){return k.grep(k.timers,function(e){return t===e.elem}).length},k.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=k.css(e,"position"),c=k(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=k.css(e,"top"),u=k.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,k.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},k.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){k.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===k.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===k.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=k(e).offset()).top+=k.css(e,"borderTopWidth",!0),i.left+=k.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-k.css(r,"marginTop",!0),left:t.left-i.left-k.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===k.css(e,"position"))e=e.offsetParent;return e||ie})}}),k.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;k.fn[t]=function(e){return _(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),k.each(["top","left"],function(e,n){k.cssHooks[n]=ze(y.pixelPosition,function(e,t){if(t)return t=_e(e,n),$e.test(t)?k(e).position()[n]+"px":t})}),k.each({Height:"height",Width:"width"},function(a,s){k.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){k.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return _(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?k.css(e,t,i):k.style(e,t,n,i)},s,n?e:void 0,n)}})}),k.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){k.fn[n]=function(e,t){return 0view->Footer = $this->view->PartialView('admin_footer'); + $this->view->SideBar = $this->view->PartialView('admin_sidebar'); + $this->view->Header = $this->view->PartialView('admin_header'); + $this->view->ToolBar = $this->view->PartialView('admin_toolbar'); + } + + function index() { + $clients = Db::select("SELECT * FROM sp_clients WHERE active = 1"); + $this->view->Clients = $clients; + + $this->view->render(__CLASS__ . '/' . __FUNCTION__); + } + + function show($id) { + $client = Db::getRow("SELECT * FROM sp_clients WHERE id = :id", [':id' => $id]); + if (!$client) { http_response_code(404); die('Not found'); } + + $this->view->Client = $client; + $this->view->render(__CLASS__ . '/' . __FUNCTION__); + } +} +``` + +## Render Conventions + +```php +// Standard admin page (default wrapper) +$this->view->render(__CLASS__ . '/' . __FUNCTION__); + +// Login / minimal wrapper +$this->view->render(__CLASS__ . '/' . __FUNCTION__, 'login'); + +// Welcome screen (config flag) +if (WELCOME) { $this->view->render(__CLASS__ . '/welcome', '', false); } +``` + +## Injecting Page-Specific Assets + +```php +function index() { + // CSS + $this->Styles[] = '/public/assets/plugins/datatables/datatables.bundle.css'; + $this->view->Styles = $this->Styles; + + // JS + $this->JavaScript[] = '/public/assets/plugins/datatables/datatables.bundle.js'; + $this->JavaScript[] = '/public/assets/js/page-script.js'; + $this->view->JavaScript = $this->JavaScript; + + $this->view->render(__CLASS__ . '/' . __FUNCTION__); +} +``` + +## Authentication + +```php +Auth::handleLogin(); // redirects to /login if not authenticated +Auth::can('users.create') // returns bool +Auth::requirePermission('users.delete'); // halts with 403 JSON if denied +``` + +## Passing Data to Views + +```php +$this->view->Users = Db::select("SELECT * FROM sp_users"); +$this->view->PageTitle = 'User List'; +// accessed in view as $Users, $PageTitle +``` + +## CRUD Pattern + +```php +function store() { + try { + if (!Validator::required($_POST['name'])) { + throw new Exception('Name is required'); + } + + $id = Db::insert('sp_clients', [ + 'name' => $_POST['name'], + 'created_at' => date('Y-m-d H:i:s') + ]); + + redirect('/clients/show/' . $id); + + } catch (Exception $e) { + if (DEBUG) { echo $e->getMessage(); } + else { + $this->view->Error = 'Could not save. Please try again.'; + $this->view->FormData = $_POST; + $this->view->render(__CLASS__ . '/create'); + } + } +} + +function update($id) { + Db::update('sp_clients', + ['name' => $_POST['name'], 'updated_at' => date('Y-m-d H:i:s')], + 'id = :id', + [':id' => $id] + ); + redirect('/clients/show/' . $id); +} + +function delete($id) { + Db::delete('sp_clients', 'id = :id', [':id' => $id]); + redirect('/clients'); +} +``` + +## Error Handling + +```php +function create() { + try { + Db::beginTransaction(); + // ... operations ... + Db::commit(); + redirect('/clients'); + + } catch (Exception $e) { + Db::rollback(); + if (DEBUG) { + echo "
Error: {$e->getMessage()}\nFile: {$e->getFile()}:{$e->getLine()}
"; + } else { + error_log("Client creation failed: " . $e->getMessage()); + $this->view->Error = 'An error occurred. Please try again.'; + $this->view->render(__CLASS__ . '/create'); + } + } +} +``` + +## Helper / Utility Code Rule + +Reusable, stateless functions → `public static` method in `app/Helpers/`, never as a private controller method. +Call via `HelperClass::methodName(...)`. diff --git a/public/controllers/docs.php b/public/controllers/docs.php new file mode 100644 index 0000000..6751c07 --- /dev/null +++ b/public/controllers/docs.php @@ -0,0 +1,29 @@ +view->PartialView('menu'); + + + $this->Styles['commenta'] = ''; + $this->view->Styles = $this->Styles; + + $this->JavaScript[] = ASSETS . "js/bootstrap.bundle.min.js"; + $this->JavaScript[] = ASSETS . "js/jquery.min.js"; + $this->JavaScript[] = ASSETS . "js/custom.js"; + $this->view->JavaScript = $this->JavaScript; + } + + + function index() { + $this->Styles['commenta'] = ''; + $this->view->Styles = $this->Styles; + + $this->view->render('docs/index'); + } + + + +} \ No newline at end of file diff --git a/public/controllers/error.php b/public/controllers/error.php new file mode 100644 index 0000000..b9a469c --- /dev/null +++ b/public/controllers/error.php @@ -0,0 +1,24 @@ +Styles['commenta'] = ''; + $this->view->Styles = $this->Styles; + + $this->JavaScript[] = ASSETS . "/js/pages/crypto-dashboard.init.js"; + $this->view->JavaScript = $this->JavaScript; + + $this->view->title = COMPANY; + $this->view->render(__CLASS__ .'/'. __FUNCTION__); + } + + +} // end class \ No newline at end of file diff --git a/public/controllers/index.php b/public/controllers/index.php new file mode 100644 index 0000000..507f119 --- /dev/null +++ b/public/controllers/index.php @@ -0,0 +1,28 @@ +view->PartialView('menu'); + + $this->Styles['commenta'] = ''; + $this->view->Styles = $this->Styles; + + $this->JavaScript[] = ASSETS . "js/bootstrap.bundle.min.js"; + $this->JavaScript[] = ASSETS . "js/jquery.min.js"; + $this->JavaScript[] = ASSETS . "js/custom.js"; + $this->view->JavaScript = $this->JavaScript; + } + + + function index() { + $this->Styles['commenta'] = ''; + $this->view->Styles = $this->Styles; + + $this->view->render('index/index'); + } + + + +} \ No newline at end of file diff --git a/public/index.php b/public/index.php new file mode 100644 index 0000000..a4079d3 --- /dev/null +++ b/public/index.php @@ -0,0 +1,11 @@ +init(); + diff --git a/public/models/games_model.php b/public/models/games_model.php new file mode 100644 index 0000000..0d85f2c --- /dev/null +++ b/public/models/games_model.php @@ -0,0 +1,17 @@ +db->select("select * from creditorinfo"); + //return json_encode($Return); + } + + +} // End Class \ No newline at end of file diff --git a/public/models/index_model.php b/public/models/index_model.php new file mode 100644 index 0000000..520028f --- /dev/null +++ b/public/models/index_model.php @@ -0,0 +1,11 @@ +db->prepare("SELECT user_id, username, role_id FROM users WHERE + username = :username AND password = :password"); + $sth->execute(array( + ':username' => $_POST['username'], + ':password' => Hash::create('sha256', $_POST['password'], HASH_PASSWORD_KEY) + )); + + $data = $sth->fetch(); + + + $count = $sth->rowCount(); + if ($count > 0) { + // login + Session::init(); + Session::pSet('UserProfile', 'roleId', $data['role_id']); + Session::pSet('UserProfile', 'loggedIn', true); + Session::pSet('UserProfile', 'userId', $data['user_id']); + + $sql = "SELECT t2.perm_controller,t2.perm_action FROM role_perm as t1 + JOIN permissions as t2 ON t1.perm_id = t2.perm_id + WHERE t1.role_id = :role_id"; + + $permission = array(); + $permission = $this->db->select($sql, array(':role_id' => $data['role_id'])); + + Session::set('permission', $permission); + + + + // This is a bit annoying to have it hard coded but its good enough for now. + // This will set the specific redirects for the access levels Business, Client & User there's only 3 screens with sub levels of permissions. + switch($data['role_id']) { + # Business + case '1' : // Administrator + case '2' : // Marketing + header('location: /admin'); + break; + + # Client + case '4' : // Agency + header('location: /dashboard'); + break; + + #End User + case '6' : // Users + header('location: /dashboard'); + break; + } + + + } else { + header('location: ../login'); + } + + } + +} \ No newline at end of file diff --git a/public/views/_error/index.php b/public/views/_error/index.php new file mode 100644 index 0000000..8a5fbab --- /dev/null +++ b/public/views/_error/index.php @@ -0,0 +1,15 @@ + +
+

404

+

Page Not Found

+ +
+ Sorry, but the page you are looking for has note been found. Try checking the URL for error, then hit the refresh button on your browser or try found something else in our app. +
+
+ +
+ +
+
+
\ No newline at end of file diff --git a/public/views/aitest/index.php b/public/views/aitest/index.php new file mode 100644 index 0000000..4f9d57f --- /dev/null +++ b/public/views/aitest/index.php @@ -0,0 +1,55 @@ +OpenAiResponse; +?> + + + +
+ +
+

Testing OpenAI / ChatGPT API

+
+
+ + +
+ + + + + + + + + + + + + + + +
+ + +
+ +
+
+

Your Question:

+ +

Your Answer:

+
+            
+        
+
+ +
+ + diff --git a/public/views/aitest/translate.php b/public/views/aitest/translate.php new file mode 100644 index 0000000..3eef3ae --- /dev/null +++ b/public/views/aitest/translate.php @@ -0,0 +1,40 @@ +A Viva Air le quedarían muy pocas horas de vuelo +Por la crisis económica, la Junta Directiva habría tomado la decisión de cerrar la operación. Se verían afectadas un millón de personas que compraron tiquetes. +En cuestión de horas la aerolínea Viva pondría punto final a toda su operación. La Junta Directiva de la compañía aérea de bajo costo habría dado luz verde para dejar en tierra la totalidad de sus aeronaves, así como la de liquidar la actividad de la compañía. + +EL COLOMBIANO pudo establecer que el órgano directivo de la empresa, en un reciente cónclave extraordinario, habría tomado la decisión de no darle más largas al asunto, y tiene lista la orden para la clausura total de la compañía aérea ante su crisis financiera, que a la fecha ya le ha representado dejar aviones sin volar. + +Trascendió que para los miembros de la Junta Directiva las finanzas de la aerolínea no le permitirían continuar con la operación, incluso señalaron que aún amparada la compañía en el Decreto 560 (norma de Recuperación Económica) y pagado el 100% de los compromisos, la operación les daría para máximo dos semanas. +La situación es tan crítica que en una carta reciente que el anterior presidente de la compañía, Félix Antelo, envió al ministro de Transporte, Guillermo Reyes, advirtió que al 31 de enero de 2023, los pasivos de la empresa aérea de bajo costo llegaban a los $4,06 billones. “Como bien sabe la compañía no tiene la capacidad por sí misma para responder con sus obligaciones”, resaltó quien hasta el jueves pasado fuera el presidente de Viva Air. + +Integración con Avianca + +Se conoció además que, para los miembros de la Junta Directiva de la aerolínea será un error grande, que le causará una tragedia al turismo y a la economía de Colombia, la decisión de la Aeronáutica Civil (Aerocivil) de no hacer nada, ni decir nada, sobre la integración de Viva y Avianca. + +Así mismo, la aerolínea de bajo costo espera que en las próximas horas, bien sea el director de la Aerocivil, Sergio París, o en su efecto el ministro Reyes, entren en contacto con los directivos de la compañía, y con el nuevo presidente de la misma, Francisco Lalinde, para lograr la tan anhelada integración con Avianca. + +De no lograrse el aval por parte de la Aerocivil a más tardar el martes, como lo habría dictado la Junta Directiva, la aerolínea cerraría la operación con lo que resultarán damnificadas más de un millón de personas que a la fecha ya tiene comprados tiquetes y a las que no se les reembolsaría el dinero en el corto plazo. + +No hay una decisión} + +EL COLOMBIANO se comunicó con voceros de la compañía aérea para confirmar las versiones de su posible cierre de operaciones mañana martes. Al respecto, de manera oficial la aerolínea de bajo costo señaló: “Hasta la fecha no hay una definición de cierre de la compañía y Viva sigue operando y a la espera de una urgente definición por parte de la Aeronáutica Civil”. + +Sin embargo, este diario pudo establecer que a la cascada de renuncias del personal del servicio en aire y en tierra, se viene ajustando la operación con los recursos y aeronaves que quedan. + +Cabe recordar que Viva anunció hace una semana y en un comunicado interno que temporalmente tendrá que prescindir del uso de cinco aviones. “Se han presentado importantes negociaciones con los dueños de los aviones buscando llegar a acuerdos de cómo continuar nuestra operación. A pesar de esto, hemos sido notificados por uno de ellos que debemos dejar en tierra, por el momento, cinco de sus aviones hasta nueva orden en los Estados Unidos”, aseguró la compañía en el documento. + +En menos de un mes la administración de la aerolínea no solo ha cancelado varias frecuencias entre la costa Caribe y Cali, además los 14 vuelos que operaban hacia San Andrés, lo que representa el 90% de la economía de la isla. + +“A días de perder Viva” + +En una inquietante carta, los trabajadores de la aerolínea se pronunciaron frente a la crítica situación financiera que vive. + +En la misiva advierten que cerca de 5.000 personas que dependen económicamente de esta empresa se encuentran en incertidumbre ante la falta de claridad de las autoridades sobre si habrá o no rescate (integración con Avianca). + +“Necesitamos una respuesta con urgencia”, dicen los trabajadores, quienes advierten que aunque han enviado cartas y han sido prudentes en su comunicación, no han recibido atención de la Aerocivil o del gobierno. El jueves de la semana pasada muchos de ellos protestaron en el aeropuerto José María Córdova. + +Con la clausura de la operación de Viva en el negocio aéreo del país, quedaría un vacío del 21%, que es la participación de la aerolínea de bajo en el mercado, y teniendo en cuenta que Avianca posee el 40%. + + + + diff --git a/public/views/docs/index.php b/public/views/docs/index.php new file mode 100644 index 0000000..9f0092a --- /dev/null +++ b/public/views/docs/index.php @@ -0,0 +1 @@ +No Documentation Here \ No newline at end of file diff --git a/public/views/index/index.php b/public/views/index/index.php new file mode 100644 index 0000000..e6157fb --- /dev/null +++ b/public/views/index/index.php @@ -0,0 +1,52 @@ + +
+ +
+
+ + +
+
+
+ + + + + Light-Weight LLM Friendly PHP Framework +
+

+ SeedProject
Framework
+

+

+ SeedProject Framework is revamped to work with LLM likes Claude Code, Gemini CLI to help you build more structured projects, harness the power of AI without messy confusing back-end code. +

+
+ + +
+ +
+
+
+
+
+
Light-Weight
+
4.9MB
+
+
+
Lines of Code
+
1k
+
+
+ + +
+
+ +
+
+ +
diff --git a/public/views/partial/menu.php b/public/views/partial/menu.php new file mode 100644 index 0000000..e69de29 diff --git a/public/views/views.md b/public/views/views.md new file mode 100644 index 0000000..d47fa66 --- /dev/null +++ b/public/views/views.md @@ -0,0 +1,170 @@ +# Views — `public/views/` + +PHP view templates, organized by controller name. + +## Directory Structure + +``` +public/views/ +├── / # snake_case folder per controller +│ ├── index.php +│ ├── show.php +│ └── edit.php +├── modals/ # Modal fragment views (AJAX loaded) +│ └── .php +├── partial/ # Reusable partial views +│ ├── admin_header.php +│ ├── admin_sidebar.php +│ ├── admin_toolbar.php +│ └── admin_footer.php +└── wrapper/ # Layout wrappers + ├── admin/ + │ ├── header.php + │ └── footer.php + └── site/ + ├── header.php + └── footer.php +``` + +## File Naming + +- Folder: `snake_case` matching the controller name (lowercased) +- Files: `snake_case.php` matching the method name + +## Rendering + +Called from the controller: + +```php +// Standard — uses __CLASS__/__FUNCTION__ convention +$this->view->render(__CLASS__ . '/' . __FUNCTION__); + +// With explicit wrapper +$this->view->render('clients/index', true, 'site'); +$this->view->render('admin/dashboard', true, 'admin'); + +// Login / minimal wrapper +$this->view->render('auth/login', 'login'); + +// No wrapper +$this->view->render('auth/login', false); +``` + +## Accessing View Data + +Variables set on the controller via `$this->view->VarName = $value` are accessed in the view as **`$this->VarName`**. + +`View::render()` calls `extract((array) $this)` before including the template, so all properties set on the view object are available as bare `$VarName` locals. + +```php +// Controller +$this->view->Users = Db::select("SELECT * FROM sp_users"); +$this->view->PageTitle = 'Users'; + +// View — use bare $VarName +

+ +

+ +``` + +## Wrappers + +Wrappers live in `public/views/wrapper/`. The wrapper auto-loads: +- `public/assets/css/.css` if it exists +- `public/assets/js/.js` if it exists + +## Partial Views + +Partials are reusable snippets loaded from the controller constructor: + +```php +// In controller __construct() +$this->view->Header = $this->view->PartialView('admin_header'); +$this->view->SideBar = $this->view->PartialView('admin_sidebar'); +$this->view->ToolBar = $this->view->PartialView('admin_toolbar'); +$this->view->Footer = $this->view->PartialView('admin_footer'); +``` + +Then echoed in the view: +```php + + +``` + +## Modal System + +Modals are loaded dynamically via AJAX. Trigger a modal with: + +```html + +``` + +`data-size` accepts any Bootstrap modal width class or a custom `mw-*` value: + +| Value | Width | +|-------|-------| +| *(omit)* | Default (~500px) | +| `mw-500px` | 500px max-width | +| `mw-650px` | 650px max-width | +| `mw-750px` | 750px max-width | +| `modal-lg` | ~800px (Bootstrap) | +| `modal-xl` | ~1140px (Bootstrap) — use for wizards / multi-step forms | + +Modal views live at `public/views/modals/.php` and render **only** the `.modal-content` fragment (no full page wrapper): + +```html + + + +``` + +## Assets Structure + +``` +public/assets/ +├── css/ +│ ├── custom.css # Global overrides +│ └── .css # Page-specific (auto-loaded by wrapper) +├── js/ +│ └── .js # Page-specific (auto-loaded by wrapper) +├── plugins/ +│ └── datatables/ +│ ├── datatables.bundle.css +│ └── datatables.bundle.js +└── media/ + └── svg/ + └── brand-logos/ # Integration / vendor logos +``` + +Inject additional assets from the controller method: + +```php +$this->Styles[] = '/public/assets/plugins/datatables/datatables.bundle.css'; +$this->view->Styles = $this->Styles; + +$this->JavaScript[] = '/public/assets/plugins/datatables/datatables.bundle.js'; +$this->view->JavaScript = $this->JavaScript; +``` diff --git a/public/views/wrapper/site/footer.php b/public/views/wrapper/site/footer.php new file mode 100644 index 0000000..a2e70ae --- /dev/null +++ b/public/views/wrapper/site/footer.php @@ -0,0 +1,19 @@ + +JavaScript) { + foreach ($this->JavaScript as $kj => $JavaScript) : + if(!is_numeric($kj)) { + echo PHP_EOL; + echo $JavaScript . PHP_EOL; + continue; + } + echo '' . PHP_EOL; + endforeach; +} +?> + + + + + + \ No newline at end of file diff --git a/public/views/wrapper/site/header.php b/public/views/wrapper/site/header.php new file mode 100644 index 0000000..3c2b6c2 --- /dev/null +++ b/public/views/wrapper/site/header.php @@ -0,0 +1,52 @@ + + + + + + CreditPullEngine + + + + + Styles) { + foreach ($this->Styles as $ks => $Styles) : + if(!is_numeric($ks)) { + echo PHP_EOL; + echo $Styles . PHP_EOL; + continue; + } + echo '' .PHP_EOL; + + endforeach; + } + ?> + diff --git a/sw.js b/sw.js new file mode 100644 index 0000000..fa7f2cf --- /dev/null +++ b/sw.js @@ -0,0 +1,19 @@ +/** An empty service worker! */ +self.addEventListener ('install', e => { + console.log("Installed!"); + e.waitUntil( + caches.open("static").then(cache => { + return cache.addAll(["/", "/assets/css/pwa.css", "/assets/icons/ComiidaIcon-192.png"]); + }) + ); +}); + + +self.addEventListener("fetch", e => { + e.respondWith( + caches.match(e.request).then(response => { + return response || fetch(e.request); + }) + ); +}); + \ No newline at end of file diff --git a/system/ErrorHandler.php b/system/ErrorHandler.php new file mode 100644 index 0000000..278f898 --- /dev/null +++ b/system/ErrorHandler.php @@ -0,0 +1,170 @@ + 'E_ERROR', + E_WARNING => 'E_WARNING', + E_PARSE => 'E_PARSE', + E_NOTICE => 'E_NOTICE', + E_CORE_ERROR => 'E_CORE_ERROR', + E_CORE_WARNING => 'E_CORE_WARNING', + E_COMPILE_ERROR => 'E_COMPILE_ERROR', + E_COMPILE_WARNING => 'E_COMPILE_WARNING', + E_USER_ERROR => 'E_USER_ERROR', + E_USER_WARNING => 'E_USER_WARNING', + E_USER_NOTICE => 'E_USER_NOTICE', + E_STRICT => 'E_STRICT', + E_RECOVERABLE_ERROR => 'E_RECOVERABLE_ERROR', + E_DEPRECATED => 'E_DEPRECATED', + E_USER_DEPRECATED => 'E_USER_DEPRECATED', + ]; + + private static array $errorTypeLabels = [ + E_ERROR => 'Fatal Error', + E_WARNING => 'Warning', + E_PARSE => 'Parse Error', + E_NOTICE => 'Notice', + E_CORE_ERROR => 'Core Error', + E_CORE_WARNING => 'Core Warning', + E_COMPILE_ERROR => 'Compile Error', + E_COMPILE_WARNING => 'Compile Warning', + E_USER_ERROR => 'User Error', + E_USER_WARNING => 'User Warning', + E_USER_NOTICE => 'User Notice', + E_STRICT => 'Strict Standards', + E_RECOVERABLE_ERROR => 'Recoverable Error', + E_DEPRECATED => 'Deprecated', + E_USER_DEPRECATED => 'User Deprecated', + ]; + + private static array $fatalTypes = [ + E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, + E_USER_ERROR, E_RECOVERABLE_ERROR, + ]; + + public static function handleError($errno, $errstr, $errfile, $errline) { + if (!(error_reporting() & $errno)) { + return; + } + + // Only persist fatal/critical errors — skip warnings, notices, deprecated, strict + if (in_array($errno, self::$fatalTypes)) { + self::persistError($errno, $errstr, $errfile, $errline); + self::logError($errno, $errstr, $errfile, $errline); + } + + self::displayError($errno, $errstr, $errfile, $errline); + + return true; + } + + public static function handleException($exception) { + $trace = $exception->getTraceAsString(); + self::persistError(E_ERROR, $exception->getMessage(), $exception->getFile(), $exception->getLine(), $trace, 'Exception'); + self::logError(E_ERROR, $exception->getMessage(), $exception->getFile(), $exception->getLine()); + self::displayError(E_ERROR, $exception->getMessage(), $exception->getFile(), $exception->getLine(), $trace); + } + + public static function handleShutdown() { + $error = error_get_last(); + if ($error !== null && in_array($error['type'], [E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE])) { + self::persistError($error['type'], $error['message'], $error['file'], $error['line']); + self::logError($error['type'], $error['message'], $error['file'], $error['line']); + self::displayError($error['type'], $error['message'], $error['file'], $error['line']); + } + } + + // ─── JSON flat-file persistence ─────────────────────────────────────────── + + private static function persistError($errno, $errstr, $errfile, $errline, $trace = null, $forcedType = null) { + try { + $errorType = $forcedType ?? (self::$errorTypeMap[$errno] ?? 'UNKNOWN'); + + $userId = null; + $orgId = null; + if (isset($_SESSION['login'])) { + $userId = $_SESSION['login']['userid'] ?? null; + $orgId = $_SESSION['login']['org_id'] ?? null; + } + + $url = ((!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http') + . '://' . ($_SERVER['HTTP_HOST'] ?? '') . ($_SERVER['REQUEST_URI'] ?? ''); + $method = $_SERVER['REQUEST_METHOD'] ?? null; + $ipAddress = $_SERVER['REMOTE_ADDR'] ?? null; + $userAgent = $_SERVER['HTTP_USER_AGENT'] ?? null; + $referer = $_SERVER['HTTP_REFERER'] ?? null; + + $entry = [ + 'id' => uniqid('err_', true), + 'userid' => $userId, + 'org_id' => $orgId, + 'errortype' => $errorType, + 'errorcode' => (string)$errno, + 'url' => substr($url, 0, 512), + 'file' => substr($errfile, 0, 512), + 'line' => (int)$errline, + 'fullerror' => $errstr, + 'trace' => $trace, + 'method' => $method ? substr($method, 0, 10) : null, + 'ip_address' => $ipAddress ? substr($ipAddress, 0, 45) : null, + 'user_agent' => $userAgent ? substr($userAgent, 0, 512) : null, + 'referer' => $referer ? substr($referer, 0, 512) : null, + 'status' => 'unresolved', + 'createdate' => date('Y-m-d H:i:s'), + ]; + + $logPath = __DIR__ . '/errors.json'; + $entries = []; + if (file_exists($logPath)) { + $raw = file_get_contents($logPath); + $decoded = json_decode($raw, true); + if (is_array($decoded)) { + $entries = $decoded; + } + } + + array_unshift($entries, $entry); + if (count($entries) > 1000) { + $entries = array_slice($entries, 0, 1000); + } + + file_put_contents($logPath, json_encode($entries, JSON_PRETTY_PRINT), LOCK_EX); + + } catch (\Throwable $e) { + // Silently fail — never let error logging crash the app + error_log('ErrorHandler::persistError failed: ' . $e->getMessage()); + } + } + + // ─── File logging ───────────────────────────────────────────────────────── + + private static function logError($errno, $errstr, $errfile, $errline) { + $message = date('[Y-m-d H:i:s]') . " Error: [$errno] $errstr in $errfile on line $errline\n"; + error_log($message, 3, __DIR__ . '/app_errors.log'); + } + + // ─── Display (debug mode only) ──────────────────────────────────────────── + + private static function displayError($errno, $errstr, $errfile, $errline, $trace = null) { + $errorType = self::$errorTypeLabels[$errno] ?? 'Unknown Error'; + + if (defined('DEBUG') && DEBUG) { + echo "
"; + echo "

" . htmlspecialchars($errorType) . " Occurred

"; + echo "

Message: " . htmlspecialchars($errstr) . "

"; + echo "

File: " . htmlspecialchars($errfile) . "

"; + echo "

Line: " . htmlspecialchars((string)$errline) . "

"; + if ($trace) { + echo "

Stack Trace:

"; + echo "
" . htmlspecialchars($trace) . "
"; + } + echo "

Request Details:

";
+            echo "URL: "    . htmlspecialchars($_SERVER['REQUEST_URI']    ?? '') . "\n";
+            echo "Method: " . htmlspecialchars($_SERVER['REQUEST_METHOD'] ?? '') . "\n";
+            echo "Time: "   . date('Y-m-d H:i:s') . "\n";
+            echo "IP: "     . htmlspecialchars($_SERVER['REMOTE_ADDR']    ?? '') . "\n";
+            echo "
"; + } + } +} diff --git a/system/errors.json b/system/errors.json new file mode 100644 index 0000000..73ad76c --- /dev/null +++ b/system/errors.json @@ -0,0 +1,362 @@ +[ + { + "id": "err_69ee699b38a214.70118470", + "userid": 2565, + "org_id": null, + "errortype": "Exception", + "errorcode": "1", + "url": "https:\/\/secure.creditpullengine.com\/appi\/pulls\/pullReport", + "file": "\/www\/wwwroot\/cpeSecure\/app\/Services\/Credit\/EquifaxCodeMap.php", + "line": 317, + "fullerror": "Call to undefined method App\\Services\\Credit\\EquifaxCodeMap::getScoreFactors()", + "trace": "#0 \/www\/wwwroot\/cpeSecure\/app\/Services\/Credit\/EquifaxAdapter.php(159): App\\Services\\Credit\\EquifaxCodeMap::getScoreFactor()\n#1 \/www\/wwwroot\/cpeSecure\/app\/Services\/Credit\/EquifaxAdapter.php(43): App\\Services\\Credit\\EquifaxAdapter->mapScoreFactors()\n#2 \/www\/wwwroot\/cpeSecure\/appi\/controllers\/pulls.php(134): App\\Services\\Credit\\EquifaxAdapter->transform()\n#3 \/www\/wwwroot\/cpeSecure\/core\/Bootstrap.php(227): pulls->pullReport()\n#4 \/www\/wwwroot\/cpeSecure\/core\/Bootstrap.php(64): Bootstrap->_callControllerMethod()\n#5 \/www\/wwwroot\/cpeSecure\/appi\/index.php(11): Bootstrap->init()\n#6 {main}", + "method": "POST", + "ip_address": "193.36.238.59", + "user_agent": "Mozilla\/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit\/537.36 (KHTML, like Gecko) Chrome\/147.0.0.0 Safari\/537.36", + "referer": "https:\/\/secure.creditpullengine.com\/pulls", + "status": "unresolved", + "createdate": "2026-04-26 15:38:03" + }, + { + "id": "err_69ee698c669d29.22210069", + "userid": 2565, + "org_id": null, + "errortype": "Exception", + "errorcode": "1", + "url": "https:\/\/secure.creditpullengine.com\/appi\/pulls\/pullReport", + "file": "\/www\/wwwroot\/cpeSecure\/app\/Services\/Credit\/EquifaxCodeMap.php", + "line": 317, + "fullerror": "Call to undefined method App\\Services\\Credit\\EquifaxCodeMap::getScoreFactors()", + "trace": "#0 \/www\/wwwroot\/cpeSecure\/app\/Services\/Credit\/EquifaxAdapter.php(159): App\\Services\\Credit\\EquifaxCodeMap::getScoreFactor()\n#1 \/www\/wwwroot\/cpeSecure\/app\/Services\/Credit\/EquifaxAdapter.php(43): App\\Services\\Credit\\EquifaxAdapter->mapScoreFactors()\n#2 \/www\/wwwroot\/cpeSecure\/appi\/controllers\/pulls.php(134): App\\Services\\Credit\\EquifaxAdapter->transform()\n#3 \/www\/wwwroot\/cpeSecure\/core\/Bootstrap.php(227): pulls->pullReport()\n#4 \/www\/wwwroot\/cpeSecure\/core\/Bootstrap.php(64): Bootstrap->_callControllerMethod()\n#5 \/www\/wwwroot\/cpeSecure\/appi\/index.php(11): Bootstrap->init()\n#6 {main}", + "method": "POST", + "ip_address": "193.36.238.59", + "user_agent": "Mozilla\/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit\/537.36 (KHTML, like Gecko) Chrome\/147.0.0.0 Safari\/537.36", + "referer": "https:\/\/secure.creditpullengine.com\/pulls", + "status": "unresolved", + "createdate": "2026-04-26 15:37:48" + }, + { + "id": "err_69ee6950756db9.31923142", + "userid": 2565, + "org_id": null, + "errortype": "Exception", + "errorcode": "1", + "url": "https:\/\/secure.creditpullengine.com\/appi\/pulls\/pullReport", + "file": "\/www\/wwwroot\/cpeSecure\/app\/Services\/Credit\/EquifaxCodeMap.php", + "line": 317, + "fullerror": "Call to undefined method App\\Services\\Credit\\EquifaxCodeMap::getScoreFactors()", + "trace": "#0 \/www\/wwwroot\/cpeSecure\/app\/Services\/Credit\/EquifaxAdapter.php(159): App\\Services\\Credit\\EquifaxCodeMap::getScoreFactor()\n#1 \/www\/wwwroot\/cpeSecure\/app\/Services\/Credit\/EquifaxAdapter.php(43): App\\Services\\Credit\\EquifaxAdapter->mapScoreFactors()\n#2 \/www\/wwwroot\/cpeSecure\/appi\/controllers\/pulls.php(134): App\\Services\\Credit\\EquifaxAdapter->transform()\n#3 \/www\/wwwroot\/cpeSecure\/core\/Bootstrap.php(227): pulls->pullReport()\n#4 \/www\/wwwroot\/cpeSecure\/core\/Bootstrap.php(64): Bootstrap->_callControllerMethod()\n#5 \/www\/wwwroot\/cpeSecure\/appi\/index.php(11): Bootstrap->init()\n#6 {main}", + "method": "POST", + "ip_address": "193.36.238.59", + "user_agent": "Mozilla\/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit\/537.36 (KHTML, like Gecko) Chrome\/147.0.0.0 Safari\/537.36", + "referer": "https:\/\/secure.creditpullengine.com\/pulls", + "status": "unresolved", + "createdate": "2026-04-26 15:36:48" + }, + { + "id": "err_69ee6934c9c873.23488128", + "userid": 2565, + "org_id": null, + "errortype": "Exception", + "errorcode": "1", + "url": "https:\/\/secure.creditpullengine.com\/appi\/pulls\/pullReport", + "file": "\/www\/wwwroot\/cpeSecure\/app\/Services\/Credit\/EquifaxCodeMap.php", + "line": 317, + "fullerror": "Call to undefined method App\\Services\\Credit\\EquifaxCodeMap::getScoreFactors()", + "trace": "#0 \/www\/wwwroot\/cpeSecure\/app\/Services\/Credit\/EquifaxAdapter.php(159): App\\Services\\Credit\\EquifaxCodeMap::getScoreFactor()\n#1 \/www\/wwwroot\/cpeSecure\/app\/Services\/Credit\/EquifaxAdapter.php(43): App\\Services\\Credit\\EquifaxAdapter->mapScoreFactors()\n#2 \/www\/wwwroot\/cpeSecure\/appi\/controllers\/pulls.php(134): App\\Services\\Credit\\EquifaxAdapter->transform()\n#3 \/www\/wwwroot\/cpeSecure\/core\/Bootstrap.php(227): pulls->pullReport()\n#4 \/www\/wwwroot\/cpeSecure\/core\/Bootstrap.php(64): Bootstrap->_callControllerMethod()\n#5 \/www\/wwwroot\/cpeSecure\/appi\/index.php(11): Bootstrap->init()\n#6 {main}", + "method": "POST", + "ip_address": "193.36.238.59", + "user_agent": "Mozilla\/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit\/537.36 (KHTML, like Gecko) Chrome\/147.0.0.0 Safari\/537.36", + "referer": "https:\/\/secure.creditpullengine.com\/pulls", + "status": "unresolved", + "createdate": "2026-04-26 15:36:20" + }, + { + "id": "err_69ee69273633b8.88201084", + "userid": 2565, + "org_id": null, + "errortype": "Exception", + "errorcode": "1", + "url": "https:\/\/secure.creditpullengine.com\/appi\/pulls\/pullReport", + "file": "\/www\/wwwroot\/cpeSecure\/app\/Services\/Credit\/EquifaxCodeMap.php", + "line": 317, + "fullerror": "Call to undefined method App\\Services\\Credit\\EquifaxCodeMap::getScoreFactors()", + "trace": "#0 \/www\/wwwroot\/cpeSecure\/app\/Services\/Credit\/EquifaxAdapter.php(159): App\\Services\\Credit\\EquifaxCodeMap::getScoreFactor()\n#1 \/www\/wwwroot\/cpeSecure\/app\/Services\/Credit\/EquifaxAdapter.php(43): App\\Services\\Credit\\EquifaxAdapter->mapScoreFactors()\n#2 \/www\/wwwroot\/cpeSecure\/appi\/controllers\/pulls.php(134): App\\Services\\Credit\\EquifaxAdapter->transform()\n#3 \/www\/wwwroot\/cpeSecure\/core\/Bootstrap.php(227): pulls->pullReport()\n#4 \/www\/wwwroot\/cpeSecure\/core\/Bootstrap.php(64): Bootstrap->_callControllerMethod()\n#5 \/www\/wwwroot\/cpeSecure\/appi\/index.php(11): Bootstrap->init()\n#6 {main}", + "method": "POST", + "ip_address": "193.36.238.59", + "user_agent": "Mozilla\/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit\/537.36 (KHTML, like Gecko) Chrome\/147.0.0.0 Safari\/537.36", + "referer": "https:\/\/secure.creditpullengine.com\/pulls", + "status": "unresolved", + "createdate": "2026-04-26 15:36:07" + }, + { + "id": "err_69ed670a033b66.19048308", + "userid": null, + "org_id": null, + "errortype": "Exception", + "errorcode": "1", + "url": "https:\/\/secure.creditpullengine.com\/public\/", + "file": "\/www\/wwwroot\/cpeSecure\/core\/Bootstrap.php", + "line": 131, + "fullerror": "Failed opening required 'public\/controllers\/index.php' (include_path='.:')", + "trace": "#0 \/www\/wwwroot\/cpeSecure\/core\/Bootstrap.php(40): Bootstrap->_loadDefaultController()\n#1 \/www\/wwwroot\/cpeSecure\/public\/index.php(11): Bootstrap->init()\n#2 {main}", + "method": "GET", + "ip_address": "89.244.95.100", + "user_agent": "Mozilla\/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit\/537.36 (KHTML, like Gecko) Chrome\/143.0.0.0 Safari\/537.36", + "referer": "https:\/\/secure.creditpullengine.com\/login", + "status": "unresolved", + "createdate": "2026-04-25 21:14:50" + }, + { + "id": "err_69ed5d01f08370.23851037", + "userid": null, + "org_id": null, + "errortype": "Exception", + "errorcode": "1", + "url": "https:\/\/secure.creditpullengine.com\/cron\/jsonReports", + "file": "\/www\/wwwroot\/cpeSecure\/public\/controllers\/cron.php", + "line": 14, + "fullerror": "Class \"Carbon\\Carbon\" not found", + "trace": "#0 \/www\/wwwroot\/cpeSecure\/core\/Bootstrap.php(148): Cron->__construct()\n#1 \/www\/wwwroot\/cpeSecure\/core\/Bootstrap.php(63): Bootstrap->_loadExistingController()\n#2 \/www\/wwwroot\/cpeSecure\/public\/index.php(11): Bootstrap->init()\n#3 \/www\/wwwroot\/cpeSecure\/index.php(2): require_once('...')\n#4 {main}", + "method": "GET", + "ip_address": "140.82.26.192", + "user_agent": "curl\/7.76.1", + "referer": null, + "status": "unresolved", + "createdate": "2026-04-25 20:32:01" + }, + { + "id": "err_69ed5d01e8bb28.09924309", + "userid": null, + "org_id": null, + "errortype": "Exception", + "errorcode": "1", + "url": "https:\/\/secure.creditpullengine.com\/cron\/jsonReports", + "file": "\/www\/wwwroot\/cpeSecure\/public\/controllers\/cron.php", + "line": 14, + "fullerror": "Class \"Carbon\\Carbon\" not found", + "trace": "#0 \/www\/wwwroot\/cpeSecure\/core\/Bootstrap.php(148): Cron->__construct()\n#1 \/www\/wwwroot\/cpeSecure\/core\/Bootstrap.php(63): Bootstrap->_loadExistingController()\n#2 \/www\/wwwroot\/cpeSecure\/public\/index.php(11): Bootstrap->init()\n#3 \/www\/wwwroot\/cpeSecure\/index.php(2): require_once('...')\n#4 {main}", + "method": "GET", + "ip_address": "207.246.75.27", + "user_agent": null, + "referer": null, + "status": "unresolved", + "createdate": "2026-04-25 20:32:01" + }, + { + "id": "err_69ed5cf5670d39.78662554", + "userid": null, + "org_id": null, + "errortype": "Exception", + "errorcode": "1", + "url": "https:\/\/secure.creditpullengine.com\/cron\/jsonReports", + "file": "\/www\/wwwroot\/cpeSecure\/public\/controllers\/cron.php", + "line": 14, + "fullerror": "Class \"Carbon\\Carbon\" not found", + "trace": "#0 \/www\/wwwroot\/cpeSecure\/core\/Bootstrap.php(148): Cron->__construct()\n#1 \/www\/wwwroot\/cpeSecure\/core\/Bootstrap.php(63): Bootstrap->_loadExistingController()\n#2 \/www\/wwwroot\/cpeSecure\/public\/index.php(11): Bootstrap->init()\n#3 \/www\/wwwroot\/cpeSecure\/index.php(2): require_once('...')\n#4 {main}", + "method": "GET", + "ip_address": "206.62.143.66", + "user_agent": "Mozilla\/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit\/537.36 (KHTML, like Gecko) Chrome\/147.0.0.0 Safari\/537.36", + "referer": null, + "status": "unresolved", + "createdate": "2026-04-25 20:31:49" + }, + { + "id": "err_69ed5cdaeef019.29989883", + "userid": null, + "org_id": null, + "errortype": "Exception", + "errorcode": "1", + "url": "https:\/\/secure.creditpullengine.com\/cron\/jsonReports", + "file": "\/www\/wwwroot\/cpeSecure\/public\/controllers\/cron.php", + "line": 14, + "fullerror": "Class \"Carbon\\Carbon\" not found", + "trace": "#0 \/www\/wwwroot\/cpeSecure\/core\/Bootstrap.php(148): Cron->__construct()\n#1 \/www\/wwwroot\/cpeSecure\/core\/Bootstrap.php(63): Bootstrap->_loadExistingController()\n#2 \/www\/wwwroot\/cpeSecure\/public\/index.php(11): Bootstrap->init()\n#3 \/www\/wwwroot\/cpeSecure\/index.php(2): require_once('...')\n#4 {main}", + "method": "GET", + "ip_address": "206.62.143.66", + "user_agent": "Mozilla\/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit\/537.36 (KHTML, like Gecko) Chrome\/147.0.0.0 Safari\/537.36", + "referer": null, + "status": "unresolved", + "createdate": "2026-04-25 20:31:22" + }, + { + "id": "err_69ed5cc5a99338.48739755", + "userid": null, + "org_id": null, + "errortype": "Exception", + "errorcode": "1", + "url": "https:\/\/secure.creditpullengine.com\/cron\/jsonReports", + "file": "\/www\/wwwroot\/cpeSecure\/public\/controllers\/cron.php", + "line": 14, + "fullerror": "Class \"Carbon\\Carbon\" not found", + "trace": "#0 \/www\/wwwroot\/cpeSecure\/core\/Bootstrap.php(148): Cron->__construct()\n#1 \/www\/wwwroot\/cpeSecure\/core\/Bootstrap.php(63): Bootstrap->_loadExistingController()\n#2 \/www\/wwwroot\/cpeSecure\/public\/index.php(11): Bootstrap->init()\n#3 \/www\/wwwroot\/cpeSecure\/index.php(2): require_once('...')\n#4 {main}", + "method": "GET", + "ip_address": "207.246.75.27", + "user_agent": null, + "referer": null, + "status": "unresolved", + "createdate": "2026-04-25 20:31:01" + }, + { + "id": "err_69ed5cc5a5d377.51767422", + "userid": null, + "org_id": null, + "errortype": "Exception", + "errorcode": "1", + "url": "https:\/\/secure.creditpullengine.com\/cron\/jsonReports", + "file": "\/www\/wwwroot\/cpeSecure\/public\/controllers\/cron.php", + "line": 14, + "fullerror": "Class \"Carbon\\Carbon\" not found", + "trace": "#0 \/www\/wwwroot\/cpeSecure\/core\/Bootstrap.php(148): Cron->__construct()\n#1 \/www\/wwwroot\/cpeSecure\/core\/Bootstrap.php(63): Bootstrap->_loadExistingController()\n#2 \/www\/wwwroot\/cpeSecure\/public\/index.php(11): Bootstrap->init()\n#3 \/www\/wwwroot\/cpeSecure\/index.php(2): require_once('...')\n#4 {main}", + "method": "GET", + "ip_address": "140.82.26.192", + "user_agent": "curl\/7.76.1", + "referer": null, + "status": "unresolved", + "createdate": "2026-04-25 20:31:01" + }, + { + "id": "err_69ed5c896b0de3.37548309", + "userid": null, + "org_id": null, + "errortype": "Exception", + "errorcode": "1", + "url": "https:\/\/secure.creditpullengine.com\/cron\/jsonReports", + "file": "\/www\/wwwroot\/cpeSecure\/public\/controllers\/cron.php", + "line": 14, + "fullerror": "Class \"App\\Core\\StripeService\" not found", + "trace": "#0 \/www\/wwwroot\/cpeSecure\/core\/Bootstrap.php(148): Cron->__construct()\n#1 \/www\/wwwroot\/cpeSecure\/core\/Bootstrap.php(63): Bootstrap->_loadExistingController()\n#2 \/www\/wwwroot\/cpeSecure\/public\/index.php(11): Bootstrap->init()\n#3 \/www\/wwwroot\/cpeSecure\/index.php(2): require_once('...')\n#4 {main}", + "method": "GET", + "ip_address": "207.246.75.27", + "user_agent": null, + "referer": null, + "status": "unresolved", + "createdate": "2026-04-25 20:30:01" + }, + { + "id": "err_69ed5c895c99d9.73562260", + "userid": null, + "org_id": null, + "errortype": "Exception", + "errorcode": "1", + "url": "https:\/\/secure.creditpullengine.com\/cron\/jsonReports", + "file": "\/www\/wwwroot\/cpeSecure\/public\/controllers\/cron.php", + "line": 14, + "fullerror": "Class \"App\\Core\\StripeService\" not found", + "trace": "#0 \/www\/wwwroot\/cpeSecure\/core\/Bootstrap.php(148): Cron->__construct()\n#1 \/www\/wwwroot\/cpeSecure\/core\/Bootstrap.php(63): Bootstrap->_loadExistingController()\n#2 \/www\/wwwroot\/cpeSecure\/public\/index.php(11): Bootstrap->init()\n#3 \/www\/wwwroot\/cpeSecure\/index.php(2): require_once('...')\n#4 {main}", + "method": "GET", + "ip_address": "140.82.26.192", + "user_agent": "curl\/7.76.1", + "referer": null, + "status": "unresolved", + "createdate": "2026-04-25 20:30:01" + }, + { + "id": "err_69e773f2990218.94993625", + "userid": 2548, + "org_id": null, + "errortype": "Exception", + "errorcode": "1", + "url": "http:\/\/nsecure.creditpullengine.com\/", + "file": "\/www\/wwwroot\/nSecure\/core\/View.php", + "line": 19, + "fullerror": "Failed opening required '\/www\/wwwroot\/nSecure\/public\/views\/index\/orgadmin.php' (include_path='.:')", + "trace": "#0 \/www\/wwwroot\/nSecure\/public\/controllers\/index.php(25): View->render()\n#1 \/www\/wwwroot\/nSecure\/core\/Bootstrap.php(133): Index->index()\n#2 \/www\/wwwroot\/nSecure\/core\/Bootstrap.php(40): Bootstrap->_loadDefaultController()\n#3 \/www\/wwwroot\/nSecure\/public\/index.php(11): Bootstrap->init()\n#4 \/www\/wwwroot\/nSecure\/index.php(2): require_once('...')\n#5 {main}", + "method": "GET", + "ip_address": "172.70.55.166", + "user_agent": "Mozilla\/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit\/537.36 (KHTML, like Gecko) Chrome\/147.0.0.0 Safari\/537.36", + "referer": "https:\/\/nsecure.creditpullengine.com\/company\/consumer-shield-llc.ZhYbs5uFyhX2G7JfdVIGsmxCGaDRq2A91vcUedk5_yU", + "status": "unresolved", + "createdate": "2026-04-21 08:56:18" + }, + { + "id": "err_69e77133a9dbc2.26211292", + "userid": 3, + "org_id": null, + "errortype": "Exception", + "errorcode": "1", + "url": "http:\/\/nsecure.creditpullengine.com\/pulls\/view\/1427.f5CHVieZZZGJISi3HECOj9z-oRbIbCuYysAbz22Y9oI", + "file": "\/www\/wwwroot\/nSecure\/public\/controllers\/pulls.php", + "line": 104, + "fullerror": "Call to undefined function redirect()", + "trace": "#0 \/www\/wwwroot\/nSecure\/core\/Bootstrap.php(222): pulls->view()\n#1 \/www\/wwwroot\/nSecure\/core\/Bootstrap.php(64): Bootstrap->_callControllerMethod()\n#2 \/www\/wwwroot\/nSecure\/public\/index.php(11): Bootstrap->init()\n#3 \/www\/wwwroot\/nSecure\/index.php(2): require_once('...')\n#4 {main}", + "method": "GET", + "ip_address": "172.70.55.166", + "user_agent": "Mozilla\/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit\/537.36 (KHTML, like Gecko) Chrome\/147.0.0.0 Safari\/537.36", + "referer": null, + "status": "unresolved", + "createdate": "2026-04-21 08:44:35" + }, + { + "id": "err_69e77014a27279.45734769", + "userid": 2566, + "org_id": null, + "errortype": "Exception", + "errorcode": "1", + "url": "http:\/\/nsecure.creditpullengine.com\/", + "file": "\/www\/wwwroot\/nSecure\/core\/View.php", + "line": 19, + "fullerror": "Failed opening required '\/www\/wwwroot\/nSecure\/public\/views\/index\/orgadmin.php' (include_path='.:')", + "trace": "#0 \/www\/wwwroot\/nSecure\/public\/controllers\/index.php(25): View->render()\n#1 \/www\/wwwroot\/nSecure\/core\/Bootstrap.php(133): Index->index()\n#2 \/www\/wwwroot\/nSecure\/core\/Bootstrap.php(40): Bootstrap->_loadDefaultController()\n#3 \/www\/wwwroot\/nSecure\/public\/index.php(11): Bootstrap->init()\n#4 \/www\/wwwroot\/nSecure\/index.php(2): require_once('...')\n#5 {main}", + "method": "GET", + "ip_address": "104.23.237.20", + "user_agent": "Mozilla\/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit\/537.36 (KHTML, like Gecko) Chrome\/147.0.0.0 Safari\/537.36", + "referer": "https:\/\/nsecure.creditpullengine.com\/admin\/users", + "status": "unresolved", + "createdate": "2026-04-21 08:39:48" + }, + { + "id": "err_69e76e93d1cc43.29318732", + "userid": 3, + "org_id": null, + "errortype": "Exception", + "errorcode": "1", + "url": "http:\/\/nsecure.creditpullengine.com\/pulls\/view\/1427.f5CHVieZZZGJISi3HECOj9z-oRbIbCuYysAbz22Y9oI", + "file": "\/www\/wwwroot\/nSecure\/public\/controllers\/pulls.php", + "line": 104, + "fullerror": "Call to undefined function redirect()", + "trace": "#0 \/www\/wwwroot\/nSecure\/core\/Bootstrap.php(222): pulls->view()\n#1 \/www\/wwwroot\/nSecure\/core\/Bootstrap.php(64): Bootstrap->_callControllerMethod()\n#2 \/www\/wwwroot\/nSecure\/public\/index.php(11): Bootstrap->init()\n#3 \/www\/wwwroot\/nSecure\/index.php(2): require_once('...')\n#4 {main}", + "method": "GET", + "ip_address": "104.23.237.20", + "user_agent": "Mozilla\/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit\/537.36 (KHTML, like Gecko) Chrome\/147.0.0.0 Safari\/537.36", + "referer": "https:\/\/nsecure.creditpullengine.com\/reports\/pulls\/consumer-shield-llc.YAj8lpRtFakNZbbopBFBpJGCDUF7bXC5B3HzJg0I3Bw", + "status": "unresolved", + "createdate": "2026-04-21 08:33:23" + }, + { + "id": "err_69e76bb0efcf58.05946519", + "userid": 3, + "org_id": null, + "errortype": "Exception", + "errorcode": "1", + "url": "http:\/\/nsecure.creditpullengine.com\/pulls\/view\/1459.jDV7q5dcrBikg_9FMFi4k6ZMtZokNkCl9Uk6JMYOIZg", + "file": "\/www\/wwwroot\/nSecure\/public\/controllers\/pulls.php", + "line": 104, + "fullerror": "Call to undefined function redirect()", + "trace": "#0 \/www\/wwwroot\/nSecure\/core\/Bootstrap.php(222): pulls->view()\n#1 \/www\/wwwroot\/nSecure\/core\/Bootstrap.php(64): Bootstrap->_callControllerMethod()\n#2 \/www\/wwwroot\/nSecure\/public\/index.php(11): Bootstrap->init()\n#3 \/www\/wwwroot\/nSecure\/index.php(2): require_once('...')\n#4 {main}", + "method": "GET", + "ip_address": "198.41.231.17", + "user_agent": "Mozilla\/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit\/537.36 (KHTML, like Gecko) Chrome\/147.0.0.0 Safari\/537.36", + "referer": "https:\/\/nsecure.creditpullengine.com\/reports\/pulls\/limpia-deudas-llc._i0s98IqNv38bmtnqrE7eluqQNSKLtkOdaJu2qOk3-I", + "status": "unresolved", + "createdate": "2026-04-21 08:21:04" + }, + { + "id": "err_69e76b1f397f19.04102361", + "userid": 2565, + "org_id": null, + "errortype": "Exception", + "errorcode": "1", + "url": "http:\/\/nsecure.creditpullengine.com\/", + "file": "\/www\/wwwroot\/nSecure\/core\/View.php", + "line": 19, + "fullerror": "Failed opening required '\/www\/wwwroot\/nSecure\/public\/views\/index\/orgadmin.php' (include_path='.:')", + "trace": "#0 \/www\/wwwroot\/nSecure\/public\/controllers\/index.php(25): View->render()\n#1 \/www\/wwwroot\/nSecure\/core\/Bootstrap.php(133): Index->index()\n#2 \/www\/wwwroot\/nSecure\/core\/Bootstrap.php(40): Bootstrap->_loadDefaultController()\n#3 \/www\/wwwroot\/nSecure\/public\/index.php(11): Bootstrap->init()\n#4 \/www\/wwwroot\/nSecure\/index.php(2): require_once('...')\n#5 {main}", + "method": "GET", + "ip_address": "172.68.7.18", + "user_agent": "Mozilla\/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit\/537.36 (KHTML, like Gecko) Chrome\/147.0.0.0 Safari\/537.36", + "referer": "https:\/\/nsecure.creditpullengine.com\/admin\/users", + "status": "unresolved", + "createdate": "2026-04-21 08:18:39" + } +] \ No newline at end of file