diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..5d6e68b Binary files /dev/null and b/.DS_Store differ diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..8f0de65 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,18 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false + +[*.{yml,yaml}] +indent_size = 2 + +[docker-compose.yml] +indent_size = 4 diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..ea0665b --- /dev/null +++ b/.env.example @@ -0,0 +1,59 @@ +APP_NAME=Laravel +APP_ENV=local +APP_KEY= +APP_DEBUG=true +APP_URL=http://localhost + +LOG_CHANNEL=stack +LOG_DEPRECATIONS_CHANNEL=null +LOG_LEVEL=debug + +DB_CONNECTION=mysql +DB_HOST=127.0.0.1 +DB_PORT=3306 +DB_DATABASE=laravel +DB_USERNAME=root +DB_PASSWORD= + +BROADCAST_DRIVER=log +CACHE_DRIVER=file +FILESYSTEM_DISK=local +QUEUE_CONNECTION=sync +SESSION_DRIVER=file +SESSION_LIFETIME=120 + +MEMCACHED_HOST=127.0.0.1 + +REDIS_HOST=127.0.0.1 +REDIS_PASSWORD=null +REDIS_PORT=6379 + +MAIL_MAILER=smtp +MAIL_HOST=mailpit +MAIL_PORT=1025 +MAIL_USERNAME=null +MAIL_PASSWORD=null +MAIL_ENCRYPTION=null +MAIL_FROM_ADDRESS="hello@example.com" +MAIL_FROM_NAME="${APP_NAME}" + +AWS_ACCESS_KEY_ID= +AWS_SECRET_ACCESS_KEY= +AWS_DEFAULT_REGION=us-east-1 +AWS_BUCKET= +AWS_USE_PATH_STYLE_ENDPOINT=false + +PUSHER_APP_ID= +PUSHER_APP_KEY= +PUSHER_APP_SECRET= +PUSHER_HOST= +PUSHER_PORT=443 +PUSHER_SCHEME=https +PUSHER_APP_CLUSTER=mt1 + +VITE_APP_NAME="${APP_NAME}" +VITE_PUSHER_APP_KEY="${PUSHER_APP_KEY}" +VITE_PUSHER_HOST="${PUSHER_HOST}" +VITE_PUSHER_PORT="${PUSHER_PORT}" +VITE_PUSHER_SCHEME="${PUSHER_SCHEME}" +VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..fcb21d3 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,11 @@ +* text=auto eol=lf + +*.blade.php diff=html +*.css diff=css +*.html diff=html +*.md diff=markdown +*.php diff=php + +/.github export-ignore +CHANGELOG.md export-ignore +.styleci.yml export-ignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..71e4b6e --- /dev/null +++ b/.gitignore @@ -0,0 +1,20 @@ +/.phpunit.cache +/node_modules +/public/build +/public/hot +/public/storage +/storage/*.key +/vendor +.env +.env.backup +.env.production +.phpunit.result.cache +Homestead.json +Homestead.yaml +auth.json +npm-debug.log +yarn-error.log +/.fleet +/.idea +/.vscode +Caddyfile \ No newline at end of file diff --git a/.htaccess b/.htaccess new file mode 100644 index 0000000..69a01f1 --- /dev/null +++ b/.htaccess @@ -0,0 +1,8 @@ + + RewriteEngine On + + # Redirigir todo hacia /public + RewriteCond %{REQUEST_URI} !^/public/ + RewriteRule ^(.*)$ public/$1 [L] + + diff --git a/LICENSE b/LICENSE index 301bf28..261eeb9 100644 --- a/LICENSE +++ b/LICENSE @@ -1,73 +1,201 @@ -Apache License -Version 2.0, January 2004 -http://www.apache.org/licenses/ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION -1. Definitions. + 1. Definitions. -"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. -"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. -"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. -"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. -"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. -"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. -"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). -"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. -"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." -"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. -2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. -3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. -4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: - (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and - (b) You must cause any modified files to carry prominent notices stating that You changed the files; and + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and - (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and - (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. - You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. -5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. -6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. -7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. -8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. -9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. -END OF TERMS AND CONDITIONS + END OF TERMS AND CONDITIONS -APPENDIX: How to apply the Apache License to your work. + APPENDIX: How to apply the Apache License to your work. -To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. -Copyright 2026 alonsojavizz + Copyright [yyyy] [name of copyright owner] -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at -http://www.apache.org/licenses/LICENSE-2.0 + http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md index dbdbbf2..1a4c26b 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,66 @@ -# vueloenglobo.mx +

Laravel Logo

+

+Build Status +Total Downloads +Latest Stable Version +License +

+ +## About Laravel + +Laravel is a web application framework with expressive, elegant syntax. We believe development must be an enjoyable and creative experience to be truly fulfilling. Laravel takes the pain out of development by easing common tasks used in many web projects, such as: + +- [Simple, fast routing engine](https://laravel.com/docs/routing). +- [Powerful dependency injection container](https://laravel.com/docs/container). +- Multiple back-ends for [session](https://laravel.com/docs/session) and [cache](https://laravel.com/docs/cache) storage. +- Expressive, intuitive [database ORM](https://laravel.com/docs/eloquent). +- Database agnostic [schema migrations](https://laravel.com/docs/migrations). +- [Robust background job processing](https://laravel.com/docs/queues). +- [Real-time event broadcasting](https://laravel.com/docs/broadcasting). + +Laravel is accessible, powerful, and provides tools required for large, robust applications. + +## Learning Laravel + +Laravel has the most extensive and thorough [documentation](https://laravel.com/docs) and video tutorial library of all modern web application frameworks, making it a breeze to get started with the framework. + +You may also try the [Laravel Bootcamp](https://bootcamp.laravel.com), where you will be guided through building a modern Laravel application from scratch. + +If you don't feel like reading, [Laracasts](https://laracasts.com) can help. Laracasts contains thousands of video tutorials on a range of topics including Laravel, modern PHP, unit testing, and JavaScript. Boost your skills by digging into our comprehensive video library. + +## Laravel Sponsors + +We would like to extend our thanks to the following sponsors for funding Laravel development. If you are interested in becoming a sponsor, please visit the [Laravel Partners program](https://partners.laravel.com). + +### Premium Partners + +- **[Vehikl](https://vehikl.com/)** +- **[Tighten Co.](https://tighten.co)** +- **[WebReinvent](https://webreinvent.com/)** +- **[Kirschbaum Development Group](https://kirschbaumdevelopment.com)** +- **[64 Robots](https://64robots.com)** +- **[Curotec](https://www.curotec.com/services/technologies/laravel/)** +- **[Cyber-Duck](https://cyber-duck.co.uk)** +- **[DevSquad](https://devsquad.com/hire-laravel-developers)** +- **[Jump24](https://jump24.co.uk)** +- **[Redberry](https://redberry.international/laravel/)** +- **[Active Logic](https://activelogic.com)** +- **[byte5](https://byte5.de)** +- **[OP.GG](https://op.gg)** + +## Contributing + +Thank you for considering contributing to the Laravel framework! The contribution guide can be found in the [Laravel documentation](https://laravel.com/docs/contributions). + +## Code of Conduct + +In order to ensure that the Laravel community is welcoming to all, please review and abide by the [Code of Conduct](https://laravel.com/docs/contributions#code-of-conduct). + +## Security Vulnerabilities + +If you discover a security vulnerability within Laravel, please send an e-mail to Taylor Otwell via [taylor@laravel.com](mailto:taylor@laravel.com). All security vulnerabilities will be promptly addressed. + +## License + +The Laravel framework is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT). diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php new file mode 100644 index 0000000..e6b9960 --- /dev/null +++ b/app/Console/Kernel.php @@ -0,0 +1,27 @@ +command('inspire')->hourly(); + } + + /** + * Register the commands for the application. + */ + protected function commands(): void + { + $this->load(__DIR__.'/Commands'); + + require base_path('routes/console.php'); + } +} diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php new file mode 100644 index 0000000..56af264 --- /dev/null +++ b/app/Exceptions/Handler.php @@ -0,0 +1,30 @@ + + */ + protected $dontFlash = [ + 'current_password', + 'password', + 'password_confirmation', + ]; + + /** + * Register the exception handling callbacks for the application. + */ + public function register(): void + { + $this->reportable(function (Throwable $e) { + // + }); + } +} diff --git a/app/Http/Controllers/AdminDashController.php b/app/Http/Controllers/AdminDashController.php new file mode 100644 index 0000000..575f0b8 --- /dev/null +++ b/app/Http/Controllers/AdminDashController.php @@ -0,0 +1,13 @@ +sitio_web = config('sitioweb.sitio_web_erp_id', 0); + $this->status = 1; + } + + public function getNombresImagenGlobosActivos() + { + try { + + $baseUrl = 'https://erp.skyballoons.mx/assets/images/globos/'; + + $globos = DB::table('globos') + ->select('nombre_globo', 'ruta_imagen') + ->where('estatus_globo', $this->status) + ->where('id_estatus', $this->status) + ->where('empresa', 'sky') + ->get() + ->map(function ($globo) use ($baseUrl) { + + if (!empty($globo->ruta_imagen)) { + + // Si ya es URL completa la dejamos igual + if (!filter_var($globo->ruta_imagen, FILTER_VALIDATE_URL)) { + + $archivo = ltrim($globo->ruta_imagen, '/'); + + $globo->ruta_imagen = $baseUrl . $archivo; + } + } + + return $globo; + }); + + return response()->json([ + 'success' => true, + 'data' => $globos + ]); + } catch (\Throwable $e) { + + Log::error('Error obteniendo globos activos', [ + 'message' => $e->getMessage() + ]); + + return response()->json([ + 'success' => false, + 'message' => 'No se pudieron obtener los globos' + ], 500); + } + } +} diff --git a/app/Http/Controllers/Api/TelegramController.php b/app/Http/Controllers/Api/TelegramController.php new file mode 100644 index 0000000..45e8c0a --- /dev/null +++ b/app/Http/Controllers/Api/TelegramController.php @@ -0,0 +1,313 @@ +token = env('TELEGRAM_BOT_TOKEN'); + $this->baseUrl = "https://api.telegram.org/bot{$this->token}"; + } + + /* + |-------------------------------------------------------------------------- + | WEBHOOK (Telegram -> Laravel) + |-------------------------------------------------------------------------- + */ + + public function webhook(Request $request) + { + $update = $request->all(); + + Log::info('Telegram Update', $update); + + if (isset($update['message'])) { + return $this->handleMessage($update['message']); + } + + if (isset($update['callback_query'])) { + return $this->handleCallback($update['callback_query']); + } + + return response()->json(['success' => true]); + } + + protected function handleMessage(array $message) + { + $chatId = $message['chat']['id']; + $text = trim($message['text'] ?? ''); + + return $this->sendMessage($chatId, "Recibido: {$text}"); + } + + protected function handleCallback(array $callback) + { + $chatId = $callback['message']['chat']['id']; + $data = $callback['data']; + + $this->answerCallbackQuery($callback['id'], 'OK'); + + return $this->sendMessage($chatId, "Botón: {$data}"); + } + + /* + |-------------------------------------------------------------------------- + | CORE REQUEST + |-------------------------------------------------------------------------- + */ + + protected function request(string $method, array $payload = []) + { + return Http::timeout(60) + ->post("{$this->baseUrl}/{$method}", $payload) + ->json(); + } + + /* + |-------------------------------------------------------------------------- + | MENSAJES BASE (TELEGRAM API) + |-------------------------------------------------------------------------- + */ + + public function sendMessage($chatId, $message, array $extra = []) + { + return $this->request('sendMessage', array_merge([ + 'chat_id' => $chatId, + 'text' => $message, + 'parse_mode' => 'HTML' + ], $extra)); + } + + public function sendPhotoFile($chatId, $path, $caption = '') + { + // 🔥 CASO 1: URL (internet) + if (filter_var($path, FILTER_VALIDATE_URL)) { + + return $this->request('sendPhoto', [ + 'chat_id' => $chatId, + 'photo' => $path, + 'caption' => $caption + ]); + } + + // 🔥 CASO 2: archivo local + if (!file_exists($path)) { + return response()->json([ + 'error' => 'Archivo no existe' + ], 400); + } + + return Http::attach( + 'photo', + fopen($path, 'r'), + basename($path) + )->post("{$this->baseUrl}/sendPhoto", [ + 'chat_id' => $chatId, + 'caption' => $caption + ])->json(); + } + + public function sendAudioFile($chatId, $path, $caption = '') + { + return Http::attach( + 'audio', + fopen($path, 'r'), + basename($path) + )->post("{$this->baseUrl}/sendAudio", [ + 'chat_id' => $chatId, + 'caption' => $caption + ])->json(); + } + + public function sendVideoFile($chatId, $path, $caption = '') + { + // 🔥 CASO 1: URL (internet) + if (filter_var($path, FILTER_VALIDATE_URL)) { + + return $this->request('sendVideo', [ + 'chat_id' => $chatId, + 'video' => $path, + 'caption' => $caption + ]); + } + + // 🔥 CASO 2: archivo local + if (!file_exists($path)) { + return response()->json([ + 'error' => 'Archivo no existe' + ], 400); + } + + return Http::attach( + 'video', + fopen($path, 'r'), + basename($path) + )->post("{$this->baseUrl}/sendVideo", [ + 'chat_id' => $chatId, + 'caption' => $caption + ])->json(); + } + + public function sendDocumentFile($chatId, $path, $caption = '') + { + return Http::attach( + 'document', + fopen($path, 'r'), + basename($path) + )->post("{$this->baseUrl}/sendDocument", [ + 'chat_id' => $chatId, + 'caption' => $caption + ])->json(); + } + + /* + |-------------------------------------------------------------------------- + | LOGS + |-------------------------------------------------------------------------- + */ + + public function sendLaravelLog(Request $request) + { + $file = storage_path('logs/laravel.log'); + + return $this->sendDocumentFile( + $request->chat_id, + $file, + 'Laravel Log' + ); + } + + /* + |-------------------------------------------------------------------------- + | CALLBACKS + |-------------------------------------------------------------------------- + */ + + public function answerCallbackQuery($id, $text = '') + { + return $this->request('answerCallbackQuery', [ + 'callback_query_id' => $id, + 'text' => $text + ]); + } + + /* + |-------------------------------------------------------------------------- + | BOT INFO + |-------------------------------------------------------------------------- + */ + + public function getMe() + { + return $this->request('getMe'); + } + + /* + |-------------------------------------------------------------------------- + | WRAPPERS (TUS RUTAS API) + |-------------------------------------------------------------------------- + */ + + public function sendText(Request $request) + { + return $this->sendMessage($request->chat_id, $request->message); + } + + public function sendImage(Request $request) + { + return $this->sendPhotoFile( + $request->chat_id, + $request->path, + $request->caption ?? '' + ); + } + + public function sendAudioApi(Request $request) + { + return $this->sendAudioFile( + $request->chat_id, + $request->path, + $request->caption ?? '' + ); + } + + public function sendVideoApi(Request $request) + { + return $this->sendVideoFile( + $request->chat_id, + $request->path, + $request->caption ?? '' + ); + } + + public function sendDocumentApi(Request $request) + { + return $this->sendDocumentFile( + $request->chat_id, + $request->path, + $request->caption ?? '' + ); + } + + public function sendLog(Request $request) + { + return $this->sendLaravelLog($request); + } + + public function notify(Request $request) + { + return $this->sendMessage( + $request->chat_id, + $request->message + ); + } + + public function botInfo() + { + return $this->getMe(); + } + + public function getChatId(Request $request) + { + $updates = $this->request('getUpdates'); + + if (!$updates || !isset($updates['result'])) { + return response()->json([ + 'success' => false, + 'message' => 'No hay mensajes' + ]); + } + + $chats = []; + + foreach ($updates['result'] as $update) { + + if (isset($update['message'])) { + $message = $update['message']; + + $chats[] = [ + 'chat_id' => $message['chat']['id'] ?? null, + 'type' => $message['chat']['type'] ?? null, + 'username' => $message['chat']['username'] ?? null, + 'first_name' => $message['chat']['first_name'] ?? null, + 'text' => $message['text'] ?? null, + 'date' => $message['date'] ?? null, + ]; + } + } + + return response()->json([ + 'success' => true, + 'chats' => $chats + ]); + } +} \ No newline at end of file diff --git a/app/Http/Controllers/Api/VuelosERPController.php b/app/Http/Controllers/Api/VuelosERPController.php new file mode 100644 index 0000000..20800a6 --- /dev/null +++ b/app/Http/Controllers/Api/VuelosERPController.php @@ -0,0 +1,480 @@ +sitio_web = config('sitioweb.sitio_web_erp_id', 0); + $this->status = 1; // Asumiendo que 1 es el estatus para vuelos activos + } + + public function getVuelosNombresPreciosPublico() + { + $vuelos = DB::table('vuelos') + ->where('id_sitio_web', $this->sitio_web) + ->where('id_estatus', $this->status ?? 0) + ->get(); + + $imagenes = DB::table('vuelos_imagenes_galeria') + ->where('id_estatus', 1) + ->orderBy('orden', 'asc') + ->get() + ->groupBy('id_vuelo'); + + $unidades = DB::table('unidades') + ->where('id_estatus', 1) + ->get() + ->keyBy('id_unidad'); + + $vuelos->transform(function ($vuelo) use ($imagenes, $unidades) { + + $vuelo->json_incluye = json_decode($vuelo->json_incluye, true); + + $vuelo->precios_adicionales = collect(json_decode($vuelo->precios_adicionales, true)) + ->map(function ($item) use ($unidades) { + + $idUnidad = (int) $item['id_unidad']; + + return [ + 'id_unidad' => $idUnidad, + 'precio' => (float) $item['precio'], + 'unidad' => $unidades->has($idUnidad) + ? [ + 'nombre_unidad' => $unidades[$idUnidad]->nombre_unidad, + 'nombre_corto' => $unidades[$idUnidad]->nombre_corto, + 'numero_personas_piezas' => $unidades[$idUnidad]->numero_personas_piezas, + ] + : null + ]; + }) + ->values(); + + $vuelo->imagenes_galeria = $imagenes->get($vuelo->id_vuelo, collect()) + ->map(function ($img) { + return [ + 'id_imagen_vuelo' => $img->id_imagen_vuelo, + 'ruta' => $img->ruta_imagen_vuelo, + 'descripcion' => $img->descripcion_imagen_vuelo, + 'orden' => $img->orden, + 'imagen_destacada' => (bool) $img->imagen_destacada, + ]; + }) + ->values(); + + return $vuelo; + }); + + return response()->json( + $vuelos, + 200, + [], + JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE + ); + } + + public function getServiciosAdicionales() + { + $response = Http::get('https://erp.skyballoons.mx/servicios-adicionales-webservice', [ + 'comercio' => 'skyballoons', + 'key' => 'f093328776ae4cd210dc7b4ee4093e45', + ]); + if ($response->successful()) { + $data = $response->json(); + return response()->json( + $data, + 200, + [], + JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE + ); + } else { + return response()->json(['error' => 'No se pudieron obtener los servicios adicionales'], 500); + } + } + + + /*public function reservarVuelo(Request $request) + { + try { + //consultar el vuelo en globo + $vuelo = DB::table('vuelos')->where('id_vuelo', $request->id_vuelo)->first(); + $data = [ + // Datos del cliente + 'nombre_reserva' => $request->responsable['nombres'], + 'apellido_reserva' => $request->responsable['apellidos'], + 'email_reserva' => $request->responsable['email'], + 'tel_reserva' => preg_replace('/\D/', '', $request->responsable['telefono']), + 'Lada' => 52, + 'fecha_vuelo' => $request->fecha, + // Datos del vuelo + 'id_vuelo' => $request->id_vuelo, + 'id_unidad' => $vuelo->id_unidad, + 'pasajeros' => ($request->adultos ?? 0) + ($request->ninios ?? 0), + 'precio_publico' => $request->total, + + // Totales + 'total_compra' => $request->total, + + // Pasajeros adicionales + 'num_pax_adicionales' => 0, + 'id_unidad_adicionales' => null, + 'precio_adicional' => 0, + + // Servicios adicionales + 'servicios_adicionales' => collect($request->servicios ?? []) + ->map(function ($servicio) { + return [ + 'id_servicio' => $servicio['id_servicio'], + 'cantidad_solicitada' => $servicio['cantidad'] ?? 1, + 'precio' => $servicio['precio'] ?? 0, + ]; + }) + ->toArray(), + ]; + return $data; + $resultado = $this->crearCotizacionCompleta($data); + + return response()->json($resultado); + } catch (\Exception $e) { + return response()->json(['error' => 'Error al procesar la reserva: ' . $e->getMessage()], 500); + } + }*/ + + public function reservarVuelo(Request $request) + { + try { + // 1. BLINDAJE: Detectar si 'servicios' llegó como string JSON y parsearlo + $serviciosRaw = $request->servicios; + if (is_string($serviciosRaw)) { + $serviciosRaw = json_decode($serviciosRaw, true); + } + // Si no es un array válido tras el decode o viene vacío, asignamos un array limpio + $serviciosArray = is_array($serviciosRaw) ? $serviciosRaw : []; + + // Consultar el vuelo en globo + $vuelo = DB::table('vuelos')->where('id_vuelo', $request->id_vuelo)->first(); + + if (!$vuelo) { + return response()->json(['error' => 'El vuelo solicitado no existe.'], 404); + } + + $data = [ + // Datos del cliente + 'nombre_reserva' => $request->responsable['nombres'] ?? '', + 'apellido_reserva' => $request->responsable['apellidos'] ?? '', + 'email_reserva' => $request->responsable['email'] ?? '', + 'tel_reserva' => preg_replace('/\D/', '', $request->responsable['telefono'] ?? ''), + 'Lada' => 52, + 'fecha_vuelo' => $request->fecha, + + // Datos del vuelo + 'id_vuelo' => $vuelo->id_vuelo, + 'id_unidad' => $vuelo->id_unidad, + 'pasajeros' => ($request->adultos ?? 0) + ($request->ninios ?? 0), + 'precio_publico' => $request->total, + + // Totales + 'total_compra' => $request->total, + + // Pasajeros adicionales + 'num_pax_adicionales' => 0, + 'id_unidad_adicionales' => null, + 'precio_adicional' => 0, + + // Servicios adicionales (procesados de forma segura) + 'servicios_adicionales' => collect($serviciosArray) + ->map(function ($servicio) { + return [ + 'id_servicio' => $servicio['id_servicio'] ?? null, + 'cantidad_solicitada' => $servicio['cantidad'] ?? 1, + 'precio' => $servicio['precio'] ?? 0, + ]; + }) + ->filter(fn($s) => !is_null($s['id_servicio'])) // Quita elementos mal formados + ->toArray(), + ]; + + // ⚠️ NOTA: Eliminamos el 'return $data;' para que el flujo continúe hacia la cotización real. + $id_cotizacion = $this->crearCotizacionCompleta($data); + $route = route('checkout.detalle.cotizacion.chatbot', ['cotizacion' => $id_cotizacion]); + + return response()->json(['id_cotizacion' => $id_cotizacion, 'route' => $route]); + } catch (\Exception $e) { + return response()->json(['error' => 'Error al procesar la reserva: ' . $e->getMessage()], 500); + } + } + + + public function crearCotizacionCompleta(array $data) + { + try { + DB::beginTransaction(); + + // 1. Generar folio + $ultimoId = (DB::table('cotizaciones')->max('id_cotizacion') ?? 0) + 1; + $folio = "COT" . str_pad($ultimoId, 7, "0", STR_PAD_LEFT); + + + $vuelo = DB::table('vuelos')->where('id_vuelo', $data['id_vuelo'])->where('id_estatus', 1)->where('id_sitio_web', $this->sitio_web)->first() ?? null; + + // 2. Insertar cotización principal + $id_cotizacion = DB::table('cotizaciones')->insertGetId([ + 'folio' => $folio, + 'nombre' => $data['nombre_reserva'], + 'apellidos' => $data['apellido_reserva'], + 'mail' => $data['email_reserva'], + 'telefono' => $data['tel_reserva'], + 'iso_country_id_primary_phone' => $data['Lada'], + 'subtotal' => $data['total_compra'], + 'total' => $data['total_compra'], + 'total_con_iva' => $data['total_compra'], + 'total_neto' => $data['total_compra'], + 'id_sitio_web' => $this->sitio_web, + 'observaciones' => 'Cotizacion desde www.vueloenglobo.mx', + 'id_user_alta' => 98, + 'fecha_alta' => now(), + 'fecha_vuelo' => $data['fecha_vuelo'] ?? null, + + ]); + + // 3. Insertar vuelo principal + DB::table('vuelos_cotizaciones')->insert([ + 'id_cotizacion' => $id_cotizacion, + 'id_vuelo' => $vuelo->id_vuelo, + 'id_unidad' => $data['id_unidad'], + 'cantidades' => $data['pasajeros'], + 'precio_vendido_p_pax_vuelo' => $vuelo->precio_publico, + 'precio_especial' => $vuelo->precio_publico, + 'id_estatus' => 1, + 'id_user_alta' => 98, + 'fecha_alta' => now(), + ]); + + + // 4. Pasajeros adicionales + /* if (!empty($data['num_pax_adicionales']) && $data['num_pax_adicionales'] > 0) { + DB::table('vuelos_cotizaciones')->insert([ + 'id_cotizacion' => $id_cotizacion, + 'id_vuelo' => $data['id_vuelo'], + 'id_unidad' => $data['id_unidad_adicionales'], + 'cantidades' => $data['num_pax_adicionales'], + 'precio_vendido_p_pax_vuelo' => $data['precio_adicional'], + 'pasajero_adicional' => 1, + 'id_estatus' => 1, + 'id_user_alta' => 98, + 'fecha_alta' => now(), + ]); + }*/ + + // 5. Servicios adicionales + if (!empty($data['servicios_adicionales'])) { + + $servicios = is_string($data['servicios_adicionales']) + ? json_decode($data['servicios_adicionales'], true) + : $data['servicios_adicionales']; + + foreach ($servicios as $servicio) { + DB::table('servicios_adicionales_cotizaciones')->insert([ + 'id_cotizacion' => $id_cotizacion, + 'id_servicio_adicional' => $servicio['id_servicio'], + 'cantidades' => $servicio['cantidad_solicitada'], + 'precio_vendido' => $servicio['precio'], + 'id_estatus' => 1, + 'id_user_alta' => 98, + 'fecha_alta' => now(), + ]); + } + } + + + + DB::commit(); + return $id_cotizacion; + return [ + 'success' => true, + 'id_cotizacion' => $id_cotizacion, + 'folio' => $folio + ]; + } catch (\Exception $e) { + DB::rollBack(); + + return [ + 'success' => false, + 'error' => $e->getMessage() + ]; + } + } + + /* + public function efectuarPago(Request $request) + { + try { + $cotizacion_id = $request->id_cotizacion ?? 0; + $cotizacion = DB::table('cotizaciones')->where('id_cotizacion', $cotizacion_id)->first(); + + //aqui va lo del pago con clip, si se efectua entonces se generan el pase de abordar y el pedido + + DB::table('pases_de_abordar')->insert([ + 'id_cotizacion' => $cotizacion_id, + 'id_lugar_cita' => 12, + 'hora_cita' => '06:00', + 'id_recibo' => 0, + 'id_estatus' => 1, + 'id_user_alta' => 98, + 'fecha_alta' => now(), + ]); + + + DB::table('pedidos')->insert([ + 'id_cotizacion' => $cotizacion_id, + 'foto_comprobante' => null, + 'cantidad_pagada' => $cotizacion->total_neto, + 'pago_confirmado' => 1, + 'fecha_pago' => date('Y-m-d'), + 'observaciones' => 'Transacción realizada vía Clip PAGO CON TARJETA desde vueloenglobo.mx | ' . $cotizacion->nombre . ' ' . $cotizacion->apellidos . ' - ID Cotizacion:' . $cotizacion_id, + 'observaciones_logistica' => 'Transacción realizada vía Clip PAGO CON TARJETA desde vueloenglobo.mx | ' . $cotizacion->nombre . ' ' . $cotizacion->apellidos . ' - ID Cotizacion:' . $cotizacion_id, + 'ultimos_digitos' => null, + 'id_cuenta_bancaria' => 1, + 'id_metodo_pago_cuenta_bancaria' => 9, + 'id_banco' => 0, + 'id_estatus' => 1, + 'id_user_alta' => 98, + 'fecha_alta' => now(), + ]); + + + $url = url('https://erp.skyballoons.mx/enviar-pase/2/' . $cotizacion_id); + Http::get($url); + } catch (\Exception $e) { + return response()->json([ + 'success' => false, + 'message' => 'Error al procesar el pago: ' . $e->getMessage() + ], 500); + } + }*/ + + + public function efectuarPago(Request $request) + { + try { + + $validated = $request->validate([ + 'token' => 'required|string', + 'id_cotizacion' => 'required|integer' + ]); + + $cotizacion = DB::table('cotizaciones') + ->where('id_cotizacion', $validated['id_cotizacion']) + ->first(); + + if (!$cotizacion) { + return response()->json([ + 'success' => false, + 'message' => 'Cotización no encontrada' + ], 404); + } + + // ========================== + // COBRO CON CLIP + // ========================== + $payload = [ + 'amount' => (float) $cotizacion->total_neto, + 'currency' => 'MXN', + 'description' => 'Pago vuelo en globo - ' . $cotizacion->folio, + 'payment_method' => [ + 'token' => $validated['token'] + ], + 'customer' => [ + 'email' => $cotizacion->mail, + 'phone' => $cotizacion->telefono + ] + ]; + + $response = Http::withHeaders([ + 'Authorization' => 'Bearer ' . config('clip.api_key'), + 'Content-Type' => 'application/json', + ])->post(config('clip.payments_url'), $payload); + + // 🔴 FALLO TOTAL: cortar flujo + if (!$response->successful()) { + return response()->json([ + 'success' => false, + 'message' => $response->json('detail') ?? 'Pago rechazado', + 'clip_response' => $response->json() + ], 400); + } + + $clipPayment = $response->json(); + + // 🔴 VALIDAR ESTATUS REAL DEL PAGO (MUY IMPORTANTE) + if (($clipPayment['status'] ?? null) !== 'approved') { + return response()->json([ + 'success' => false, + 'message' => 'El pago no fue aprobado', + 'clip_response' => $clipPayment + ], 400); + } + + // ========================== + // SOLO SI FUE APROBADO + // ========================== + DB::beginTransaction(); + + DB::table('pases_de_abordar')->insert([ + 'id_cotizacion' => $cotizacion->id_cotizacion, + 'id_lugar_cita' => 12, + 'hora_cita' => '06:00', + 'id_recibo' => 0, + 'id_estatus' => 1, + 'id_user_alta' => 98, + 'fecha_alta' => now(), + ]); + + DB::table('pedidos')->insert([ + 'id_cotizacion' => $cotizacion->id_cotizacion, + 'foto_comprobante' => null, + 'cantidad_pagada' => $cotizacion->total_neto, + 'pago_confirmado' => 1, + 'fecha_pago' => now(), + 'observaciones' => 'Clip payment ID: ' . ($clipPayment['id'] ?? ''), + 'observaciones_logistica' => 'Clip payment ID: ' . ($clipPayment['id'] ?? ''), + 'ultimos_digitos' => $clipPayment['payment_method']['card']['last4'] ?? null, + 'id_cuenta_bancaria' => 1, + 'id_metodo_pago_cuenta_bancaria' => 9, + 'id_banco' => 0, + 'id_estatus' => 1, + 'id_user_alta' => 98, + 'fecha_alta' => now(), + ]); + + DB::commit(); + + Http::timeout(30)->get( + 'https://erp.skyballoons.mx/enviar-pase/2/' . $cotizacion->id_cotizacion + ); + + return response()->json([ + 'success' => true, + 'message' => 'Pago realizado correctamente', + 'payment_id' => $clipPayment['id'] ?? null + ]); + } catch (\Exception $e) { + + DB::rollBack(); + + return response()->json([ + 'success' => false, + 'message' => $e->getMessage() + ], 500); + } + } +} diff --git a/app/Http/Controllers/ApiConsultasController.php b/app/Http/Controllers/ApiConsultasController.php new file mode 100644 index 0000000..17f32e0 --- /dev/null +++ b/app/Http/Controllers/ApiConsultasController.php @@ -0,0 +1,26 @@ +where('id_sitio_web', $sitioId) + ->where('id_estatus', 1) + ->get(); + } + + /** + * Obtener vuelos con categoría + */ + public static function getWithCategoria($sitioId = 6) + { + return DB::table('vuelos as v') + ->join('categorias_vuelos as c', 'v.id_categoria', '=', 'c.id_categoria') + ->select( + 'v.*', + 'c.nombre_categoria' + ) + ->where('v.id_sitio_web', $sitioId) + ->where('v.id_estatus', 1) + ->get(); + } + + /** + * Obtener vuelos con precios + */ + public static function getWithPrecios($sitioId = 6) + { + return DB::table('vuelos as v') + ->leftJoin('vuelos_precios as p', 'v.id_vuelo', '=', 'p.id_vuelo') + ->select( + 'v.id_vuelo', + 'v.nombre_vuelo', + 'p.precio_vuelo_original', + 'p.precio_vuelo_especial', + 'p.moneda_precio' + ) + ->where('v.id_sitio_web', $sitioId) + ->where('v.id_estatus', 1) + ->get(); + } + + /** + * Obtener vuelos con imágenes + */ + public static function getWithGaleria($sitioId = 6) + { + return DB::table('vuelos as v') + ->leftJoin('vuelos_imagenes_galeria as g', 'v.id_vuelo', '=', 'g.id_vuelo') + ->select( + 'v.id_vuelo', + 'v.nombre_vuelo', + 'g.ruta_imagen_vuelo', + 'g.imagen_destacada' + ) + ->where('v.id_sitio_web', $sitioId) + ->where('v.id_estatus', 1) + ->get(); + } + + /** + * Obtener vuelos con items incluidos + */ + public static function getWithIncluye($vueloId) + { + return DB::table('vuelos_items_incluye') + ->where('id_vuelo', $vueloId) + ->where('id_estatus', 1) + ->get(); + } + + /** + * Obtener fechas bloqueadas de un vuelo + */ + public static function getFechasBloqueadas($vueloId) + { + return DB::table('fechas_vuelos_bloqueadas') + ->where('id_vuelo', $vueloId) + ->where('id_estatus', 1) + ->pluck('fechas_vuelo'); + } + + /** + * Obtener fechas disponibles para venta + */ + public static function getFechasVenta($vueloId) + { + return DB::table('vuelos_fechas_venta') + ->where('id_vuelo', $vueloId) + ->where('id_estatus', 1) + ->pluck('fecha_venta'); + } + + /** + * Obtener vuelos con precios en USD + */ + public static function getVuelosUSD($sitioId = 6) + { + return DB::table('vuelos as v') + ->join('vuelos_usd as u', 'v.id_vuelo', '=', 'u.id_vuelo') + ->select( + 'v.id_vuelo', + 'v.nombre_vuelo', + 'u.precio_usd', + 'u.precio_especial_usd' + ) + ->where('v.id_sitio_web', $sitioId) + ->where('v.id_estatus', 1) + ->get(); + } +} diff --git a/app/Http/Controllers/Auth/ConfirmPasswordController.php b/app/Http/Controllers/Auth/ConfirmPasswordController.php new file mode 100644 index 0000000..3559954 --- /dev/null +++ b/app/Http/Controllers/Auth/ConfirmPasswordController.php @@ -0,0 +1,39 @@ +middleware('auth'); + } +} diff --git a/app/Http/Controllers/Auth/ForgotPasswordController.php b/app/Http/Controllers/Auth/ForgotPasswordController.php new file mode 100644 index 0000000..465c39c --- /dev/null +++ b/app/Http/Controllers/Auth/ForgotPasswordController.php @@ -0,0 +1,22 @@ +middleware('guest')->except('logout'); + $this->middleware('auth')->only('logout'); + } +} diff --git a/app/Http/Controllers/Auth/RegisterController.php b/app/Http/Controllers/Auth/RegisterController.php new file mode 100644 index 0000000..8a28cd0 --- /dev/null +++ b/app/Http/Controllers/Auth/RegisterController.php @@ -0,0 +1,71 @@ +middleware('guest'); + } + + /** + * Get a validator for an incoming registration request. + * + * @return \Illuminate\Contracts\Validation\Validator + */ + protected function validator(array $data) + { + return Validator::make($data, [ + 'name' => ['required', 'string', 'max:255'], + 'email' => ['required', 'string', 'email', 'max:255', 'unique:users'], + 'password' => ['required', 'string', 'min:8', 'confirmed'], + ]); + } + + /** + * Create a new user instance after a valid registration. + * + * @return User + */ + protected function create(array $data) + { + return User::create([ + 'name' => $data['name'], + 'email' => $data['email'], + 'password' => Hash::make($data['password']), + ]); + } +} diff --git a/app/Http/Controllers/Auth/ResetPasswordController.php b/app/Http/Controllers/Auth/ResetPasswordController.php new file mode 100644 index 0000000..fe965b2 --- /dev/null +++ b/app/Http/Controllers/Auth/ResetPasswordController.php @@ -0,0 +1,29 @@ +middleware('auth'); + $this->middleware('signed')->only('verify'); + $this->middleware('throttle:6,1')->only('verify', 'resend'); + } +} diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php new file mode 100644 index 0000000..77ec359 --- /dev/null +++ b/app/Http/Controllers/Controller.php @@ -0,0 +1,12 @@ +max('id_cotizacion') + 1; + + $folio = 'COT' . str_pad($ultimoId, 7, '0', STR_PAD_LEFT); + + $id_cotizacion = DB::table('cotizaciones')->insertGetId([ + 'folio' => $folio, + 'nombre' => $datos['nombre'], + 'apellidos' => $datos['apellidos'], + 'mail' => $datos['email'], + 'telefono' => $datos['telefono'], + 'total' => $datos['total_compra'], + 'subtotal' => $datos['total_compra'], + 'id_sitio_web' => self::$sitio_web_id, + 'id_user_alta' => 98, + 'fecha_alta' => Carbon::now(), + ]); + + DB::table('posibles_compras')->insert([ + 'id_cotizacion' => $id_cotizacion, + 'id_sitio_web' => self::$sitio_web_id, + 'total_reservado' => $datos['total_compra'], + 'total_reserva' => $datos['total_a_pagar'], + 'fecha_alta' => Carbon::now(), + ]); + + DB::commit(); + + return [ + 'exito' => 1, + 'id_cotizacion' => $id_cotizacion, + 'folio' => $folio + ]; + } catch (\Exception $e) { + DB::rollBack(); + + return [ + 'exito' => 0, + 'error' => $e->getMessage() + ]; + } + } + + + + public function crearCotizacionCompleta(array $data) + { + try { + DB::beginTransaction(); + + // 1. Generar folio + $ultimoId = (DB::table('cotizaciones')->max('id_cotizacion') ?? 0) + 1; + $folio = "COT" . str_pad($ultimoId, 7, "0", STR_PAD_LEFT); + + + $vuelo = DB::table('vuelos')->where('id_vuelo', $data['id_vuelo'])->first(); + + // 2. Insertar cotización principal + $id_cotizacion = DB::table('cotizaciones')->insertGetId([ + 'folio' => $folio, + 'nombre' => $data['nombre_reserva'], + 'apellidos' => $data['apellido_reserva'], + 'mail' => $data['email_reserva'], + 'telefono' => $data['tel_reserva'], + 'iso_country_id_primary_phone' => $data['Lada'], + 'subtotal' => $data['total_compra'], + 'total' => $data['total_compra'], + 'total_con_iva' => $data['total_compra'], + 'total_neto' => $data['total_compra'], + 'id_sitio_web' => self::$sitio_web_id, + 'observaciones' => 'Cotizacion desde www.vueloenglobo.mx', + 'id_user_alta' => 98, + 'fecha_alta' => now(), + 'fecha_vuelo' => $data['fecha_vuelo'] ?? null, + + ]); + + // 3. Insertar vuelo principal + DB::table('vuelos_cotizaciones')->insert([ + 'id_cotizacion' => $id_cotizacion, + 'id_vuelo' => $data['id_vuelo'], + 'id_unidad' => $data['id_unidad'], + 'cantidades' => $data['pasajeros'], + 'precio_vendido_p_pax_vuelo' => $vuelo->precio_publico, + 'precio_especial' => $vuelo->precio_publico, + 'id_estatus' => 1, + 'id_user_alta' => 98, + 'fecha_alta' => now(), + ]); + + DB::table('pedidos')->insert([ + 'id_cotizacion' => $id_cotizacion, + 'foto_comprobante' => null, + 'cantidad_pagada' => $data['total_compra'], + 'pago_confirmado' => 1, + 'fecha_pago' => date('Y-m-d'), + 'observaciones' => 'Transacción realizada vía Clip PAGO CON TARJETA desde vueloenglobo.mx | ' . $data['nombre_reserva'] . ' ' . $data['apellido_reserva'] . ' - ID Cotizacion:' . $id_cotizacion, + 'observaciones_logistica' => 'Transacción realizada vía Clip PAGO CON TARJETA desde vueloenglobo.mx | ' . $data['nombre_reserva'] . ' ' . $data['apellido_reserva'] . ' - ID Cotizacion:' . $id_cotizacion, + 'ultimos_digitos' => null, + 'id_cuenta_bancaria' => 1, + 'id_metodo_pago_cuenta_bancaria' => 9, + 'id_banco' => 0, + 'id_estatus' => 1, + 'id_user_alta' => 98, + + 'fecha_alta' => now(), + ]); + + + DB::table('pases_de_abordar')->insert([ + 'id_cotizacion' => $id_cotizacion, + 'id_lugar_cita' => 12, + 'hora_cita' => '06:00', + 'id_recibo' => 0, + 'id_estatus' => 1, + 'id_user_alta' => 98, + 'fecha_alta' => now(), + ]); + + // 4. Pasajeros adicionales + if (!empty($data['num_pax_adicionales']) && $data['num_pax_adicionales'] > 0) { + DB::table('vuelos_cotizaciones')->insert([ + 'id_cotizacion' => $id_cotizacion, + 'id_vuelo' => $data['id_vuelo'], + 'id_unidad' => $data['id_unidad_adicionales'], + 'cantidades' => $data['num_pax_adicionales'], + 'precio_vendido_p_pax_vuelo' => $data['precio_adicional'], + 'pasajero_adicional' => 1, + 'id_estatus' => 1, + 'id_user_alta' => 98, + 'fecha_alta' => now(), + ]); + } + + // 5. Servicios adicionales + if (!empty($data['servicios_adicionales'])) { + + $servicios = is_string($data['servicios_adicionales']) + ? json_decode($data['servicios_adicionales'], true) + : $data['servicios_adicionales']; + + foreach ($servicios as $servicio) { + DB::table('servicios_adicionales_cotizaciones')->insert([ + 'id_cotizacion' => $id_cotizacion, + 'id_servicio_adicional' => $servicio['id_servicio'], + 'cantidades' => $servicio['cantidad_solicitada'], + 'precio_vendido' => $servicio['precio'], + 'id_estatus' => 1, + 'id_user_alta' => 98, + 'fecha_alta' => now(), + ]); + } + } + + + $url = url('https://erp.skyballoons.mx/enviar-pase/2/' . $id_cotizacion); + FacadesHttp::get($url); + + DB::commit(); + + return [ + 'success' => true, + 'id_cotizacion' => $id_cotizacion, + 'folio' => $folio + ]; + } catch (\Exception $e) { + DB::rollBack(); + + return [ + 'success' => false, + 'error' => $e->getMessage() + ]; + } + } + + + public function generaCot(Request $request) + { + $data = [ + // Datos del cliente + 'nombre_reserva' => $request->responsable['nombres'], + 'apellido_reserva' => $request->responsable['apellidos'], + 'email_reserva' => $request->responsable['email'], + 'tel_reserva' => preg_replace('/\D/', '', $request->responsable['telefono']), + 'Lada' => 52, // o extraerla dinámicamente + 'fecha_vuelo' => $request->fecha, + // Datos del vuelo + 'id_vuelo' => $request->id_vuelo, + 'id_unidad' => 1, // obtener según tu lógica de negocio + 'pasajeros' => ($request->adultos ?? 0) + ($request->ninios ?? 0), + 'precio_publico' => $request->total, + + // Totales + 'total_compra' => $request->total, + + // Pasajeros adicionales + 'num_pax_adicionales' => 0, + 'id_unidad_adicionales' => null, + 'precio_adicional' => 0, + + // Servicios adicionales + 'servicios_adicionales' => collect($request->servicios ?? []) + ->map(function ($servicio) { + return [ + 'id_servicio' => $servicio['id_servicio'], + 'cantidad_solicitada' => $servicio['cantidad'] ?? 1, + 'precio' => $servicio['precio'] ?? 0, + ]; + }) + ->toArray(), + ]; + + $resultado = $this->crearCotizacionCompleta($data); + + return response()->json($resultado); + } + + /* public function detalleCotizacionChatbot(Request $request, $cotizacion) + { + $pedidos = DB::table('pedidos') + ->where('id_cotizacion', $cotizacion) + ->first() ?? null; + + if ($pedidos) { + return response()->json(['error' => 'La cotización ya ha sido pagada.']); + } + + // return $pago!=null? response()->json(['error' => 'La cotización ya ha sido pagada.']) : ''; + $cotizacion = DB::table('cotizaciones') + ->where('id_cotizacion', $cotizacion) + ->first(); + + $vueloCotizado = DB::table('vuelos_cotizaciones') + ->where('id_cotizacion', $cotizacion->id_cotizacion) + ->first(); + + $vuelo = DB::table('vuelos') + ->where('id_vuelo', $vueloCotizado->id_vuelo) + ->first(); + + + $resultado = [ + 'folio' => $cotizacion->folio, + 'cliente' => $cotizacion->nombre . ' ' . $cotizacion->apellidos, + 'email' => $cotizacion->mail, + 'telefono' => $cotizacion->telefono, + 'fecha_vuelo' => $cotizacion->fecha_vuelo, + 'total' => $cotizacion->total, + 'moneda' => $cotizacion->tipo_moneda, + 'vuelo' => [ + 'id_vuelo_cotizacion' => $vueloCotizado->id_vuelo_cotizacion, + 'cantidad_pax' => $vueloCotizado->cantidades, + 'precio_unitario' => $vueloCotizado->precio_vendido_p_pax_vuelo, + 'precio_especial' => $vueloCotizado->precio_especial, + ] + ]; + + + + + $adicionalesCotizados = DB::table('servicios_adicionales_cotizaciones') + ->where('id_cotizacion', $cotizacion->id_cotizacion) + ->get(); + return $adicionalesCotizados; + $datosPago = [ + 'total' => $cotizacion->total_neto ?? 0, + 'moneda' => $cotizacion->moneda ?? 'MXN', + 'simbolo' => ($cotizacion->moneda ?? 'MXN') === 'USD' ? 'US$' : '$', + 'descripcionPago' => $cotizacion->folio ?? 'Vuelo cotizado', + 'id_cotizacion' => $cotizacion->id_cotizacion, + 'vuelo' => $cotizacion->folio, + 'adicionales' => $adicionalesCotizados, + 'responsable' => [ + 'nombres' => $cotizacion->nombre ?? '', + 'apellidos' => $cotizacion->apellidos ?? '', + 'email' => $cotizacion->mail ?? '', + 'telefono' => $cotizacion->telefono ?? '', + ], + 'fecha_vuelo' => $cotizacion->fecha_vuelo ?? null, + 'adultos' => $cotizacion->adultos ?? 1, + 'ninios' => $cotizacion->ninios ?? 0, + 'isSpanish' => app()->getLocale() === 'es', + ]; + // return $vuelo; + return view('cotizacion.chatbot.checkout', compact('datosPago', 'vueloCotizado', 'adicionalesCotizados', 'vuelo')); + } */ + + public function detalleCotizacionChatbot(Request $request, $cotizacion) + { + // Verificar si ya está pagada + $pedidos = DB::table('pedidos') + ->where('id_cotizacion', $cotizacion) + ->first(); + + if ($pedidos) { + return response()->json(['error' => 'La cotización ya ha sido pagada.']); + } + + // Obtener cotización + $cotizacion = DB::table('cotizaciones') + ->where('id_cotizacion', $cotizacion) + ->first(); + + if (!$cotizacion) { + return response()->json(['error' => 'Cotización no encontrada']); + } + + // Obtener vuelo cotizado + $vueloCotizado = DB::table('vuelos_cotizaciones') + ->where('id_cotizacion', $cotizacion->id_cotizacion) + ->first(); + + $vuelo = null; + if ($vueloCotizado) { + $vuelo = DB::table('vuelos') + ->where('id_vuelo', $vueloCotizado->id_vuelo) + ->first(); + } + + // Obtener adicionales + $adicionalesCotizados = DB::table('servicios_adicionales_cotizaciones') + ->where('id_cotizacion', $cotizacion->id_cotizacion) + ->get(); + + // Preparar array datosPago + $datosPago = [ + 'total' => $cotizacion->total ?? 0, + 'moneda' => $cotizacion->tipo_moneda ?? 'MXN', + 'simbolo' => ($cotizacion->tipo_moneda ?? 'MXN') === 'USD' ? 'US$' : '$', + 'descripcionPago' => $cotizacion->folio ?? 'Vuelo cotizado', + 'id_cotizacion' => $cotizacion->id_cotizacion, + 'vuelo_folio' => $cotizacion->folio, + 'adicionales' => $adicionalesCotizados, + 'responsable' => [ + 'nombres' => $cotizacion->nombre ?? '', + 'apellidos' => $cotizacion->apellidos ?? '', + 'email' => $cotizacion->mail ?? '', + 'telefono' => $cotizacion->telefono ?? '', + ], + 'fecha_vuelo' => $cotizacion->fecha_vuelo ?? null, + 'adultos' => $cotizacion->adultos ?? 1, + 'ninios' => $cotizacion->ninios ?? 0, + 'isSpanish' => app()->getLocale() === 'es', + // Información del vuelo + 'nombre_vuelo' => $vuelo->nombre_vuelo ?? 'Vuelo sin nombre', + 'json_incluye' => isset($vuelo->json_incluye) ? json_decode($vuelo->json_incluye, true) : [], + 'cantidad_pax' => $vueloCotizado->cantidades ?? 0, + 'precio_unitario' => $vueloCotizado->precio_vendido_p_pax_vuelo ?? 0, + ]; + return view('cotizacion.chatbot.checkout', compact('datosPago', 'vueloCotizado', 'adicionalesCotizados', 'vuelo')); + } + + public function compraFinalizada(Request $request) + { + $objCotizacion = null; + + if (isset($request->cotizacion)) { + $objCotizacion = DB::table('cotizaciones') + ->where('id_cotizacion', decrypt($request->cotizacion)) + ->first(); + } + + return view('cotizacion.chatbot.success'); + } +} diff --git a/app/Http/Controllers/EsConsultasController.php b/app/Http/Controllers/EsConsultasController.php new file mode 100644 index 0000000..6185ae5 --- /dev/null +++ b/app/Http/Controllers/EsConsultasController.php @@ -0,0 +1,256 @@ +where([ + ['vuelos.id_sitio_web', 6], + ['vuelos.visibilidad_pagina_web', 1] + ]) + ->rightJoin('unidades', 'vuelos.id_unidad', '=', 'unidades.id_unidad') + ->orderBy('vuelos.id_vuelo', 'desc') + ->get(); + return $vuelos; + } + + public static function getDatosVuelo($vuelo) + { + $sql = DB::select(" + SELECT + sitios_web.*, + vuelos.id_vuelo AS aux_id_vuelo, + + ( + SELECT COUNT(1) + FROM recibos_conceptos rc + WHERE rc.id = vuelos.id_vuelo + AND rc.tipo = 'vuelo' + ) AS numero_de_veces_vendido_vuelos, + + unidades.id_unidad, + unidades.nombre_unidad, + unidades.nombre_corto, + unidades.nombre_unidad_enUS, + unidades.nombre_corto_enUS, + unidades.numero_personas_piezas, + + categorias_vuelos.nombre_categoria, + + IFNULL(galeria.id_galeria, 0) AS id_galeria, + galeria.json_rutas_imagenes AS imagenes_galeria, + IFNULL(galeria.nombre_galeria, 0) AS nombre_galeria, + + vuelos.*, + + ubicaciones.id_ubicacion, + CONCAT( + 'ID: ', ubicaciones.id_ubicacion, ' - ', + ubicaciones.nombre_ubicacion, ', ', + ubicaciones.estado, ', ', + paises.langES + ) AS nombre_ubicacion, + + IFNULL(vuelos_usd.titulo_vuelo, vuelos.nombre_vuelo) AS titulo_vuelo_usd, + vuelos.nombre_vuelo AS titulo_vuelo, + IFNULL(vuelos_usd.json_incluye, vuelos.json_incluye) AS json_incluye_usd, + IFNULL(vuelos_usd.precio_usd, 0) AS precio_usd + + FROM vuelos + + LEFT JOIN vuelos_usd + ON vuelos_usd.id_vuelo = vuelos.id_vuelo + + LEFT JOIN galeria + ON galeria.id_galeria = vuelos.id_galeria + + INNER JOIN ubicaciones + ON vuelos.id_ubicacion = ubicaciones.id_ubicacion + + INNER JOIN paises + ON ubicaciones.id_pais = paises.code + + INNER JOIN categorias_vuelos + ON vuelos.id_categoria = categorias_vuelos.id_categoria + + INNER JOIN unidades + ON unidades.id_unidad = vuelos.id_unidad + + LEFT JOIN sitios_web + ON sitios_web.id_sitio_web = vuelos.id_sitio_web + + WHERE + vuelos.id_estatus = 1 + AND vuelos.id_sitio_web = ? + AND vuelos.enlace_permanente = ? + AND vuelos.visibilidad_pagina_web = 1 + AND vuelos.no_se_vera_en_home_vuelos = 0 + + ORDER BY numero_de_veces_vendido_vuelos DESC + ", [6, $vuelo]); + return $sql; + } + + + public static function getInfoVuelo($vuelo) + { + return DB::select(" + SELECT sitios_web.*, + vuelos.id_vuelo as aux_id_vuelo, + (SELECT COUNT(1) + FROM recibos_conceptos + WHERE recibos_conceptos.id = vuelos.id_vuelo + AND recibos_conceptos.tipo = 'vuelo') as numero_de_veces_vendido_vuelos, + unidades.id_unidad, + unidades.nombre_unidad, + unidades.nombre_corto, + unidades.nombre_unidad_enUS, + unidades.nombre_corto_enUS, + unidades.numero_personas_piezas, + categorias_vuelos.nombre_categoria, + IFNULL(galeria.id_galeria, 0) AS id_galeria, + galeria.json_rutas_imagenes AS imagenes_galeria, + IFNULL(galeria.nombre_galeria, 0) AS nombre_galeria, + vuelos.*, + ubicaciones.id_ubicacion, + CONCAT('ID: ', ubicaciones.id_ubicacion, ' - ', ubicaciones.nombre_ubicacion, ', ', ubicaciones.estado, ', ', paises.langES) AS nombre_ubicacion, + IFNULL(vuelos_usd.titulo_vuelo, vuelos.nombre_vuelo) AS titulo_vuelo_usd, + vuelos.nombre_vuelo AS titulo_vuelo, + IFNULL(vuelos_usd.json_incluye, vuelos.json_incluye) AS json_incluye_usd, + IFNULL(vuelos_usd.precio_usd, 0) AS precio_usd + FROM vuelos + LEFT JOIN vuelos_usd ON vuelos_usd.id_vuelo = vuelos.id_vuelo + LEFT JOIN galeria ON galeria.id_galeria = vuelos.id_galeria + INNER JOIN ubicaciones ON vuelos.id_ubicacion = ubicaciones.id_ubicacion + INNER JOIN paises ON ubicaciones.id_pais = paises.code + INNER JOIN categorias_vuelos ON vuelos.id_categoria = categorias_vuelos.id_categoria + INNER JOIN unidades ON unidades.id_unidad = vuelos.id_unidad + LEFT JOIN sitios_web ON sitios_web.id_sitio_web = vuelos.id_sitio_web + WHERE vuelos.id_estatus = 1 + AND vuelos.enlace_permanente = ? + AND vuelos.visibilidad_pagina_web = 1 + AND vuelos.no_se_vera_en_home_vuelos = 0 + ORDER BY numero_de_veces_vendido_vuelos DESC + ", [$vuelo]); + } + + + public static function obtenerCotizacion() + { + $id_cotizacion = (Cotizaciones::max('id_cotizacion') ?? 0) + 1; + return [ + "folio" => "COT" . str_pad($id_cotizacion, 7, "0", STR_PAD_LEFT), + "id_cotizacion" => $id_cotizacion + ]; + } + + + public function registrarCotizacion(Request $request) + { + + try { + $pasajeros = $request->input('num_pasajeros'); + $folio_cotizaciones = EsConsultasController::obtenerCotizacion(); + $vuelo = new Vuelos; + $vuelo_cotizacion = new VuelosCotizaciones(); + $cotizaciones = new Cotizaciones(); + $datos_vuelo = $vuelo::where(['nombre_vuelo' => $request->input('vuelo_elegido'), 'id_sitio_web' => 6])->get(); + $datos_vuelo = json_decode($datos_vuelo, true); + $total = $pasajeros * (int)$datos_vuelo[0]["precio_publico"]; + + $cotizaciones->id_sitio_web = 6; + $cotizaciones->folio = $folio_cotizaciones['folio']; + $cotizaciones->nombre = ($request->has('nombre') ? $request->input('nombre') : $request->input('email')); + $cotizaciones->apellidos = ($request->has('apellido') ? $request->input('apellido') : 'N/A'); + $cotizaciones->id_medio_contacto = 13; + $cotizaciones->id_genero = 1; + $cotizaciones->telefono = $request->input('phone'); + $cotizaciones->celular = $request->input('phone'); + $cotizaciones->cp = 0; + $cotizaciones->mail = $request->input('email'); + $cotizaciones->fecha_vuelo = ($request->input('fecha_vuelo_cot') == null ? null : $request->input('fecha_vuelo_cot')); + $cotizaciones->cotizacion_abierta = ($request->input('fecha_vuelo_cot') == null ? 1 : 0); + $cotizaciones->vuelos = $request->input('vuelo_elegido'); + $cotizaciones->servicios_adicionales = ""; + $cotizaciones->tipo_descuento = 0; + $cotizaciones->descuento = 0; + $cotizaciones->subtotal = $total; + $cotizaciones->total = $total; + $cotizaciones->total_con_iva = $total; + $cotizaciones->requiere_factura = 0; + $cotizaciones->observaciones = "Cotizacion desde www.globopuertoteotihuacan.com"; + $cotizaciones->tipo_moneda = "MXN"; + $cotizaciones->pais_persona = 484; + $cotizaciones->estado_persona = 1; + $cotizaciones->id_estatus = 1; + $cotizaciones->fecha_alta = Carbon::now(); + $cotizaciones->id_user_alta = 98; + $cotizaciones->save(); + + $vuelo_cotizacion->id_vuelo = $datos_vuelo[0]["id_vuelo"]; + $vuelo_cotizacion->id_unidad = $datos_vuelo[0]["id_unidad"]; + $vuelo_cotizacion->cantidades = $pasajeros; + $vuelo_cotizacion->precio_vendido_p_pax_vuelo = $total; + $vuelo_cotizacion->precio_especial = 0; + $vuelo_cotizacion->id_cotizacion = $folio_cotizaciones["id_cotizacion"]; + $vuelo_cotizacion->pasajero_adicional = 0; + $vuelo_cotizacion->id_estatus = 1; + $vuelo_cotizacion->id_user_alta = 98; + $vuelo_cotizacion->fecha_alta = Carbon::now(); + $vuelo_cotizacion->save(); + + return response()->json([ + "exito" => 1 + ]); + } catch (Exception $e) { + return response()->json([ + "exito" => 0, + "error" => $e->getMessage() + ]); + } + } + + + public static function obtenerServiciosAdicionales() + { + try { + $response = Http::timeout(10)->get( + 'https://erp.skyballoons.mx/servicios-adicionales-webservice', + [ + 'comercio' => 'skyballoons', + 'key' => 'f093328776ae4cd210dc7b4ee4093e45' + ] + ); + + if ($response->successful()) { + return $response->json(); + } + + return []; + } catch (\Exception $e) { + return []; + } + } +} diff --git a/app/Http/Controllers/HomeController.php b/app/Http/Controllers/HomeController.php new file mode 100644 index 0000000..7cbc2c3 --- /dev/null +++ b/app/Http/Controllers/HomeController.php @@ -0,0 +1,28 @@ +middleware('auth'); + } + + /** + * Show the application dashboard. + * + * @return \Illuminate\Contracts\Support\Renderable + */ + public function index() + { + return view('home'); + } +} diff --git a/app/Http/Controllers/IdiomaController.php b/app/Http/Controllers/IdiomaController.php new file mode 100644 index 0000000..70125c7 --- /dev/null +++ b/app/Http/Controllers/IdiomaController.php @@ -0,0 +1,25 @@ + $locale]); + app()->setLocale($locale); + } + + return redirect()->back(); + } +} diff --git a/app/Http/Controllers/Page/TelegramController.php b/app/Http/Controllers/Page/TelegramController.php new file mode 100644 index 0000000..eb9a640 --- /dev/null +++ b/app/Http/Controllers/Page/TelegramController.php @@ -0,0 +1,227 @@ +token = '8617998449:AAFK4DNGPV3efRpmcjeMVZhdTUeErGj-ePc'; + $this->baseUrl = "https://api.telegram.org/bot{$this->token}"; + + // Si prefieres usar config (recomendado para producción) + // $this->token = config('services.telegram.token'); + // $this->baseUrl = config('services.telegram.base_url') . "/bot{$this->token}"; + } + + /** + * Enviar texto simple + */ + public function sendText($chatId, $text, $parseMode = null) + { + $params = [ + 'chat_id' => $chatId, + 'text' => $text, + ]; + + if ($parseMode) { + $params['parse_mode'] = $parseMode; + } + + $response = Http::post("{$this->baseUrl}/sendMessage", $params); + + return $response->json(); + } + + /** + * Enviar mensaje con teclado personalizado + */ + public function sendKeyboard($chatId, $text, $buttons = []) + { + $keyboard = [ + 'keyboard' => $buttons, + 'resize_keyboard' => true, + 'one_time_keyboard' => false, + ]; + + $response = Http::post("{$this->baseUrl}/sendMessage", [ + 'chat_id' => $chatId, + 'text' => $text, + 'reply_markup' => json_encode($keyboard), + ]); + + return $response->json(); + } + + /** + * Enviar botones inline + */ + public function sendInlineKeyboard($chatId, $text, $buttons = []) + { + $inlineKeyboard = [ + 'inline_keyboard' => $buttons, + ]; + + $response = Http::post("{$this->baseUrl}/sendMessage", [ + 'chat_id' => $chatId, + 'text' => $text, + 'reply_markup' => json_encode($inlineKeyboard), + ]); + + return $response->json(); + } + + /** + * Webhook principal con sistema de comandos mejorado + */ + public function webhook(Request $request) + { + $update = $request->all(); + + Log::info('Webhook recibido', $update); + + if (isset($update['message'])) { + $chatId = $update['message']['chat']['id']; + $text = trim($update['message']['text'] ?? ''); + $firstName = $update['message']['from']['first_name'] ?? 'Usuario'; + + // Sistema de comandos + $comandos = [ + '/start' => function () use ($firstName) { + return "🎈 ¡Bienvenido a SkyBalloons, {$firstName}!\n\n" . "Somos especialistas en decoración con globos para eventos.\n\n" . "🎯 Comandos disponibles:\n" . "/help - Ver ayuda\n" . "/info - Información del bot\n" . "/contacto - Contactar soporte\n" . "/precios - Solicitar precios\n" . '/eventos - Tipos de eventos'; + }, + + '/help' => function () { + return "📖 *Comandos disponibles:*\n\n" . "/start - Iniciar el bot\n" . "/help - Mostrar esta ayuda\n" . "/info - Información del bot\n" . "/contacto - Datos de contacto\n" . "/precios - Información de precios\n" . "/eventos - Tipos de eventos que realizamos\n\n" . '💡 *Ejemplo:* Envíame un mensaje directo y te responderé.'; + }, + + '/info' => function () { + return "🤖 *Información del Bot*\n\n" . "📌 Nombre: SkyBalloons Bot\n" . "👤 Usuario: @skyballoons_bot\n" . '📱 Framework: Laravel ' . app()->version() . "\n" . "🔄 Versión: 2.0\n" . '📅 Estado: Activo (modo prueba)'; + }, + + '/contacto' => function () { + return "📞 *Contacto SkyBalloons*\n\n" . "📧 Email: skyballoons@ejemplo.com\n" . "📱 Telegram: @skyballoons_soporte\n" . "📷 Instagram: @skyballoons\n" . "📍 Ubicación: Ciudad de México\n\n" . '⏰ Horario: Lunes a Viernes 9am - 6pm'; + }, + + '/precios' => function () { + return "💰 *Precios Referenciales*\n\n" . "🎈 Globo latex (docena): $120 MXN\n" . "✨ Globo metalizado (docena): $250 MXN\n" . "🎉 Arco de globos: Desde $800 MXN\n" . "🎊 Centro de mesa: Desde $350 MXN\n" . "🎈 Globos con helio (c/u): $40 MXN\n\n" . '📩 Solicita cotización personalizada con /cotizar'; + }, + + '/eventos' => function () { + return "🎪 *Eventos que realizamos*\n\n" . "✅ Cumpleaños\n" . "✅ Bodas\n" . "✅ Baby showers\n" . "✅ Graduaciones\n" . "✅ Eventos corporativos\n" . "✅ Aniversarios\n" . "✅ Fiestas infantiles\n\n" . '¡Pide tu presupuesto sin compromiso!'; + }, + + '/cotizar' => function () use ($chatId) { + // Aquí podrías iniciar un flujo de conversación + return "📝 *Solicitud de cotización*\n\n" . "Por favor, responde estas preguntas:\n" . "1. ¿Qué tipo de evento será?\n" . "2. ¿Cuántos invitados aproximadamente?\n" . "3. ¿Cuándo es la fecha del evento?\n" . "4. ¿Tienes algún diseño en mente?\n\n" . 'Un asesor te contactará en breve.'; + }, + ]; + + // Verificar si el comando existe + if (isset($comandos[$text])) { + $respuesta = $comandos[$text](); + $this->sendText($chatId, $respuesta, 'Markdown'); + } + // Responder mensajes de texto normales + else { + $respuesta = "🎈 ¡Hola {$firstName}!\n\n" . "Recibí tu mensaje: \"{$text}\"\n\n" . "Usa /help para ver todos los comandos disponibles.\n\n" . "💡 *Tips:*\n" . "• /precios - Ver lista de precios\n" . "• /eventos - Tipos de eventos\n" . '• /cotizar - Solicitar cotización'; + + $this->sendText($chatId, $respuesta, 'Markdown'); + } + } + // Manejar callback queries (botones inline) + elseif (isset($update['callback_query'])) { + $callbackQuery = $update['callback_query']; + $chatId = $callbackQuery['message']['chat']['id']; + $data = $callbackQuery['data']; + + // Responder al callback + Http::post("{$this->baseUrl}/answerCallbackQuery", [ + 'callback_query_id' => $callbackQuery['id'], + 'text' => "Procesando: {$data}", + 'show_alert' => false, + ]); + + // Enviar mensaje según el callback + $this->sendText($chatId, "Has seleccionado: {$data}\n\nUn asesor te contactará pronto."); + } + + return response()->json(['ok' => true], 200); + } + + /** + * Obtener actualizaciones manualmente (sin webhook) + */ + public function getUpdates(Request $request) + { + $offset = $request->get('offset', 0); + $limit = $request->get('limit', 100); + $timeout = $request->get('timeout', 30); + + $response = Http::get("{$this->baseUrl}/getUpdates", [ + 'offset' => $offset, + 'limit' => $limit, + 'timeout' => $timeout, + ]); + + return response()->json($response->json()); + } + + /** + * Configurar webhook + */ + public function setWebhook(Request $request) + { + $url = $request->get('url'); + + if (!$url) { + return response()->json(['error' => 'URL requerida'], 400); + } + + $response = Http::post("{$this->baseUrl}/setWebhook", [ + 'url' => $url, + ]); + + return response()->json($response->json()); + } + + /** + * Eliminar webhook + */ + public function deleteWebhook() + { + $response = Http::post("{$this->baseUrl}/deleteWebhook"); + + return response()->json($response->json()); + } + + /** + * Obtener información del webhook + */ + public function getWebhookInfo() + { + $response = Http::get("{$this->baseUrl}/getWebhookInfo"); + + return response()->json($response->json()); + } + + /** + * Enviar mensaje directo (endpoint para pruebas) + */ + public function sendMessage(Request $request) + { + $chatId = $request->get('chat_id', 7981938691); // Tu chat ID por defecto + $text = $request->get('text', 'Mensaje de prueba'); + + return response()->json($this->sendText($chatId, $text, 'Markdown')); + } +} diff --git a/app/Http/Controllers/Page/VuelosController.php b/app/Http/Controllers/Page/VuelosController.php new file mode 100644 index 0000000..a74ae7d --- /dev/null +++ b/app/Http/Controllers/Page/VuelosController.php @@ -0,0 +1,412 @@ +sitio_web = config('sitioweb.sitio_web_erp_id', 0); + } + + /** + * Muestra el detalle de un vuelo específico. + * + * @param Request $request + * @param int $id ID del vuelo + * + * @return \Illuminate\Http\JsonResponse + * + * Respuesta: + * - 200: Objeto del vuelo + * - 404: Vuelo no encontrado + */ + public function show(Request $request, int $id) + { + $lang = $request->get('lang', 'en'); + + $vuelo = $this->getVuelosData(collect([$id]), $lang)->first(); + + if (!$vuelo) { + return response()->json( + [ + 'message' => 'Vuelo no encontrado', + ], + 404, + ); + } + + return response()->json($vuelo); + } + + /** + * Obtiene colección de vuelos visibles en la web. + * + * @return \Illuminate\Http\JsonResponse + */ + public function getVuelosCollection() + { + return $this->buildCollectionResponse(function ($query) { + $query->where('visibilidad_pagina_web', 1); + }); + } + + /** + * Obtiene colección de vuelos visibles en la home. + * + * @return \Illuminate\Http\JsonResponse + */ + public function getVuelosCollectionHome() + { + return $this->buildCollectionResponse(function ($query) { + $query->where('visibilidad_pagina_web', 1)->where('show_flight_in_home_view', 0); + }); + } + + /** + * Builder reutilizable para colecciones de vuelos. + * + * @param callable $callback Filtros dinámicos sobre la query base + * + * @return \Illuminate\Http\JsonResponse + */ + private function buildCollectionResponse(callable $callback) + { + $lang = app()->getLocale(); + + $query = DB::table('vuelos')->where('id_sitio_web', $this->sitio_web)->where('id_estatus', 1); + + $callback($query); + + $ids = $query->pluck('id_vuelo'); + + return response()->json([ + 'vuelos' => $this->getVuelosData($ids, $lang)->values(), + ]); + } + + /** + * Motor principal de transformación de vuelos. + * + * @param Collection $ids IDs de vuelos + * @param string $lang Idioma actual + * + * @return Collection + */ + private function getVuelosData(Collection $ids, string $lang): Collection + { + if ($ids->isEmpty()) { + return collect(); + } + + $data = $this->fetchAllData($ids, $lang); + + return $ids->map(fn($id) => $this->transformVuelo($id, $data, $lang))->filter(); + } + + /** + * Obtiene todos los datos relacionados en bloque. + * + * @param Collection $ids + * @param string $lang + * + * @return array + */ + private function fetchAllData(Collection $ids, string $lang): array + { + $vuelos = DB::table('vuelos')->whereIn('id_vuelo', $ids)->get()->keyBy('id_vuelo'); + + return [ + 'vuelos' => $vuelos, + + 'vuelos_en' => $lang === 'en' ? DB::table('vuelos_usd')->whereIn('id_vuelo', $ids)->get()->keyBy('id_vuelo') : collect(), + + 'categorias' => DB::table('categorias_vuelos')->whereIn('id_categoria', $vuelos->pluck('id_categoria'))->get()->keyBy('id_categoria'), + + 'precios' => DB::table('vuelos_precios')->whereIn('id_vuelo', $ids)->where('id_estatus', 1)->get()->keyBy('id_vuelo'), + + 'imagenes' => DB::table('vuelos_imagenes_galeria')->whereIn('id_vuelo', $ids)->where('id_estatus', 1)->orderBy('orden')->get()->groupBy('id_vuelo'), + + 'incluye' => DB::table('vuelos_items_incluye')->whereIn('id_vuelo', $ids)->where('id_estatus', 1)->get()->groupBy('id_vuelo'), + ]; + } + + /** + * Transforma un vuelo a su estructura final. + * + * @param int $id + * @param array $data + * @param string $lang + * + * @return array|null + */ + private function transformVuelo(int $id, array $data, string $lang): ?array + { + $vuelo = $data['vuelos'][$id] ?? null; + + if (!$vuelo) { + return null; + } + + $vuelo = $this->mergeIdioma($vuelo, $data['vuelos_en'][$id] ?? null, $lang); + $vuelo = $this->normalizeVuelo($vuelo); + + $categoria = $data['categorias'][$vuelo->id_categoria] ?? null; + + return [ + 'vuelo' => $vuelo, + 'categoria' => [ + 'nombre' => $lang === 'en' ? $categoria->nombre_categoria_usd ?? null : $categoria->nombre_categoria ?? null, + ], + 'precios' => $data['precios'][$id] ?? null, + 'imagenes' => $data['imagenes'][$id] ?? collect(), + 'incluye' => $data['incluye'][$id] ?? collect(), + ]; + } + + /** + * Aplica traducción al vuelo si el idioma es inglés. + * + * @param mixed $vuelo + * @param mixed $vuelo_en + * @param string $lang + * + * @return mixed + */ + private function mergeIdioma($vuelo, $vuelo_en, string $lang) + { + if ($lang !== 'en' || !$vuelo_en) { + return $vuelo; + } + + $map = [ + 'nombre_vuelo' => 'titulo_vuelo', + 'descripcion_corta' => 'descripcion_corta', + 'descripcion_larga' => 'descripcion_larga', + 'json_incluye' => 'json_incluye', + 'servicios_adicionales' => 'servicios_adicionales', + 'precio_publico' => 'precio_usd', + 'precio_especial' => 'precio_especial_usd', + ]; + + foreach ($map as $target => $source) { + if (isset($vuelo_en->$source)) { + $vuelo->$target = $vuelo_en->$source; + } + } + + return $vuelo; + } + + /** + * Normaliza campos JSON del vuelo. + * + * @param mixed $vuelo + * + * @return mixed + */ + private function normalizeVuelo($vuelo) + { + $vuelo->json_incluye = $this->safeJsonDecode($vuelo->json_incluye); + $vuelo->precios_adicionales = $this->safeJsonDecode($vuelo->precios_adicionales); + + return $vuelo; + } + + /** + * Decodifica JSON de forma segura. + * + * @param mixed $value + * + * @return array + */ + private function safeJsonDecode($value) + { + if (!$value || $value === 'null') { + return []; + } + + return json_decode($value, true) ?? []; + } + + public static function obtenerServiciosAdicionales() + { + return DB::table('servicios_adicionales as sa') + ->leftJoin('unidades as u', 'u.id_unidad', '=', 'sa.id_unidad') + ->leftJoin('categorias as c', 'c.id_categoria', '=', 'sa.additional_service_category_id') + ->select( + DB::raw('COALESCE(c.nombre_categoria, "Sin categoría") as nombre_categoria'), + DB::raw(" + JSON_ARRAYAGG( + JSON_OBJECT( + 'id', sa.id_servicio_adicional, + 'descripcion', TRIM(sa.descripcion), + 'precio', CAST(sa.precio AS DECIMAL(10,2)), + 'observaciones', TRIM(sa.observaciones), + 'unidad', u.nombre_unidad, + 'imagenes', COALESCE(( + SELECT JSON_ARRAYAGG( + JSON_UNQUOTE( + JSON_EXTRACT(sag2.json_rutas_imagenes, '$[0]') + ) + ) + FROM servicios_adicionales_galeria sag2 + WHERE sag2.id_servicio_adicional = sa.id_servicio_adicional + AND sag2.id_estatus = 1 + ), JSON_ARRAY()) + ) + ) as servicios + "), + ) + ->where('sa.id_estatus', 1) + ->where('sa.visibilidad_web', 1) + ->groupBy('nombre_categoria') + ->orderBy('nombre_categoria') // ordena categorías + ->get() + ->map(function ($item) { + return [ + 'nombre_categoria' => $item->nombre_categoria, + 'servicios' => json_decode($item->servicios, true), // 🔥 JSON → array real + ]; + }) + ->values(); + } + + /** + * Busca el ID del vuelo a partir del slug (multilenguaje). + * + * @param string $slug + * @param string $lang + * @return int|null + */ + private function findVueloIdBySlug(string $slug, string $lang): ?int + { + // Buscar en tabla base (ES) + $query = DB::table('vuelos')->where('id_sitio_web', $this->sitio_web)->where('id_estatus', 1); + + if ($lang === 'en') { + // Buscar en tabla traducida + return DB::table('vuelos_usd')->where('slug', $slug)->value('id_vuelo'); + } + + return $query->where('slug', $slug)->value('id_vuelo'); + } + + /** + * Muestra el detalle de un vuelo por su enlace_permanente (slug permanente) + * + * Usa el idioma actual de la aplicación (sesión) + * + * @param Request $request + * @param string $enlace_permanente + * @return \Illuminate\Http\JsonResponse + */ + public function showByPermanentLink(Request $request, string $enlace_permanente) + { + $lang = app()->getLocale(); // 'es' o 'en' + + $id = $this->findVueloIdByPermanentLink($enlace_permanente, $lang); + + if (!$id) { + return response()->json([ + 'message' => $lang === 'en' + ? 'Flight not found' + : 'Vuelo no encontrado' + ], 404); + } + + $vuelo = $this->getVuelosData(collect([$id]), $lang)->first(); + + if (!$vuelo) { + return response()->json([ + 'message' => $lang === 'en' + ? 'Flight not found' + : 'Vuelo no encontrado' + ], 404); + } + + return response()->json($vuelo); + } + + + /** + * Busca el ID del vuelo a partir del enlace permanente (slug canónico). + * + * @param string $enlace_permanente + * @param string $lang + * @return int|null + */ + private function findVueloIdByPermanentLink(string $enlace_permanente, string $lang): ?int + { + // Siempre buscar primero en la tabla base (canónica) + $id = DB::table('vuelos') + ->where('id_sitio_web', $this->sitio_web) + ->where('id_estatus', 1) + ->where('enlace_permanente', $enlace_permanente) + ->value('id_vuelo'); + + if ($id) { + return $id; + } + + // Fallback opcional: si estás en inglés y existe en tabla traducida + if ($lang === 'en') { + return DB::table('vuelos_usd') + ->where('enlace_permanente', $enlace_permanente) + ->value('id_vuelo'); + } + + return null; + } + + public function getVuelosFoot() + { + + + } + + public static function cotizaVuelo($slug, Request $request) + { + //Insertar en NNewsletter + \Log::info('Cotización recibida:', $request->all()); + // return $request; + DB::table('newsletter_skyballoons')->insert([ + 'correo_electronico' => $request->email, + 'origin_url' => 'https://' . DB::table('sitios_web')->where('id_sitio_web', 2)->value('url_web'), + 'id_sitio_web' => 2, + 'fecha_alta' => now(), + ]); + } +} + + diff --git a/app/Http/Controllers/PagesController.php b/app/Http/Controllers/PagesController.php new file mode 100644 index 0000000..6e5686c --- /dev/null +++ b/app/Http/Controllers/PagesController.php @@ -0,0 +1,485 @@ +objVueloController = new VuelosController(); + $this->telegramObject = new TelegramController(); + } + + //pagina de inicio + public function index() + { + // SEO mejorado para vueloenglobo.mx + SEOTools::setTitle('Vuelos en globo - vueloenglobo.mx | Experiencias en Teotihuacán'); + SEOTools::setDescription('Reserva vuelos en globo sobre Teotihuacán. Experiencias seguras, guías expertos y las mejores vistas al mejor precio. Reserva online en VueloEnGlobo.mx.'); + SEOTools::metatags()->setKeywords(['vuelos en globo', 'vuelo en globo teotihuacan', 'vuelos baratos', 'experiencias en globo', 'reservar vuelo en globo']); + SEOTools::setCanonical('https://vueloenglobo.mx/'); + SEOTools::opengraph()->addProperty('type', 'website'); + + //$vuelos = EsConsultasController::getVueloCompleto(); + $response = $this->objVueloController->getVuelosCollectionHome(); + $data = $response->getData(true); + $vuelos = $data['vuelos']; + //return $vuelos; + return view('template.home', compact('vuelos')); + } + + /** + * Método que me carga y agrupa los vuelos disponibles por categoría + * @return \Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View + */ + public function vuelosEnGlobo() + { + $vuelos = $this->agruparVuelosListado($this->objVueloController->getVuelosCollection()); + return view('template.vuelos-en-globo', compact('vuelos')); + } + + /** + * Método que agrupar por categoría los vuelos en globo + * @param mixed $listadoVuelos + * @return array + */ + private function agruparVuelosListado($listadoVuelos) + { + $data = $listadoVuelos->getData(true); + $agrupados = []; + + foreach ($data['vuelos'] as $item) { + $categoria = $item['categoria']['nombre'] ?? 'Sin categoría'; + + if (!isset($agrupados[$categoria])) { + $agrupados[$categoria] = []; + } + + $agrupados[$categoria][] = $item; + } + + return $agrupados; + } + + public function vuelosEnGloboCopy() + { + $vuelos = EsConsultasController::getVueloCompleto(); + // return $vuelos; + return view('template.vuelos-en-globo', compact('vuelos')); + } + + public function vueloSeleccionado(Request $request, $vuelo) + { + $objClase = new VuelosController(); + $response = $objClase->showByPermanentLink($request, $vuelo); + $datosVuelo = $response->getData(true); + return view('template.vuelo-seleccionado', compact('datosVuelo')); + } + + // public function vueloSeleccionado($vuelo) + // { + // $datosVuelo = EsConsultasController::getInfoVuelo($vuelo); + // return view('template.vuelo-seleccionado', compact('datosVuelo')); + // } + + public function realizaPago($vuelo) + { + $datosVuelo = EsConsultasController::getInfoVuelo($vuelo); + $adicionales = EsConsultasController::obtenerServiciosAdicionales(); + //return $adicionales; + return view('template.checkout', compact('datosVuelo', 'adicionales')); + } + + //esta pendiente + public function vueloSeleccionado2($vuelo) + { + $datos_vuelo = EsConsultasController::getDatosVuelo($vuelo); + + if (!$datos_vuelo) { + abort(404); + } + // return $datos_vuelo; + return view('template.vuelo-seleccionado', [ + 'datos_vuelo' => $datos_vuelo, + 'incluye' => json_decode($datos_vuelo->json_incluye ?? '[]', true) ?: [], + 'adicionales' => json_decode($datos_vuelo->precios_adicionales ?? '[]', true) ?: [], + ]); + } + + // pagina de contacto + public function contacto() + { + return view('template.contacto'); + } + + // pagina de servicios + public function servicios() + { + return view('template.servicios'); + } + + // pagina de nosotros + public function nosotros() + { + return view('template.nosotros'); + } + + // pagina de preguntas frecuentes + public function preguntasFrecuentes() + { + return view('template.preguntasFrecuentes'); + } + + // pagina de terminos y condiciones + public function terminosCondiciones() + { + return view('template.terminosCondiciones'); + } + + public function cotizarVuelo($slug, Request $request) + { + //obtener el vuelo + $vuelo = DB::table('vuelos')->where('id_vuelo', $request->id_vuelo)->first(); + $partes = explode(' ', $request->nombre); + $nombres = implode(' ', array_slice($partes, 0, -2)); + $apellidoPaterno = $partes[count($partes) - 2]; + $apellidoMaterno = end($partes); + + CotizacionController::crearCotizacionYPosibleCompraErp([ + 'nombre' => $nombres, + 'apellidos' => $apellidoPaterno . ' ' . $apellidoMaterno, + 'email' => $request->email, + 'telefono' => $request->telefono, + 'total_compra' => $vuelo->precio_publico * $request->pasajeros, + 'total_a_pagar' => $vuelo->precio_publico * $request->pasajeros, + ]); + + PagesController::sendTelegramMessage('se cotizo un vuelo: ' . $request->nombre_vuelo . ' a nombre de: ' . $request->nombre . '. mas información consultarla en el ERP'); + $response = VuelosController::cotizaVuelo($slug, $request); + return json_encode(['mesaaje' => 'Se cotizo el vuelo exitosamente']); + } + + private static function sendTelegramMessage($message) + { + $botToken = '8617998449:AAFK4DNGPV3efRpmcjeMVZhdTUeErGj-ePc'; + $chatId = '8446032916'; + + Http::post("https://api.telegram.org/bot{$botToken}/sendMessage", [ + 'chat_id' => $chatId, + 'text' => $message, + ]); + } + + /** + * Método que se muestra al cliente para completar el proceso de su + * @param mixed $slug + * @param Request $request + * @return \Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View + */ + public function reservarVueloAhora($slug = null, Request $request) + { + $objClase = new VuelosController(); + $response = $objClase->showByPermanentLink($request, $slug); + $datosVuelo = $response->getData(true); + $adicionales = $this->getServiciosAdicionales(); + + SEOTools::setTitle($datosVuelo['vuelo']['nombre_vuelo'] . ' | www.vueloenglobo.mx'); + SEOTools::setDescription($datosVuelo['vuelo']['descripcion_corta']); + SEOTools::metatags()->setKeywords(['vuelos', 'vuelos baratos', 'ofertas']); + + return view('template.checkout', compact('datosVuelo', 'adicionales')); + } + + /** + * Obtiene y transforma los servicios adicionales desde la API externa + * + * @return array Array de categorías con sus servicios + */ + private function getServiciosAdicionales(): array + { + try { + $response = Http::get('https://erp.skyballoons.mx/servicios-adicionales-webservice', [ + 'comercio' => 'skyballoons', + 'key' => 'f093328776ae4cd210dc7b4ee4093e45', + ]); + + if (!$response->successful()) { + return []; + } + + $datos = $response->json(); + if (!is_array($datos) || empty($datos)) { + return []; + } + + // Transformar al formato de categorías (una sola categoría por simplicidad) + $servicios = array_map(function ($item) { + return [ + 'id' => $item['id_servicio_adicional'] ?? null, + 'descripcion' => $item['nombre'] ?? 'Servicio', + 'precio' => (float) ($item['precio'] ?? 0), + 'precio_dolares' => (float) ($item['precio_dolares'] ?? 0), + 'imagenes' => $item['galeria'] ?? [], + 'observaciones' => $item['descripcion'] ?? '', + ]; + }, $datos); + + return [ + [ + 'nombre_categoria' => 'Servicios adicionales', + 'servicios' => array_filter($servicios, fn($s) => !is_null($s['id'])), + ], + ]; + } catch (\Exception $e) { + \Log::error('Error al obtener servicios adicionales: ' . $e->getMessage()); + return []; + } + } + + public function getNombresDeVuelos() + { + $vuelos = $this->agruparVuelosListado($this->objVueloController->getVuelosCollection()); + + return collect($vuelos)->map(function ($items) { + return collect($items) + ->map(function ($item) { + return [ + 'nombre_vuelo' => $item['vuelo']['nombre_vuelo'] ?? null, + 'enlace_permanente' => $item['vuelo']['enlace_permanente'] ?? null, + ]; + }) + ->values(); + }); + } + + public function procesarR(Request $request) + { + try { + Log::info('Procesando pago', $request->all()); + + $payload = [ + 'amount' => (float) $request->input('total'), + 'currency' => 'MXN', + 'description' => $request->input('payment_description', 'Vuelo en Globo'), + 'payment_method' => [ + 'token' => $request->input('card_token'), + ], + 'customer' => [ + 'email' => $request->input('payment_email'), + 'phone' => preg_replace('/[^0-9]/', '', $request->input('payment_phone')), + ], + ]; + + $response = Http::withHeaders([ + 'Authorization' => 'Bearer ' . config('clip.api_key'), + 'Content-Type' => 'application/json', + ])->post(config('clip.payments_url'), $payload); + + \Log::info('Respuesta Clip', ['status' => $response->status(), 'body' => $response->body()]); + + if ($response->successful()) { + $paymentData = $response->json(); + $paymentStatus = $paymentData['status'] ?? 'pending'; + $paymentMessage = $paymentData['message'] ?? 'Pago procesado exitosamente'; + + if ($paymentStatus == 'approved') { + PagesController::sendTelegramMessage('Se realizó compra de vuelo en globo. ID: ' . ($paymentData['id'] ?? 'N/A')); + } + + return response()->json( + [ + 'success' => true, + 'payment_status' => $paymentStatus, + 'payment_message' => $paymentMessage, + 'redirect' => '#', // route('gracias') + 'data' => $paymentData, + ], + 200, + ); + } else { + $errorBody = $response->json(); + + // Motivo de rechazo que viene de Clip (ajusta el campo según la documentación real) + $reason = $errorBody['reason'] ?? ($errorBody['error_code']['reason'] ?? ($errorBody['rejection_reason'] ?? '')); + + // Mapa de motivos de rechazo a mensajes en español + $reasonMessages = [ + 'not_sufficient_funds' => 'Fondos insuficientes en la tarjeta.', + 'Not sufficient funds' => 'Fondos insuficientes en la tarjeta.', + 'do_not_honor' => 'El banco emisor rechazó la transacción (Do not honor).', + 'Do not honor' => 'El banco emisor rechazó la transacción.', + 'restricted_card' => 'La tarjeta está restringida y no puede usarse.', + 'Restricted card' => 'Tarjeta restringida.', + 'exceeds_withdrawal_amount_limit' => 'La transacción excede el límite de retiro de esta tarjeta.', + 'Exceeds withdrawal amount limit' => 'Excede el límite de retiro permitido.', + ]; + + // Si hay un motivo conocido, usamos el mensaje traducido; si no, el mensaje original + if ($reason && isset($reasonMessages[$reason])) { + $errorMessage = $reasonMessages[$reason]; + } else { + $errorMessage = $errorBody['detail'] ?? ($errorBody['error_code']['message'] ?? ($errorBody['message'] ?? 'Error al procesar el pago')); + } + + PagesController::sendTelegramMessage('Error en compra: ' . $errorMessage); + + return response()->json( + [ + 'success' => false, + 'message' => $errorMessage, + 'payment_status' => 'rejected', + 'code' => $errorBody['error_code'] ?? null, + 'reason' => $reason ?: null, + ], + $response->status(), + ); + } + } catch (\Exception $e) { + \Log::error('Error en procesar pago: ' . $e->getMessage(), [ + 'trace' => $e->getTraceAsString(), + ]); + + PagesController::sendTelegramMessage('Error en compra: '); + + return response()->json( + [ + 'success' => false, + 'message' => 'Error interno del servidor. Por favor intenta de nuevo.', + 'payment_status' => 'error', + ], + 500, + ); + } + } + + public function procesar(Request $request) + { + // 1. Validación del payload real que envías desde frontend + // $validated = $request->validate([ + // 'payment_email' => 'required|email', + // 'payment_phone' => 'required|string', + // 'payment_description' => 'nullable|string|max:255', + // 'card_token' => 'required|string', + // 'total' => 'required|numeric', + + // // datos del vuelo / reserva + // 'id_vuelo' => 'required|integer', + // 'adultos' => 'nullable|integer', + // 'ninios' => 'nullable|integer', + // 'fecha' => 'nullable|date', + // 'cupon' => 'nullable|string', + // ]); + $validated = $request->all(); // Elimina esta línea y descomenta la validación real cuando estés listo para validar los datos de entrada + // 2. API Key + $apiKey = config('clip.api_key'); + + if (empty($apiKey)) { + Log::error('Clip API Key no configurada'); + + return response()->json([ + 'success' => false, + 'message' => 'Error de configuración del servidor de pagos.', + ], 500); + } + + // 3. Payload oficial Clip + $payload = [ + "amount" => (float) $validated['total'], + "currency" => "MXN", + "description" => $validated['payment_description'] ?? 'Vuelo en Globo Zero', + + "payment_method" => [ + "token" => $validated['card_token'] + ], + + "customer" => [ + "email" => $validated['payment_email'], + "phone" => $validated['payment_phone'], + ], + + "metadata" => [ + "id_vuelo" => $validated['id_vuelo'], + "adultos" => $validated['adultos'] ?? 0, + "ninios" => $validated['ninios'] ?? 0, + "fecha" => $validated['fecha'], + ] + ]; + + try { + // 4. Petición a Clip + $response = Http::withHeaders([ + "Content-Type" => "application/json", + "Authorization" => "Bearer " . $apiKey, + ])->post("https://api.payclip.com/payments", $payload); + + $body = $response->json(); + + Log::info('Respuesta Clip:', [ + 'status' => $response->status(), + 'body' => $body + ]); + + // 5. Respuesta HTTP inválida + if (!$response->successful()) { + return response()->json([ + 'success' => false, + 'message' => $body['message'] ?? 'Error al procesar pago con Clip', + 'error' => $body + ], $response->status()); + } + + // 6. Estado del pago + $status = strtolower($body['status'] ?? ''); + + if (in_array($status, ['approved', 'paid'])) { + + // 7. AQUÍ YA PUEDES GUARDAR LA RESERVA + // Reserva::create([...]); + + return response()->json([ + 'success' => true, + 'payment_id' => $body['id'] ?? null, + 'payment_status' => $status, + 'message' => 'Pago aprobado correctamente', + ]); + } + + if (in_array($status, ['declined'])) { + return response()->json([ + 'success' => false, + 'payment_status' => $status, + 'message' => 'Pago rechazado por el banco.', + ], 422); + } + + return response()->json([ + 'success' => false, + 'payment_status' => $status, + 'message' => 'Estado de pago: ' . $status, + ], 422); + + } catch (\Exception $e) { + + Log::error('Error Clip: ' . $e->getMessage()); + + return response()->json([ + 'success' => false, + 'message' => 'Error de conexión con el proveedor de pagos.', + ], 500); + } + } +} diff --git a/app/Http/Controllers/Pagina/SitioWebController.php b/app/Http/Controllers/Pagina/SitioWebController.php new file mode 100644 index 0000000..8001d5d --- /dev/null +++ b/app/Http/Controllers/Pagina/SitioWebController.php @@ -0,0 +1,30 @@ +telegramObject = new TelegramController(); + } + + private function getSeoIndex(){ + SEOTools::setTitle('Vuelos en globo - vueloenglobo.mx | Experiencias en Teotihuacán'); + SEOTools::setDescription('Reserva vuelos en globo sobre Teotihuacán. Experiencias seguras, guías expertos y las mejores vistas al mejor precio. Reserva online en VueloEnGlobo.mx.'); + SEOTools::metatags()->setKeywords(['vuelos en globo', 'vuelo en globo teotihuacan', 'vuelos baratos', 'experiencias en globo', 'reservar vuelo en globo']); + SEOTools::setCanonical('https://vueloenglobo.mx/'); + SEOTools::opengraph()->addProperty('type', 'website'); + } + + public function index() + { + $this->getSeoIndex(); + return view('pagina.index'); + } +} diff --git a/app/Http/Controllers/PaseAbordarController.php b/app/Http/Controllers/PaseAbordarController.php new file mode 100644 index 0000000..36098d7 --- /dev/null +++ b/app/Http/Controllers/PaseAbordarController.php @@ -0,0 +1,611 @@ +get('reserva_pendiente'); + if (!$reserva) { + return response()->json(['error' => 'No hay reserva pendiente'], 400); + } + + $id_cotizacion = $reserva['id_cotizacion']; + $folio = $reserva['folio']; + $datos = $reserva['datos']; + + // Si no se pasó el monto pagado, usar el total de la compra + if ($montoPagado === null) { + $montoPagado = $datos['total_compra']; + } + + DB::beginTransaction(); + + try { + // 1. Insertar en la tabla cotizaciones + $fecha_vuelo = isset($datos['fecha_vuelo']) + ? Carbon::createFromFormat('m/d/Y', $datos['fecha_vuelo'])->format('Y-m-d') + : null; + + // Construir el JSON de vuelos (exactamente como lo hacías con modelos) + $vuelos_field = [ + [ + 'moneda' => 'MXN', + 'precio_vendido_p_pax_vuelo' => $datos['precio_publico'], + 'id_vuelo' => $datos['id_vuelo'], + 'id_unidad' => $datos['id_unidad'], + 'cantidades' => $datos['pasajeros'], + 'precio_especial' => '0' + ] + ]; + + if (!empty($datos['num_pax_adicionales']) && $datos['num_pax_adicionales'] > 0) { + $vuelos_field[0]['pasajeros_adicionales'] = [ + [ + 'moneda' => 'MXN', + 'cantidades' => $datos['num_pax_adicionales'], + 'id_unidad' => $datos['id_unidad_adicionales'], + 'precio' => $datos['precio_adicional'], + 'precio_especial' => $datos['precio_adicional'] + ] + ]; + } + + DB::table('cotizaciones')->insert([ + 'id_sitio_web' => 6, + 'cotizacion_abierta' => isset($datos['fecha_libre']) ? 1 : 0, + 'folio' => $folio, + 'nombre' => $datos['nombre_reserva'], + 'apellidos' => $datos['apellido_reserva'], + 'id_medio_contacto' => 12, + 'id_genero' => 1, + 'telefono' => $datos['tel_reserva'], + 'iso_country_id_primary_phone' => $datos['Lada'], + 'celular' => $datos['tel_reserva'], + 'iso_country_id_secondary_phone' => $datos['Lada'], + 'cp' => 0, + 'mail' => $datos['email_reserva'], + 'fecha_vuelo' => $fecha_vuelo, + 'vuelos' => json_encode($vuelos_field), + 'servicios_adicionales' => $datos['servicios_adicionales'], + 'tipo_descuento' => null, + 'descuento' => null, + 'descuento_usd' => null, + 'subtotal' => $datos['total_compra'], + 'total' => $datos['total_compra'], + 'total_con_iva' => $datos['total_compra'], + 'subtotal_usd' => 0, + 'total_usd' => 0, + 'tipo_cargo' => 0, + 'total_cargos' => 0, + 'requiere_factura' => 0, + 'observaciones' => 'checkout Globopuerto Teotihuacan', + 'id_motivo' => 1, + 'tipo_moneda' => 'MXN', + 'pais_persona' => 484, + 'estado_persona' => 1, + 'id_user_alta' => 98, + 'peso_kg' => 0, + 'number_of_pax' => $this->getNumberOfPaxes( + $vuelos_field, + $vuelos_field[0]['pasajeros_adicionales'] ?? [], + $datos['id_vuelo'] + ), + 'fecha_alta' => Carbon::now(), + ]); + + // 2. Insertar vuelos_cotizaciones (vuelo principal) + DB::table('vuelos_cotizaciones')->insert([ + 'id_vuelo' => $datos['id_vuelo'], + 'id_unidad' => $datos['id_unidad'], + 'cantidades' => $datos['pasajeros'], + 'precio_vendido_p_pax_vuelo' => $datos['precio_publico'], + 'precio_vendido_p_pax_usd' => 0, + 'precio_especial' => $datos['precio_publico'], + 'id_cotizacion' => $id_cotizacion, + 'pasajero_adicional' => 0, + 'id_estatus' => 1, + 'id_user_alta' => 98, + 'fecha_alta' => Carbon::now(), + ]); + + // 3. Insertar vuelos_cotizaciones adicionales (si existen) + if (!empty($datos['num_pax_adicionales']) && $datos['num_pax_adicionales'] > 0) { + DB::table('vuelos_cotizaciones')->insert([ + 'id_vuelo' => $datos['id_vuelo'], + 'id_unidad' => $datos['id_unidad_adicionales'], + 'cantidades' => $datos['num_pax_adicionales'], + 'precio_vendido_p_pax_vuelo' => $datos['precio_adicional'], + 'precio_vendido_p_pax_usd' => 0, + 'precio_especial' => $datos['precio_adicional'], + 'id_cotizacion' => $id_cotizacion, + 'pasajero_adicional' => 1, + 'id_estatus' => 1, + 'id_user_alta' => 98, + 'id_user_mod' => 98, + 'fecha_alta' => Carbon::now(), + 'fecha_mod' => Carbon::now(), + ]); + } + + // 4. Insertar servicios_adicionales_cotizaciones + if (!empty($datos['servicios_adicionales'])) { + $servicios = json_decode($datos['servicios_adicionales'], true); + foreach ($servicios as $servicio) { + DB::table('servicios_adicionales_cotizaciones')->insert([ + 'id_servicio_adicional' => $servicio['id_servicio'], + 'cantidades' => $servicio['cantidad_solicitada'], + 'id_unidad' => $servicio['id_unidad'], + 'precio_vendido' => $servicio['precio'], + 'precio_vendido_usd' => 0, + 'id_cotizacion' => $id_cotizacion, + 'id_estatus' => 1, + 'id_user_alta' => 88, + 'fecha_alta' => Carbon::now(), + ]); + } + } + + // 5. Insertar el pago llamando al procedimiento almacenado (usando DB::statement) + $params_pago = [ + 'hora_cita' => $datos['hora_cita'], + 'id_cotizacion' => $id_cotizacion, + 'foto_comprobante' => null, + 'cantidad_pagada' => $montoPagado, + 'cantidad_pagada_usd' => 0, + 'pago_confirmado_default' => 1, + 'fecha_pago' => Carbon::now()->toDateString(), + 'observaciones' => 'Pago exitoso desde checkout', + 'observaciones_logistica' => 'Pago exitoso', + 'ultimos_digitos' => null, + 'id_cuenta_bancaria' => 0, + 'id_metodo_pago_cuenta_bancaria' => ($metodoPago == 'card' ? 6 : ($metodoPago == 'oxxo' ? 8 : 9)), + 'id_banco' => ($metodoPago == 'card' ? 11 : ($metodoPago == 'oxxo' ? 12 : 2)), + 'id_user_alta' => 98, + 'fecha_alta' => Carbon::now(), + 'crear_pase' => 1, + 'id_lugar_cita' => $datos['id_lugar_cita'], + 'generar_pase_ingles' => 0, + ]; + + // Llamar al procedimiento almacenado sp_crear_pagos_reservacion + DB::statement('CALL sp_crear_pagos_reservacion(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)', [ + $params_pago['hora_cita'], + $params_pago['id_cotizacion'], + $params_pago['foto_comprobante'], + $params_pago['cantidad_pagada'], + $params_pago['cantidad_pagada_usd'], + $params_pago['pago_confirmado_default'], + $params_pago['fecha_pago'], + $params_pago['observaciones'], + $params_pago['observaciones_logistica'], + $params_pago['ultimos_digitos'], + $params_pago['id_cuenta_bancaria'], + $params_pago['id_metodo_pago_cuenta_bancaria'], + $params_pago['id_banco'], + $params_pago['id_user_alta'], + $params_pago['fecha_alta'], + $params_pago['crear_pase'], + $params_pago['id_lugar_cita'], + $params_pago['generar_pase_ingles'], + ]); + + // 6. Enviar el pase (opcional, pero mantén la llamada a la API) + //$url_mail = "https://erp.skyballoons.mx/enviar-pase/6/" . $id_cotizacion; + //file_get_contents($url_mail); // o usar Http::get si prefieres + + DB::commit(); + + // Limpiar la sesión para que no se pueda reutilizar + session()->forget('reserva_pendiente'); + + return response()->json([ + 'exito' => 1, + 'id_cotizacion' => $id_cotizacion, + 'folio' => $folio + ]); + + } catch (\Exception $e) { + DB::rollBack(); + return response()->json([ + 'exito' => 0, + 'error' => $e->getMessage() + ], 500); + } + } + + public function guardarPosibleCompra(Request $request) + { + $datos = $request->all(); + + // Generar un ID de cotización (solo numérico, aún no se inserta) + $id_cotizacion = DB::table('cotizaciones')->max('id_cotizacion') + 1; + $folio = 'COT' . str_pad($id_cotizacion, 7, '0', STR_PAD_LEFT); + + // Guardar en posibles_compras (solo lo que tiene la tabla) + DB::table('posibles_compras')->insert([ + 'id_cotizacion' => $id_cotizacion, + 'id_sitio_web' => 6, + 'total_reservado' => $datos['total_compra'], + 'total_reserva' => $datos['total_compra'], + 'fecha_alta' => Carbon::now(), + ]); + + // Guardar TODOS los datos de la reserva en sesión (no en BD) + session()->put('reserva_pendiente', [ + 'id_cotizacion' => $id_cotizacion, + 'folio' => $folio, + 'datos' => $datos, + ]); + + return response()->json([ + 'exito' => 1, + 'id_cotizacion' => $id_cotizacion + ]); + } + + + + + // + /* + public function imprimirPaseAbordar(Request $request) + { + // dd($request->all()); + + $generator = new BarcodeGeneratorPNG(); + $barcodeBase64 = base64_encode( + $generator->getBarcode('VG1025TEO2026', $generator::TYPE_CODE_128) + ); + $barcodeImg = 'data:image/png;base64,' . $barcodeBase64; + $logo = public_path('assets/img/vueloenglobo/logo-vueloenglobo.png'); + + $html = ' +

Pase de Abordar

+

PASE DE ABORDAR

+ '; + + + $data = [ + 'pasajero' => 'JAVIER ALONSO', + 'fecha' => '25 MAY 2026', + 'hora' => '05:30', + 'zona' => 'SKY 01', + 'folio' => 'VG1025', + 'barcode' => $barcodeImg, + 'logo' => $logo, + ]; + + $pdf = Pdf::loadView('pdf.pases.pase-abordar', $data)->setPaper('letter', 'portrait'); + + return response()->streamDownload(fn() => print $pdf->output(), 'pase-de-abordar.pdf'); + } */ + + public function imprimirPaseAbordar(Request $request) + { + // Log para ver qué está llegando (para debug) + \Log::info('Datos recibidos en imprimirPaseAbordar:', $request->all()); + + // Validación de los datos principales + $validated = $request->validate([ + 'reservation_id' => 'required', + 'id_vuelo' => 'required|integer', + 'vuelo_nombre' => 'nullable|string', + 'vuelo_descripcion' => 'nullable|string', + 'vuelo_hora' => 'nullable|string', + 'fecha_vuelo' => 'nullable|string', + 'fecha_libre' => 'boolean', + 'adultos' => 'required|integer|min:1', + 'ninios' => 'required|integer|min:0', + 'responsable' => 'required|array', + 'responsable.nombres' => 'required|string', + 'responsable.apellidos' => 'required|string', + 'responsable.email' => 'required|email', + 'responsable.telefono' => 'required|string', + 'pasajeros' => 'required|array|min:1', + 'pasajeros.*.nombres' => 'required|string', + 'pasajeros.*.apellidos' => 'required|string', + 'pasajeros.*.fecha_nacimiento' => 'required|date', + 'pasajeros.*.peso_kg' => 'required|numeric', + 'pasajeros.*.tipo' => 'required|string|in:adulto,nino', + 'servicios' => 'nullable|array', + 'servicios.*.id' => 'required|integer', + 'servicios.*.cantidad' => 'required|integer|min:1', + 'servicios.*.precio' => 'required|numeric', + 'servicios.*.descripcion' => 'nullable|string', + 'subtotal_vuelo' => 'required|numeric', + 'total_extras' => 'required|numeric', + 'descuento' => 'nullable|numeric', + 'total' => 'required|numeric', + 'payment_email' => 'nullable|email', + 'payment_phone' => 'nullable|string', + 'payment_description' => 'nullable|string', + 'moneda' => 'nullable|string', + 'simbolo' => 'nullable|string', + 'categoria' => 'nullable|string', + 'incluye' => 'nullable|array', + 'rating' => 'nullable|numeric', + 'highlighted_text' => 'nullable|string', + 'highlighted_bg_color' => 'nullable|string', + ]); + + try { + // Generar código de barras único + $generator = new BarcodeGeneratorPNG(); + $barcodeNumber = $request->reservation_id . rand(1000, 9999); + $barcodeBase64 = base64_encode($generator->getBarcode($barcodeNumber, $generator::TYPE_CODE_128)); + $barcodeImg = 'data:image/png;base64,' . $barcodeBase64; + + // Logo en Base64 + $logoPath = public_path('assets/img/vueloenglobo/logo-vueloenglobo.png'); + $logoBase64 = ''; + if (file_exists($logoPath)) { + $logoData = base64_encode(file_get_contents($logoPath)); + $logoBase64 = 'data:image/png;base64,' . $logoData; + } else { + // Logo por defecto si no existe + $logoBase64 = 'https://placehold.co/60x60/4A63D2/white?text=SB'; + } + + // Obtener los pasajeros + $pasajeros = $request->pasajeros; + $pasajeroPrincipal = $pasajeros[0] ?? null; + $nombrePasajero = $pasajeroPrincipal + ? strtoupper(($pasajeroPrincipal['nombres'] ?? '') . ' ' . ($pasajeroPrincipal['apellidos'] ?? '')) + : 'PASAJERO NO ESPECIFICADO'; + + // Formatear la fecha del vuelo - CORREGIDO + $fechaVueloFormateada = $request->fecha_vuelo; + if ($request->fecha_libre) { + $fechaVueloFormateada = 'POR CONFIRMAR'; + } elseif ($fechaVueloFormateada) { + try { + // Intentar parsear fecha en formato dd/mm/YYYY + if (preg_match('/^\d{2}\/\d{2}\/\d{4}$/', $fechaVueloFormateada)) { + $fechaCarbon = \Carbon\Carbon::createFromFormat('d/m/Y', $fechaVueloFormateada); + $fechaVueloFormateada = $fechaCarbon->format('d/m/Y'); + } + // Intentar parsear fecha en formato YYYY-mm-dd + elseif (preg_match('/^\d{4}-\d{2}-\d{2}$/', $fechaVueloFormateada)) { + $fechaCarbon = \Carbon\Carbon::parse($fechaVueloFormateada); + $fechaVueloFormateada = $fechaCarbon->format('d/m/Y'); + } + // Intentar parsear cualquier otro formato + else { + $fechaCarbon = \Carbon\Carbon::parse($fechaVueloFormateada); + $fechaVueloFormateada = $fechaCarbon->format('d/m/Y'); + } + } catch (\Exception $e) { + \Log::warning('No se pudo parsear la fecha del vuelo: ' . $fechaVueloFormateada . ' - Error: ' . $e->getMessage()); + $fechaVueloFormateada = date('d/m/Y'); // Usar fecha actual como fallback + } + } else { + $fechaVueloFormateada = date('d/m/Y'); // Fecha actual si no hay fecha + } + + // Formatear hora del vuelo - CORREGIDO + $horaCita = '05:30 AM'; + if ($request->vuelo_hora && !empty($request->vuelo_hora)) { + try { + // Intentar parsear hora en diferentes formatos + $horaCarbon = \Carbon\Carbon::parse($request->vuelo_hora); + $horaCita = $horaCarbon->format('h:i A'); + } catch (\Exception $e) { + \Log::warning('No se pudo parsear la hora del vuelo: ' . $request->vuelo_hora); + $horaCita = $request->vuelo_hora; // Usar la hora tal como viene + } + } + + // Datos del vuelo + $datosVuelo = [ + 'nombre_vuelo' => $request->vuelo_nombre ?? 'Vuelo en Globo', + 'tipo_vuelo' => $request->categoria ?? 'Compartido', + 'fecha_vuelo' => $fechaVueloFormateada, + 'hora_cita' => $horaCita, + 'lugar_despegue' => 'SKY BALLOONS ', + 'duracion' => '40 min', + 'piloto' => '', + 'precio_total' => $request->total ?? 0, + 'anticipo' => $request->total ?? 0, + 'saldo_pendiente' => 0, + 'limite_pago' => date('d/m/Y', strtotime('+7 days')), + ]; + + // Datos del responsable + $responsable = $request->responsable; + $nombreResponsable = strtoupper( + ($responsable['nombres'] ?? '') . ' ' . ($responsable['apellidos'] ?? '') + ); + $emailResponsable = $responsable['email'] ?? ''; + $telefonoResponsable = $responsable['telefono'] ?? ''; + + // Procesar servicios adicionales + $serviciosAdicionales = []; + if (!empty($request->servicios)) { + foreach ($request->servicios as $servicio) { + $serviciosAdicionales[] = [ + 'nombre' => $servicio['descripcion'] ?? 'Servicio adicional', + 'cantidad' => $servicio['cantidad'] ?? 1, + 'precio_unitario' => $servicio['precio'] ?? 0, + 'precio_total' => ($servicio['cantidad'] ?? 1) * ($servicio['precio'] ?? 0), + 'estado' => 'Contratado', + 'clase' => 'contratado', + ]; + } + } + + // Si no hay servicios adicionales contratados, mostrar algunos por defecto + if (empty($serviciosAdicionales)) { + $serviciosAdicionales = [ + [ + 'nombre' => 'Fotografía profesional', + 'cantidad' => 1, + 'precio_unitario' => 500, + 'precio_total' => 500, + 'estado' => 'No contratado', + 'clase' => 'no-contratado', + ], + [ + 'nombre' => 'Video recuerdo', + 'cantidad' => 1, + 'precio_unitario' => 0, + 'precio_total' => 0, + 'estado' => 'No contratado', + 'clase' => 'no-contratado', + ], + ]; + } + + // Procesar lo que incluye el vuelo + $incluye = []; + if (!empty($request->incluye)) { + foreach ($request->incluye as $item) { + $incluye[] = $item['titulo_item'] ?? $item['nombre'] ?? 'Item incluido'; + } + } + + // Resumen de pasajeros - CORREGIDO (manejo de fechas de nacimiento) + $resumenPasajeros = []; + foreach ($pasajeros as $pasajero) { + $fechaNacimientoFormateada = 'No especificada'; + if (isset($pasajero['fecha_nacimiento']) && !empty($pasajero['fecha_nacimiento'])) { + try { + // Intentar parsear fecha de nacimiento + if (preg_match('/^\d{2}\/\d{2}\/\d{4}$/', $pasajero['fecha_nacimiento'])) { + $fechaNac = \Carbon\Carbon::createFromFormat('d/m/Y', $pasajero['fecha_nacimiento']); + $fechaNacimientoFormateada = $fechaNac->format('d/m/Y'); + } else { + $fechaNac = \Carbon\Carbon::parse($pasajero['fecha_nacimiento']); + $fechaNacimientoFormateada = $fechaNac->format('d/m/Y'); + } + } catch (\Exception $e) { + \Log::warning('No se pudo parsear fecha de nacimiento: ' . $pasajero['fecha_nacimiento']); + $fechaNacimientoFormateada = $pasajero['fecha_nacimiento']; + } + } + + $resumenPasajeros[] = [ + 'nombre_completo' => strtoupper( + ($pasajero['nombres'] ?? '') . ' ' . ($pasajero['apellidos'] ?? '') + ), + 'tipo' => $pasajero['tipo'] ?? 'adulto', + 'peso_kg' => $pasajero['peso_kg'] ?? 'No especificado', + 'fecha_nacimiento' => $fechaNacimientoFormateada, + ]; + } + + // Información de pago + $infoPago = [ + 'metodo' => 'Tarjeta de crédito/débito', + 'email' => $request->payment_email ?? $emailResponsable, + 'telefono' => $request->payment_phone ?? $telefonoResponsable, + 'descripcion' => $request->payment_description ?? $request->vuelo_nombre ?? 'Reserva de vuelo', + 'moneda' => $request->moneda ?? 'MXN', + 'simbolo' => $request->simbolo ?? '$', + ]; + + // Preparar todos los datos para la vista + $data = [ + // Datos del vuelo + 'nombre_vuelo' => $datosVuelo['nombre_vuelo'], + 'tipo_vuelo' => $datosVuelo['tipo_vuelo'], + 'fecha_vuelo' => $datosVuelo['fecha_vuelo'], + 'hora_cita' => $datosVuelo['hora_cita'], + 'lugar_despegue' => $datosVuelo['lugar_despegue'], + 'duracion' => $datosVuelo['duracion'], + 'piloto' => $datosVuelo['piloto'], + 'highlighted_text' => $request->highlighted_text ?? '', + 'highlighted_bg_color' => $request->highlighted_bg_color ?? '#2563eb', + 'rating' => $request->rating ?? 0, + + // Datos del responsable + 'nombre_responsable' => $nombreResponsable, + 'email_responsable' => $emailResponsable, + 'telefono_responsable' => $telefonoResponsable, + + // Datos de pasajeros + 'nombre_pasajero' => $nombrePasajero, + 'lista_pasajeros' => $pasajeros, + 'resumen_pasajeros' => $resumenPasajeros, + + // Totales y precios + 'precio_total' => $datosVuelo['precio_total'], + 'subtotal_vuelo' => $request->subtotal_vuelo ?? 0, + 'total_extras' => $request->total_extras ?? 0, + 'descuento' => $request->descuento ?? 0, + 'anticipo' => $datosVuelo['anticipo'], + 'saldo_pendiente' => $datosVuelo['saldo_pendiente'], + 'limite_pago' => $datosVuelo['limite_pago'], + + // Información de pago + 'info_pago' => $infoPago, + + // Servicios e inclusiones + 'servicios_adicionales' => $serviciosAdicionales, + 'incluye' => $incluye, + + // Datos de la reserva + 'folio_reserva' => $request->reservation_id, + 'fecha_reserva' => now()->format('d/m/Y H:i:s'), + + // Códigos e imágenes + 'barcode' => $barcodeImg, + 'barcode_number' => $barcodeNumber, + 'logo' => $logoBase64, + + // Datos adicionales + 'adultos' => $request->adultos ?? 1, + 'ninios' => $request->ninios ?? 0, + 'total_pasajeros' => ($request->adultos ?? 0) + ($request->ninios ?? 0), + 'vuelo_descripcion' => $request->vuelo_descripcion ?? '', + ]; + + // Generar PDF + $pdf = Pdf::loadView('pdf.pases.pase-abordar', $data) + ->setPaper('letter', 'portrait') + ->setOptions([ + 'defaultFont' => 'sans-serif', + 'isHtml5ParserEnabled' => true, + 'isRemoteEnabled' => true, + 'chroot' => public_path(), + ]); + + // Guardar una copia del PDF (opcional) + $pdfPath = storage_path('app/public/pases-abordar/'); + if (!file_exists($pdfPath)) { + mkdir($pdfPath, 0755, true); + } + $pdfPathCompleto = $pdfPath . "pase_abordar_" . $request->reservation_id . "_" . date('Ymd_His') . ".pdf"; + $pdf->save($pdfPathCompleto); + + // Enviar correo con el pase de abordar (opcional - comentado) + try { + // Mail::to($emailResponsable)->send(new PaseAbordarMail($data, $pdf)); + // \Log::info("Correo enviado a: {$emailResponsable}"); + } catch (\Exception $e) { + \Log::error("Error enviando correo: " . $e->getMessage()); + } + + // Descargar el PDF + return $pdf->download("pase_abordar_" . $request->reservation_id . ".pdf"); + + } catch (\Exception $e) { + \Log::error('Error generando pase de abordar: ' . $e->getMessage()); + \Log::error('Trace: ' . $e->getTraceAsString()); + + return response()->json([ + 'success' => false, + 'message' => 'Error al generar el pase de abordar: ' . $e->getMessage() + ], 500); + } + } +} diff --git a/app/Http/Controllers/PaymentController.php b/app/Http/Controllers/PaymentController.php new file mode 100644 index 0000000..edfe140 --- /dev/null +++ b/app/Http/Controllers/PaymentController.php @@ -0,0 +1,49 @@ +validate([ + 'token' => 'required|string', + 'amount' => 'required|numeric|min:0.01', + 'description' => 'nullable|string|max:255', + 'email' => 'required|email', + 'phone' => 'required|string', + ]); + + $payload = [ + 'amount' => (float) $validated['amount'], + 'currency' => 'MXN', + 'description' => $validated['description'] ?? 'Pago con SDK Clip', + 'payment_method' => [ + 'token' => $validated['token'] + ], + 'customer' => [ + 'email' => $validated['email'], + 'phone' => $validated['phone'] + ] + ]; + + $response = Http::withHeaders([ + 'Authorization' => 'Bearer ' . config('clip.api_key'), + 'Content-Type' => 'application/json', + ])->post(config('clip.payments_url'), $payload); + + if ($response->successful()) { + return response()->json($response->json(), 201); + } + + return response()->json([ + 'error' => $response->json('detail') ?? 'Error al procesar el pago', + 'code' => $response->json('error_code') + ], $response->status()); + } +} diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php new file mode 100644 index 0000000..326d3c3 --- /dev/null +++ b/app/Http/Kernel.php @@ -0,0 +1,98 @@ + + */ + protected $middleware = [ + // \App\Http\Middleware\TrustHosts::class, + TrustProxies::class, + HandleCors::class, + PreventRequestsDuringMaintenance::class, + ValidatePostSize::class, + TrimStrings::class, + ConvertEmptyStringsToNull::class, + //MinifyHtml::class, + ]; + + /** + * The application's route middleware groups. + * + * @var array> + */ + protected $middlewareGroups = [ + 'web' => [ + EncryptCookies::class, + AddQueuedCookiesToResponse::class, + StartSession::class, + ShareErrorsFromSession::class, + VerifyCsrfToken::class, + SubstituteBindings::class, + SetLocale::class, + + ], + + 'api' => [ + // \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class, + ThrottleRequests::class . ':api', + SubstituteBindings::class, + ], + ]; + + /** + * The application's middleware aliases. + * + * Aliases may be used instead of class names to conveniently assign middleware to routes and groups. + * + * @var array + */ + protected $middlewareAliases = [ + 'auth' => Authenticate::class, + 'auth.basic' => AuthenticateWithBasicAuth::class, + 'auth.session' => AuthenticateSession::class, + 'cache.headers' => SetCacheHeaders::class, + 'can' => Authorize::class, + 'guest' => RedirectIfAuthenticated::class, + 'password.confirm' => RequirePassword::class, + 'precognitive' => HandlePrecognitiveRequests::class, + 'signed' => ValidateSignature::class, + 'throttle' => ThrottleRequests::class, + 'verified' => EnsureEmailIsVerified::class, + 'auth.bearer' => VerifyBearerToken::class, + ]; +} diff --git a/app/Http/Middleware/Authenticate.php b/app/Http/Middleware/Authenticate.php new file mode 100644 index 0000000..d4ef644 --- /dev/null +++ b/app/Http/Middleware/Authenticate.php @@ -0,0 +1,17 @@ +expectsJson() ? null : route('login'); + } +} diff --git a/app/Http/Middleware/EncryptCookies.php b/app/Http/Middleware/EncryptCookies.php new file mode 100644 index 0000000..867695b --- /dev/null +++ b/app/Http/Middleware/EncryptCookies.php @@ -0,0 +1,17 @@ + + */ + protected $except = [ + // + ]; +} diff --git a/app/Http/Middleware/MinifyHtml.php b/app/Http/Middleware/MinifyHtml.php new file mode 100644 index 0000000..53e0d97 --- /dev/null +++ b/app/Http/Middleware/MinifyHtml.php @@ -0,0 +1,34 @@ +headers->get('Content-Type'), 'text/html')) { + $content = $response->getContent(); + + $minify = preg_replace([ + '/>\s+/s', // espacios después de tags + '/\s+', + '<', + '\\1' + ], $content); + + $response->setContent($minify); + } + + return $response; + } +} diff --git a/app/Http/Middleware/PreventRequestsDuringMaintenance.php b/app/Http/Middleware/PreventRequestsDuringMaintenance.php new file mode 100644 index 0000000..74cbd9a --- /dev/null +++ b/app/Http/Middleware/PreventRequestsDuringMaintenance.php @@ -0,0 +1,17 @@ + + */ + protected $except = [ + // + ]; +} diff --git a/app/Http/Middleware/RedirectIfAuthenticated.php b/app/Http/Middleware/RedirectIfAuthenticated.php new file mode 100644 index 0000000..afc78c4 --- /dev/null +++ b/app/Http/Middleware/RedirectIfAuthenticated.php @@ -0,0 +1,30 @@ +check()) { + return redirect(RouteServiceProvider::HOME); + } + } + + return $next($request); + } +} diff --git a/app/Http/Middleware/SetLocale.php b/app/Http/Middleware/SetLocale.php new file mode 100644 index 0000000..dc22e8a --- /dev/null +++ b/app/Http/Middleware/SetLocale.php @@ -0,0 +1,24 @@ +setLocale($locale); + + return $next($request); + } +} diff --git a/app/Http/Middleware/TrimStrings.php b/app/Http/Middleware/TrimStrings.php new file mode 100644 index 0000000..88cadca --- /dev/null +++ b/app/Http/Middleware/TrimStrings.php @@ -0,0 +1,19 @@ + + */ + protected $except = [ + 'current_password', + 'password', + 'password_confirmation', + ]; +} diff --git a/app/Http/Middleware/TrustHosts.php b/app/Http/Middleware/TrustHosts.php new file mode 100644 index 0000000..c9c58bd --- /dev/null +++ b/app/Http/Middleware/TrustHosts.php @@ -0,0 +1,20 @@ + + */ + public function hosts(): array + { + return [ + $this->allSubdomainsOfApplicationUrl(), + ]; + } +} diff --git a/app/Http/Middleware/TrustProxies.php b/app/Http/Middleware/TrustProxies.php new file mode 100644 index 0000000..3391630 --- /dev/null +++ b/app/Http/Middleware/TrustProxies.php @@ -0,0 +1,28 @@ +|string|null + */ + protected $proxies; + + /** + * The headers that should be used to detect proxies. + * + * @var int + */ + protected $headers = + Request::HEADER_X_FORWARDED_FOR | + Request::HEADER_X_FORWARDED_HOST | + Request::HEADER_X_FORWARDED_PORT | + Request::HEADER_X_FORWARDED_PROTO | + Request::HEADER_X_FORWARDED_AWS_ELB; +} diff --git a/app/Http/Middleware/ValidateSignature.php b/app/Http/Middleware/ValidateSignature.php new file mode 100644 index 0000000..093bf64 --- /dev/null +++ b/app/Http/Middleware/ValidateSignature.php @@ -0,0 +1,22 @@ + + */ + protected $except = [ + // 'fbclid', + // 'utm_campaign', + // 'utm_content', + // 'utm_medium', + // 'utm_source', + // 'utm_term', + ]; +} diff --git a/app/Http/Middleware/VerifyBearerToken.php b/app/Http/Middleware/VerifyBearerToken.php new file mode 100644 index 0000000..c926ad3 --- /dev/null +++ b/app/Http/Middleware/VerifyBearerToken.php @@ -0,0 +1,56 @@ +bearerToken(); + + /** + * Token esperado desde .env + */ + $expectedToken = env('API_BEARER_TOKEN'); + + /** + * Validación básica de existencia + */ + if (!$expectedToken) { + return response()->json([ + 'error' => 'API token no configurado en .env' + ], 500); + } + + /** + * Validación de token faltante + */ + if (!$token) { + return response()->json([ + 'error' => 'Token no enviado' + ], 401); + } + + /** + * Comparación segura + */ + if (!hash_equals($expectedToken, $token)) { + return response()->json([ + 'error' => 'No autorizado' + ], 401); + } + + /** + * Continuar request + */ + return $next($request); + } +} \ No newline at end of file diff --git a/app/Http/Middleware/VerifyCsrfToken.php b/app/Http/Middleware/VerifyCsrfToken.php new file mode 100644 index 0000000..9e86521 --- /dev/null +++ b/app/Http/Middleware/VerifyCsrfToken.php @@ -0,0 +1,17 @@ + + */ + protected $except = [ + // + ]; +} diff --git a/app/Models/Cotizaciones.php b/app/Models/Cotizaciones.php new file mode 100644 index 0000000..ce7cd15 --- /dev/null +++ b/app/Models/Cotizaciones.php @@ -0,0 +1,11 @@ + + */ + protected $fillable = [ + 'name', + 'email', + 'password', + ]; + + /** + * The attributes that should be hidden for serialization. + * + * @var array + */ + protected $hidden = [ + 'password', + 'remember_token', + ]; + + /** + * The attributes that should be cast. + * + * @var array + */ + protected $casts = [ + 'email_verified_at' => 'datetime', + 'password' => 'hashed', + ]; +} diff --git a/app/Models/Vuelos.php b/app/Models/Vuelos.php new file mode 100644 index 0000000..7a313f3 --- /dev/null +++ b/app/Models/Vuelos.php @@ -0,0 +1,11 @@ + + */ + protected $policies = [ + // + ]; + + /** + * Register any authentication / authorization services. + */ + public function boot(): void + { + // + } +} diff --git a/app/Providers/BroadcastServiceProvider.php b/app/Providers/BroadcastServiceProvider.php new file mode 100644 index 0000000..2be04f5 --- /dev/null +++ b/app/Providers/BroadcastServiceProvider.php @@ -0,0 +1,19 @@ +> + */ + protected $listen = [ + Registered::class => [ + SendEmailVerificationNotification::class, + ], + ]; + + /** + * Register any events for your application. + */ + public function boot(): void + { + // + } + + /** + * Determine if events and listeners should be automatically discovered. + */ + public function shouldDiscoverEvents(): bool + { + return false; + } +} diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php new file mode 100644 index 0000000..1cf5f15 --- /dev/null +++ b/app/Providers/RouteServiceProvider.php @@ -0,0 +1,40 @@ +by($request->user()?->id ?: $request->ip()); + }); + + $this->routes(function () { + Route::middleware('api') + ->prefix('api') + ->group(base_path('routes/api.php')); + + Route::middleware('web') + ->group(base_path('routes/web.php')); + }); + } +} diff --git a/artisan b/artisan new file mode 100644 index 0000000..67a3329 --- /dev/null +++ b/artisan @@ -0,0 +1,53 @@ +#!/usr/bin/env php +make(Illuminate\Contracts\Console\Kernel::class); + +$status = $kernel->handle( + $input = new Symfony\Component\Console\Input\ArgvInput, + new Symfony\Component\Console\Output\ConsoleOutput +); + +/* +|-------------------------------------------------------------------------- +| Shutdown The Application +|-------------------------------------------------------------------------- +| +| Once Artisan has finished running, we will fire off the shutdown events +| so that any final work may be done by the application before we shut +| down the process. This is the last thing to happen to the request. +| +*/ + +$kernel->terminate($input, $status); + +exit($status); diff --git a/bootstrap/app.php b/bootstrap/app.php new file mode 100644 index 0000000..037e17d --- /dev/null +++ b/bootstrap/app.php @@ -0,0 +1,55 @@ +singleton( + Illuminate\Contracts\Http\Kernel::class, + App\Http\Kernel::class +); + +$app->singleton( + Illuminate\Contracts\Console\Kernel::class, + App\Console\Kernel::class +); + +$app->singleton( + Illuminate\Contracts\Debug\ExceptionHandler::class, + App\Exceptions\Handler::class +); + +/* +|-------------------------------------------------------------------------- +| Return The Application +|-------------------------------------------------------------------------- +| +| This script returns the application instance. The instance is given to +| the calling script so we can separate the building of the instances +| from the actual running of the application and sending responses. +| +*/ + +return $app; diff --git a/bootstrap/cache/.gitignore b/bootstrap/cache/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/bootstrap/cache/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..ba8985f --- /dev/null +++ b/composer.json @@ -0,0 +1,70 @@ +{ + "name": "laravel/laravel", + "type": "project", + "description": "The skeleton application for the Laravel framework.", + "keywords": ["laravel", "framework"], + "license": "MIT", + "require": { + "php": "^8.1", + "artesaos/seotools": "^1.4", + "barryvdh/laravel-dompdf": "^3.1", + "guzzlehttp/guzzle": "^7.2", + "laravel/framework": "^10.10", + "laravel/sanctum": "^3.3", + "laravel/tinker": "^2.8", + "laravel/ui": "^4.6", + "picqer/php-barcode-generator": "^3.2" + }, + "require-dev": { + "fakerphp/faker": "^1.9.1", + "laravel/pint": "^1.0", + "laravel/sail": "^1.18", + "mockery/mockery": "^1.4.4", + "nunomaduro/collision": "^7.0", + "phpunit/phpunit": "^10.1", + "spatie/laravel-ignition": "^2.0" + }, + "autoload": { + "psr-4": { + "App\\": "app/", + "Database\\Factories\\": "database/factories/", + "Database\\Seeders\\": "database/seeders/" + } + }, + "autoload-dev": { + "psr-4": { + "Tests\\": "tests/" + } + }, + "scripts": { + "post-autoload-dump": [ + "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump", + "@php artisan package:discover --ansi" + ], + "post-update-cmd": [ + "@php artisan vendor:publish --tag=laravel-assets --ansi --force" + ], + "post-root-package-install": [ + "@php -r \"file_exists('.env') || copy('.env.example', '.env');\"" + ], + "post-create-project-cmd": [ + "@php artisan key:generate --ansi" + ] + }, + "extra": { + "laravel": { + "dont-discover": [] + } + }, + "config": { + "optimize-autoloader": true, + "preferred-install": "dist", + "sort-packages": true, + "allow-plugins": { + "pestphp/pest-plugin": true, + "php-http/discovery": true + } + }, + "minimum-stability": "stable", + "prefer-stable": true +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..8ad1b03 --- /dev/null +++ b/composer.lock @@ -0,0 +1,8969 @@ +{ + "_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": "c1ca79267266e4213ad8a1959907aaff", + "packages": [ + { + "name": "artesaos/seotools", + "version": "v1.4.1", + "source": { + "type": "git", + "url": "https://github.com/artesaos/seotools.git", + "reference": "c92d09f0ad4491181863850a3ab2e1534361409b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/artesaos/seotools/zipball/c92d09f0ad4491181863850a3ab2e1534361409b", + "reference": "c92d09f0ad4491181863850a3ab2e1534361409b", + "shasum": "" + }, + "require": { + "ext-json": "*", + "illuminate/config": "^10.0 || ^11.0 || ^12.0 || ^13.0", + "illuminate/support": "^10.0 || ^11.0 || ^12.0 || ^13.0", + "php": "^8.1" + }, + "require-dev": { + "orchestra/testbench": "^8.0 || ^9.0 || ^10.0 || ^11.0", + "phpunit/phpunit": "^9.0 || ^10.0 || ^11.5.3" + }, + "type": "library", + "extra": { + "laravel": { + "aliases": { + "SEO": "Artesaos\\SEOTools\\Facades\\SEOTools", + "JsonLd": "Artesaos\\SEOTools\\Facades\\JsonLd", + "SEOMeta": "Artesaos\\SEOTools\\Facades\\SEOMeta", + "Twitter": "Artesaos\\SEOTools\\Facades\\TwitterCard", + "OpenGraph": "Artesaos\\SEOTools\\Facades\\OpenGraph" + }, + "providers": [ + "Artesaos\\SEOTools\\Providers\\SEOToolsServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Artesaos\\SEOTools\\": "src/SEOTools/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Vinicius", + "email": "luiz.vinicius73@gmail.com" + } + ], + "description": "SEO Tools for Laravel and Lumen", + "keywords": [ + "JSON-LD", + "laravel", + "lumen", + "metatags", + "opengraph", + "seo", + "seotools", + "webmaster" + ], + "support": { + "issues": "https://github.com/artesaos/seotools/issues", + "source": "https://github.com/artesaos/seotools" + }, + "time": "2026-03-28T12:37:33+00:00" + }, + { + "name": "barryvdh/laravel-dompdf", + "version": "v3.1.2", + "source": { + "type": "git", + "url": "https://github.com/barryvdh/laravel-dompdf.git", + "reference": "ee3b72b19ccdf57d0243116ecb2b90261344dedc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/barryvdh/laravel-dompdf/zipball/ee3b72b19ccdf57d0243116ecb2b90261344dedc", + "reference": "ee3b72b19ccdf57d0243116ecb2b90261344dedc", + "shasum": "" + }, + "require": { + "dompdf/dompdf": "^3.0", + "illuminate/support": "^9|^10|^11|^12|^13.0", + "php": "^8.1" + }, + "require-dev": { + "larastan/larastan": "^2.7|^3.0", + "orchestra/testbench": "^7|^8|^9.16|^10|^11.0", + "phpro/grumphp": "^2.5", + "squizlabs/php_codesniffer": "^3.5" + }, + "type": "library", + "extra": { + "laravel": { + "aliases": { + "PDF": "Barryvdh\\DomPDF\\Facade\\Pdf", + "Pdf": "Barryvdh\\DomPDF\\Facade\\Pdf" + }, + "providers": [ + "Barryvdh\\DomPDF\\ServiceProvider" + ] + }, + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "psr-4": { + "Barryvdh\\DomPDF\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Barry vd. Heuvel", + "email": "barryvdh@gmail.com" + } + ], + "description": "A DOMPDF Wrapper for Laravel", + "keywords": [ + "dompdf", + "laravel", + "pdf" + ], + "support": { + "issues": "https://github.com/barryvdh/laravel-dompdf/issues", + "source": "https://github.com/barryvdh/laravel-dompdf/tree/v3.1.2" + }, + "funding": [ + { + "url": "https://fruitcake.nl", + "type": "custom" + }, + { + "url": "https://github.com/barryvdh", + "type": "github" + } + ], + "time": "2026-02-21T08:51:10+00:00" + }, + { + "name": "brick/math", + "version": "0.12.3", + "source": { + "type": "git", + "url": "https://github.com/brick/math.git", + "reference": "866551da34e9a618e64a819ee1e01c20d8a588ba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/brick/math/zipball/866551da34e9a618e64a819ee1e01c20d8a588ba", + "reference": "866551da34e9a618e64a819ee1e01c20d8a588ba", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.2", + "phpunit/phpunit": "^10.1", + "vimeo/psalm": "6.8.8" + }, + "type": "library", + "autoload": { + "psr-4": { + "Brick\\Math\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Arbitrary-precision arithmetic library", + "keywords": [ + "Arbitrary-precision", + "BigInteger", + "BigRational", + "arithmetic", + "bigdecimal", + "bignum", + "bignumber", + "brick", + "decimal", + "integer", + "math", + "mathematics", + "rational" + ], + "support": { + "issues": "https://github.com/brick/math/issues", + "source": "https://github.com/brick/math/tree/0.12.3" + }, + "funding": [ + { + "url": "https://github.com/BenMorel", + "type": "github" + } + ], + "time": "2025-02-28T13:11:00+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": "dflydev/dot-access-data", + "version": "v3.0.3", + "source": { + "type": "git", + "url": "https://github.com/dflydev/dflydev-dot-access-data.git", + "reference": "a23a2bf4f31d3518f3ecb38660c95715dfead60f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/a23a2bf4f31d3518f3ecb38660c95715dfead60f", + "reference": "a23a2bf4f31d3518f3ecb38660c95715dfead60f", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^0.12.42", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.3", + "scrutinizer/ocular": "1.6.0", + "squizlabs/php_codesniffer": "^3.5", + "vimeo/psalm": "^4.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Dflydev\\DotAccessData\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Dragonfly Development Inc.", + "email": "info@dflydev.com", + "homepage": "http://dflydev.com" + }, + { + "name": "Beau Simensen", + "email": "beau@dflydev.com", + "homepage": "http://beausimensen.com" + }, + { + "name": "Carlos Frutos", + "email": "carlos@kiwing.it", + "homepage": "https://github.com/cfrutos" + }, + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com" + } + ], + "description": "Given a deep data structure, access data by dot notation.", + "homepage": "https://github.com/dflydev/dflydev-dot-access-data", + "keywords": [ + "access", + "data", + "dot", + "notation" + ], + "support": { + "issues": "https://github.com/dflydev/dflydev-dot-access-data/issues", + "source": "https://github.com/dflydev/dflydev-dot-access-data/tree/v3.0.3" + }, + "time": "2024-07-08T12:26:09+00:00" + }, + { + "name": "doctrine/inflector", + "version": "2.1.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/inflector.git", + "reference": "6d6c96277ea252fc1304627204c3d5e6e15faa3b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/6d6c96277ea252fc1304627204c3d5e6e15faa3b", + "reference": "6d6c96277ea252fc1304627204c3d5e6e15faa3b", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^12.0 || ^13.0", + "phpstan/phpstan": "^1.12 || ^2.0", + "phpstan/phpstan-phpunit": "^1.4 || ^2.0", + "phpstan/phpstan-strict-rules": "^1.6 || ^2.0", + "phpunit/phpunit": "^8.5 || ^12.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Inflector\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Inflector is a small library that can perform string manipulations with regard to upper/lowercase and singular/plural forms of words.", + "homepage": "https://www.doctrine-project.org/projects/inflector.html", + "keywords": [ + "inflection", + "inflector", + "lowercase", + "manipulation", + "php", + "plural", + "singular", + "strings", + "uppercase", + "words" + ], + "support": { + "issues": "https://github.com/doctrine/inflector/issues", + "source": "https://github.com/doctrine/inflector/tree/2.1.0" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finflector", + "type": "tidelift" + } + ], + "time": "2025-08-10T19:31:58+00:00" + }, + { + "name": "doctrine/lexer", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/lexer.git", + "reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd", + "reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "doctrine/coding-standard": "^12", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^10.5", + "psalm/plugin-phpunit": "^0.18.3", + "vimeo/psalm": "^5.21" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Lexer\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", + "homepage": "https://www.doctrine-project.org/projects/lexer.html", + "keywords": [ + "annotations", + "docblock", + "lexer", + "parser", + "php" + ], + "support": { + "issues": "https://github.com/doctrine/lexer/issues", + "source": "https://github.com/doctrine/lexer/tree/3.0.1" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Flexer", + "type": "tidelift" + } + ], + "time": "2024-02-05T11:56:58+00:00" + }, + { + "name": "dompdf/dompdf", + "version": "v3.1.5", + "source": { + "type": "git", + "url": "https://github.com/dompdf/dompdf.git", + "reference": "f11ead23a8a76d0ff9bbc6c7c8fd7e05ca328496" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dompdf/dompdf/zipball/f11ead23a8a76d0ff9bbc6c7c8fd7e05ca328496", + "reference": "f11ead23a8a76d0ff9bbc6c7c8fd7e05ca328496", + "shasum": "" + }, + "require": { + "dompdf/php-font-lib": "^1.0.0", + "dompdf/php-svg-lib": "^1.0.0", + "ext-dom": "*", + "ext-mbstring": "*", + "masterminds/html5": "^2.0", + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "ext-gd": "*", + "ext-json": "*", + "ext-zip": "*", + "mockery/mockery": "^1.3", + "phpunit/phpunit": "^7.5 || ^8 || ^9 || ^10 || ^11", + "squizlabs/php_codesniffer": "^3.5", + "symfony/process": "^4.4 || ^5.4 || ^6.2 || ^7.0" + }, + "suggest": { + "ext-gd": "Needed to process images", + "ext-gmagick": "Improves image processing performance", + "ext-imagick": "Improves image processing performance", + "ext-zlib": "Needed for pdf stream compression" + }, + "type": "library", + "autoload": { + "psr-4": { + "Dompdf\\": "src/" + }, + "classmap": [ + "lib/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1" + ], + "authors": [ + { + "name": "The Dompdf Community", + "homepage": "https://github.com/dompdf/dompdf/blob/master/AUTHORS.md" + } + ], + "description": "DOMPDF is a CSS 2.1 compliant HTML to PDF converter", + "homepage": "https://github.com/dompdf/dompdf", + "support": { + "issues": "https://github.com/dompdf/dompdf/issues", + "source": "https://github.com/dompdf/dompdf/tree/v3.1.5" + }, + "time": "2026-03-03T13:54:37+00:00" + }, + { + "name": "dompdf/php-font-lib", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/dompdf/php-font-lib.git", + "reference": "a6e9a688a2a80016ac080b97be73d3e10c444c9a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dompdf/php-font-lib/zipball/a6e9a688a2a80016ac080b97be73d3e10c444c9a", + "reference": "a6e9a688a2a80016ac080b97be73d3e10c444c9a", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^7.5 || ^8 || ^9 || ^10 || ^11 || ^12" + }, + "type": "library", + "autoload": { + "psr-4": { + "FontLib\\": "src/FontLib" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1-or-later" + ], + "authors": [ + { + "name": "The FontLib Community", + "homepage": "https://github.com/dompdf/php-font-lib/blob/master/AUTHORS.md" + } + ], + "description": "A library to read, parse, export and make subsets of different types of font files.", + "homepage": "https://github.com/dompdf/php-font-lib", + "support": { + "issues": "https://github.com/dompdf/php-font-lib/issues", + "source": "https://github.com/dompdf/php-font-lib/tree/1.0.2" + }, + "time": "2026-01-20T14:10:26+00:00" + }, + { + "name": "dompdf/php-svg-lib", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/dompdf/php-svg-lib.git", + "reference": "8259ffb930817e72b1ff1caef5d226501f3dfeb1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dompdf/php-svg-lib/zipball/8259ffb930817e72b1ff1caef5d226501f3dfeb1", + "reference": "8259ffb930817e72b1ff1caef5d226501f3dfeb1", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": "^7.1 || ^8.0", + "sabberworm/php-css-parser": "^8.4 || ^9.0" + }, + "require-dev": { + "phpunit/phpunit": "^7.5 || ^8 || ^9 || ^10 || ^11" + }, + "type": "library", + "autoload": { + "psr-4": { + "Svg\\": "src/Svg" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "The SvgLib Community", + "homepage": "https://github.com/dompdf/php-svg-lib/blob/master/AUTHORS.md" + } + ], + "description": "A library to read, parse and export to PDF SVG files.", + "homepage": "https://github.com/dompdf/php-svg-lib", + "support": { + "issues": "https://github.com/dompdf/php-svg-lib/issues", + "source": "https://github.com/dompdf/php-svg-lib/tree/1.0.2" + }, + "time": "2026-01-02T16:01:13+00:00" + }, + { + "name": "dragonmantank/cron-expression", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/dragonmantank/cron-expression.git", + "reference": "d61a8a9604ec1f8c3d150d09db6ce98b32675013" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/d61a8a9604ec1f8c3d150d09db6ce98b32675013", + "reference": "d61a8a9604ec1f8c3d150d09db6ce98b32675013", + "shasum": "" + }, + "require": { + "php": "^8.2|^8.3|^8.4|^8.5" + }, + "replace": { + "mtdowling/cron-expression": "^1.0" + }, + "require-dev": { + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^1.12.32|^2.1.31", + "phpunit/phpunit": "^8.5.48|^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Cron\\": "src/Cron/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Chris Tankersley", + "email": "chris@ctankersley.com", + "homepage": "https://github.com/dragonmantank" + } + ], + "description": "CRON for PHP: Calculate the next or previous run date and determine if a CRON expression is due", + "keywords": [ + "cron", + "schedule" + ], + "support": { + "issues": "https://github.com/dragonmantank/cron-expression/issues", + "source": "https://github.com/dragonmantank/cron-expression/tree/v3.6.0" + }, + "funding": [ + { + "url": "https://github.com/dragonmantank", + "type": "github" + } + ], + "time": "2025-10-31T18:51:33+00:00" + }, + { + "name": "egulias/email-validator", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/egulias/EmailValidator.git", + "reference": "d42c8731f0624ad6bdc8d3e5e9a4524f68801cfa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/d42c8731f0624ad6bdc8d3e5e9a4524f68801cfa", + "reference": "d42c8731f0624ad6bdc8d3e5e9a4524f68801cfa", + "shasum": "" + }, + "require": { + "doctrine/lexer": "^2.0 || ^3.0", + "php": ">=8.1", + "symfony/polyfill-intl-idn": "^1.26" + }, + "require-dev": { + "phpunit/phpunit": "^10.2", + "vimeo/psalm": "^5.12" + }, + "suggest": { + "ext-intl": "PHP Internationalization Libraries are required to use the SpoofChecking validation" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Egulias\\EmailValidator\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Eduardo Gulias Davis" + } + ], + "description": "A library for validating emails against several RFCs", + "homepage": "https://github.com/egulias/EmailValidator", + "keywords": [ + "email", + "emailvalidation", + "emailvalidator", + "validation", + "validator" + ], + "support": { + "issues": "https://github.com/egulias/EmailValidator/issues", + "source": "https://github.com/egulias/EmailValidator/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/egulias", + "type": "github" + } + ], + "time": "2025-03-06T22:45:56+00:00" + }, + { + "name": "fruitcake/php-cors", + "version": "v1.4.0", + "source": { + "type": "git", + "url": "https://github.com/fruitcake/php-cors.git", + "reference": "38aaa6c3fd4c157ffe2a4d10aa8b9b16ba8de379" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/fruitcake/php-cors/zipball/38aaa6c3fd4c157ffe2a4d10aa8b9b16ba8de379", + "reference": "38aaa6c3fd4c157ffe2a4d10aa8b9b16ba8de379", + "shasum": "" + }, + "require": { + "php": "^8.1", + "symfony/http-foundation": "^5.4|^6.4|^7.3|^8" + }, + "require-dev": { + "phpstan/phpstan": "^2", + "phpunit/phpunit": "^9", + "squizlabs/php_codesniffer": "^4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "autoload": { + "psr-4": { + "Fruitcake\\Cors\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fruitcake", + "homepage": "https://fruitcake.nl" + }, + { + "name": "Barryvdh", + "email": "barryvdh@gmail.com" + } + ], + "description": "Cross-origin resource sharing library for the Symfony HttpFoundation", + "homepage": "https://github.com/fruitcake/php-cors", + "keywords": [ + "cors", + "laravel", + "symfony" + ], + "support": { + "issues": "https://github.com/fruitcake/php-cors/issues", + "source": "https://github.com/fruitcake/php-cors/tree/v1.4.0" + }, + "funding": [ + { + "url": "https://fruitcake.nl", + "type": "custom" + }, + { + "url": "https://github.com/barryvdh", + "type": "github" + } + ], + "time": "2025-12-03T09:33:47+00:00" + }, + { + "name": "graham-campbell/result-type", + "version": "v1.1.4", + "source": { + "type": "git", + "url": "https://github.com/GrahamCampbell/Result-Type.git", + "reference": "e01f4a821471308ba86aa202fed6698b6b695e3b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/e01f4a821471308ba86aa202fed6698b6b695e3b", + "reference": "e01f4a821471308ba86aa202fed6698b6b695e3b", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "phpoption/phpoption": "^1.9.5" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.41 || ^9.6.22 || ^10.5.45 || ^11.5.7" + }, + "type": "library", + "autoload": { + "psr-4": { + "GrahamCampbell\\ResultType\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + } + ], + "description": "An Implementation Of The Result Type", + "keywords": [ + "Graham Campbell", + "GrahamCampbell", + "Result Type", + "Result-Type", + "result" + ], + "support": { + "issues": "https://github.com/GrahamCampbell/Result-Type/issues", + "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.4" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/graham-campbell/result-type", + "type": "tidelift" + } + ], + "time": "2025-12-27T19:43:20+00:00" + }, + { + "name": "guzzlehttp/guzzle", + "version": "7.11.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "c987f8ce84b8434fa430795eca0f3430663da72b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/c987f8ce84b8434fa430795eca0f3430663da72b", + "reference": "c987f8ce84b8434fa430795eca0f3430663da72b", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/promises": "^2.5", + "guzzlehttp/psr7": "^2.11", + "php": "^7.2.5 || ^8.0", + "psr/http-client": "^1.0", + "symfony/deprecation-contracts": "^2.5 || ^3.0", + "symfony/polyfill-php80": "^1.24" + }, + "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", + "guzzlehttp/test-server": "^0.4", + "php-http/message-factory": "^1.1", + "phpunit/phpunit": "^8.5.52 || ^9.6.34", + "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.11.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": "2026-06-02T12:40:51+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "2.5.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "4360e982f87f5f258bf872d094647791db2f4c8e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/4360e982f87f5f258bf872d094647791db2f4c8e", + "reference": "4360e982f87f5f258bf872d094647791db2f4c8e", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "symfony/deprecation-contracts": "^2.5 || ^3.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.52 || ^9.6.34" + }, + "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.5.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": "2026-06-02T12:23:43+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "2.11.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "bbb5e61349fa5cb822b3e87842b951088b76b81f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/bbb5e61349fa5cb822b3e87842b951088b76b81f", + "reference": "bbb5e61349fa5cb822b3e87842b951088b76b81f", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.1 || ^2.0", + "ralouphie/getallheaders": "^3.0", + "symfony/deprecation-contracts": "^2.5 || ^3.0", + "symfony/polyfill-php80": "^1.24" + }, + "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": "1.1.0", + "jshttp/mime-db": "1.54.0.1", + "phpunit/phpunit": "^8.5.52 || ^9.6.34" + }, + "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.11.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-06-02T12:30:48+00:00" + }, + { + "name": "guzzlehttp/uri-template", + "version": "v1.0.6", + "source": { + "type": "git", + "url": "https://github.com/guzzle/uri-template.git", + "reference": "eef7f87bab6f204eba3c39224d8075c70c637946" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/uri-template/zipball/eef7f87bab6f204eba3c39224d8075c70c637946", + "reference": "eef7f87bab6f204eba3c39224d8075c70c637946", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "symfony/polyfill-php80": "^1.24" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.52 || ^9.6.34", + "uri-template/tests": "1.0.0" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\UriTemplate\\": "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" + } + ], + "description": "A polyfill class for uri_template of PHP", + "keywords": [ + "guzzlehttp", + "uri-template" + ], + "support": { + "issues": "https://github.com/guzzle/uri-template/issues", + "source": "https://github.com/guzzle/uri-template/tree/v1.0.6" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/uri-template", + "type": "tidelift" + } + ], + "time": "2026-05-23T22:00:21+00:00" + }, + { + "name": "laravel/framework", + "version": "10.50.2", + "source": { + "type": "git", + "url": "https://github.com/laravel/framework.git", + "reference": "3ff39b7a9b83e633383ec9b019827ed54b6d38bc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/framework/zipball/3ff39b7a9b83e633383ec9b019827ed54b6d38bc", + "reference": "3ff39b7a9b83e633383ec9b019827ed54b6d38bc", + "shasum": "" + }, + "require": { + "brick/math": "^0.9.3|^0.10.2|^0.11|^0.12", + "composer-runtime-api": "^2.2", + "doctrine/inflector": "^2.0.5", + "dragonmantank/cron-expression": "^3.3.2", + "egulias/email-validator": "^3.2.1|^4.0", + "ext-ctype": "*", + "ext-filter": "*", + "ext-hash": "*", + "ext-mbstring": "*", + "ext-openssl": "*", + "ext-session": "*", + "ext-tokenizer": "*", + "fruitcake/php-cors": "^1.2", + "guzzlehttp/uri-template": "^1.0", + "laravel/prompts": "^0.1.9", + "laravel/serializable-closure": "^1.3", + "league/commonmark": "^2.2.1", + "league/flysystem": "^3.8.0", + "monolog/monolog": "^3.0", + "nesbot/carbon": "^2.67", + "nunomaduro/termwind": "^1.13", + "php": "^8.1", + "psr/container": "^1.1.1|^2.0.1", + "psr/log": "^1.0|^2.0|^3.0", + "psr/simple-cache": "^1.0|^2.0|^3.0", + "ramsey/uuid": "^4.7", + "symfony/console": "^6.2", + "symfony/error-handler": "^6.2", + "symfony/finder": "^6.2", + "symfony/http-foundation": "^6.4", + "symfony/http-kernel": "^6.2", + "symfony/mailer": "^6.2", + "symfony/mime": "^6.2", + "symfony/process": "^6.2", + "symfony/routing": "^6.2", + "symfony/uid": "^6.2", + "symfony/var-dumper": "^6.2", + "tijsverkoyen/css-to-inline-styles": "^2.2.5", + "vlucas/phpdotenv": "^5.4.1", + "voku/portable-ascii": "^2.0" + }, + "conflict": { + "carbonphp/carbon-doctrine-types": ">=3.0", + "doctrine/dbal": ">=4.0", + "mockery/mockery": "1.6.8", + "phpunit/phpunit": ">=11.0.0", + "tightenco/collect": "<5.5.33" + }, + "provide": { + "psr/container-implementation": "1.1|2.0", + "psr/simple-cache-implementation": "1.0|2.0|3.0" + }, + "replace": { + "illuminate/auth": "self.version", + "illuminate/broadcasting": "self.version", + "illuminate/bus": "self.version", + "illuminate/cache": "self.version", + "illuminate/collections": "self.version", + "illuminate/conditionable": "self.version", + "illuminate/config": "self.version", + "illuminate/console": "self.version", + "illuminate/container": "self.version", + "illuminate/contracts": "self.version", + "illuminate/cookie": "self.version", + "illuminate/database": "self.version", + "illuminate/encryption": "self.version", + "illuminate/events": "self.version", + "illuminate/filesystem": "self.version", + "illuminate/hashing": "self.version", + "illuminate/http": "self.version", + "illuminate/log": "self.version", + "illuminate/macroable": "self.version", + "illuminate/mail": "self.version", + "illuminate/notifications": "self.version", + "illuminate/pagination": "self.version", + "illuminate/pipeline": "self.version", + "illuminate/process": "self.version", + "illuminate/queue": "self.version", + "illuminate/redis": "self.version", + "illuminate/routing": "self.version", + "illuminate/session": "self.version", + "illuminate/support": "self.version", + "illuminate/testing": "self.version", + "illuminate/translation": "self.version", + "illuminate/validation": "self.version", + "illuminate/view": "self.version" + }, + "require-dev": { + "ably/ably-php": "^1.0", + "aws/aws-sdk-php": "^3.235.5", + "doctrine/dbal": "^3.5.1", + "ext-gmp": "*", + "fakerphp/faker": "^1.21", + "guzzlehttp/guzzle": "^7.5", + "league/flysystem-aws-s3-v3": "^3.0", + "league/flysystem-ftp": "^3.0", + "league/flysystem-path-prefixing": "^3.3", + "league/flysystem-read-only": "^3.3", + "league/flysystem-sftp-v3": "^3.0", + "mockery/mockery": "^1.5.1", + "nyholm/psr7": "^1.2", + "orchestra/testbench-core": "^8.23.4", + "pda/pheanstalk": "^4.0", + "phpstan/phpstan": "~1.11.11", + "phpunit/phpunit": "^10.0.7", + "predis/predis": "^2.0.2", + "symfony/cache": "^6.2", + "symfony/http-client": "^6.2.4", + "symfony/psr-http-message-bridge": "^2.0" + }, + "suggest": { + "ably/ably-php": "Required to use the Ably broadcast driver (^1.0).", + "aws/aws-sdk-php": "Required to use the SQS queue driver, DynamoDb failed job storage, and SES mail driver (^3.235.5).", + "brianium/paratest": "Required to run tests in parallel (^6.0).", + "doctrine/dbal": "Required to rename columns and drop SQLite columns (^3.5.1).", + "ext-apcu": "Required to use the APC cache driver.", + "ext-fileinfo": "Required to use the Filesystem class.", + "ext-ftp": "Required to use the Flysystem FTP driver.", + "ext-gd": "Required to use Illuminate\\Http\\Testing\\FileFactory::image().", + "ext-memcached": "Required to use the memcache cache driver.", + "ext-pcntl": "Required to use all features of the queue worker and console signal trapping.", + "ext-pdo": "Required to use all database features.", + "ext-posix": "Required to use all features of the queue worker.", + "ext-redis": "Required to use the Redis cache and queue drivers (^4.0|^5.0).", + "fakerphp/faker": "Required to use the eloquent factory builder (^1.9.1).", + "filp/whoops": "Required for friendly error pages in development (^2.14.3).", + "guzzlehttp/guzzle": "Required to use the HTTP Client and the ping methods on schedules (^7.5).", + "laravel/tinker": "Required to use the tinker console command (^2.0).", + "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (^3.0).", + "league/flysystem-ftp": "Required to use the Flysystem FTP driver (^3.0).", + "league/flysystem-path-prefixing": "Required to use the scoped driver (^3.3).", + "league/flysystem-read-only": "Required to use read-only disks (^3.3)", + "league/flysystem-sftp-v3": "Required to use the Flysystem SFTP driver (^3.0).", + "mockery/mockery": "Required to use mocking (^1.5.1).", + "nyholm/psr7": "Required to use PSR-7 bridging features (^1.2).", + "pda/pheanstalk": "Required to use the beanstalk queue driver (^4.0).", + "phpunit/phpunit": "Required to use assertions and run tests (^9.5.8|^10.0.7).", + "predis/predis": "Required to use the predis connector (^2.0.2).", + "psr/http-message": "Required to allow Storage::put to accept a StreamInterface (^1.0).", + "pusher/pusher-php-server": "Required to use the Pusher broadcast driver (^6.0|^7.0).", + "symfony/cache": "Required to PSR-6 cache bridge (^6.2).", + "symfony/filesystem": "Required to enable support for relative symbolic links (^6.2).", + "symfony/http-client": "Required to enable support for the Symfony API mail transports (^6.2).", + "symfony/mailgun-mailer": "Required to enable support for the Mailgun mail transport (^6.2).", + "symfony/postmark-mailer": "Required to enable support for the Postmark mail transport (^6.2).", + "symfony/psr-http-message-bridge": "Required to use PSR-7 bridging features (^2.0)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "10.x-dev" + } + }, + "autoload": { + "files": [ + "src/Illuminate/Collections/functions.php", + "src/Illuminate/Collections/helpers.php", + "src/Illuminate/Events/functions.php", + "src/Illuminate/Filesystem/functions.php", + "src/Illuminate/Foundation/helpers.php", + "src/Illuminate/Support/helpers.php" + ], + "psr-4": { + "Illuminate\\": "src/Illuminate/", + "Illuminate\\Support\\": [ + "src/Illuminate/Macroable/", + "src/Illuminate/Collections/", + "src/Illuminate/Conditionable/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Laravel Framework.", + "homepage": "https://laravel.com", + "keywords": [ + "framework", + "laravel" + ], + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2026-02-15T14:12:07+00:00" + }, + { + "name": "laravel/prompts", + "version": "v0.1.25", + "source": { + "type": "git", + "url": "https://github.com/laravel/prompts.git", + "reference": "7b4029a84c37cb2725fc7f011586e2997040bc95" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/prompts/zipball/7b4029a84c37cb2725fc7f011586e2997040bc95", + "reference": "7b4029a84c37cb2725fc7f011586e2997040bc95", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "illuminate/collections": "^10.0|^11.0", + "php": "^8.1", + "symfony/console": "^6.2|^7.0" + }, + "conflict": { + "illuminate/console": ">=10.17.0 <10.25.0", + "laravel/framework": ">=10.17.0 <10.25.0" + }, + "require-dev": { + "mockery/mockery": "^1.5", + "pestphp/pest": "^2.3", + "phpstan/phpstan": "^1.11", + "phpstan/phpstan-mockery": "^1.1" + }, + "suggest": { + "ext-pcntl": "Required for the spinner to be animated." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "0.1.x-dev" + } + }, + "autoload": { + "files": [ + "src/helpers.php" + ], + "psr-4": { + "Laravel\\Prompts\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Add beautiful and user-friendly forms to your command-line applications.", + "support": { + "issues": "https://github.com/laravel/prompts/issues", + "source": "https://github.com/laravel/prompts/tree/v0.1.25" + }, + "time": "2024-08-12T22:06:33+00:00" + }, + { + "name": "laravel/sanctum", + "version": "v3.3.3", + "source": { + "type": "git", + "url": "https://github.com/laravel/sanctum.git", + "reference": "8c104366459739f3ada0e994bcd3e6fd681ce3d5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/sanctum/zipball/8c104366459739f3ada0e994bcd3e6fd681ce3d5", + "reference": "8c104366459739f3ada0e994bcd3e6fd681ce3d5", + "shasum": "" + }, + "require": { + "ext-json": "*", + "illuminate/console": "^9.21|^10.0", + "illuminate/contracts": "^9.21|^10.0", + "illuminate/database": "^9.21|^10.0", + "illuminate/support": "^9.21|^10.0", + "php": "^8.0.2" + }, + "require-dev": { + "mockery/mockery": "^1.0", + "orchestra/testbench": "^7.28.2|^8.8.3", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^9.6" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Laravel\\Sanctum\\SanctumServiceProvider" + ] + }, + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Laravel\\Sanctum\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Laravel Sanctum provides a featherweight authentication system for SPAs and simple APIs.", + "keywords": [ + "auth", + "laravel", + "sanctum" + ], + "support": { + "issues": "https://github.com/laravel/sanctum/issues", + "source": "https://github.com/laravel/sanctum" + }, + "time": "2023-12-19T18:44:48+00:00" + }, + { + "name": "laravel/serializable-closure", + "version": "v1.3.7", + "source": { + "type": "git", + "url": "https://github.com/laravel/serializable-closure.git", + "reference": "4f48ade902b94323ca3be7646db16209ec76be3d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/4f48ade902b94323ca3be7646db16209ec76be3d", + "reference": "4f48ade902b94323ca3be7646db16209ec76be3d", + "shasum": "" + }, + "require": { + "php": "^7.3|^8.0" + }, + "require-dev": { + "illuminate/support": "^8.0|^9.0|^10.0|^11.0", + "nesbot/carbon": "^2.61|^3.0", + "pestphp/pest": "^1.21.3", + "phpstan/phpstan": "^1.8.2", + "symfony/var-dumper": "^5.4.11|^6.2.0|^7.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Laravel\\SerializableClosure\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + }, + { + "name": "Nuno Maduro", + "email": "nuno@laravel.com" + } + ], + "description": "Laravel Serializable Closure provides an easy and secure way to serialize closures in PHP.", + "keywords": [ + "closure", + "laravel", + "serializable" + ], + "support": { + "issues": "https://github.com/laravel/serializable-closure/issues", + "source": "https://github.com/laravel/serializable-closure" + }, + "time": "2024-11-14T18:34:49+00:00" + }, + { + "name": "laravel/tinker", + "version": "v2.11.1", + "source": { + "type": "git", + "url": "https://github.com/laravel/tinker.git", + "reference": "c9f80cc835649b5c1842898fb043f8cc098dd741" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/tinker/zipball/c9f80cc835649b5c1842898fb043f8cc098dd741", + "reference": "c9f80cc835649b5c1842898fb043f8cc098dd741", + "shasum": "" + }, + "require": { + "illuminate/console": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0", + "illuminate/contracts": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0", + "illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0", + "php": "^7.2.5|^8.0", + "psy/psysh": "^0.11.1|^0.12.0", + "symfony/var-dumper": "^4.3.4|^5.0|^6.0|^7.0|^8.0" + }, + "require-dev": { + "mockery/mockery": "~1.3.3|^1.4.2", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^8.5.8|^9.3.3|^10.0" + }, + "suggest": { + "illuminate/database": "The Illuminate Database package (^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0)." + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Laravel\\Tinker\\TinkerServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Laravel\\Tinker\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Powerful REPL for the Laravel framework.", + "keywords": [ + "REPL", + "Tinker", + "laravel", + "psysh" + ], + "support": { + "issues": "https://github.com/laravel/tinker/issues", + "source": "https://github.com/laravel/tinker/tree/v2.11.1" + }, + "time": "2026-02-06T14:12:35+00:00" + }, + { + "name": "laravel/ui", + "version": "v4.6.3", + "source": { + "type": "git", + "url": "https://github.com/laravel/ui.git", + "reference": "ff27db15416c1ed8ad9848f5692e47595dd5de27" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/ui/zipball/ff27db15416c1ed8ad9848f5692e47595dd5de27", + "reference": "ff27db15416c1ed8ad9848f5692e47595dd5de27", + "shasum": "" + }, + "require": { + "illuminate/console": "^9.21|^10.0|^11.0|^12.0|^13.0", + "illuminate/filesystem": "^9.21|^10.0|^11.0|^12.0|^13.0", + "illuminate/support": "^9.21|^10.0|^11.0|^12.0|^13.0", + "illuminate/validation": "^9.21|^10.0|^11.0|^12.0|^13.0", + "php": "^8.0", + "symfony/console": "^6.0|^7.0|^8.0" + }, + "require-dev": { + "orchestra/testbench": "^7.35|^8.15|^9.0|^10.0|^11.0", + "phpunit/phpunit": "^9.3|^10.4|^11.5|^12.5|^13.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Laravel\\Ui\\UiServiceProvider" + ] + }, + "branch-alias": { + "dev-master": "4.x-dev" + } + }, + "autoload": { + "psr-4": { + "Laravel\\Ui\\": "src/", + "Illuminate\\Foundation\\Auth\\": "auth-backend/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Laravel UI utilities and presets.", + "keywords": [ + "laravel", + "ui" + ], + "support": { + "source": "https://github.com/laravel/ui/tree/v4.6.3" + }, + "time": "2026-03-17T13:41:52+00:00" + }, + { + "name": "league/commonmark", + "version": "2.8.2", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/commonmark.git", + "reference": "59fb075d2101740c337c7216e3f32b36c204218b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/59fb075d2101740c337c7216e3f32b36c204218b", + "reference": "59fb075d2101740c337c7216e3f32b36c204218b", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "league/config": "^1.1.1", + "php": "^7.4 || ^8.0", + "psr/event-dispatcher": "^1.0", + "symfony/deprecation-contracts": "^2.1 || ^3.0", + "symfony/polyfill-php80": "^1.16" + }, + "require-dev": { + "cebe/markdown": "^1.0", + "commonmark/cmark": "0.31.1", + "commonmark/commonmark.js": "0.31.1", + "composer/package-versions-deprecated": "^1.8", + "embed/embed": "^4.4", + "erusev/parsedown": "^1.0", + "ext-json": "*", + "github/gfm": "0.29.0", + "michelf/php-markdown": "^1.4 || ^2.0", + "nyholm/psr7": "^1.5", + "phpstan/phpstan": "^1.8.2", + "phpunit/phpunit": "^9.5.21 || ^10.5.9 || ^11.0.0", + "scrutinizer/ocular": "^1.8.1", + "symfony/finder": "^5.3 | ^6.0 | ^7.0 || ^8.0", + "symfony/process": "^5.4 | ^6.0 | ^7.0 || ^8.0", + "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0 | ^7.0 || ^8.0", + "unleashedtech/php-coding-standard": "^3.1.1", + "vimeo/psalm": "^4.24.0 || ^5.0.0 || ^6.0.0" + }, + "suggest": { + "symfony/yaml": "v2.3+ required if using the Front Matter extension" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.9-dev" + } + }, + "autoload": { + "psr-4": { + "League\\CommonMark\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com", + "role": "Lead Developer" + } + ], + "description": "Highly-extensible PHP Markdown parser which fully supports the CommonMark spec and GitHub-Flavored Markdown (GFM)", + "homepage": "https://commonmark.thephpleague.com", + "keywords": [ + "commonmark", + "flavored", + "gfm", + "github", + "github-flavored", + "markdown", + "md", + "parser" + ], + "support": { + "docs": "https://commonmark.thephpleague.com/", + "forum": "https://github.com/thephpleague/commonmark/discussions", + "issues": "https://github.com/thephpleague/commonmark/issues", + "rss": "https://github.com/thephpleague/commonmark/releases.atom", + "source": "https://github.com/thephpleague/commonmark" + }, + "funding": [ + { + "url": "https://www.colinodell.com/sponsor", + "type": "custom" + }, + { + "url": "https://www.paypal.me/colinpodell/10.00", + "type": "custom" + }, + { + "url": "https://github.com/colinodell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/league/commonmark", + "type": "tidelift" + } + ], + "time": "2026-03-19T13:16:38+00:00" + }, + { + "name": "league/config", + "version": "v1.2.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/config.git", + "reference": "754b3604fb2984c71f4af4a9cbe7b57f346ec1f3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/config/zipball/754b3604fb2984c71f4af4a9cbe7b57f346ec1f3", + "reference": "754b3604fb2984c71f4af4a9cbe7b57f346ec1f3", + "shasum": "" + }, + "require": { + "dflydev/dot-access-data": "^3.0.1", + "nette/schema": "^1.2", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.8.2", + "phpunit/phpunit": "^9.5.5", + "scrutinizer/ocular": "^1.8.1", + "unleashedtech/php-coding-standard": "^3.1", + "vimeo/psalm": "^4.7.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.2-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Config\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com", + "role": "Lead Developer" + } + ], + "description": "Define configuration arrays with strict schemas and access values with dot notation", + "homepage": "https://config.thephpleague.com", + "keywords": [ + "array", + "config", + "configuration", + "dot", + "dot-access", + "nested", + "schema" + ], + "support": { + "docs": "https://config.thephpleague.com/", + "issues": "https://github.com/thephpleague/config/issues", + "rss": "https://github.com/thephpleague/config/releases.atom", + "source": "https://github.com/thephpleague/config" + }, + "funding": [ + { + "url": "https://www.colinodell.com/sponsor", + "type": "custom" + }, + { + "url": "https://www.paypal.me/colinpodell/10.00", + "type": "custom" + }, + { + "url": "https://github.com/colinodell", + "type": "github" + } + ], + "time": "2022-12-11T20:36:23+00:00" + }, + { + "name": "league/flysystem", + "version": "3.34.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem.git", + "reference": "2daaac3b0d4c83ea7ed5d8586e786f5d00f3540e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/2daaac3b0d4c83ea7ed5d8586e786f5d00f3540e", + "reference": "2daaac3b0d4c83ea7ed5d8586e786f5d00f3540e", + "shasum": "" + }, + "require": { + "league/flysystem-local": "^3.0.0", + "league/mime-type-detection": "^1.0.0", + "php": "^8.0.2" + }, + "conflict": { + "async-aws/core": "<1.19.0", + "async-aws/s3": "<1.14.0", + "aws/aws-sdk-php": "3.209.31 || 3.210.0", + "guzzlehttp/guzzle": "<7.0", + "guzzlehttp/ringphp": "<1.1.1", + "phpseclib/phpseclib": "3.0.15", + "symfony/http-client": "<5.2" + }, + "require-dev": { + "async-aws/s3": "^1.5 || ^2.0", + "async-aws/simple-s3": "^1.1 || ^2.0", + "aws/aws-sdk-php": "^3.295.10", + "composer/semver": "^3.0", + "ext-fileinfo": "*", + "ext-ftp": "*", + "ext-mongodb": "^1.3|^2", + "ext-zip": "*", + "friendsofphp/php-cs-fixer": "^3.5", + "google/cloud-storage": "^1.23", + "guzzlehttp/psr7": "^2.6", + "microsoft/azure-storage-blob": "^1.1", + "mongodb/mongodb": "^1.2|^2", + "phpseclib/phpseclib": "^3.0.36", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^9.5.11|^10.0", + "sabre/dav": "^4.6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\Flysystem\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "File storage abstraction for PHP", + "keywords": [ + "WebDAV", + "aws", + "cloud", + "file", + "files", + "filesystem", + "filesystems", + "ftp", + "s3", + "sftp", + "storage" + ], + "support": { + "issues": "https://github.com/thephpleague/flysystem/issues", + "source": "https://github.com/thephpleague/flysystem/tree/3.34.0" + }, + "time": "2026-05-14T10:28:08+00:00" + }, + { + "name": "league/flysystem-local", + "version": "3.31.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem-local.git", + "reference": "2f669db18a4c20c755c2bb7d3a7b0b2340488079" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/2f669db18a4c20c755c2bb7d3a7b0b2340488079", + "reference": "2f669db18a4c20c755c2bb7d3a7b0b2340488079", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "league/flysystem": "^3.0.0", + "league/mime-type-detection": "^1.0.0", + "php": "^8.0.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\Flysystem\\Local\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "Local filesystem adapter for Flysystem.", + "keywords": [ + "Flysystem", + "file", + "files", + "filesystem", + "local" + ], + "support": { + "source": "https://github.com/thephpleague/flysystem-local/tree/3.31.0" + }, + "time": "2026-01-23T15:30:45+00:00" + }, + { + "name": "league/mime-type-detection", + "version": "1.16.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/mime-type-detection.git", + "reference": "2d6702ff215bf922936ccc1ad31007edc76451b9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/2d6702ff215bf922936ccc1ad31007edc76451b9", + "reference": "2d6702ff215bf922936ccc1ad31007edc76451b9", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.2", + "phpstan/phpstan": "^0.12.68", + "phpunit/phpunit": "^8.5.8 || ^9.3 || ^10.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\MimeTypeDetection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "Mime-type detection for Flysystem", + "support": { + "issues": "https://github.com/thephpleague/mime-type-detection/issues", + "source": "https://github.com/thephpleague/mime-type-detection/tree/1.16.0" + }, + "funding": [ + { + "url": "https://github.com/frankdejonge", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/league/flysystem", + "type": "tidelift" + } + ], + "time": "2024-09-21T08:32:55+00:00" + }, + { + "name": "masterminds/html5", + "version": "2.10.0", + "source": { + "type": "git", + "url": "https://github.com/Masterminds/html5-php.git", + "reference": "fcf91eb64359852f00d921887b219479b4f21251" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/fcf91eb64359852f00d921887b219479b4f21251", + "reference": "fcf91eb64359852f00d921887b219479b4f21251", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7 || ^8 || ^9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Masterminds\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matt Butcher", + "email": "technosophos@gmail.com" + }, + { + "name": "Matt Farina", + "email": "matt@mattfarina.com" + }, + { + "name": "Asmir Mustafic", + "email": "goetas@gmail.com" + } + ], + "description": "An HTML5 parser and serializer.", + "homepage": "http://masterminds.github.io/html5-php", + "keywords": [ + "HTML5", + "dom", + "html", + "parser", + "querypath", + "serializer", + "xml" + ], + "support": { + "issues": "https://github.com/Masterminds/html5-php/issues", + "source": "https://github.com/Masterminds/html5-php/tree/2.10.0" + }, + "time": "2025-07-25T09:04:22+00:00" + }, + { + "name": "monolog/monolog", + "version": "3.10.0", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/monolog.git", + "reference": "b321dd6749f0bf7189444158a3ce785cc16d69b0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/b321dd6749f0bf7189444158a3ce785cc16d69b0", + "reference": "b321dd6749f0bf7189444158a3ce785cc16d69b0", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/log": "^2.0 || ^3.0" + }, + "provide": { + "psr/log-implementation": "3.0.0" + }, + "require-dev": { + "aws/aws-sdk-php": "^3.0", + "doctrine/couchdb": "~1.0@dev", + "elasticsearch/elasticsearch": "^7 || ^8", + "ext-json": "*", + "graylog2/gelf-php": "^1.4.2 || ^2.0", + "guzzlehttp/guzzle": "^7.4.5", + "guzzlehttp/psr7": "^2.2", + "mongodb/mongodb": "^1.8 || ^2.0", + "php-amqplib/php-amqplib": "~2.4 || ^3", + "php-console/php-console": "^3.1.8", + "phpstan/phpstan": "^2", + "phpstan/phpstan-deprecation-rules": "^2", + "phpstan/phpstan-strict-rules": "^2", + "phpunit/phpunit": "^10.5.17 || ^11.0.7", + "predis/predis": "^1.1 || ^2", + "rollbar/rollbar": "^4.0", + "ruflin/elastica": "^7 || ^8", + "symfony/mailer": "^5.4 || ^6", + "symfony/mime": "^5.4 || ^6" + }, + "suggest": { + "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-curl": "Required to send log messages using the IFTTTHandler, the LogglyHandler, the SendGridHandler, the SlackWebhookHandler or the TelegramBotHandler", + "ext-mbstring": "Allow to work properly with unicode symbols", + "ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)", + "ext-openssl": "Required to send log messages using SSL", + "ext-sockets": "Allow sending log messages to a Syslog server (via UDP driver)", + "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)", + "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", + "rollbar/rollbar": "Allow sending log messages to Rollbar", + "ruflin/elastica": "Allow sending log messages to an Elastic Search server" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Monolog\\": "src/Monolog" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" + } + ], + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "homepage": "https://github.com/Seldaek/monolog", + "keywords": [ + "log", + "logging", + "psr-3" + ], + "support": { + "issues": "https://github.com/Seldaek/monolog/issues", + "source": "https://github.com/Seldaek/monolog/tree/3.10.0" + }, + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/monolog/monolog", + "type": "tidelift" + } + ], + "time": "2026-01-02T08:56:05+00:00" + }, + { + "name": "nesbot/carbon", + "version": "2.73.0", + "source": { + "type": "git", + "url": "https://github.com/CarbonPHP/carbon.git", + "reference": "9228ce90e1035ff2f0db84b40ec2e023ed802075" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/9228ce90e1035ff2f0db84b40ec2e023ed802075", + "reference": "9228ce90e1035ff2f0db84b40ec2e023ed802075", + "shasum": "" + }, + "require": { + "carbonphp/carbon-doctrine-types": "*", + "ext-json": "*", + "php": "^7.1.8 || ^8.0", + "psr/clock": "^1.0", + "symfony/polyfill-mbstring": "^1.0", + "symfony/polyfill-php80": "^1.16", + "symfony/translation": "^3.4 || ^4.0 || ^5.0 || ^6.0" + }, + "provide": { + "psr/clock-implementation": "1.0" + }, + "require-dev": { + "doctrine/dbal": "^2.0 || ^3.1.4 || ^4.0", + "doctrine/orm": "^2.7 || ^3.0", + "friendsofphp/php-cs-fixer": "^3.0", + "kylekatarnls/multi-tester": "^2.0", + "ondrejmirtes/better-reflection": "<6", + "phpmd/phpmd": "^2.9", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^0.12.99 || ^1.7.14", + "phpunit/php-file-iterator": "^2.0.5 || ^3.0.6", + "phpunit/phpunit": "^7.5.20 || ^8.5.26 || ^9.5.20", + "squizlabs/php_codesniffer": "^3.4" + }, + "bin": [ + "bin/carbon" + ], + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Carbon\\Laravel\\ServiceProvider" + ] + }, + "phpstan": { + "includes": [ + "extension.neon" + ] + }, + "branch-alias": { + "dev-2.x": "2.x-dev", + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Carbon\\": "src/Carbon/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Brian Nesbitt", + "email": "brian@nesbot.com", + "homepage": "https://markido.com" + }, + { + "name": "kylekatarnls", + "homepage": "https://github.com/kylekatarnls" + } + ], + "description": "An API extension for DateTime that supports 281 different languages.", + "homepage": "https://carbon.nesbot.com", + "keywords": [ + "date", + "datetime", + "time" + ], + "support": { + "docs": "https://carbon.nesbot.com/docs", + "issues": "https://github.com/briannesbitt/Carbon/issues", + "source": "https://github.com/briannesbitt/Carbon" + }, + "funding": [ + { + "url": "https://github.com/sponsors/kylekatarnls", + "type": "github" + }, + { + "url": "https://opencollective.com/Carbon#sponsor", + "type": "opencollective" + }, + { + "url": "https://tidelift.com/subscription/pkg/packagist-nesbot-carbon?utm_source=packagist-nesbot-carbon&utm_medium=referral&utm_campaign=readme", + "type": "tidelift" + } + ], + "time": "2025-01-08T20:10:23+00:00" + }, + { + "name": "nette/schema", + "version": "v1.3.5", + "source": { + "type": "git", + "url": "https://github.com/nette/schema.git", + "reference": "f0ab1a3cda782dbc5da270d28545236aa80c4002" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/schema/zipball/f0ab1a3cda782dbc5da270d28545236aa80c4002", + "reference": "f0ab1a3cda782dbc5da270d28545236aa80c4002", + "shasum": "" + }, + "require": { + "nette/utils": "^4.0", + "php": "8.1 - 8.5" + }, + "require-dev": { + "nette/phpstan-rules": "^1.0", + "nette/tester": "^2.6", + "phpstan/extension-installer": "^1.4@stable", + "phpstan/phpstan": "^2.1.39@stable", + "tracy/tracy": "^2.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "autoload": { + "psr-4": { + "Nette\\": "src" + }, + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "📐 Nette Schema: validating data structures against a given Schema.", + "homepage": "https://nette.org", + "keywords": [ + "config", + "nette" + ], + "support": { + "issues": "https://github.com/nette/schema/issues", + "source": "https://github.com/nette/schema/tree/v1.3.5" + }, + "time": "2026-02-23T03:47:12+00:00" + }, + { + "name": "nette/utils", + "version": "v4.1.4", + "source": { + "type": "git", + "url": "https://github.com/nette/utils.git", + "reference": "7da6c396d7ebe142bc857c20479d5e70a5e1aac7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/utils/zipball/7da6c396d7ebe142bc857c20479d5e70a5e1aac7", + "reference": "7da6c396d7ebe142bc857c20479d5e70a5e1aac7", + "shasum": "" + }, + "require": { + "php": "8.2 - 8.5" + }, + "conflict": { + "nette/finder": "<3", + "nette/schema": "<1.2.2" + }, + "require-dev": { + "jetbrains/phpstorm-attributes": "^1.2", + "nette/phpstan-rules": "^1.0", + "nette/tester": "^2.5", + "phpstan/extension-installer": "^1.4@stable", + "phpstan/phpstan": "^2.1@stable", + "tracy/tracy": "^2.9" + }, + "suggest": { + "ext-gd": "to use Image", + "ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()", + "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()", + "ext-json": "to use Nette\\Utils\\Json", + "ext-mbstring": "to use Strings::lower() etc...", + "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.1-dev" + } + }, + "autoload": { + "psr-4": { + "Nette\\": "src" + }, + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.", + "homepage": "https://nette.org", + "keywords": [ + "array", + "core", + "datetime", + "images", + "json", + "nette", + "paginator", + "password", + "slugify", + "string", + "unicode", + "utf-8", + "utility", + "validation" + ], + "support": { + "issues": "https://github.com/nette/utils/issues", + "source": "https://github.com/nette/utils/tree/v4.1.4" + }, + "time": "2026-05-11T20:49:54+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v5.7.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82", + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-json": "*", + "ext-tokenizer": "*", + "php": ">=7.4" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v5.7.0" + }, + "time": "2025-12-06T11:56:16+00:00" + }, + { + "name": "nunomaduro/termwind", + "version": "v1.17.0", + "source": { + "type": "git", + "url": "https://github.com/nunomaduro/termwind.git", + "reference": "5369ef84d8142c1d87e4ec278711d4ece3cbf301" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/5369ef84d8142c1d87e4ec278711d4ece3cbf301", + "reference": "5369ef84d8142c1d87e4ec278711d4ece3cbf301", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": "^8.1", + "symfony/console": "^6.4.15" + }, + "require-dev": { + "illuminate/console": "^10.48.24", + "illuminate/support": "^10.48.24", + "laravel/pint": "^1.18.2", + "pestphp/pest": "^2.36.0", + "pestphp/pest-plugin-mock": "2.0.0", + "phpstan/phpstan": "^1.12.11", + "phpstan/phpstan-strict-rules": "^1.6.1", + "symfony/var-dumper": "^6.4.15", + "thecodingmachine/phpstan-strict-rules": "^1.0.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Termwind\\Laravel\\TermwindServiceProvider" + ] + } + }, + "autoload": { + "files": [ + "src/Functions.php" + ], + "psr-4": { + "Termwind\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" + } + ], + "description": "Its like Tailwind CSS, but for the console.", + "keywords": [ + "cli", + "console", + "css", + "package", + "php", + "style" + ], + "support": { + "issues": "https://github.com/nunomaduro/termwind/issues", + "source": "https://github.com/nunomaduro/termwind/tree/v1.17.0" + }, + "funding": [ + { + "url": "https://www.paypal.com/paypalme/enunomaduro", + "type": "custom" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + }, + { + "url": "https://github.com/xiCO2k", + "type": "github" + } + ], + "time": "2024-11-21T10:36:35+00:00" + }, + { + "name": "phpoption/phpoption", + "version": "1.9.5", + "source": { + "type": "git", + "url": "https://github.com/schmittjoh/php-option.git", + "reference": "75365b91986c2405cf5e1e012c5595cd487a98be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/75365b91986c2405cf5e1e012c5595cd487a98be", + "reference": "75365b91986c2405cf5e1e012c5595cd487a98be", + "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 || ^10.5.53 || ^11.5.34" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + }, + "branch-alias": { + "dev-master": "1.9-dev" + } + }, + "autoload": { + "psr-4": { + "PhpOption\\": "src/PhpOption/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Johannes M. Schmitt", + "email": "schmittjoh@gmail.com", + "homepage": "https://github.com/schmittjoh" + }, + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + } + ], + "description": "Option Type for PHP", + "keywords": [ + "language", + "option", + "php", + "type" + ], + "support": { + "issues": "https://github.com/schmittjoh/php-option/issues", + "source": "https://github.com/schmittjoh/php-option/tree/1.9.5" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpoption/phpoption", + "type": "tidelift" + } + ], + "time": "2025-12-27T19:41:33+00:00" + }, + { + "name": "picqer/php-barcode-generator", + "version": "v3.2.4", + "source": { + "type": "git", + "url": "https://github.com/picqer/php-barcode-generator.git", + "reference": "16a5cfb74eb8b7a0aa2e60427d98599aa8259380" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/picqer/php-barcode-generator/zipball/16a5cfb74eb8b7a0aa2e60427d98599aa8259380", + "reference": "16a5cfb74eb8b7a0aa2e60427d98599aa8259380", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": "^8.2" + }, + "require-dev": { + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^9.5" + }, + "suggest": { + "ext-bcmath": "Barcode IMB (Intelligent Mail Barcode) needs bcmath extension", + "ext-gd": "For JPG and PNG generators, GD or Imagick is required", + "ext-imagick": "For JPG and PNG generators, GD or Imagick is required" + }, + "type": "library", + "autoload": { + "psr-4": { + "Picqer\\Barcode\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Casper Bakker", + "email": "info@picqer.com", + "homepage": "https://picqer.com" + }, + { + "name": "Nicola Asuni", + "email": "info@tecnick.com", + "homepage": "http://nicolaasuni.tecnick.com" + } + ], + "description": "An easy to use, non-bloated, barcode generator in PHP. Creates SVG, PNG, JPG and HTML images from the most used 1D barcode standards.", + "homepage": "https://github.com/picqer/php-barcode-generator", + "keywords": [ + "CODABAR", + "Code11", + "Code93", + "EAN13", + "KIX", + "KIXCODE", + "MSI", + "POSTNET", + "Pharma", + "Standard 2 of 5", + "barcode", + "barcode generator", + "code128", + "code39", + "ean", + "html", + "jpeg", + "jpg", + "php", + "png", + "svg", + "upc" + ], + "support": { + "issues": "https://github.com/picqer/php-barcode-generator/issues", + "source": "https://github.com/picqer/php-barcode-generator/tree/v3.2.4" + }, + "time": "2026-01-08T08:57:40+00:00" + }, + { + "name": "psr/clock", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/clock.git", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/clock/zipball/e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Clock\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for reading the clock.", + "homepage": "https://github.com/php-fig/clock", + "keywords": [ + "clock", + "now", + "psr", + "psr-20", + "time" + ], + "support": { + "issues": "https://github.com/php-fig/clock/issues", + "source": "https://github.com/php-fig/clock/tree/1.0.0" + }, + "time": "2022-11-25T14:36:26+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/event-dispatcher", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Standard interfaces for event handling.", + "keywords": [ + "events", + "psr", + "psr-14" + ], + "support": { + "issues": "https://github.com/php-fig/event-dispatcher/issues", + "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + }, + "time": "2019-01-08T18:20:26+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": "psr/log", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.2" + }, + "time": "2024-09-11T13:17:53+00:00" + }, + { + "name": "psr/simple-cache", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/simple-cache.git", + "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/764e0b3939f5ca87cb904f570ef9be2d78a07865", + "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\SimpleCache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interfaces for simple caching", + "keywords": [ + "cache", + "caching", + "psr", + "psr-16", + "simple-cache" + ], + "support": { + "source": "https://github.com/php-fig/simple-cache/tree/3.0.0" + }, + "time": "2021-10-29T13:26:27+00:00" + }, + { + "name": "psy/psysh", + "version": "v0.12.23", + "source": { + "type": "git", + "url": "https://github.com/bobthecow/psysh.git", + "reference": "4dcc0f08047d52bbde475eda481146fd8e27e1a4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/4dcc0f08047d52bbde475eda481146fd8e27e1a4", + "reference": "4dcc0f08047d52bbde475eda481146fd8e27e1a4", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-tokenizer": "*", + "nikic/php-parser": "^5.0 || ^4.0", + "php": "^8.0 || ^7.4", + "symfony/console": "^8.0 || ^7.0 || ^6.0 || ^5.0 || ^4.0 || ^3.4", + "symfony/var-dumper": "^8.0 || ^7.0 || ^6.0 || ^5.0 || ^4.0 || ^3.4" + }, + "conflict": { + "symfony/console": "4.4.37 || 5.3.14 || 5.3.15 || 5.4.3 || 5.4.4 || 6.0.3 || 6.0.4" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.2", + "composer/class-map-generator": "^1.6" + }, + "suggest": { + "composer/class-map-generator": "Improved tab completion performance with better class discovery.", + "ext-pcntl": "Enabling the PCNTL extension makes PsySH a lot happier :)", + "ext-posix": "If you have PCNTL, you'll want the POSIX extension as well." + }, + "bin": [ + "bin/psysh" + ], + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": false, + "forward-command": false + }, + "branch-alias": { + "dev-main": "0.12.x-dev" + } + }, + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Psy\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Justin Hileman", + "email": "justin@justinhileman.info" + } + ], + "description": "An interactive shell for modern PHP.", + "homepage": "https://psysh.org", + "keywords": [ + "REPL", + "console", + "interactive", + "shell" + ], + "support": { + "issues": "https://github.com/bobthecow/psysh/issues", + "source": "https://github.com/bobthecow/psysh/tree/v0.12.23" + }, + "time": "2026-05-23T13:41:31+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": "ramsey/collection", + "version": "2.1.1", + "source": { + "type": "git", + "url": "https://github.com/ramsey/collection.git", + "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/collection/zipball/344572933ad0181accbf4ba763e85a0306a8c5e2", + "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "captainhook/plugin-composer": "^5.3", + "ergebnis/composer-normalize": "^2.45", + "fakerphp/faker": "^1.24", + "hamcrest/hamcrest-php": "^2.0", + "jangregor/phpstan-prophecy": "^2.1", + "mockery/mockery": "^1.6", + "php-parallel-lint/php-console-highlighter": "^1.0", + "php-parallel-lint/php-parallel-lint": "^1.4", + "phpspec/prophecy-phpunit": "^2.3", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-mockery": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^10.5", + "ramsey/coding-standard": "^2.3", + "ramsey/conventional-commits": "^1.6", + "roave/security-advisories": "dev-latest" + }, + "type": "library", + "extra": { + "captainhook": { + "force-install": true + }, + "ramsey/conventional-commits": { + "configFile": "conventional-commits.json" + } + }, + "autoload": { + "psr-4": { + "Ramsey\\Collection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ben Ramsey", + "email": "ben@benramsey.com", + "homepage": "https://benramsey.com" + } + ], + "description": "A PHP library for representing and manipulating collections.", + "keywords": [ + "array", + "collection", + "hash", + "map", + "queue", + "set" + ], + "support": { + "issues": "https://github.com/ramsey/collection/issues", + "source": "https://github.com/ramsey/collection/tree/2.1.1" + }, + "time": "2025-03-22T05:38:12+00:00" + }, + { + "name": "ramsey/uuid", + "version": "4.9.2", + "source": { + "type": "git", + "url": "https://github.com/ramsey/uuid.git", + "reference": "8429c78ca35a09f27565311b98101e2826affde0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/8429c78ca35a09f27565311b98101e2826affde0", + "reference": "8429c78ca35a09f27565311b98101e2826affde0", + "shasum": "" + }, + "require": { + "brick/math": "^0.8.16 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13 || ^0.14", + "php": "^8.0", + "ramsey/collection": "^1.2 || ^2.0" + }, + "replace": { + "rhumsaa/uuid": "self.version" + }, + "require-dev": { + "captainhook/captainhook": "^5.25", + "captainhook/plugin-composer": "^5.3", + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "ergebnis/composer-normalize": "^2.47", + "mockery/mockery": "^1.6", + "paragonie/random-lib": "^2", + "php-mock/php-mock": "^2.6", + "php-mock/php-mock-mockery": "^1.5", + "php-parallel-lint/php-parallel-lint": "^1.4.0", + "phpbench/phpbench": "^1.2.14", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-mockery": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^9.6", + "slevomat/coding-standard": "^8.18", + "squizlabs/php_codesniffer": "^3.13" + }, + "suggest": { + "ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.", + "ext-gmp": "Enables faster math with arbitrary-precision integers using GMP.", + "ext-uuid": "Enables the use of PeclUuidTimeGenerator and PeclUuidRandomGenerator.", + "paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter", + "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." + }, + "type": "library", + "extra": { + "captainhook": { + "force-install": true + } + }, + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Ramsey\\Uuid\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A PHP library for generating and working with universally unique identifiers (UUIDs).", + "keywords": [ + "guid", + "identifier", + "uuid" + ], + "support": { + "issues": "https://github.com/ramsey/uuid/issues", + "source": "https://github.com/ramsey/uuid/tree/4.9.2" + }, + "time": "2025-12-14T04:43:48+00:00" + }, + { + "name": "sabberworm/php-css-parser", + "version": "v9.3.0", + "source": { + "type": "git", + "url": "https://github.com/MyIntervals/PHP-CSS-Parser.git", + "reference": "88dbd0f7f91abbfe4402d0a3071e9ff4d81ed949" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/MyIntervals/PHP-CSS-Parser/zipball/88dbd0f7f91abbfe4402d0a3071e9ff4d81ed949", + "reference": "88dbd0f7f91abbfe4402d0a3071e9ff4d81ed949", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "php": "^7.2.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", + "thecodingmachine/safe": "^1.3 || ^2.5 || ^3.4" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "1.4.0", + "phpstan/extension-installer": "1.4.3", + "phpstan/phpstan": "1.12.32 || 2.1.32", + "phpstan/phpstan-phpunit": "1.4.2 || 2.0.8", + "phpstan/phpstan-strict-rules": "1.6.2 || 2.0.7", + "phpunit/phpunit": "8.5.52", + "rawr/phpunit-data-provider": "3.3.1", + "rector/rector": "1.2.10 || 2.2.8", + "rector/type-perfect": "1.0.0 || 2.1.0", + "squizlabs/php_codesniffer": "4.0.1", + "thecodingmachine/phpstan-safe-rule": "1.2.0 || 1.4.1" + }, + "suggest": { + "ext-mbstring": "for parsing UTF-8 CSS" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "9.4.x-dev" + } + }, + "autoload": { + "files": [ + "src/Rule/Rule.php", + "src/RuleSet/RuleContainer.php" + ], + "psr-4": { + "Sabberworm\\CSS\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Raphael Schweikert" + }, + { + "name": "Oliver Klee", + "email": "github@oliverklee.de" + }, + { + "name": "Jake Hotson", + "email": "jake.github@qzdesign.co.uk" + } + ], + "description": "Parser for CSS Files written in PHP", + "homepage": "https://www.sabberworm.com/blog/2010/6/10/php-css-parser", + "keywords": [ + "css", + "parser", + "stylesheet" + ], + "support": { + "issues": "https://github.com/MyIntervals/PHP-CSS-Parser/issues", + "source": "https://github.com/MyIntervals/PHP-CSS-Parser/tree/v9.3.0" + }, + "time": "2026-03-03T17:31:43+00:00" + }, + { + "name": "symfony/console", + "version": "v6.4.41", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "d21b17ed158e79180fac3895ff751707970eeb57" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/d21b17ed158e79180fac3895ff751707970eeb57", + "reference": "d21b17ed158e79180fac3895ff751707970eeb57", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^5.4|^6.0|^7.0" + }, + "conflict": { + "symfony/dependency-injection": "<5.4", + "symfony/dotenv": "<5.4", + "symfony/event-dispatcher": "<5.4", + "symfony/lock": "<5.4", + "symfony/process": "<5.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^5.4|^6.0|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/event-dispatcher": "^5.4|^6.0|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/lock": "^5.4|^6.0|^7.0", + "symfony/messenger": "^5.4|^6.0|^7.0", + "symfony/process": "^5.4|^6.0|^7.0", + "symfony/stopwatch": "^5.4|^6.0|^7.0", + "symfony/var-dumper": "^5.4|^6.0|^7.0" + }, + "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/v6.4.41" + }, + "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-05-24T08:48:41+00:00" + }, + { + "name": "symfony/css-selector", + "version": "v8.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/css-selector.git", + "reference": "dc0e2be45c9b5588c82414f02ac574b4b986abcd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/dc0e2be45c9b5588c82414f02ac574b4b986abcd", + "reference": "dc0e2be45c9b5588c82414f02ac574b4b986abcd", + "shasum": "" + }, + "require": { + "php": ">=8.4.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\CssSelector\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Jean-François Simon", + "email": "jeanfrancois.simon@sensiolabs.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Converts CSS selectors to XPath expressions", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/css-selector/tree/v8.1.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-05-29T05:06:50+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.7.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "50f59d1f3ca46d41ac911f97a78626b6756af35b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/50f59d1f3ca46d41ac911f97a78626b6756af35b", + "reference": "50f59d1f3ca46d41ac911f97a78626b6756af35b", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.7-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.7.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-13T15:52:40+00:00" + }, + { + "name": "symfony/error-handler", + "version": "v6.4.36", + "source": { + "type": "git", + "url": "https://github.com/symfony/error-handler.git", + "reference": "2ea68f0e1835ad6a126f93bbc14cd236c10ab361" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/2ea68f0e1835ad6a126f93bbc14cd236c10ab361", + "reference": "2ea68f0e1835ad6a126f93bbc14cd236c10ab361", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/log": "^1|^2|^3", + "symfony/var-dumper": "^5.4|^6.0|^7.0" + }, + "conflict": { + "symfony/deprecation-contracts": "<2.5", + "symfony/http-kernel": "<6.4" + }, + "require-dev": { + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/serializer": "^5.4|^6.0|^7.0" + }, + "bin": [ + "Resources/bin/patch-type-declarations" + ], + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\ErrorHandler\\": "" + }, + "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": "Provides tools to manage errors and ease debugging PHP code", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/error-handler/tree/v6.4.36" + }, + "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-03-10T15:56:14+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v7.4.9", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "e4a2e29753c7801f7a8340e066cfa788f3bc8101" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/e4a2e29753c7801f7a8340e066cfa788f3bc8101", + "reference": "e4a2e29753c7801f7a8340e066cfa788f3bc8101", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/event-dispatcher-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/dependency-injection": "<6.4", + "symfony/service-contracts": "<2.5" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/error-handler": "^6.4|^7.0|^8.0", + "symfony/expression-language": "^6.4|^7.0|^8.0", + "symfony/framework-bundle": "^6.4|^7.0|^8.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/stopwatch": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "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": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/event-dispatcher/tree/v7.4.9" + }, + "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-18T13:18:21+00:00" + }, + { + "name": "symfony/event-dispatcher-contracts", + "version": "v3.7.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "ccba7060602b7fed0b03c85bf025257f76d9ef32" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/ccba7060602b7fed0b03c85bf025257f76d9ef32", + "reference": "ccba7060602b7fed0b03c85bf025257f76d9ef32", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/event-dispatcher": "^1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.7-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\EventDispatcher\\": "" + } + }, + "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 dispatching event", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.7.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-01-05T13:30:16+00:00" + }, + { + "name": "symfony/finder", + "version": "v6.4.34", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "9590e86be1d1c57bfbb16d0dd040345378c20896" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/9590e86be1d1c57bfbb16d0dd040345378c20896", + "reference": "9590e86be1d1c57bfbb16d0dd040345378c20896", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "symfony/filesystem": "^6.0|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "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": "Finds files and directories via an intuitive fluent interface", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/finder/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-01-28T15:16:37+00:00" + }, + { + "name": "symfony/http-foundation", + "version": "v6.4.41", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-foundation.git", + "reference": "48d76c29a67a301e0f7779a512bf76417395ffef" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/48d76c29a67a301e0f7779a512bf76417395ffef", + "reference": "48d76c29a67a301e0f7779a512bf76417395ffef", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.1", + "symfony/polyfill-php83": "^1.27" + }, + "conflict": { + "symfony/cache": "<6.4.12|>=7.0,<7.1.5" + }, + "require-dev": { + "doctrine/dbal": "^2.13.1|^3|^4", + "predis/predis": "^1.1|^2.0", + "symfony/cache": "^6.4.12|^7.1.5", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/expression-language": "^5.4|^6.0|^7.0", + "symfony/http-kernel": "^5.4.12|^6.0.12|^6.1.4|^7.0", + "symfony/mime": "^5.4|^6.0|^7.0", + "symfony/rate-limiter": "^5.4|^6.0|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpFoundation\\": "" + }, + "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": "Defines an object-oriented layer for the HTTP specification", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/http-foundation/tree/v6.4.41" + }, + "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-05-24T10:54:17+00:00" + }, + { + "name": "symfony/http-kernel", + "version": "v6.4.41", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-kernel.git", + "reference": "155f6c1fa29720e8c947591da3be4014951fba6f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/155f6c1fa29720e8c947591da3be4014951fba6f", + "reference": "155f6c1fa29720e8c947591da3be4014951fba6f", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/log": "^1|^2|^3", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/error-handler": "^6.4|^7.0", + "symfony/event-dispatcher": "^5.4|^6.0|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "symfony/browser-kit": "<5.4", + "symfony/cache": "<5.4", + "symfony/config": "<6.1", + "symfony/console": "<5.4", + "symfony/dependency-injection": "<6.4", + "symfony/doctrine-bridge": "<5.4", + "symfony/form": "<5.4", + "symfony/http-client": "<5.4", + "symfony/http-client-contracts": "<2.5", + "symfony/mailer": "<5.4", + "symfony/messenger": "<5.4", + "symfony/translation": "<5.4", + "symfony/translation-contracts": "<2.5", + "symfony/twig-bridge": "<5.4", + "symfony/validator": "<6.4", + "symfony/var-dumper": "<6.3", + "twig/twig": "<2.13" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/cache": "^1.0|^2.0|^3.0", + "symfony/browser-kit": "^5.4|^6.0|^7.0", + "symfony/clock": "^6.2|^7.0", + "symfony/config": "^6.1|^7.0", + "symfony/console": "^5.4|^6.0|^7.0", + "symfony/css-selector": "^5.4|^6.0|^7.0", + "symfony/dependency-injection": "^6.4.1|^7.0.1", + "symfony/dom-crawler": "^5.4|^6.0|^7.0", + "symfony/expression-language": "^5.4|^6.0|^7.0", + "symfony/finder": "^5.4|^6.0|^7.0", + "symfony/http-client-contracts": "^2.5|^3", + "symfony/process": "^5.4|^6.0|^7.0", + "symfony/property-access": "^5.4.5|^6.0.5|^7.0", + "symfony/routing": "^5.4|^6.0|^7.0", + "symfony/serializer": "^6.4.4|^7.0.4", + "symfony/stopwatch": "^5.4|^6.0|^7.0", + "symfony/translation": "^5.4|^6.0|^7.0", + "symfony/translation-contracts": "^2.5|^3", + "symfony/uid": "^5.4|^6.0|^7.0", + "symfony/validator": "^6.4|^7.0", + "symfony/var-dumper": "^5.4|^6.4|^7.0", + "symfony/var-exporter": "^6.2|^7.0", + "twig/twig": "^2.13|^3.0.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpKernel\\": "" + }, + "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": "Provides a structured process for converting a Request into a Response", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/http-kernel/tree/v6.4.41" + }, + "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-05-27T08:22:22+00:00" + }, + { + "name": "symfony/mailer", + "version": "v6.4.40", + "source": { + "type": "git", + "url": "https://github.com/symfony/mailer.git", + "reference": "94fd44f3052e02340b0dd4447a7d7a5856e32da2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/mailer/zipball/94fd44f3052e02340b0dd4447a7d7a5856e32da2", + "reference": "94fd44f3052e02340b0dd4447a7d7a5856e32da2", + "shasum": "" + }, + "require": { + "egulias/email-validator": "^2.1.10|^3|^4", + "php": ">=8.1", + "psr/event-dispatcher": "^1", + "psr/log": "^1|^2|^3", + "symfony/event-dispatcher": "^5.4|^6.0|^7.0", + "symfony/mime": "^6.2|^7.0", + "symfony/service-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/http-client-contracts": "<2.5", + "symfony/http-kernel": "<5.4", + "symfony/messenger": "<6.2", + "symfony/mime": "<6.2", + "symfony/twig-bridge": "<6.2.1" + }, + "require-dev": { + "symfony/console": "^5.4|^6.0|^7.0", + "symfony/http-client": "^5.4|^6.0|^7.0", + "symfony/messenger": "^6.2|^7.0", + "symfony/twig-bridge": "^6.2|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Mailer\\": "" + }, + "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": "Helps sending emails", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/mailer/tree/v6.4.40" + }, + "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-05-19T20:33:22+00:00" + }, + { + "name": "symfony/mime", + "version": "v6.4.41", + "source": { + "type": "git", + "url": "https://github.com/symfony/mime.git", + "reference": "5575d37f8841e4e31d5df79ab3db078ae557ff8e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/mime/zipball/5575d37f8841e4e31d5df79ab3db078ae557ff8e", + "reference": "5575d37f8841e4e31d5df79ab3db078ae557ff8e", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-intl-idn": "^1.10", + "symfony/polyfill-mbstring": "^1.0" + }, + "conflict": { + "egulias/email-validator": "~3.0.0", + "phpdocumentor/reflection-docblock": "<3.2.2", + "phpdocumentor/type-resolver": "<1.4.0", + "symfony/mailer": "<5.4", + "symfony/serializer": "<6.4.3|>7.0,<7.0.3" + }, + "require-dev": { + "egulias/email-validator": "^2.1.10|^3.1|^4", + "league/html-to-markdown": "^5.0", + "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/process": "^5.4|^6.4|^7.0", + "symfony/property-access": "^5.4|^6.0|^7.0", + "symfony/property-info": "^5.4|^6.0|^7.0", + "symfony/serializer": "^6.4.3|^7.0.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Mime\\": "" + }, + "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": "Allows manipulating MIME messages", + "homepage": "https://symfony.com", + "keywords": [ + "mime", + "mime-type" + ], + "support": { + "source": "https://github.com/symfony/mime/tree/v6.4.41" + }, + "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-05-23T14:40:34+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.38.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "e9247d281d694a5120554d9afaf54e070e88a603" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/e9247d281d694a5120554d9afaf54e070e88a603", + "reference": "e9247d281d694a5120554d9afaf54e070e88a603", + "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.38.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": "2026-05-26T05:58:03+00:00" + }, + { + "name": "symfony/polyfill-intl-idn", + "version": "v1.38.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-idn.git", + "reference": "dc21118016c039a66235cf93d96b435ffb282412" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/dc21118016c039a66235cf93d96b435ffb282412", + "reference": "dc21118016c039a66235cf93d96b435ffb282412", + "shasum": "" + }, + "require": { + "php": ">=7.2", + "symfony/polyfill-intl-normalizer": "^1.10" + }, + "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\\Idn\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Laurent Bassin", + "email": "laurent@bassin.info" + }, + { + "name": "Trevor Rowbotham", + "email": "trevor.rowbotham@pm.me" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "idn", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.38.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": "2026-05-25T15:22:23+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.38.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "2d446c214bdbe5b71bde5011b060a05fece3ae6b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/2d446c214bdbe5b71bde5011b060a05fece3ae6b", + "reference": "2d446c214bdbe5b71bde5011b060a05fece3ae6b", + "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.38.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-05-25T13:48:31+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.38.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "14c5439eec4ccff081ac14eca2dc57feb2a66d92" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/14c5439eec4ccff081ac14eca2dc57feb2a66d92", + "reference": "14c5439eec4ccff081ac14eca2dc57feb2a66d92", + "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.38.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": "2026-05-26T12:51:13+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/polyfill-php83", + "version": "v1.38.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php83.git", + "reference": "8339098cae28673c15cce00d80734af0453054e2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/8339098cae28673c15cce00d80734af0453054e2", + "reference": "8339098cae28673c15cce00d80734af0453054e2", + "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\\Php83\\": "" + }, + "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 8.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php83/tree/v1.38.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": "2026-05-26T12:51:13+00:00" + }, + { + "name": "symfony/polyfill-uuid", + "version": "v1.37.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-uuid.git", + "reference": "26dfec253c4cf3e51b541b52ddf7e42cb0908e94" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/26dfec253c4cf3e51b541b52ddf7e42cb0908e94", + "reference": "26dfec253c4cf3e51b541b52ddf7e42cb0908e94", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "provide": { + "ext-uuid": "*" + }, + "suggest": { + "ext-uuid": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Uuid\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Grégoire Pineau", + "email": "lyrixx@lyrixx.info" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for uuid functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "uuid" + ], + "support": { + "source": "https://github.com/symfony/polyfill-uuid/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/process", + "version": "v6.4.41", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "c8fc09bdfe9fde9aaa89b415a4477feaccec16a7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/c8fc09bdfe9fde9aaa89b415a4477feaccec16a7", + "reference": "c8fc09bdfe9fde9aaa89b415a4477feaccec16a7", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "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": "Executes commands in sub-processes", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/process/tree/v6.4.41" + }, + "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-05-23T13:47:21+00:00" + }, + { + "name": "symfony/routing", + "version": "v6.4.41", + "source": { + "type": "git", + "url": "https://github.com/symfony/routing.git", + "reference": "af04c79671fd8df0805a44c83fa2b0ba56c8329e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/routing/zipball/af04c79671fd8df0805a44c83fa2b0ba56c8329e", + "reference": "af04c79671fd8df0805a44c83fa2b0ba56c8329e", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "doctrine/annotations": "<1.12", + "symfony/config": "<6.2", + "symfony/dependency-injection": "<5.4", + "symfony/yaml": "<5.4" + }, + "require-dev": { + "doctrine/annotations": "^1.12|^2", + "psr/log": "^1|^2|^3", + "symfony/config": "^6.2|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/expression-language": "^5.4|^6.0|^7.0", + "symfony/http-foundation": "^5.4|^6.0|^7.0", + "symfony/yaml": "^5.4|^6.0|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Routing\\": "" + }, + "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": "Maps an HTTP request to a set of configuration variables", + "homepage": "https://symfony.com", + "keywords": [ + "router", + "routing", + "uri", + "url" + ], + "support": { + "source": "https://github.com/symfony/routing/tree/v6.4.41" + }, + "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-05-24T11:18:16+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v3.7.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "d25d82433a80eba6aa0e6c24b61d7370d99e444a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/d25d82433a80eba6aa0e6c24b61d7370d99e444a", + "reference": "d25d82433a80eba6aa0e6c24b61d7370d99e444a", + "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.7-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.7.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-03-28T09:44:51+00:00" + }, + { + "name": "symfony/string", + "version": "v7.4.13", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "961683010db3b27ec6ebcd7308e6e1ee8fa7ffde" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/961683010db3b27ec6ebcd7308e6e1ee8fa7ffde", + "reference": "961683010db3b27ec6ebcd7308e6e1ee8fa7ffde", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3.0", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.33", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/translation-contracts": "<2.5" + }, + "require-dev": { + "symfony/emoji": "^7.1|^8.0", + "symfony/http-client": "^6.4|^7.0|^8.0", + "symfony/intl": "^6.4|^7.0|^8.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^6.4|^7.0|^8.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/v7.4.13" + }, + "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-05-23T15:23:29+00:00" + }, + { + "name": "symfony/translation", + "version": "v6.4.38", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation.git", + "reference": "afaa31b0c12d9a659eed1ea97f268a614cc1299c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation/zipball/afaa31b0c12d9a659eed1ea97f268a614cc1299c", + "reference": "afaa31b0c12d9a659eed1ea97f268a614cc1299c", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/translation-contracts": "^2.5|^3.0" + }, + "conflict": { + "symfony/config": "<5.4", + "symfony/console": "<5.4", + "symfony/dependency-injection": "<5.4", + "symfony/http-client-contracts": "<2.5", + "symfony/http-kernel": "<5.4", + "symfony/service-contracts": "<2.5", + "symfony/twig-bundle": "<5.4", + "symfony/yaml": "<5.4" + }, + "provide": { + "symfony/translation-implementation": "2.3|3.0" + }, + "require-dev": { + "nikic/php-parser": "^4.18|^5.0", + "psr/log": "^1|^2|^3", + "symfony/config": "^5.4|^6.0|^7.0", + "symfony/console": "^5.4|^6.0|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/finder": "^5.4|^6.0|^7.0", + "symfony/http-client-contracts": "^2.5|^3.0", + "symfony/http-kernel": "^5.4|^6.0|^7.0", + "symfony/intl": "^5.4|^6.0|^7.0", + "symfony/polyfill-intl-icu": "^1.21", + "symfony/routing": "^5.4|^6.0|^7.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/yaml": "^5.4|^6.0|^7.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\Translation\\": "" + }, + "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": "Provides tools to internationalize your application", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/translation/tree/v6.4.38" + }, + "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-05-06T08:55:54+00:00" + }, + { + "name": "symfony/translation-contracts", + "version": "v3.7.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation-contracts.git", + "reference": "0ab302977a952b42fd51475c4ebac81f8da0a95d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/0ab302977a952b42fd51475c4ebac81f8da0a95d", + "reference": "0ab302977a952b42fd51475c4ebac81f8da0a95d", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.7-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Translation\\": "" + }, + "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 translation", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/translation-contracts/tree/v3.7.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-01-05T13:30:16+00:00" + }, + { + "name": "symfony/uid", + "version": "v6.4.32", + "source": { + "type": "git", + "url": "https://github.com/symfony/uid.git", + "reference": "6b973c385f00341b246f697d82dc01a09107acdd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/uid/zipball/6b973c385f00341b246f697d82dc01a09107acdd", + "reference": "6b973c385f00341b246f697d82dc01a09107acdd", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/polyfill-uuid": "^1.15" + }, + "require-dev": { + "symfony/console": "^5.4|^6.0|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Uid\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Grégoire Pineau", + "email": "lyrixx@lyrixx.info" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to generate and represent UIDs", + "homepage": "https://symfony.com", + "keywords": [ + "UID", + "ulid", + "uuid" + ], + "support": { + "source": "https://github.com/symfony/uid/tree/v6.4.32" + }, + "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-12-23T15:07:59+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v6.4.36", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "7c8ad9ce4faf6c8a99948e70ce02b601a0439782" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/7c8ad9ce4faf6c8a99948e70ce02b601a0439782", + "reference": "7c8ad9ce4faf6c8a99948e70ce02b601a0439782", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/console": "<5.4" + }, + "require-dev": { + "symfony/console": "^5.4|^6.0|^7.0", + "symfony/error-handler": "^6.3|^7.0", + "symfony/http-kernel": "^5.4|^6.0|^7.0", + "symfony/process": "^5.4|^6.0|^7.0", + "symfony/uid": "^5.4|^6.0|^7.0", + "twig/twig": "^2.13|^3.0.4" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "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 mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v6.4.36" + }, + "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-03-30T15:36:00+00:00" + }, + { + "name": "thecodingmachine/safe", + "version": "v3.4.0", + "source": { + "type": "git", + "url": "https://github.com/thecodingmachine/safe.git", + "reference": "705683a25bacf0d4860c7dea4d7947bfd09eea19" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thecodingmachine/safe/zipball/705683a25bacf0d4860c7dea4d7947bfd09eea19", + "reference": "705683a25bacf0d4860c7dea4d7947bfd09eea19", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "^1.4", + "phpstan/phpstan": "^2", + "phpunit/phpunit": "^10", + "squizlabs/php_codesniffer": "^3.2" + }, + "type": "library", + "autoload": { + "files": [ + "lib/special_cases.php", + "generated/apache.php", + "generated/apcu.php", + "generated/array.php", + "generated/bzip2.php", + "generated/calendar.php", + "generated/classobj.php", + "generated/com.php", + "generated/cubrid.php", + "generated/curl.php", + "generated/datetime.php", + "generated/dir.php", + "generated/eio.php", + "generated/errorfunc.php", + "generated/exec.php", + "generated/fileinfo.php", + "generated/filesystem.php", + "generated/filter.php", + "generated/fpm.php", + "generated/ftp.php", + "generated/funchand.php", + "generated/gettext.php", + "generated/gmp.php", + "generated/gnupg.php", + "generated/hash.php", + "generated/ibase.php", + "generated/ibmDb2.php", + "generated/iconv.php", + "generated/image.php", + "generated/imap.php", + "generated/info.php", + "generated/inotify.php", + "generated/json.php", + "generated/ldap.php", + "generated/libxml.php", + "generated/lzf.php", + "generated/mailparse.php", + "generated/mbstring.php", + "generated/misc.php", + "generated/mysql.php", + "generated/mysqli.php", + "generated/network.php", + "generated/oci8.php", + "generated/opcache.php", + "generated/openssl.php", + "generated/outcontrol.php", + "generated/pcntl.php", + "generated/pcre.php", + "generated/pgsql.php", + "generated/posix.php", + "generated/ps.php", + "generated/pspell.php", + "generated/readline.php", + "generated/rnp.php", + "generated/rpminfo.php", + "generated/rrd.php", + "generated/sem.php", + "generated/session.php", + "generated/shmop.php", + "generated/sockets.php", + "generated/sodium.php", + "generated/solr.php", + "generated/spl.php", + "generated/sqlsrv.php", + "generated/ssdeep.php", + "generated/ssh2.php", + "generated/stream.php", + "generated/strings.php", + "generated/swoole.php", + "generated/uodbc.php", + "generated/uopz.php", + "generated/url.php", + "generated/var.php", + "generated/xdiff.php", + "generated/xml.php", + "generated/xmlrpc.php", + "generated/yaml.php", + "generated/yaz.php", + "generated/zip.php", + "generated/zlib.php" + ], + "classmap": [ + "lib/DateTime.php", + "lib/DateTimeImmutable.php", + "lib/Exceptions/", + "generated/Exceptions/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHP core functions that throw exceptions instead of returning FALSE on error", + "support": { + "issues": "https://github.com/thecodingmachine/safe/issues", + "source": "https://github.com/thecodingmachine/safe/tree/v3.4.0" + }, + "funding": [ + { + "url": "https://github.com/OskarStark", + "type": "github" + }, + { + "url": "https://github.com/shish", + "type": "github" + }, + { + "url": "https://github.com/silasjoisten", + "type": "github" + }, + { + "url": "https://github.com/staabm", + "type": "github" + } + ], + "time": "2026-02-04T18:08:13+00:00" + }, + { + "name": "tijsverkoyen/css-to-inline-styles", + "version": "v2.4.0", + "source": { + "type": "git", + "url": "https://github.com/tijsverkoyen/CssToInlineStyles.git", + "reference": "f0292ccf0ec75843d65027214426b6b163b48b41" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tijsverkoyen/CssToInlineStyles/zipball/f0292ccf0ec75843d65027214426b6b163b48b41", + "reference": "f0292ccf0ec75843d65027214426b6b163b48b41", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "php": "^7.4 || ^8.0", + "symfony/css-selector": "^5.4 || ^6.0 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^8.5.21 || ^9.5.10" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "TijsVerkoyen\\CssToInlineStyles\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Tijs Verkoyen", + "email": "css_to_inline_styles@verkoyen.eu", + "role": "Developer" + } + ], + "description": "CssToInlineStyles is a class that enables you to convert HTML-pages/files into HTML-pages/files with inline styles. This is very useful when you're sending emails.", + "homepage": "https://github.com/tijsverkoyen/CssToInlineStyles", + "support": { + "issues": "https://github.com/tijsverkoyen/CssToInlineStyles/issues", + "source": "https://github.com/tijsverkoyen/CssToInlineStyles/tree/v2.4.0" + }, + "time": "2025-12-02T11:56:42+00:00" + }, + { + "name": "vlucas/phpdotenv", + "version": "v5.6.3", + "source": { + "type": "git", + "url": "https://github.com/vlucas/phpdotenv.git", + "reference": "955e7815d677a3eaa7075231212f2110983adecc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/955e7815d677a3eaa7075231212f2110983adecc", + "reference": "955e7815d677a3eaa7075231212f2110983adecc", + "shasum": "" + }, + "require": { + "ext-pcre": "*", + "graham-campbell/result-type": "^1.1.4", + "php": "^7.2.5 || ^8.0", + "phpoption/phpoption": "^1.9.5", + "symfony/polyfill-ctype": "^1.26", + "symfony/polyfill-mbstring": "^1.26", + "symfony/polyfill-php80": "^1.26" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "ext-filter": "*", + "phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2" + }, + "suggest": { + "ext-filter": "Required to use the boolean validator." + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + }, + "branch-alias": { + "dev-master": "5.6-dev" + } + }, + "autoload": { + "psr-4": { + "Dotenv\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Vance Lucas", + "email": "vance@vancelucas.com", + "homepage": "https://github.com/vlucas" + } + ], + "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.", + "keywords": [ + "dotenv", + "env", + "environment" + ], + "support": { + "issues": "https://github.com/vlucas/phpdotenv/issues", + "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.3" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/vlucas/phpdotenv", + "type": "tidelift" + } + ], + "time": "2025-12-27T19:49:13+00:00" + }, + { + "name": "voku/portable-ascii", + "version": "2.1.1", + "source": { + "type": "git", + "url": "https://github.com/voku/portable-ascii.git", + "reference": "8e1051fe39379367aecf014f41744ce7539a856f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/voku/portable-ascii/zipball/8e1051fe39379367aecf014f41744ce7539a856f", + "reference": "8e1051fe39379367aecf014f41744ce7539a856f", + "shasum": "" + }, + "require": { + "php": ">=7.1.0" + }, + "require-dev": { + "phpunit/phpunit": "~8.5 || ~9.6 || ~10.5 || ~11.5" + }, + "suggest": { + "ext-intl": "Use Intl for transliterator_transliterate() support" + }, + "type": "library", + "autoload": { + "psr-4": { + "voku\\": "src/voku/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Lars Moelleken", + "homepage": "https://www.moelleken.org/" + } + ], + "description": "Portable ASCII library - performance optimized (ascii) string functions for php.", + "homepage": "https://github.com/voku/portable-ascii", + "keywords": [ + "ascii", + "clean", + "php" + ], + "support": { + "issues": "https://github.com/voku/portable-ascii/issues", + "source": "https://github.com/voku/portable-ascii/tree/2.1.1" + }, + "funding": [ + { + "url": "https://www.paypal.me/moelleken", + "type": "custom" + }, + { + "url": "https://github.com/voku", + "type": "github" + }, + { + "url": "https://opencollective.com/portable-ascii", + "type": "open_collective" + }, + { + "url": "https://www.patreon.com/voku", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/voku/portable-ascii", + "type": "tidelift" + } + ], + "time": "2026-04-26T05:33:54+00:00" + } + ], + "packages-dev": [ + { + "name": "fakerphp/faker", + "version": "v1.24.1", + "source": { + "type": "git", + "url": "https://github.com/FakerPHP/Faker.git", + "reference": "e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5", + "reference": "e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0", + "psr/container": "^1.0 || ^2.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" + }, + "conflict": { + "fzaninotto/faker": "*" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.4.1", + "doctrine/persistence": "^1.3 || ^2.0", + "ext-intl": "*", + "phpunit/phpunit": "^9.5.26", + "symfony/phpunit-bridge": "^5.4.16" + }, + "suggest": { + "doctrine/orm": "Required to use Faker\\ORM\\Doctrine", + "ext-curl": "Required by Faker\\Provider\\Image to download images.", + "ext-dom": "Required by Faker\\Provider\\HtmlLorem for generating random HTML.", + "ext-iconv": "Required by Faker\\Provider\\ru_RU\\Text::realText() for generating real Russian text.", + "ext-mbstring": "Required for multibyte Unicode string functionality." + }, + "type": "library", + "autoload": { + "psr-4": { + "Faker\\": "src/Faker/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "François Zaninotto" + } + ], + "description": "Faker is a PHP library that generates fake data for you.", + "keywords": [ + "data", + "faker", + "fixtures" + ], + "support": { + "issues": "https://github.com/FakerPHP/Faker/issues", + "source": "https://github.com/FakerPHP/Faker/tree/v1.24.1" + }, + "time": "2024-11-21T13:46:39+00:00" + }, + { + "name": "filp/whoops", + "version": "2.18.4", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "d2102955e48b9fd9ab24280a7ad12ed552752c4d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/d2102955e48b9fd9ab24280a7ad12ed552752c4d", + "reference": "d2102955e48b9fd9ab24280a7ad12ed552752c4d", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "require-dev": { + "mockery/mockery": "^1.0", + "phpunit/phpunit": "^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^4.0 || ^5.0" + }, + "suggest": { + "symfony/var-dumper": "Pretty print complex values better with var-dumper available", + "whoops/soap": "Formats errors as SOAP responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Whoops\\": "src/Whoops/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Filipe Dobreira", + "homepage": "https://github.com/filp", + "role": "Developer" + } + ], + "description": "php error handling for cool kids", + "homepage": "https://filp.github.io/whoops/", + "keywords": [ + "error", + "exception", + "handling", + "library", + "throwable", + "whoops" + ], + "support": { + "issues": "https://github.com/filp/whoops/issues", + "source": "https://github.com/filp/whoops/tree/2.18.4" + }, + "funding": [ + { + "url": "https://github.com/denis-sokolov", + "type": "github" + } + ], + "time": "2025-08-08T12:00:00+00:00" + }, + { + "name": "hamcrest/hamcrest-php", + "version": "v2.1.1", + "source": { + "type": "git", + "url": "https://github.com/hamcrest/hamcrest-php.git", + "reference": "f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487", + "reference": "f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487", + "shasum": "" + }, + "require": { + "php": "^7.4|^8.0" + }, + "replace": { + "cordoval/hamcrest-php": "*", + "davedevelopment/hamcrest-php": "*", + "kodova/hamcrest-php": "*" + }, + "require-dev": { + "phpunit/php-file-iterator": "^1.4 || ^2.0 || ^3.0", + "phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0 || ^8.0 || ^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1-dev" + } + }, + "autoload": { + "classmap": [ + "hamcrest" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "This is the PHP port of Hamcrest Matchers", + "keywords": [ + "test" + ], + "support": { + "issues": "https://github.com/hamcrest/hamcrest-php/issues", + "source": "https://github.com/hamcrest/hamcrest-php/tree/v2.1.1" + }, + "time": "2025-04-30T06:54:44+00:00" + }, + { + "name": "laravel/pint", + "version": "v1.29.1", + "source": { + "type": "git", + "url": "https://github.com/laravel/pint.git", + "reference": "0770e9b7fafd50d4586881d456d6eb41c9247a80" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/pint/zipball/0770e9b7fafd50d4586881d456d6eb41c9247a80", + "reference": "0770e9b7fafd50d4586881d456d6eb41c9247a80", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-mbstring": "*", + "ext-tokenizer": "*", + "ext-xml": "*", + "php": "^8.2.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.95.1", + "illuminate/view": "^12.56.0", + "larastan/larastan": "^3.9.6", + "laravel-zero/framework": "^12.1.0", + "mockery/mockery": "^1.6.12", + "nunomaduro/termwind": "^2.4.0", + "pestphp/pest": "^3.8.6", + "shipfastlabs/agent-detector": "^1.1.3" + }, + "bin": [ + "builds/pint" + ], + "type": "project", + "autoload": { + "psr-4": { + "App\\": "app/", + "Database\\Seeders\\": "database/seeders/", + "Database\\Factories\\": "database/factories/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" + } + ], + "description": "An opinionated code formatter for PHP.", + "homepage": "https://laravel.com", + "keywords": [ + "dev", + "format", + "formatter", + "lint", + "linter", + "php" + ], + "support": { + "issues": "https://github.com/laravel/pint/issues", + "source": "https://github.com/laravel/pint" + }, + "time": "2026-04-20T15:26:14+00:00" + }, + { + "name": "laravel/sail", + "version": "v1.61.0", + "source": { + "type": "git", + "url": "https://github.com/laravel/sail.git", + "reference": "68ef35015630fe510432e63e11e21749006df688" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/sail/zipball/68ef35015630fe510432e63e11e21749006df688", + "reference": "68ef35015630fe510432e63e11e21749006df688", + "shasum": "" + }, + "require": { + "illuminate/console": "^9.52.16|^10.0|^11.0|^12.0|^13.0", + "illuminate/contracts": "^9.52.16|^10.0|^11.0|^12.0|^13.0", + "illuminate/support": "^9.52.16|^10.0|^11.0|^12.0|^13.0", + "php": "^8.0", + "symfony/console": "^6.0|^7.0|^8.0", + "symfony/yaml": "^6.0|^7.0|^8.0" + }, + "require-dev": { + "orchestra/testbench": "^7.0|^8.0|^9.0|^10.0|^11.0", + "phpstan/phpstan": "^2.0" + }, + "bin": [ + "bin/sail" + ], + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Laravel\\Sail\\SailServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Laravel\\Sail\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Docker files for running a basic Laravel application.", + "keywords": [ + "docker", + "laravel" + ], + "support": { + "issues": "https://github.com/laravel/sail/issues", + "source": "https://github.com/laravel/sail" + }, + "time": "2026-05-23T23:33:57+00:00" + }, + { + "name": "mockery/mockery", + "version": "1.6.12", + "source": { + "type": "git", + "url": "https://github.com/mockery/mockery.git", + "reference": "1f4efdd7d3beafe9807b08156dfcb176d18f1699" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mockery/mockery/zipball/1f4efdd7d3beafe9807b08156dfcb176d18f1699", + "reference": "1f4efdd7d3beafe9807b08156dfcb176d18f1699", + "shasum": "" + }, + "require": { + "hamcrest/hamcrest-php": "^2.0.1", + "lib-pcre": ">=7.0", + "php": ">=7.3" + }, + "conflict": { + "phpunit/phpunit": "<8.0" + }, + "require-dev": { + "phpunit/phpunit": "^8.5 || ^9.6.17", + "symplify/easy-coding-standard": "^12.1.14" + }, + "type": "library", + "autoload": { + "files": [ + "library/helpers.php", + "library/Mockery.php" + ], + "psr-4": { + "Mockery\\": "library/Mockery" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Pádraic Brady", + "email": "padraic.brady@gmail.com", + "homepage": "https://github.com/padraic", + "role": "Author" + }, + { + "name": "Dave Marshall", + "email": "dave.marshall@atstsolutions.co.uk", + "homepage": "https://davedevelopment.co.uk", + "role": "Developer" + }, + { + "name": "Nathanael Esayeas", + "email": "nathanael.esayeas@protonmail.com", + "homepage": "https://github.com/ghostwriter", + "role": "Lead Developer" + } + ], + "description": "Mockery is a simple yet flexible PHP mock object framework", + "homepage": "https://github.com/mockery/mockery", + "keywords": [ + "BDD", + "TDD", + "library", + "mock", + "mock objects", + "mockery", + "stub", + "test", + "test double", + "testing" + ], + "support": { + "docs": "https://docs.mockery.io/", + "issues": "https://github.com/mockery/mockery/issues", + "rss": "https://github.com/mockery/mockery/releases.atom", + "security": "https://github.com/mockery/mockery/security/advisories", + "source": "https://github.com/mockery/mockery" + }, + "time": "2024-05-16T03:13:13+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.13.4", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3 <3.2.2" + }, + "require-dev": { + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpspec/prophecy": "^1.10", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" + }, + "type": "library", + "autoload": { + "files": [ + "src/DeepCopy/deep_copy.php" + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2025-08-01T08:46:24+00:00" + }, + { + "name": "nunomaduro/collision", + "version": "v7.12.0", + "source": { + "type": "git", + "url": "https://github.com/nunomaduro/collision.git", + "reference": "995245421d3d7593a6960822063bdba4f5d7cf1a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nunomaduro/collision/zipball/995245421d3d7593a6960822063bdba4f5d7cf1a", + "reference": "995245421d3d7593a6960822063bdba4f5d7cf1a", + "shasum": "" + }, + "require": { + "filp/whoops": "^2.17.0", + "nunomaduro/termwind": "^1.17.0", + "php": "^8.1.0", + "symfony/console": "^6.4.17" + }, + "conflict": { + "laravel/framework": ">=11.0.0" + }, + "require-dev": { + "brianium/paratest": "^7.4.8", + "laravel/framework": "^10.48.29", + "laravel/pint": "^1.21.2", + "laravel/sail": "^1.41.0", + "laravel/sanctum": "^3.3.3", + "laravel/tinker": "^2.10.1", + "nunomaduro/larastan": "^2.10.0", + "orchestra/testbench-core": "^8.35.0", + "pestphp/pest": "^2.36.0", + "phpunit/phpunit": "^10.5.36", + "sebastian/environment": "^6.1.0", + "spatie/laravel-ignition": "^2.9.1" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "NunoMaduro\\Collision\\Adapters\\Laravel\\CollisionServiceProvider" + ] + } + }, + "autoload": { + "files": [ + "./src/Adapters/Phpunit/Autoload.php" + ], + "psr-4": { + "NunoMaduro\\Collision\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" + } + ], + "description": "Cli error handling for console/command-line PHP applications.", + "keywords": [ + "artisan", + "cli", + "command-line", + "console", + "error", + "handling", + "laravel", + "laravel-zero", + "php", + "symfony" + ], + "support": { + "issues": "https://github.com/nunomaduro/collision/issues", + "source": "https://github.com/nunomaduro/collision" + }, + "funding": [ + { + "url": "https://www.paypal.com/paypalme/enunomaduro", + "type": "custom" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + }, + { + "url": "https://www.patreon.com/nunomaduro", + "type": "patreon" + } + ], + "time": "2025-03-14T22:35:49+00:00" + }, + { + "name": "phar-io/manifest", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "54750ef60c58e43759730615a392c31c80e23176" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:33:53+00:00" + }, + { + "name": "phar-io/version", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.2.1" + }, + "time": "2022-02-21T01:04:05+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "10.1.16", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "7e308268858ed6baedc8704a304727d20bc07c77" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/7e308268858ed6baedc8704a304727d20bc07c77", + "reference": "7e308268858ed6baedc8704a304727d20bc07c77", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^4.19.1 || ^5.1.0", + "php": ">=8.1", + "phpunit/php-file-iterator": "^4.1.0", + "phpunit/php-text-template": "^3.0.1", + "sebastian/code-unit-reverse-lookup": "^3.0.0", + "sebastian/complexity": "^3.2.0", + "sebastian/environment": "^6.1.0", + "sebastian/lines-of-code": "^2.0.2", + "sebastian/version": "^4.0.1", + "theseer/tokenizer": "^1.2.3" + }, + "require-dev": { + "phpunit/phpunit": "^10.1" + }, + "suggest": { + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "10.1.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.16" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-08-22T04:31:57+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "4.1.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/a95037b6d9e608ba092da1b23931e537cadc3c3c", + "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/4.1.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-08-31T06:24:48+00:00" + }, + { + "name": "phpunit/php-invoker", + "version": "4.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7", + "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^10.0" + }, + "suggest": { + "ext-pcntl": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/4.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:56:09+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/0c7b06ff49e3d5072f057eb1fa59258bf287a748", + "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/3.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-08-31T14:07:24+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "6.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/e2a2d67966e740530f4a3343fe2e030ffdc1161d", + "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "source": "https://github.com/sebastianbergmann/php-timer/tree/6.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:57:52+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "10.5.63", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "33198268dad71e926626b618f3ec3966661e4d90" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/33198268dad71e926626b618f3ec3966661e4d90", + "reference": "33198268dad71e926626b618f3ec3966661e4d90", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.13.4", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", + "php": ">=8.1", + "phpunit/php-code-coverage": "^10.1.16", + "phpunit/php-file-iterator": "^4.1.0", + "phpunit/php-invoker": "^4.0.0", + "phpunit/php-text-template": "^3.0.1", + "phpunit/php-timer": "^6.0.0", + "sebastian/cli-parser": "^2.0.1", + "sebastian/code-unit": "^2.0.0", + "sebastian/comparator": "^5.0.5", + "sebastian/diff": "^5.1.1", + "sebastian/environment": "^6.1.0", + "sebastian/exporter": "^5.1.4", + "sebastian/global-state": "^6.0.2", + "sebastian/object-enumerator": "^5.0.0", + "sebastian/recursion-context": "^5.0.1", + "sebastian/type": "^4.0.0", + "sebastian/version": "^4.0.1" + }, + "suggest": { + "ext-soap": "To be able to generate mocks based on WSDL files" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "10.5-dev" + } + }, + "autoload": { + "files": [ + "src/Framework/Assert/Functions.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.63" + }, + "funding": [ + { + "url": "https://phpunit.de/sponsors.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", + "type": "tidelift" + } + ], + "time": "2026-01-27T05:48:37+00:00" + }, + { + "name": "sebastian/cli-parser", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/c34583b87e7b7a8055bf6c450c2c77ce32a24084", + "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/2.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T07:12:49+00:00" + }, + { + "name": "sebastian/code-unit", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "a81fee9eef0b7a76af11d121767abc44c104e503" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/a81fee9eef0b7a76af11d121767abc44c104e503", + "reference": "a81fee9eef0b7a76af11d121767abc44c104e503", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the PHP code units", + "homepage": "https://github.com/sebastianbergmann/code-unit", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit/issues", + "source": "https://github.com/sebastianbergmann/code-unit/tree/2.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:58:43+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/5e3a687f7d8ae33fb362c5c0743794bbb2420a1d", + "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/3.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:59:15+00:00" + }, + { + "name": "sebastian/comparator", + "version": "5.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "55dfef806eb7dfeb6e7a6935601fef866f8ca48d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/55dfef806eb7dfeb6e7a6935601fef866f8ca48d", + "reference": "55dfef806eb7dfeb6e7a6935601fef866f8ca48d", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-mbstring": "*", + "php": ">=8.1", + "sebastian/diff": "^5.0", + "sebastian/exporter": "^5.0" + }, + "require-dev": { + "phpunit/phpunit": "^10.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "security": "https://github.com/sebastianbergmann/comparator/security/policy", + "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator", + "type": "tidelift" + } + ], + "time": "2026-01-24T09:25:16+00:00" + }, + { + "name": "sebastian/complexity", + "version": "3.2.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "68ff824baeae169ec9f2137158ee529584553799" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/68ff824baeae169ec9f2137158ee529584553799", + "reference": "68ff824baeae169ec9f2137158ee529584553799", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "security": "https://github.com/sebastianbergmann/complexity/security/policy", + "source": "https://github.com/sebastianbergmann/complexity/tree/3.2.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-12-21T08:37:17+00:00" + }, + { + "name": "sebastian/diff", + "version": "5.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/c41e007b4b62af48218231d6c2275e4c9b975b2e", + "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0", + "symfony/process": "^6.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "security": "https://github.com/sebastianbergmann/diff/security/policy", + "source": "https://github.com/sebastianbergmann/diff/tree/5.1.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T07:15:17+00:00" + }, + { + "name": "sebastian/environment", + "version": "6.1.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "8074dbcd93529b357029f5cc5058fd3e43666984" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/8074dbcd93529b357029f5cc5058fd3e43666984", + "reference": "8074dbcd93529b357029f5cc5058fd3e43666984", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "https://github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "security": "https://github.com/sebastianbergmann/environment/security/policy", + "source": "https://github.com/sebastianbergmann/environment/tree/6.1.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-23T08:47:14+00:00" + }, + { + "name": "sebastian/exporter", + "version": "5.1.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "0735b90f4da94969541dac1da743446e276defa6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/0735b90f4da94969541dac1da743446e276defa6", + "reference": "0735b90f4da94969541dac1da743446e276defa6", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=8.1", + "sebastian/recursion-context": "^5.0" + }, + "require-dev": { + "phpunit/phpunit": "^10.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "https://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "security": "https://github.com/sebastianbergmann/exporter/security/policy", + "source": "https://github.com/sebastianbergmann/exporter/tree/5.1.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter", + "type": "tidelift" + } + ], + "time": "2025-09-24T06:09:11+00:00" + }, + { + "name": "sebastian/global-state", + "version": "6.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/987bafff24ecc4c9ac418cab1145b96dd6e9cbd9", + "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "sebastian/object-reflector": "^3.0", + "sebastian/recursion-context": "^5.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "https://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "security": "https://github.com/sebastianbergmann/global-state/security/policy", + "source": "https://github.com/sebastianbergmann/global-state/tree/6.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T07:19:19+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/856e7f6a75a84e339195d48c556f23be2ebf75d0", + "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "support": { + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/2.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-12-21T08:38:20+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "5.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/202d0e344a580d7f7d04b3fafce6933e59dae906", + "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "sebastian/object-reflector": "^3.0", + "sebastian/recursion-context": "^5.0" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/5.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T07:08:32+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "24ed13d98130f0e7122df55d06c5c4942a577957" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/24ed13d98130f0e7122df55d06c5c4942a577957", + "reference": "24ed13d98130f0e7122df55d06c5c4942a577957", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/3.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T07:06:18+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "5.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "47e34210757a2f37a97dcd207d032e1b01e64c7a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/47e34210757a2f37a97dcd207d032e1b01e64c7a", + "reference": "47e34210757a2f37a97dcd207d032e1b01e64c7a", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "https://github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/5.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context", + "type": "tidelift" + } + ], + "time": "2025-08-10T07:50:56+00:00" + }, + { + "name": "sebastian/type", + "version": "4.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "462699a16464c3944eefc02ebdd77882bd3925bf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/462699a16464c3944eefc02ebdd77882bd3925bf", + "reference": "462699a16464c3944eefc02ebdd77882bd3925bf", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "source": "https://github.com/sebastianbergmann/type/tree/4.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T07:10:45+00:00" + }, + { + "name": "sebastian/version", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c51fa83a5d8f43f1402e3f32a005e6262244ef17", + "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "source": "https://github.com/sebastianbergmann/version/tree/4.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-07T11:34:05+00:00" + }, + { + "name": "spatie/backtrace", + "version": "1.8.2", + "source": { + "type": "git", + "url": "https://github.com/spatie/backtrace.git", + "reference": "8ffe78be5ed355b5009e3dd989d183433e9a5adc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/backtrace/zipball/8ffe78be5ed355b5009e3dd989d183433e9a5adc", + "reference": "8ffe78be5ed355b5009e3dd989d183433e9a5adc", + "shasum": "" + }, + "require": { + "php": "^7.3 || ^8.0" + }, + "require-dev": { + "ext-json": "*", + "laravel/serializable-closure": "^1.3 || ^2.0", + "phpunit/phpunit": "^9.3 || ^11.4.3", + "spatie/phpunit-snapshot-assertions": "^4.2 || ^5.1.6", + "symfony/var-dumper": "^5.1|^6.0|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Spatie\\Backtrace\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van de Herten", + "email": "freek@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "A better backtrace", + "homepage": "https://github.com/spatie/backtrace", + "keywords": [ + "Backtrace", + "spatie" + ], + "support": { + "issues": "https://github.com/spatie/backtrace/issues", + "source": "https://github.com/spatie/backtrace/tree/1.8.2" + }, + "funding": [ + { + "url": "https://github.com/sponsors/spatie", + "type": "github" + }, + { + "url": "https://spatie.be/open-source/support-us", + "type": "other" + } + ], + "time": "2026-03-11T13:48:28+00:00" + }, + { + "name": "spatie/error-solutions", + "version": "1.1.3", + "source": { + "type": "git", + "url": "https://github.com/spatie/error-solutions.git", + "reference": "e495d7178ca524f2dd0fe6a1d99a1e608e1c9936" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/error-solutions/zipball/e495d7178ca524f2dd0fe6a1d99a1e608e1c9936", + "reference": "e495d7178ca524f2dd0fe6a1d99a1e608e1c9936", + "shasum": "" + }, + "require": { + "php": "^8.0" + }, + "require-dev": { + "illuminate/broadcasting": "^10.0|^11.0|^12.0", + "illuminate/cache": "^10.0|^11.0|^12.0", + "illuminate/support": "^10.0|^11.0|^12.0", + "livewire/livewire": "^2.11|^3.5.20", + "openai-php/client": "^0.10.1", + "orchestra/testbench": "8.22.3|^9.0|^10.0", + "pestphp/pest": "^2.20|^3.0", + "phpstan/phpstan": "^2.1", + "psr/simple-cache": "^3.0", + "psr/simple-cache-implementation": "^3.0", + "spatie/ray": "^1.28", + "symfony/cache": "^5.4|^6.0|^7.0", + "symfony/process": "^5.4|^6.0|^7.0", + "vlucas/phpdotenv": "^5.5" + }, + "suggest": { + "openai-php/client": "Require get solutions from OpenAI", + "simple-cache-implementation": "To cache solutions from OpenAI" + }, + "type": "library", + "autoload": { + "psr-4": { + "Spatie\\Ignition\\": "legacy/ignition", + "Spatie\\ErrorSolutions\\": "src", + "Spatie\\LaravelIgnition\\": "legacy/laravel-ignition" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ruben Van Assche", + "email": "ruben@spatie.be", + "role": "Developer" + } + ], + "description": "This is my package error-solutions", + "homepage": "https://github.com/spatie/error-solutions", + "keywords": [ + "error-solutions", + "spatie" + ], + "support": { + "issues": "https://github.com/spatie/error-solutions/issues", + "source": "https://github.com/spatie/error-solutions/tree/1.1.3" + }, + "funding": [ + { + "url": "https://github.com/Spatie", + "type": "github" + } + ], + "time": "2025-02-14T12:29:50+00:00" + }, + { + "name": "spatie/flare-client-php", + "version": "1.11.1", + "source": { + "type": "git", + "url": "https://github.com/spatie/flare-client-php.git", + "reference": "53f41b08a27cc039e1a8ed2be9a202e924f31bad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/flare-client-php/zipball/53f41b08a27cc039e1a8ed2be9a202e924f31bad", + "reference": "53f41b08a27cc039e1a8ed2be9a202e924f31bad", + "shasum": "" + }, + "require": { + "illuminate/pipeline": "^8.0|^9.0|^10.0|^11.0|^12.0|^13.0", + "php": "^8.0", + "spatie/backtrace": "^1.6.1", + "symfony/http-foundation": "^5.2|^6.0|^7.0|^8.0", + "symfony/mime": "^5.2|^6.0|^7.0|^8.0", + "symfony/process": "^5.2|^6.0|^7.0|^8.0", + "symfony/var-dumper": "^5.2|^6.0|^7.0|^8.0" + }, + "require-dev": { + "dms/phpunit-arraysubset-asserts": "^0.5.0", + "pestphp/pest": "^1.20|^2.0", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan-deprecation-rules": "^1.0", + "phpstan/phpstan-phpunit": "^1.0", + "spatie/pest-plugin-snapshots": "^1.0|^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.3.x-dev" + } + }, + "autoload": { + "files": [ + "src/helpers.php" + ], + "psr-4": { + "Spatie\\FlareClient\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Send PHP errors to Flare", + "homepage": "https://github.com/spatie/flare-client-php", + "keywords": [ + "exception", + "flare", + "reporting", + "spatie" + ], + "support": { + "issues": "https://github.com/spatie/flare-client-php/issues", + "source": "https://github.com/spatie/flare-client-php/tree/1.11.1" + }, + "funding": [ + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2026-05-15T09:31:32+00:00" + }, + { + "name": "spatie/ignition", + "version": "1.16.0", + "source": { + "type": "git", + "url": "https://github.com/spatie/ignition.git", + "reference": "b59385bb7aa24dae81bcc15850ebecfda7b40838" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/ignition/zipball/b59385bb7aa24dae81bcc15850ebecfda7b40838", + "reference": "b59385bb7aa24dae81bcc15850ebecfda7b40838", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-mbstring": "*", + "php": "^8.0", + "spatie/backtrace": "^1.7.1", + "spatie/error-solutions": "^1.1.2", + "spatie/flare-client-php": "^1.9", + "symfony/console": "^5.4.42|^6.0|^7.0|^8.0", + "symfony/http-foundation": "^5.4.42|^6.0|^7.0|^8.0", + "symfony/mime": "^5.4.42|^6.0|^7.0|^8.0", + "symfony/var-dumper": "^5.4.42|^6.0|^7.0|^8.0" + }, + "require-dev": { + "illuminate/cache": "^9.52|^10.0|^11.0|^12.0|^13.0", + "mockery/mockery": "^1.4", + "pestphp/pest": "^1.20|^2.0|^3.0", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan-deprecation-rules": "^1.0", + "phpstan/phpstan-phpunit": "^1.0", + "psr/simple-cache-implementation": "*", + "symfony/cache": "^5.4.38|^6.0|^7.0|^8.0", + "symfony/process": "^5.4.35|^6.0|^7.0|^8.0", + "vlucas/phpdotenv": "^5.5" + }, + "suggest": { + "openai-php/client": "Require get solutions from OpenAI", + "simple-cache-implementation": "To cache solutions from OpenAI" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.5.x-dev" + } + }, + "autoload": { + "psr-4": { + "Spatie\\Ignition\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Spatie", + "email": "info@spatie.be", + "role": "Developer" + } + ], + "description": "A beautiful error page for PHP applications.", + "homepage": "https://flareapp.io/ignition", + "keywords": [ + "error", + "flare", + "laravel", + "page" + ], + "support": { + "docs": "https://flareapp.io/docs/ignition-for-laravel/introduction", + "forum": "https://twitter.com/flareappio", + "issues": "https://github.com/spatie/ignition/issues", + "source": "https://github.com/spatie/ignition" + }, + "funding": [ + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2026-03-17T10:51:08+00:00" + }, + { + "name": "spatie/laravel-ignition", + "version": "2.9.1", + "source": { + "type": "git", + "url": "https://github.com/spatie/laravel-ignition.git", + "reference": "1baee07216d6748ebd3a65ba97381b051838707a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/laravel-ignition/zipball/1baee07216d6748ebd3a65ba97381b051838707a", + "reference": "1baee07216d6748ebd3a65ba97381b051838707a", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "ext-json": "*", + "ext-mbstring": "*", + "illuminate/support": "^10.0|^11.0|^12.0", + "php": "^8.1", + "spatie/ignition": "^1.15", + "symfony/console": "^6.2.3|^7.0", + "symfony/var-dumper": "^6.2.3|^7.0" + }, + "require-dev": { + "livewire/livewire": "^2.11|^3.3.5", + "mockery/mockery": "^1.5.1", + "openai-php/client": "^0.8.1|^0.10", + "orchestra/testbench": "8.22.3|^9.0|^10.0", + "pestphp/pest": "^2.34|^3.7", + "phpstan/extension-installer": "^1.3.1", + "phpstan/phpstan-deprecation-rules": "^1.1.1|^2.0", + "phpstan/phpstan-phpunit": "^1.3.16|^2.0", + "vlucas/phpdotenv": "^5.5" + }, + "suggest": { + "openai-php/client": "Require get solutions from OpenAI", + "psr/simple-cache-implementation": "Needed to cache solutions from OpenAI" + }, + "type": "library", + "extra": { + "laravel": { + "aliases": { + "Flare": "Spatie\\LaravelIgnition\\Facades\\Flare" + }, + "providers": [ + "Spatie\\LaravelIgnition\\IgnitionServiceProvider" + ] + } + }, + "autoload": { + "files": [ + "src/helpers.php" + ], + "psr-4": { + "Spatie\\LaravelIgnition\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Spatie", + "email": "info@spatie.be", + "role": "Developer" + } + ], + "description": "A beautiful error page for Laravel applications.", + "homepage": "https://flareapp.io/ignition", + "keywords": [ + "error", + "flare", + "laravel", + "page" + ], + "support": { + "docs": "https://flareapp.io/docs/ignition-for-laravel/introduction", + "forum": "https://twitter.com/flareappio", + "issues": "https://github.com/spatie/laravel-ignition/issues", + "source": "https://github.com/spatie/laravel-ignition" + }, + "funding": [ + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2025-02-20T13:13:55+00:00" + }, + { + "name": "symfony/yaml", + "version": "v7.4.13", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "a7ec3b1156faf8815db7683ec7c1e7338e6f977c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/a7ec3b1156faf8815db7683ec7c1e7338e6f977c", + "reference": "a7ec3b1156faf8815db7683ec7c1e7338e6f977c", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "symfony/console": "<6.4" + }, + "require-dev": { + "symfony/console": "^6.4|^7.0|^8.0" + }, + "bin": [ + "Resources/bin/yaml-lint" + ], + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "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": "Loads and dumps YAML files", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/yaml/tree/v7.4.13" + }, + "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-05-25T06:06:12+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.3.1", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "b7489ce515e168639d17feec34b8847c326b0b3c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b7489ce515e168639d17feec34b8847c326b0b3c", + "reference": "b7489ce515e168639d17feec34b8847c326b0b3c", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "support": { + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/1.3.1" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2025-11-17T20:03:58+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": {}, + "prefer-stable": true, + "prefer-lowest": false, + "platform": { + "php": "^8.1" + }, + "platform-dev": {}, + "plugin-api-version": "2.9.0" +} diff --git a/config/app.php b/config/app.php new file mode 100644 index 0000000..363b534 --- /dev/null +++ b/config/app.php @@ -0,0 +1,189 @@ + env('APP_NAME', 'Laravel'), + + /* + |-------------------------------------------------------------------------- + | Application Environment + |-------------------------------------------------------------------------- + | + | This value determines the "environment" your application is currently + | running in. This may determine how you prefer to configure various + | services the application utilizes. Set this in your ".env" file. + | + */ + + 'env' => env('APP_ENV', 'production'), + + /* + |-------------------------------------------------------------------------- + | Application Debug Mode + |-------------------------------------------------------------------------- + | + | When your application is in debug mode, detailed error messages with + | stack traces will be shown on every error that occurs within your + | application. If disabled, a simple generic error page is shown. + | + */ + + 'debug' => (bool) env('APP_DEBUG', false), + + /* + |-------------------------------------------------------------------------- + | Application URL + |-------------------------------------------------------------------------- + | + | This URL is used by the console to properly generate URLs when using + | the Artisan command line tool. You should set this to the root of + | your application so that it is used when running Artisan tasks. + | + */ + + 'url' => env('APP_URL', 'http://localhost'), + + 'asset_url' => env('ASSET_URL'), + + /* + |-------------------------------------------------------------------------- + | Application Timezone + |-------------------------------------------------------------------------- + | + | Here you may specify the default timezone for your application, which + | will be used by the PHP date and date-time functions. We have gone + | ahead and set this to a sensible default for you out of the box. + | + */ + + 'timezone' => 'UTC', + + /* + |-------------------------------------------------------------------------- + | Application Locale Configuration + |-------------------------------------------------------------------------- + | + | The application locale determines the default locale that will be used + | by the translation service provider. You are free to set this value + | to any of the locales which will be supported by the application. + | + */ + + 'locale' => 'en', + + /* + |-------------------------------------------------------------------------- + | Application Fallback Locale + |-------------------------------------------------------------------------- + | + | The fallback locale determines the locale to use when the current one + | is not available. You may change the value to correspond to any of + | the language folders that are provided through your application. + | + */ + + 'fallback_locale' => 'en', + + /* + |-------------------------------------------------------------------------- + | Faker Locale + |-------------------------------------------------------------------------- + | + | This locale will be used by the Faker PHP library when generating fake + | data for your database seeds. For example, this will be used to get + | localized telephone numbers, street address information and more. + | + */ + + 'faker_locale' => 'en_US', + + /* + |-------------------------------------------------------------------------- + | Encryption Key + |-------------------------------------------------------------------------- + | + | This key is used by the Illuminate encrypter service and should be set + | to a random, 32 character string, otherwise these encrypted strings + | will not be safe. Please do this before deploying an application! + | + */ + + 'key' => env('APP_KEY'), + + 'cipher' => 'AES-256-CBC', + + /* + |-------------------------------------------------------------------------- + | Maintenance Mode Driver + |-------------------------------------------------------------------------- + | + | These configuration options determine the driver used to determine and + | manage Laravel's "maintenance mode" status. The "cache" driver will + | allow maintenance mode to be controlled across multiple machines. + | + | Supported drivers: "file", "cache" + | + */ + + 'maintenance' => [ + 'driver' => 'file', + // 'store' => 'redis', + ], + + /* + |-------------------------------------------------------------------------- + | Autoloaded Service Providers + |-------------------------------------------------------------------------- + | + | The service providers listed here will be automatically loaded on the + | request to your application. Feel free to add your own services to + | this array to grant expanded functionality to your applications. + | + */ + + 'providers' => ServiceProvider::defaultProviders()->merge([ + /* + * Package Service Providers... + */ + + /* + * Application Service Providers... + */ + App\Providers\AppServiceProvider::class, + App\Providers\AuthServiceProvider::class, + // App\Providers\BroadcastServiceProvider::class, + App\Providers\EventServiceProvider::class, + App\Providers\RouteServiceProvider::class, + ])->toArray(), + + /* + |-------------------------------------------------------------------------- + | Class Aliases + |-------------------------------------------------------------------------- + | + | This array of class aliases will be registered when this application + | is started. However, feel free to register as many as you wish as + | the aliases are "lazy" loaded so they don't hinder performance. + | + */ + + 'aliases' => Facade::defaultAliases()->merge([ + 'SEOTools' => Artesaos\SEOTools\Facades\SEOTools::class, + // 'Example' => App\Facades\Example::class, + ])->toArray(), + +]; diff --git a/config/auth.php b/config/auth.php new file mode 100644 index 0000000..3601f20 --- /dev/null +++ b/config/auth.php @@ -0,0 +1,122 @@ + [ + 'guard' => 'web', + 'passwords' => 'users', + ], + + /* + |-------------------------------------------------------------------------- + | Authentication Guards + |-------------------------------------------------------------------------- + | + | Next, you may define every authentication guard for your application. + | Of course, a great default configuration has been defined for you + | here which uses session storage and the Eloquent user provider. + | + | All authentication drivers have a user provider. This defines how the + | users are actually retrieved out of your database or other storage + | mechanisms used by this application to persist your user's data. + | + | Supported: "session" + | + */ + + 'guards' => [ + 'web' => [ + 'driver' => 'session', + 'provider' => 'users', + ], + ], + + 'providers' => [ + 'users_mysql2' => [ + 'driver' => 'eloquent', + 'model' => App\Models\User::class, + ], + ], + + /* + |-------------------------------------------------------------------------- + | User Providers + |-------------------------------------------------------------------------- + | + | All authentication drivers have a user provider. This defines how the + | users are actually retrieved out of your database or other storage + | mechanisms used by this application to persist your user's data. + | + | If you have multiple user tables or models you may configure multiple + | sources which represent each model / table. These sources may then + | be assigned to any extra authentication guards you have defined. + | + | Supported: "database", "eloquent" + | + */ + + 'providers' => [ + 'users' => [ + 'driver' => 'eloquent', + 'model' => App\Models\User::class, + ], + + // 'users' => [ + // 'driver' => 'database', + // 'table' => 'users', + // ], + ], + + /* + |-------------------------------------------------------------------------- + | Resetting Passwords + |-------------------------------------------------------------------------- + | + | You may specify multiple password reset configurations if you have more + | than one user table or model in the application and you want to have + | separate password reset settings based on the specific user types. + | + | The expiry time is the number of minutes that each reset token will be + | considered valid. This security feature keeps tokens short-lived so + | they have less time to be guessed. You may change this as needed. + | + | The throttle setting is the number of seconds a user must wait before + | generating more password reset tokens. This prevents the user from + | quickly generating a very large amount of password reset tokens. + | + */ + + 'passwords' => [ + 'users' => [ + 'provider' => 'users', + 'table' => 'password_reset_tokens', + 'expire' => 60, + 'throttle' => 60, + ], + ], + + /* + |-------------------------------------------------------------------------- + | Password Confirmation Timeout + |-------------------------------------------------------------------------- + | + | Here you may define the amount of seconds before a password confirmation + | times out and the user is prompted to re-enter their password via the + | confirmation screen. By default, the timeout lasts for three hours. + | + */ + + 'password_timeout' => 10800, + +]; diff --git a/config/broadcasting.php b/config/broadcasting.php new file mode 100644 index 0000000..2410485 --- /dev/null +++ b/config/broadcasting.php @@ -0,0 +1,71 @@ + env('BROADCAST_DRIVER', 'null'), + + /* + |-------------------------------------------------------------------------- + | Broadcast Connections + |-------------------------------------------------------------------------- + | + | Here you may define all of the broadcast connections that will be used + | to broadcast events to other systems or over websockets. Samples of + | each available type of connection are provided inside this array. + | + */ + + 'connections' => [ + + 'pusher' => [ + 'driver' => 'pusher', + 'key' => env('PUSHER_APP_KEY'), + 'secret' => env('PUSHER_APP_SECRET'), + 'app_id' => env('PUSHER_APP_ID'), + 'options' => [ + 'cluster' => env('PUSHER_APP_CLUSTER'), + 'host' => env('PUSHER_HOST') ?: 'api-'.env('PUSHER_APP_CLUSTER', 'mt1').'.pusher.com', + 'port' => env('PUSHER_PORT', 443), + 'scheme' => env('PUSHER_SCHEME', 'https'), + 'encrypted' => true, + 'useTLS' => env('PUSHER_SCHEME', 'https') === 'https', + ], + 'client_options' => [ + // Guzzle client options: https://docs.guzzlephp.org/en/stable/request-options.html + ], + ], + + 'ably' => [ + 'driver' => 'ably', + 'key' => env('ABLY_KEY'), + ], + + 'redis' => [ + 'driver' => 'redis', + 'connection' => 'default', + ], + + 'log' => [ + 'driver' => 'log', + ], + + 'null' => [ + 'driver' => 'null', + ], + + ], + +]; diff --git a/config/cache.php b/config/cache.php new file mode 100644 index 0000000..d4171e2 --- /dev/null +++ b/config/cache.php @@ -0,0 +1,111 @@ + env('CACHE_DRIVER', 'file'), + + /* + |-------------------------------------------------------------------------- + | Cache Stores + |-------------------------------------------------------------------------- + | + | Here you may define all of the cache "stores" for your application as + | well as their drivers. You may even define multiple stores for the + | same cache driver to group types of items stored in your caches. + | + | Supported drivers: "apc", "array", "database", "file", + | "memcached", "redis", "dynamodb", "octane", "null" + | + */ + + 'stores' => [ + + 'apc' => [ + 'driver' => 'apc', + ], + + 'array' => [ + 'driver' => 'array', + 'serialize' => false, + ], + + 'database' => [ + 'driver' => 'database', + 'table' => 'cache', + 'connection' => null, + 'lock_connection' => null, + ], + + 'file' => [ + 'driver' => 'file', + 'path' => storage_path('framework/cache/data'), + 'lock_path' => storage_path('framework/cache/data'), + ], + + 'memcached' => [ + 'driver' => 'memcached', + 'persistent_id' => env('MEMCACHED_PERSISTENT_ID'), + 'sasl' => [ + env('MEMCACHED_USERNAME'), + env('MEMCACHED_PASSWORD'), + ], + 'options' => [ + // Memcached::OPT_CONNECT_TIMEOUT => 2000, + ], + 'servers' => [ + [ + 'host' => env('MEMCACHED_HOST', '127.0.0.1'), + 'port' => env('MEMCACHED_PORT', 11211), + 'weight' => 100, + ], + ], + ], + + 'redis' => [ + 'driver' => 'redis', + 'connection' => 'cache', + 'lock_connection' => 'default', + ], + + 'dynamodb' => [ + 'driver' => 'dynamodb', + 'key' => env('AWS_ACCESS_KEY_ID'), + 'secret' => env('AWS_SECRET_ACCESS_KEY'), + 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), + 'table' => env('DYNAMODB_CACHE_TABLE', 'cache'), + 'endpoint' => env('DYNAMODB_ENDPOINT'), + ], + + 'octane' => [ + 'driver' => 'octane', + ], + + ], + + /* + |-------------------------------------------------------------------------- + | Cache Key Prefix + |-------------------------------------------------------------------------- + | + | When utilizing the APC, database, memcached, Redis, or DynamoDB cache + | stores there might be other applications using the same cache. For + | that reason, you may prefix every cache key to avoid collisions. + | + */ + + 'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_cache_'), + +]; diff --git a/config/clip.php b/config/clip.php new file mode 100644 index 0000000..75aab76 --- /dev/null +++ b/config/clip.php @@ -0,0 +1,6 @@ + env('CLIP_API_KEY'), + 'payments_url' => env('CLIP_PAYMENTS_URL', 'https://api.payclip.com/payments'), +]; diff --git a/config/cors.php b/config/cors.php new file mode 100644 index 0000000..8a39e6d --- /dev/null +++ b/config/cors.php @@ -0,0 +1,34 @@ + ['api/*', 'sanctum/csrf-cookie'], + + 'allowed_methods' => ['*'], + + 'allowed_origins' => ['*'], + + 'allowed_origins_patterns' => [], + + 'allowed_headers' => ['*'], + + 'exposed_headers' => [], + + 'max_age' => 0, + + 'supports_credentials' => false, + +]; diff --git a/config/database.php b/config/database.php new file mode 100644 index 0000000..3e86576 --- /dev/null +++ b/config/database.php @@ -0,0 +1,172 @@ + env('DB_CONNECTION', 'mysql'), + + /* + |-------------------------------------------------------------------------- + | Database Connections + |-------------------------------------------------------------------------- + | + | Here are each of the database connections setup for your application. + | Of course, examples of configuring each database platform that is + | supported by Laravel is shown below to make development simple. + | + | + | All database work in Laravel is done through the PHP PDO facilities + | so make sure you have the driver for your particular database of + | choice installed on your machine before you begin development. + | + */ + + 'connections' => [ + + 'sqlite' => [ + 'driver' => 'sqlite', + 'url' => env('DATABASE_URL'), + 'database' => env('DB_DATABASE', database_path('database.sqlite')), + 'prefix' => '', + 'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true), + ], + + 'mysql' => [ + 'driver' => 'mysql', + 'url' => env('DATABASE_URL'), + 'host' => env('DB_HOST', '127.0.0.1'), + 'port' => env('DB_PORT', '3306'), + 'database' => env('DB_DATABASE', 'forge'), + 'username' => env('DB_USERNAME', 'forge'), + 'password' => env('DB_PASSWORD', ''), + 'unix_socket' => env('DB_SOCKET', ''), + 'charset' => 'utf8mb4', + 'collation' => 'utf8mb4_unicode_ci', + 'prefix' => '', + 'prefix_indexes' => true, + 'strict' => true, + 'engine' => null, + 'options' => extension_loaded('pdo_mysql') ? array_filter([ + PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'), + ]) : [], + ], + + + 'mysql2' => [ + 'driver' => 'mysql', + 'url' => env('DATABASE_URL_2'), + 'host' => env('DB_HOST_2', '127.0.0.1'), + 'port' => env('DB_PORT_2', '3306'), + 'database' => env('DB_DATABASE_2', 'forge'), + 'username' => env('DB_USERNAME_2', 'forge'), + 'password' => env('DB_PASSWORD_2', ''), + 'unix_socket' => env('DB_SOCKET_2', ''), + 'charset' => 'utf8mb4', + 'collation' => 'utf8mb4_unicode_ci', + 'prefix' => '', + 'prefix_indexes' => true, + 'strict' => true, + 'engine' => null, + 'options' => extension_loaded('pdo_mysql') ? array_filter([ + PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA_2'), + ]) : [], + ], + + 'pgsql' => [ + 'driver' => 'pgsql', + 'url' => env('DATABASE_URL'), + 'host' => env('DB_HOST', '127.0.0.1'), + 'port' => env('DB_PORT', '5432'), + 'database' => env('DB_DATABASE', 'forge'), + 'username' => env('DB_USERNAME', 'forge'), + 'password' => env('DB_PASSWORD', ''), + 'charset' => 'utf8', + 'prefix' => '', + 'prefix_indexes' => true, + 'search_path' => 'public', + 'sslmode' => 'prefer', + ], + + 'sqlsrv' => [ + 'driver' => 'sqlsrv', + 'url' => env('DATABASE_URL'), + 'host' => env('DB_HOST', 'localhost'), + 'port' => env('DB_PORT', '1433'), + 'database' => env('DB_DATABASE', 'forge'), + 'username' => env('DB_USERNAME', 'forge'), + 'password' => env('DB_PASSWORD', ''), + 'charset' => 'utf8', + 'prefix' => '', + 'prefix_indexes' => true, + // 'encrypt' => env('DB_ENCRYPT', 'yes'), + // 'trust_server_certificate' => env('DB_TRUST_SERVER_CERTIFICATE', 'false'), + ], + + ], + + /* + |-------------------------------------------------------------------------- + | Migration Repository Table + |-------------------------------------------------------------------------- + | + | This table keeps track of all the migrations that have already run for + | your application. Using this information, we can determine which of + | the migrations on disk haven't actually been run in the database. + | + */ + + 'migrations' => 'migrations', + + /* + |-------------------------------------------------------------------------- + | Redis Databases + |-------------------------------------------------------------------------- + | + | Redis is an open source, fast, and advanced key-value store that also + | provides a richer body of commands than a typical key-value system + | such as APC or Memcached. Laravel makes it easy to dig right in. + | + */ + + 'redis' => [ + + 'client' => env('REDIS_CLIENT', 'phpredis'), + + 'options' => [ + 'cluster' => env('REDIS_CLUSTER', 'redis'), + 'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_') . '_database_'), + ], + + 'default' => [ + 'url' => env('REDIS_URL'), + 'host' => env('REDIS_HOST', '127.0.0.1'), + 'username' => env('REDIS_USERNAME'), + 'password' => env('REDIS_PASSWORD'), + 'port' => env('REDIS_PORT', '6379'), + 'database' => env('REDIS_DB', '0'), + ], + + 'cache' => [ + 'url' => env('REDIS_URL'), + 'host' => env('REDIS_HOST', '127.0.0.1'), + 'username' => env('REDIS_USERNAME'), + 'password' => env('REDIS_PASSWORD'), + 'port' => env('REDIS_PORT', '6379'), + 'database' => env('REDIS_CACHE_DB', '1'), + ], + + ], + +]; diff --git a/config/dompdf.php b/config/dompdf.php new file mode 100644 index 0000000..677d93c --- /dev/null +++ b/config/dompdf.php @@ -0,0 +1,301 @@ + false, // Throw an Exception on warnings from dompdf + + 'public_path' => null, // Override the public path if needed + + /* + * Dejavu Sans font is missing glyphs for converted entities, turn it off if you need to show € and £. + */ + 'convert_entities' => true, + + 'options' => [ + /** + * The location of the DOMPDF font directory + * + * The location of the directory where DOMPDF will store fonts and font metrics + * Note: This directory must exist and be writable by the webserver process. + * *Please note the trailing slash.* + * + * Notes regarding fonts: + * Additional .afm font metrics can be added by executing load_font.php from command line. + * + * Only the original "Base 14 fonts" are present on all pdf viewers. Additional fonts must + * be embedded in the pdf file or the PDF may not display correctly. This can significantly + * increase file size unless font subsetting is enabled. Before embedding a font please + * review your rights under the font license. + * + * Any font specification in the source HTML is translated to the closest font available + * in the font directory. + * + * The pdf standard "Base 14 fonts" are: + * Courier, Courier-Bold, Courier-BoldOblique, Courier-Oblique, + * Helvetica, Helvetica-Bold, Helvetica-BoldOblique, Helvetica-Oblique, + * Times-Roman, Times-Bold, Times-BoldItalic, Times-Italic, + * Symbol, ZapfDingbats. + */ + 'font_dir' => storage_path('fonts'), // advised by dompdf (https://github.com/dompdf/dompdf/pull/782) + + /** + * The location of the DOMPDF font cache directory + * + * This directory contains the cached font metrics for the fonts used by DOMPDF. + * This directory can be the same as DOMPDF_FONT_DIR + * + * Note: This directory must exist and be writable by the webserver process. + */ + 'font_cache' => storage_path('fonts'), + + /** + * The location of a temporary directory. + * + * The directory specified must be writeable by the webserver process. + * The temporary directory is required to download remote images and when + * using the PDFLib back end. + */ + 'temp_dir' => sys_get_temp_dir(), + + /** + * ==== IMPORTANT ==== + * + * dompdf's "chroot": Prevents dompdf from accessing system files or other + * files on the webserver. All local files opened by dompdf must be in a + * subdirectory of this directory. DO NOT set it to '/' since this could + * allow an attacker to use dompdf to read any files on the server. This + * should be an absolute path. + * This is only checked on command line call by dompdf.php, but not by + * direct class use like: + * $dompdf = new DOMPDF(); $dompdf->load_html($htmldata); $dompdf->render(); $pdfdata = $dompdf->output(); + */ + 'chroot' => realpath(base_path()), + + /** + * Protocol whitelist + * + * Protocols and PHP wrappers allowed in URIs, and the validation rules + * that determine if a resouce may be loaded. Full support is not guaranteed + * for the protocols/wrappers specified + * by this array. + * + * @var array + */ + 'allowed_protocols' => [ + 'data://' => ['rules' => []], + 'file://' => ['rules' => []], + 'http://' => ['rules' => []], + 'https://' => ['rules' => []], + ], + + /** + * Operational artifact (log files, temporary files) path validation + */ + 'artifactPathValidation' => null, + + /** + * @var string + */ + 'log_output_file' => null, + + /** + * Whether to enable font subsetting or not. + */ + 'enable_font_subsetting' => false, + + /** + * The PDF rendering backend to use + * + * Valid settings are 'PDFLib', 'CPDF' (the bundled R&OS PDF class), 'GD' and + * 'auto'. 'auto' will look for PDFLib and use it if found, or if not it will + * fall back on CPDF. 'GD' renders PDFs to graphic files. + * {@link * Canvas_Factory} ultimately determines which rendering class to + * instantiate based on this setting. + * + * Both PDFLib & CPDF rendering backends provide sufficient rendering + * capabilities for dompdf, however additional features (e.g. object, + * image and font support, etc.) differ between backends. Please see + * {@link PDFLib_Adapter} for more information on the PDFLib backend + * and {@link CPDF_Adapter} and lib/class.pdf.php for more information + * on CPDF. Also see the documentation for each backend at the links + * below. + * + * The GD rendering backend is a little different than PDFLib and + * CPDF. Several features of CPDF and PDFLib are not supported or do + * not make any sense when creating image files. For example, + * multiple pages are not supported, nor are PDF 'objects'. Have a + * look at {@link GD_Adapter} for more information. GD support is + * experimental, so use it at your own risk. + * + * @link http://www.pdflib.com + * @link http://www.ros.co.nz/pdf + * @link http://www.php.net/image + */ + 'pdf_backend' => 'CPDF', + + /** + * html target media view which should be rendered into pdf. + * List of types and parsing rules for future extensions: + * http://www.w3.org/TR/REC-html40/types.html + * screen, tty, tv, projection, handheld, print, braille, aural, all + * Note: aural is deprecated in CSS 2.1 because it is replaced by speech in CSS 3. + * Note, even though the generated pdf file is intended for print output, + * the desired content might be different (e.g. screen or projection view of html file). + * Therefore allow specification of content here. + */ + 'default_media_type' => 'screen', + + /** + * The default paper size. + * + * North America standard is "letter"; other countries generally "a4" + * + * @see CPDF_Adapter::PAPER_SIZES for valid sizes ('letter', 'legal', 'A4', etc.) + */ + 'default_paper_size' => 'a4', + + /** + * The default paper orientation. + * + * The orientation of the page (portrait or landscape). + * + * @var string + */ + 'default_paper_orientation' => 'portrait', + + /** + * The default font family + * + * Used if no suitable fonts can be found. This must exist in the font folder. + * + * @var string + */ + 'default_font' => 'serif', + + /** + * Image DPI setting + * + * This setting determines the default DPI setting for images and fonts. The + * DPI may be overridden for inline images by explictly setting the + * image's width & height style attributes (i.e. if the image's native + * width is 600 pixels and you specify the image's width as 72 points, + * the image will have a DPI of 600 in the rendered PDF. The DPI of + * background images can not be overridden and is controlled entirely + * via this parameter. + * + * For the purposes of DOMPDF, pixels per inch (PPI) = dots per inch (DPI). + * If a size in html is given as px (or without unit as image size), + * this tells the corresponding size in pt. + * This adjusts the relative sizes to be similar to the rendering of the + * html page in a reference browser. + * + * In pdf, always 1 pt = 1/72 inch + * + * Rendering resolution of various browsers in px per inch: + * Windows Firefox and Internet Explorer: + * SystemControl->Display properties->FontResolution: Default:96, largefonts:120, custom:? + * Linux Firefox: + * about:config *resolution: Default:96 + * (xorg screen dimension in mm and Desktop font dpi settings are ignored) + * + * Take care about extra font/image zoom factor of browser. + * + * In images, size in pixel attribute, img css style, are overriding + * the real image dimension in px for rendering. + * + * @var int + */ + 'dpi' => 96, + + /** + * Enable embedded PHP + * + * If this setting is set to true then DOMPDF will automatically evaluate embedded PHP contained + * within tags. + * + * ==== IMPORTANT ==== Enabling this for documents you do not trust (e.g. arbitrary remote html pages) + * is a security risk. + * Embedded scripts are run with the same level of system access available to dompdf. + * Set this option to false (recommended) if you wish to process untrusted documents. + * This setting may increase the risk of system exploit. + * Do not change this settings without understanding the consequences. + * Additional documentation is available on the dompdf wiki at: + * https://github.com/dompdf/dompdf/wiki + * + * @var bool + */ + 'enable_php' => false, + + /** + * Enable inline JavaScript + * + * If this setting is set to true then DOMPDF will automatically insert JavaScript code contained + * within tags as written into the PDF. + * NOTE: This is PDF-based JavaScript to be executed by the PDF viewer, + * not browser-based JavaScript executed by Dompdf. + * + * @var bool + */ + 'enable_javascript' => true, + + /** + * Enable remote file access + * + * If this setting is set to true, DOMPDF will access remote sites for + * images and CSS files as required. + * + * ==== IMPORTANT ==== + * This can be a security risk, in particular in combination with isPhpEnabled and + * allowing remote html code to be passed to $dompdf = new DOMPDF(); $dompdf->load_html(...); + * This allows anonymous users to download legally doubtful internet content which on + * tracing back appears to being downloaded by your server, or allows malicious php code + * in remote html pages to be executed by your server with your account privileges. + * + * This setting may increase the risk of system exploit. Do not change + * this settings without understanding the consequences. Additional + * documentation is available on the dompdf wiki at: + * https://github.com/dompdf/dompdf/wiki + * + * @var bool + */ + 'enable_remote' => false, + + /** + * List of allowed remote hosts + * + * Each value of the array must be a valid hostname. + * + * This will be used to filter which resources can be loaded in combination with + * isRemoteEnabled. If enable_remote is FALSE, then this will have no effect. + * + * Leave to NULL to allow any remote host. + * + * @var array|null + */ + 'allowed_remote_hosts' => null, + + /** + * A ratio applied to the fonts height to be more like browsers' line height + */ + 'font_height_ratio' => 1.1, + + /** + * Use the HTML5 Lib parser + * + * @deprecated This feature is now always on in dompdf 2.x + * + * @var bool + */ + 'enable_html5_parser' => true, + ], + +]; diff --git a/config/filesystems.php b/config/filesystems.php new file mode 100644 index 0000000..e9d9dbd --- /dev/null +++ b/config/filesystems.php @@ -0,0 +1,76 @@ + env('FILESYSTEM_DISK', 'local'), + + /* + |-------------------------------------------------------------------------- + | Filesystem Disks + |-------------------------------------------------------------------------- + | + | Here you may configure as many filesystem "disks" as you wish, and you + | may even configure multiple disks of the same driver. Defaults have + | been set up for each driver as an example of the required values. + | + | Supported Drivers: "local", "ftp", "sftp", "s3" + | + */ + + 'disks' => [ + + 'local' => [ + 'driver' => 'local', + 'root' => storage_path('app'), + 'throw' => false, + ], + + 'public' => [ + 'driver' => 'local', + 'root' => storage_path('app/public'), + 'url' => env('APP_URL').'/storage', + 'visibility' => 'public', + 'throw' => false, + ], + + 's3' => [ + 'driver' => 's3', + 'key' => env('AWS_ACCESS_KEY_ID'), + 'secret' => env('AWS_SECRET_ACCESS_KEY'), + 'region' => env('AWS_DEFAULT_REGION'), + 'bucket' => env('AWS_BUCKET'), + 'url' => env('AWS_URL'), + 'endpoint' => env('AWS_ENDPOINT'), + 'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false), + 'throw' => false, + ], + + ], + + /* + |-------------------------------------------------------------------------- + | Symbolic Links + |-------------------------------------------------------------------------- + | + | Here you may configure the symbolic links that will be created when the + | `storage:link` Artisan command is executed. The array keys should be + | the locations of the links and the values should be their targets. + | + */ + + 'links' => [ + public_path('storage') => storage_path('app/public'), + ], + +]; diff --git a/config/hashing.php b/config/hashing.php new file mode 100644 index 0000000..0e8a0bb --- /dev/null +++ b/config/hashing.php @@ -0,0 +1,54 @@ + 'bcrypt', + + /* + |-------------------------------------------------------------------------- + | Bcrypt Options + |-------------------------------------------------------------------------- + | + | Here you may specify the configuration options that should be used when + | passwords are hashed using the Bcrypt algorithm. This will allow you + | to control the amount of time it takes to hash the given password. + | + */ + + 'bcrypt' => [ + 'rounds' => env('BCRYPT_ROUNDS', 12), + 'verify' => true, + ], + + /* + |-------------------------------------------------------------------------- + | Argon Options + |-------------------------------------------------------------------------- + | + | Here you may specify the configuration options that should be used when + | passwords are hashed using the Argon algorithm. These will allow you + | to control the amount of time it takes to hash the given password. + | + */ + + 'argon' => [ + 'memory' => 65536, + 'threads' => 1, + 'time' => 4, + 'verify' => true, + ], + +]; diff --git a/config/logging.php b/config/logging.php new file mode 100644 index 0000000..c44d276 --- /dev/null +++ b/config/logging.php @@ -0,0 +1,131 @@ + env('LOG_CHANNEL', 'stack'), + + /* + |-------------------------------------------------------------------------- + | Deprecations Log Channel + |-------------------------------------------------------------------------- + | + | This option controls the log channel that should be used to log warnings + | regarding deprecated PHP and library features. This allows you to get + | your application ready for upcoming major versions of dependencies. + | + */ + + 'deprecations' => [ + 'channel' => env('LOG_DEPRECATIONS_CHANNEL', 'null'), + 'trace' => false, + ], + + /* + |-------------------------------------------------------------------------- + | Log Channels + |-------------------------------------------------------------------------- + | + | Here you may configure the log channels for your application. Out of + | the box, Laravel uses the Monolog PHP logging library. This gives + | you a variety of powerful log handlers / formatters to utilize. + | + | Available Drivers: "single", "daily", "slack", "syslog", + | "errorlog", "monolog", + | "custom", "stack" + | + */ + + 'channels' => [ + 'stack' => [ + 'driver' => 'stack', + 'channels' => ['single'], + 'ignore_exceptions' => false, + ], + + 'single' => [ + 'driver' => 'single', + 'path' => storage_path('logs/laravel.log'), + 'level' => env('LOG_LEVEL', 'debug'), + 'replace_placeholders' => true, + ], + + 'daily' => [ + 'driver' => 'daily', + 'path' => storage_path('logs/laravel.log'), + 'level' => env('LOG_LEVEL', 'debug'), + 'days' => 14, + 'replace_placeholders' => true, + ], + + 'slack' => [ + 'driver' => 'slack', + 'url' => env('LOG_SLACK_WEBHOOK_URL'), + 'username' => 'Laravel Log', + 'emoji' => ':boom:', + 'level' => env('LOG_LEVEL', 'critical'), + 'replace_placeholders' => true, + ], + + 'papertrail' => [ + 'driver' => 'monolog', + 'level' => env('LOG_LEVEL', 'debug'), + 'handler' => env('LOG_PAPERTRAIL_HANDLER', SyslogUdpHandler::class), + 'handler_with' => [ + 'host' => env('PAPERTRAIL_URL'), + 'port' => env('PAPERTRAIL_PORT'), + 'connectionString' => 'tls://'.env('PAPERTRAIL_URL').':'.env('PAPERTRAIL_PORT'), + ], + 'processors' => [PsrLogMessageProcessor::class], + ], + + 'stderr' => [ + 'driver' => 'monolog', + 'level' => env('LOG_LEVEL', 'debug'), + 'handler' => StreamHandler::class, + 'formatter' => env('LOG_STDERR_FORMATTER'), + 'with' => [ + 'stream' => 'php://stderr', + ], + 'processors' => [PsrLogMessageProcessor::class], + ], + + 'syslog' => [ + 'driver' => 'syslog', + 'level' => env('LOG_LEVEL', 'debug'), + 'facility' => LOG_USER, + 'replace_placeholders' => true, + ], + + 'errorlog' => [ + 'driver' => 'errorlog', + 'level' => env('LOG_LEVEL', 'debug'), + 'replace_placeholders' => true, + ], + + 'null' => [ + 'driver' => 'monolog', + 'handler' => NullHandler::class, + ], + + 'emergency' => [ + 'path' => storage_path('logs/laravel.log'), + ], + ], + +]; diff --git a/config/mail.php b/config/mail.php new file mode 100644 index 0000000..e894b2e --- /dev/null +++ b/config/mail.php @@ -0,0 +1,134 @@ + env('MAIL_MAILER', 'smtp'), + + /* + |-------------------------------------------------------------------------- + | Mailer Configurations + |-------------------------------------------------------------------------- + | + | Here you may configure all of the mailers used by your application plus + | their respective settings. Several examples have been configured for + | you and you are free to add your own as your application requires. + | + | Laravel supports a variety of mail "transport" drivers to be used while + | sending an e-mail. You will specify which one you are using for your + | mailers below. You are free to add additional mailers as required. + | + | Supported: "smtp", "sendmail", "mailgun", "ses", "ses-v2", + | "postmark", "log", "array", "failover", "roundrobin" + | + */ + + 'mailers' => [ + 'smtp' => [ + 'transport' => 'smtp', + 'url' => env('MAIL_URL'), + 'host' => env('MAIL_HOST', 'smtp.mailgun.org'), + 'port' => env('MAIL_PORT', 587), + 'encryption' => env('MAIL_ENCRYPTION', 'tls'), + 'username' => env('MAIL_USERNAME'), + 'password' => env('MAIL_PASSWORD'), + 'timeout' => null, + 'local_domain' => env('MAIL_EHLO_DOMAIN'), + ], + + 'ses' => [ + 'transport' => 'ses', + ], + + 'postmark' => [ + 'transport' => 'postmark', + // 'message_stream_id' => null, + // 'client' => [ + // 'timeout' => 5, + // ], + ], + + 'mailgun' => [ + 'transport' => 'mailgun', + // 'client' => [ + // 'timeout' => 5, + // ], + ], + + 'sendmail' => [ + 'transport' => 'sendmail', + 'path' => env('MAIL_SENDMAIL_PATH', '/usr/sbin/sendmail -bs -i'), + ], + + 'log' => [ + 'transport' => 'log', + 'channel' => env('MAIL_LOG_CHANNEL'), + ], + + 'array' => [ + 'transport' => 'array', + ], + + 'failover' => [ + 'transport' => 'failover', + 'mailers' => [ + 'smtp', + 'log', + ], + ], + + 'roundrobin' => [ + 'transport' => 'roundrobin', + 'mailers' => [ + 'ses', + 'postmark', + ], + ], + ], + + /* + |-------------------------------------------------------------------------- + | Global "From" Address + |-------------------------------------------------------------------------- + | + | You may wish for all e-mails sent by your application to be sent from + | the same address. Here, you may specify a name and address that is + | used globally for all e-mails that are sent by your application. + | + */ + + 'from' => [ + 'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'), + 'name' => env('MAIL_FROM_NAME', 'Example'), + ], + + /* + |-------------------------------------------------------------------------- + | Markdown Mail Settings + |-------------------------------------------------------------------------- + | + | If you are using Markdown based email rendering, you may configure your + | theme and component paths here, allowing you to customize the design + | of the emails. Or, you may simply stick with the Laravel defaults! + | + */ + + 'markdown' => [ + 'theme' => 'default', + + 'paths' => [ + resource_path('views/vendor/mail'), + ], + ], + +]; diff --git a/config/queue.php b/config/queue.php new file mode 100644 index 0000000..01c6b05 --- /dev/null +++ b/config/queue.php @@ -0,0 +1,109 @@ + env('QUEUE_CONNECTION', 'sync'), + + /* + |-------------------------------------------------------------------------- + | Queue Connections + |-------------------------------------------------------------------------- + | + | Here you may configure the connection information for each server that + | is used by your application. A default configuration has been added + | for each back-end shipped with Laravel. You are free to add more. + | + | Drivers: "sync", "database", "beanstalkd", "sqs", "redis", "null" + | + */ + + 'connections' => [ + + 'sync' => [ + 'driver' => 'sync', + ], + + 'database' => [ + 'driver' => 'database', + 'table' => 'jobs', + 'queue' => 'default', + 'retry_after' => 90, + 'after_commit' => false, + ], + + 'beanstalkd' => [ + 'driver' => 'beanstalkd', + 'host' => 'localhost', + 'queue' => 'default', + 'retry_after' => 90, + 'block_for' => 0, + 'after_commit' => false, + ], + + 'sqs' => [ + 'driver' => 'sqs', + 'key' => env('AWS_ACCESS_KEY_ID'), + 'secret' => env('AWS_SECRET_ACCESS_KEY'), + 'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'), + 'queue' => env('SQS_QUEUE', 'default'), + 'suffix' => env('SQS_SUFFIX'), + 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), + 'after_commit' => false, + ], + + 'redis' => [ + 'driver' => 'redis', + 'connection' => 'default', + 'queue' => env('REDIS_QUEUE', 'default'), + 'retry_after' => 90, + 'block_for' => null, + 'after_commit' => false, + ], + + ], + + /* + |-------------------------------------------------------------------------- + | Job Batching + |-------------------------------------------------------------------------- + | + | The following options configure the database and table that store job + | batching information. These options can be updated to any database + | connection and table which has been defined by your application. + | + */ + + 'batching' => [ + 'database' => env('DB_CONNECTION', 'mysql'), + 'table' => 'job_batches', + ], + + /* + |-------------------------------------------------------------------------- + | Failed Queue Jobs + |-------------------------------------------------------------------------- + | + | These options configure the behavior of failed queue job logging so you + | can control which database and table are used to store the jobs that + | have failed. You may change them to any database / table you wish. + | + */ + + 'failed' => [ + 'driver' => env('QUEUE_FAILED_DRIVER', 'database-uuids'), + 'database' => env('DB_CONNECTION', 'mysql'), + 'table' => 'failed_jobs', + ], + +]; diff --git a/config/sanctum.php b/config/sanctum.php new file mode 100644 index 0000000..35d75b3 --- /dev/null +++ b/config/sanctum.php @@ -0,0 +1,83 @@ + explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf( + '%s%s', + 'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1', + Sanctum::currentApplicationUrlWithPort() + ))), + + /* + |-------------------------------------------------------------------------- + | Sanctum Guards + |-------------------------------------------------------------------------- + | + | This array contains the authentication guards that will be checked when + | Sanctum is trying to authenticate a request. If none of these guards + | are able to authenticate the request, Sanctum will use the bearer + | token that's present on an incoming request for authentication. + | + */ + + 'guard' => ['web'], + + /* + |-------------------------------------------------------------------------- + | Expiration Minutes + |-------------------------------------------------------------------------- + | + | This value controls the number of minutes until an issued token will be + | considered expired. This will override any values set in the token's + | "expires_at" attribute, but first-party sessions are not affected. + | + */ + + 'expiration' => null, + + /* + |-------------------------------------------------------------------------- + | Token Prefix + |-------------------------------------------------------------------------- + | + | Sanctum can prefix new tokens in order to take advantage of numerous + | security scanning initiatives maintained by open source platforms + | that notify developers if they commit tokens into repositories. + | + | See: https://docs.github.com/en/code-security/secret-scanning/about-secret-scanning + | + */ + + 'token_prefix' => env('SANCTUM_TOKEN_PREFIX', ''), + + /* + |-------------------------------------------------------------------------- + | Sanctum Middleware + |-------------------------------------------------------------------------- + | + | When authenticating your first-party SPA with Sanctum you may need to + | customize some of the middleware Sanctum uses while processing the + | request. You may change the middleware listed below as required. + | + */ + + 'middleware' => [ + 'authenticate_session' => Laravel\Sanctum\Http\Middleware\AuthenticateSession::class, + 'encrypt_cookies' => App\Http\Middleware\EncryptCookies::class, + 'verify_csrf_token' => App\Http\Middleware\VerifyCsrfToken::class, + ], + +]; diff --git a/config/seotools.php b/config/seotools.php new file mode 100644 index 0000000..ba38b40 --- /dev/null +++ b/config/seotools.php @@ -0,0 +1,69 @@ + env('SEO_TOOLS_INERTIA', false), + 'meta' => [ + /* + * The default configurations to be used by the meta generator. + */ + 'defaults' => [ + 'title' => "It's", // set false to total remove + 'titleBefore' => false, // Put defaults.title before page title, like 'It's Over 9000! - Dashboard' + 'description' => 'For those who helped create the Genki Dama', // set false to total remove + 'separator' => ' - ', + 'keywords' => [], + 'canonical' => false, // Set to null or 'full' to use Url::full(), set to 'current' to use Url::current(), set false to total remove + 'robots' => false, // Set to 'all', 'none' or any combination of index/noindex and follow/nofollow + ], + /* + * Webmaster tags are always added. + */ + 'webmaster_tags' => [ + 'google' => null, + 'bing' => null, + 'alexa' => null, + 'pinterest' => null, + 'yandex' => null, + 'norton' => null, + ], + + 'add_notranslate_class' => false, + ], + 'opengraph' => [ + /* + * The default configurations to be used by the opengraph generator. + */ + 'defaults' => [ + 'title' => 'Over', // set false to total remove + 'description' => 'For those who helped create the Genki Dama', // set false to total remove + 'url' => false, // Set null for using Url::current(), set false to total remove + 'type' => false, + 'site_name' => false, + 'images' => [], + ], + ], + 'twitter' => [ + /* + * The default values to be used by the twitter cards generator. + */ + 'defaults' => [ + //'card' => 'summary', + //'site' => '@LuizVinicius73', + ], + ], + 'json-ld' => [ + /* + * The default configurations to be used by the json-ld generator. + */ + 'defaults' => [ + 'title' => 'Over 9000 Thousand!', // set false to total remove + 'description' => 'For those who helped create the Genki Dama', // set false to total remove + 'url' => false, // Set to null or 'full' to use Url::full(), set to 'current' to use Url::current(), set false to total remove + 'type' => 'WebPage', + 'images' => [], + ], + ], +]; diff --git a/config/services.php b/config/services.php new file mode 100644 index 0000000..2f211db --- /dev/null +++ b/config/services.php @@ -0,0 +1,37 @@ + [ + 'domain' => env('MAILGUN_DOMAIN'), + 'secret' => env('MAILGUN_SECRET'), + 'endpoint' => env('MAILGUN_ENDPOINT', 'api.mailgun.net'), + 'scheme' => 'https', + ], + + 'postmark' => [ + 'token' => env('POSTMARK_TOKEN'), + ], + + 'ses' => [ + 'key' => env('AWS_ACCESS_KEY_ID'), + 'secret' => env('AWS_SECRET_ACCESS_KEY'), + 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), + ], + + 'telegram' => [ + 'token' => env('TELEGRAM_BOT_TOKEN'), + 'base_url' => env('TELEGRAM_API_URL', 'https://api.telegram.org'), + ], +]; diff --git a/config/session.php b/config/session.php new file mode 100644 index 0000000..e738cb3 --- /dev/null +++ b/config/session.php @@ -0,0 +1,214 @@ + env('SESSION_DRIVER', 'file'), + + /* + |-------------------------------------------------------------------------- + | Session Lifetime + |-------------------------------------------------------------------------- + | + | Here you may specify the number of minutes that you wish the session + | to be allowed to remain idle before it expires. If you want them + | to immediately expire on the browser closing, set that option. + | + */ + + 'lifetime' => env('SESSION_LIFETIME', 120), + + 'expire_on_close' => false, + + /* + |-------------------------------------------------------------------------- + | Session Encryption + |-------------------------------------------------------------------------- + | + | This option allows you to easily specify that all of your session data + | should be encrypted before it is stored. All encryption will be run + | automatically by Laravel and you can use the Session like normal. + | + */ + + 'encrypt' => false, + + /* + |-------------------------------------------------------------------------- + | Session File Location + |-------------------------------------------------------------------------- + | + | When using the native session driver, we need a location where session + | files may be stored. A default has been set for you but a different + | location may be specified. This is only needed for file sessions. + | + */ + + 'files' => storage_path('framework/sessions'), + + /* + |-------------------------------------------------------------------------- + | Session Database Connection + |-------------------------------------------------------------------------- + | + | When using the "database" or "redis" session drivers, you may specify a + | connection that should be used to manage these sessions. This should + | correspond to a connection in your database configuration options. + | + */ + + 'connection' => env('SESSION_CONNECTION'), + + /* + |-------------------------------------------------------------------------- + | Session Database Table + |-------------------------------------------------------------------------- + | + | When using the "database" session driver, you may specify the table we + | should use to manage the sessions. Of course, a sensible default is + | provided for you; however, you are free to change this as needed. + | + */ + + 'table' => 'sessions', + + /* + |-------------------------------------------------------------------------- + | Session Cache Store + |-------------------------------------------------------------------------- + | + | While using one of the framework's cache driven session backends you may + | list a cache store that should be used for these sessions. This value + | must match with one of the application's configured cache "stores". + | + | Affects: "apc", "dynamodb", "memcached", "redis" + | + */ + + 'store' => env('SESSION_STORE'), + + /* + |-------------------------------------------------------------------------- + | Session Sweeping Lottery + |-------------------------------------------------------------------------- + | + | Some session drivers must manually sweep their storage location to get + | rid of old sessions from storage. Here are the chances that it will + | happen on a given request. By default, the odds are 2 out of 100. + | + */ + + 'lottery' => [2, 100], + + /* + |-------------------------------------------------------------------------- + | Session Cookie Name + |-------------------------------------------------------------------------- + | + | Here you may change the name of the cookie used to identify a session + | instance by ID. The name specified here will get used every time a + | new session cookie is created by the framework for every driver. + | + */ + + 'cookie' => env( + 'SESSION_COOKIE', + Str::slug(env('APP_NAME', 'laravel'), '_').'_session' + ), + + /* + |-------------------------------------------------------------------------- + | Session Cookie Path + |-------------------------------------------------------------------------- + | + | The session cookie path determines the path for which the cookie will + | be regarded as available. Typically, this will be the root path of + | your application but you are free to change this when necessary. + | + */ + + 'path' => '/', + + /* + |-------------------------------------------------------------------------- + | Session Cookie Domain + |-------------------------------------------------------------------------- + | + | Here you may change the domain of the cookie used to identify a session + | in your application. This will determine which domains the cookie is + | available to in your application. A sensible default has been set. + | + */ + + 'domain' => env('SESSION_DOMAIN'), + + /* + |-------------------------------------------------------------------------- + | HTTPS Only Cookies + |-------------------------------------------------------------------------- + | + | By setting this option to true, session cookies will only be sent back + | to the server if the browser has a HTTPS connection. This will keep + | the cookie from being sent to you when it can't be done securely. + | + */ + + 'secure' => env('SESSION_SECURE_COOKIE'), + + /* + |-------------------------------------------------------------------------- + | HTTP Access Only + |-------------------------------------------------------------------------- + | + | Setting this value to true will prevent JavaScript from accessing the + | value of the cookie and the cookie will only be accessible through + | the HTTP protocol. You are free to modify this option if needed. + | + */ + + 'http_only' => true, + + /* + |-------------------------------------------------------------------------- + | Same-Site Cookies + |-------------------------------------------------------------------------- + | + | This option determines how your cookies behave when cross-site requests + | take place, and can be used to mitigate CSRF attacks. By default, we + | will set this value to "lax" since this is a secure default value. + | + | Supported: "lax", "strict", "none", null + | + */ + + 'same_site' => 'lax', + + /* + |-------------------------------------------------------------------------- + | Partitioned Cookies + |-------------------------------------------------------------------------- + | + | Setting this value to true will tie the cookie to the top-level site for + | a cross-site context. Partitioned cookies are accepted by the browser + | when flagged "secure" and the Same-Site attribute is set to "none". + | + */ + + 'partitioned' => false, + +]; diff --git a/config/sitioweb.php b/config/sitioweb.php new file mode 100644 index 0000000..3d7c66a --- /dev/null +++ b/config/sitioweb.php @@ -0,0 +1,9 @@ + 'Vuelo en globo', + 'sitio_web_erp_id' => '2', + 'numero_atencion_clientes' => '+52 55 1234 5678', + 'correo_atencion_clientes' => '', + 'telegram_chat_id' => '8446032916' +]; \ No newline at end of file diff --git a/config/view.php b/config/view.php new file mode 100644 index 0000000..22b8a18 --- /dev/null +++ b/config/view.php @@ -0,0 +1,36 @@ + [ + resource_path('views'), + ], + + /* + |-------------------------------------------------------------------------- + | Compiled View Path + |-------------------------------------------------------------------------- + | + | This option determines where all the compiled Blade templates will be + | stored for your application. Typically, this is within the storage + | directory. However, as usual, you are free to change this value. + | + */ + + 'compiled' => env( + 'VIEW_COMPILED_PATH', + realpath(storage_path('framework/views')) + ), + +]; diff --git a/cspell.json b/cspell.json new file mode 100644 index 0000000..3b9a39e --- /dev/null +++ b/cspell.json @@ -0,0 +1,68 @@ +{ + "version": "0.2", + + "language": "en,es", + + "languageSettings": [ + { + "languageId": "markdown", + "dictionaries": ["en", "es", "custom"] + }, + { + "languageId": "php", + "dictionaries": ["en", "es", "custom"] + }, + { + "languageId": "javascript", + "dictionaries": ["en", "es", "custom"] + } + ], + + "ignorePaths": [ + "**/node_modules/**", + "**/vendor/**", + "**/dist/**", + "**/build/**", + "**/storage/**", + "**/public/**", + "**/*.min.js", + "**/*.min.css", + "**/*.lock" + ], + + "dictionaryDefinitions": [ + { + "name": "custom", + "path": "./.cspell/custom-words.txt", + "addWords": true + } + ], + + "dictionaries": ["en", "es", "custom"], + + "words": [ + "Laravel", + "Blade", + "Eloquent", + "Alpinejs", + "Livewire", + "Tailwind", + "Select2", + "DataTable", + "middleware", + "csrf", + "API", + "JSON", + "MVC", + "CRUD", + "PDO" + ], + + "ignoreWords": [], + + "caseSensitive": false, + + "allowCompoundWords": true, + + "import": [] +} \ No newline at end of file diff --git a/database/.gitignore b/database/.gitignore new file mode 100644 index 0000000..9b19b93 --- /dev/null +++ b/database/.gitignore @@ -0,0 +1 @@ +*.sqlite* diff --git a/database/factories/UserFactory.php b/database/factories/UserFactory.php new file mode 100644 index 0000000..584104c --- /dev/null +++ b/database/factories/UserFactory.php @@ -0,0 +1,44 @@ + + */ +class UserFactory extends Factory +{ + /** + * The current password being used by the factory. + */ + protected static ?string $password; + + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + 'name' => fake()->name(), + 'email' => fake()->unique()->safeEmail(), + 'email_verified_at' => now(), + 'password' => static::$password ??= Hash::make('password'), + 'remember_token' => Str::random(10), + ]; + } + + /** + * Indicate that the model's email address should be unverified. + */ + public function unverified(): static + { + return $this->state(fn (array $attributes) => [ + 'email_verified_at' => null, + ]); + } +} diff --git a/database/migrations/2014_10_12_000000_create_users_table.php b/database/migrations/2014_10_12_000000_create_users_table.php new file mode 100644 index 0000000..c9817b7 --- /dev/null +++ b/database/migrations/2014_10_12_000000_create_users_table.php @@ -0,0 +1,33 @@ +id(); + $table->string('name'); + $table->string('email')->unique(); + $table->timestamp('email_verified_at')->nullable(); + $table->string('password'); + $table->rememberToken(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('users'); + } +}; diff --git a/database/migrations/2014_10_12_100000_create_password_reset_tokens_table.php b/database/migrations/2014_10_12_100000_create_password_reset_tokens_table.php new file mode 100644 index 0000000..e36064d --- /dev/null +++ b/database/migrations/2014_10_12_100000_create_password_reset_tokens_table.php @@ -0,0 +1,29 @@ +string('email')->primary(); + $table->string('token'); + $table->timestamp('created_at')->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('password_reset_tokens'); + } +}; diff --git a/database/migrations/2014_10_12_100000_create_password_resets_table.php b/database/migrations/2014_10_12_100000_create_password_resets_table.php new file mode 100644 index 0000000..815967c --- /dev/null +++ b/database/migrations/2014_10_12_100000_create_password_resets_table.php @@ -0,0 +1,33 @@ +string('email')->index(); + $table->string('token'); + $table->timestamp('created_at')->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('password_resets'); + } +}; diff --git a/database/migrations/2019_08_19_000000_create_failed_jobs_table.php b/database/migrations/2019_08_19_000000_create_failed_jobs_table.php new file mode 100644 index 0000000..e36d3d0 --- /dev/null +++ b/database/migrations/2019_08_19_000000_create_failed_jobs_table.php @@ -0,0 +1,33 @@ +id(); + $table->string('uuid')->unique(); + $table->text('connection'); + $table->text('queue'); + $table->longText('payload'); + $table->longText('exception'); + $table->timestamp('failed_at')->useCurrent(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('failed_jobs'); + } +}; diff --git a/database/migrations/2019_12_14_000001_create_personal_access_tokens_table.php b/database/migrations/2019_12_14_000001_create_personal_access_tokens_table.php new file mode 100644 index 0000000..6abcc35 --- /dev/null +++ b/database/migrations/2019_12_14_000001_create_personal_access_tokens_table.php @@ -0,0 +1,34 @@ +id(); + $table->morphs('tokenable'); + $table->string('name'); + $table->string('token', 64)->unique(); + $table->text('abilities')->nullable(); + $table->timestamp('last_used_at')->nullable(); + $table->timestamp('expires_at')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('personal_access_tokens'); + } +}; diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php new file mode 100644 index 0000000..a9f4519 --- /dev/null +++ b/database/seeders/DatabaseSeeder.php @@ -0,0 +1,22 @@ +create(); + + // \App\Models\User::factory()->create([ + // 'name' => 'Test User', + // 'email' => 'test@example.com', + // ]); + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..56f5ddc --- /dev/null +++ b/package.json @@ -0,0 +1,13 @@ +{ + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build" + }, + "devDependencies": { + "axios": "^1.6.4", + "laravel-vite-plugin": "^1.0.0", + "vite": "^5.0.0" + } +} diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..bc86714 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,32 @@ + + + + + tests/Unit + + + tests/Feature + + + + + app + + + + + + + + + + + + + + + diff --git a/public/.DS_Store b/public/.DS_Store new file mode 100644 index 0000000..bfe53dd Binary files /dev/null and b/public/.DS_Store differ diff --git a/public/.htaccess b/public/.htaccess new file mode 100644 index 0000000..3aec5e2 --- /dev/null +++ b/public/.htaccess @@ -0,0 +1,21 @@ + + + Options -MultiViews -Indexes + + + RewriteEngine On + + # Handle Authorization Header + RewriteCond %{HTTP:Authorization} . + RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] + + # Redirect Trailing Slashes If Not A Folder... + RewriteCond %{REQUEST_FILENAME} !-d + RewriteCond %{REQUEST_URI} (.+)/$ + RewriteRule ^ %1 [L,R=301] + + # Send Requests To Front Controller... + RewriteCond %{REQUEST_FILENAME} !-d + RewriteCond %{REQUEST_FILENAME} !-f + RewriteRule ^ index.php [L] + diff --git a/public/assets/.DS_Store b/public/assets/.DS_Store new file mode 100644 index 0000000..a8042c1 Binary files /dev/null and b/public/assets/.DS_Store differ diff --git a/public/assets/audio/beep.mp3 b/public/assets/audio/beep.mp3 new file mode 100644 index 0000000..24fb57a Binary files /dev/null and b/public/assets/audio/beep.mp3 differ diff --git a/public/assets/css/aviso.css b/public/assets/css/aviso.css new file mode 100644 index 0000000..5a6b6bd --- /dev/null +++ b/public/assets/css/aviso.css @@ -0,0 +1,180 @@ +/* ==================== */ +/* AVISO DE PRIVACIDAD - ESTILOS AISLADOS */ +/* ==================== */ + +.privacy-container { + max-width: var(--max-width, 1200px); + margin: 0 auto; + padding: 2rem clamp(1rem, 3vw, 2rem); + margin-top: calc(var(--navbar-height, 80px) + 1rem); +} + +/* Encabezado principal */ +.privacy-header { + text-align: center; + margin-bottom: 3rem; + padding: 3rem 2rem; + background: var(--secondary, #1a1d23); + border-radius: 40px; + border: 1px solid var(--border, rgba(255, 255, 255, 0.12)); +} + +.privacy-header h1 { + font-family: "Space Grotesk", sans-serif; + font-size: clamp(2rem, 4vw, 3rem); + font-weight: 700; + background: linear-gradient( + 135deg, + var(--text-primary, #fff), + var(--accent, #04cdff) + ); + -webkit-background-clip: text; + background-clip: text; + color: transparent; + margin-bottom: 0.5rem; +} + +.privacy-header .company { + color: var(--accent, #04cdff); + font-size: 1.1rem; +} + +.privacy-header .date { + font-size: 0.85rem; + color: var(--text-tertiary, #718096); + margin-top: 1rem; +} + +/* Tabla de contenidos */ +.privacy-toc { + background: var(--surface, rgba(26, 29, 35, 0.85)); + backdrop-filter: blur(10px); + border-radius: 24px; + padding: 1.5rem; + margin-bottom: 2rem; + border: 1px solid var(--border, rgba(255, 255, 255, 0.12)); +} + +.privacy-toc h3 { + margin-bottom: 1rem; + color: var(--text-primary, #fff); + font-size: 1.2rem; +} + +.privacy-toc-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); + gap: 0.5rem; +} + +.privacy-toc-link { + color: var(--text-secondary, #a0aec0); + text-decoration: none; + font-size: 0.85rem; + display: block; + padding: 0.5rem 0; + transition: all var(--transition-fast, 0.2s ease); +} + +.privacy-toc-link:hover { + color: var(--accent, #04cdff); + transform: translateX(5px); +} + +/* Secciones */ +.privacy-section { + background: var(--surface, rgba(26, 29, 35, 0.85)); + backdrop-filter: blur(10px); + border-radius: 24px; + padding: 1.8rem; + margin-bottom: 1.5rem; + border: 1px solid var(--border, rgba(255, 255, 255, 0.12)); + transition: all var(--transition-base, 0.4s ease); + scroll-margin-top: 100px; +} + +.privacy-section:hover { + border-color: var(--accent, #04cdff); + box-shadow: var(--shadow-md, 0 8px 32px rgba(0, 0, 0, 0.4)); +} + +.privacy-section h2 { + color: var(--text-primary, #fff); + font-size: 1.5rem; + padding-bottom: 0.5rem; + border-bottom: 2px solid var(--accent, #04cdff); + margin-bottom: 1rem; + font-family: "Space Grotesk", sans-serif; +} + +.privacy-section h3 { + color: var(--accent, #04cdff); + font-size: 1.1rem; + margin: 1.2rem 0 0.8rem 0; +} + +.privacy-section p { + line-height: 1.7; + color: var(--text-secondary, #a0aec0); + margin-bottom: 0.8rem; +} + +.privacy-section ul, +.privacy-section ol { + margin: 0.8rem 0; + padding-left: 1.5rem; + color: var(--text-secondary, #a0aec0); +} + +.privacy-section li { + line-height: 1.7; + margin-bottom: 0.3rem; +} + +.privacy-highlight { + background: rgba(4, 205, 255, 0.08); + border-left: 4px solid var(--accent, #04cdff); + padding: 1.2rem; + margin: 1.2rem 0; + border-radius: 16px; +} + +.privacy-warning { + background: rgba(245, 101, 101, 0.1); + border-left: 4px solid #f56565; + padding: 1.2rem; + margin: 1.2rem 0; + border-radius: 16px; +} + +.privacy-warning strong, +.privacy-highlight strong { + color: var(--text-primary, #fff); +} + +.privacy-legal-note { + text-align: center; + font-size: 0.8rem; + color: var(--text-tertiary, #718096); + margin-top: 2rem; + padding-top: 1.5rem; + border-top: 1px solid var(--border, rgba(255, 255, 255, 0.12)); +} + +@media (max-width: 768px) { + .privacy-container { + margin-top: calc(var(--navbar-height-mobile, 70px) + 0.5rem); + } + + .privacy-section h2 { + font-size: 1.3rem; + } + + .privacy-toc-grid { + grid-template-columns: 1fr; + } + + .privacy-header { + padding: 2rem 1rem; + } +} diff --git a/public/assets/css/checkout.css b/public/assets/css/checkout.css new file mode 100644 index 0000000..08895c6 --- /dev/null +++ b/public/assets/css/checkout.css @@ -0,0 +1,2000 @@ +/* ==================== */ +/* VARIABLES GLOBALES UNIFICADAS */ +/* ==================== */ +:root { + --primary: #0a0b0d; + --secondary: #1a1d23; + --accent: #04cdff; + --accent-gradient: linear-gradient(135deg, #04cdff, #0099cc); + --gold: #ffd700; + --gold-gradient: linear-gradient(135deg, #ffd700, #b8860b); + --text-primary: #ffffff; + --text-secondary: #a0aec0; + --text-tertiary: #718096; + --surface: rgba(26, 29, 35, 0.85); + --surface-light: rgba(255, 255, 255, 0.08); + --border: rgba(255, 255, 255, 0.12); + --success: #38b2ac; + --error: #f56565; + --shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.25); + --shadow-md: 0 8px 32px rgba(0, 0, 0, 0.4); + --shadow-lg: 0 20px 60px rgba(0, 0, 0, 0.5); + --shadow-xl: 0 40px 80px rgba(0, 0, 0, 0.6); + --transition-fast: 200ms cubic-bezier(0.4, 0, 0.2, 1); + --transition-base: 400ms cubic-bezier(0.4, 0, 0.2, 1); + --transition-slow: 600ms cubic-bezier(0.4, 0, 0.2, 1); + --navbar-height: 80px; + --navbar-height-mobile: 70px; + --max-width: 1440px; +} + +body.light-mode { + --primary: #f8fafc; + --secondary: #ffffff; + --surface: rgba(255, 255, 255, 0.95); + --surface-light: rgba(0, 0, 0, 0.04); + --border: rgba(0, 0, 0, 0.12); + --text-primary: #1a202c; + --text-secondary: #4a5568; + --text-tertiary: #718096; + --shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.08); + --shadow-md: 0 8px 32px rgba(0, 0, 0, 0.12); + --shadow-lg: 0 20px 60px rgba(0, 0, 0, 0.15); + --shadow-xl: 0 40px 80px rgba(0, 0, 0, 0.2); +} + +/* Reset y Base */ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +html { + scroll-behavior: smooth; +} + +body { + font-family: + "Inter", + -apple-system, + BlinkMacSystemFont, + sans-serif; + background: var(--primary); + color: var(--text-primary); + overflow-x: hidden; + line-height: 1.6; + transition: + background-color var(--transition-base), + color var(--transition-base); + padding-top: var(--navbar-height); +} + +@media (max-width: 768px) { + body { + padding-top: var(--navbar-height-mobile); + } +} + +/* Utilidades */ +.hide-mobile { + display: none; +} + +@media (min-width: 769px) { + .hide-mobile { + display: flex; + } + .hide-desktop { + display: none !important; + } +} + +/* Scrollbar */ +::-webkit-scrollbar { + width: 10px; +} + +::-webkit-scrollbar-track { + background: var(--secondary); +} + +::-webkit-scrollbar-thumb { + background: var(--surface); + border-radius: 5px; +} + +::-webkit-scrollbar-thumb:hover { + background: var(--surface-light); +} + +/* ==================== */ +/* ANIMACIONES GLOBALES */ +/* ==================== */ +@keyframes fadeInUp { + from { + opacity: 0; + transform: translateY(30px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes slideInRight { + from { + transform: translateX(100%); + opacity: 0; + } + to { + transform: translateX(0); + opacity: 1; + } +} + +@keyframes slideOutRight { + from { + transform: translateX(0); + opacity: 1; + } + to { + transform: translateX(100%); + opacity: 0; + } +} + +/* ==================== */ +/* COMPONENTE: PRELOADER */ +/* ==================== */ +#preloader { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: var(--primary); + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + z-index: 9999; + transition: + opacity var(--transition-slow), + visibility var(--transition-slow); +} + +#preloader.loaded { + opacity: 0; + visibility: hidden; +} + +.preloader-container { + display: flex; + flex-direction: column; + align-items: center; + gap: 2rem; + text-align: center; + max-width: 300px; + width: 90%; +} + +.preloader-logo { + width: 120px; + height: 120px; + position: relative; + animation: float 3s ease-in-out infinite; + display: flex; + align-items: center; + justify-content: center; +} + +.preloader-logo img { + width: 100%; + height: 100%; + object-fit: contain; + filter: drop-shadow(0 0 20px rgba(4, 205, 255, 0.3)); +} + +.preloader-logo::before { + content: ""; + position: absolute; + top: -10px; + left: -10px; + right: -10px; + bottom: -10px; + background: rgba(4, 205, 255, 0.1); + border-radius: 50%; + animation: pulse 2s ease-in-out infinite; + z-index: -1; +} + +.preloader-text { + font-family: "Space Grotesk", sans-serif; + font-size: clamp(1.2rem, 2vw, 1.5rem); + font-weight: 600; + color: var(--text-primary); + margin-top: 1rem; + position: relative; +} + +.preloader-text::after { + content: "cargando."; + animation: dots 3s steps(4, end) infinite; +} + +.preloader-progress { + width: 100%; + height: 4px; + background: rgba(255, 255, 255, 0.1); + border-radius: 2px; + overflow: hidden; + margin-top: 1rem; +} + +.preloader-progress-bar { + width: 0%; + height: 100%; + background: var(--accent-gradient); + border-radius: 2px; + transition: width 0.3s ease; +} + +.preloader-subtext { + color: var(--text-tertiary); + font-size: clamp(0.85rem, 1.5vw, 0.9rem); + margin-top: 0.5rem; + opacity: 0.8; +} + +@keyframes float { + 0%, + 100% { + transform: translateY(0); + } + 50% { + transform: translateY(-20px); + } +} + +@keyframes pulse { + 0%, + 100% { + opacity: 0.5; + transform: scale(1); + } + 50% { + opacity: 1; + transform: scale(1.1); + } +} + +@keyframes dots { + 0%, + 20% { + content: "cargando."; + } + 40%, + 60% { + content: "cargando.."; + } + 80%, + 100% { + content: "cargando..."; + } +} + +/* ==================== */ +/* COMPONENTE: NAVBAR PREMIUM */ +/* ==================== */ +.navbar-premium { + position: fixed; + top: 0; + left: 0; + right: 0; + height: var(--navbar-height); + background: rgba(10, 11, 13, 0.85); + backdrop-filter: blur(20px); + -webkit-backdrop-filter: blur(20px); + border-bottom: 1px solid var(--border); + z-index: 1000; + transition: all var(--transition-base); + padding: 0 clamp(1rem, 3vw, 2rem); +} + +@media (max-width: 768px) { + .navbar-premium { + height: var(--navbar-height-mobile); + padding: 0 1rem; + } + .nav-desktop { + display: none !important; + } + .nav-actions .profile-btn, + .nav-actions .cta-btn { + display: none !important; + } + .hamburger-menu { + display: flex !important; + } +} + +@media (min-width: 769px) and (max-width: 1024px) { + .navbar-premium { + padding: 0 1.5rem; + } + .nav-desktop { + display: none !important; + } + .hamburger-menu { + display: flex !important; + } +} + +.light-mode .navbar-premium { + background: rgba(248, 250, 252, 0.85); +} + +.navbar-container { + max-width: var(--max-width); + height: 100%; + margin: 0 auto; + display: flex; + align-items: center; + justify-content: space-between; +} + +.navbar-logo { + display: flex; + align-items: center; + gap: 12px; + text-decoration: none; + transition: transform var(--transition-fast); +} + +.navbar-logo:hover { + transform: translateY(-2px); +} + +.logo-icon { + width: 42px; + height: 42px; + background: var(--accent-gradient); + border-radius: 12px; + display: flex; + align-items: center; + justify-content: center; + color: white; + font-size: 20px; + font-weight: 800; + box-shadow: var(--shadow-md); + flex-shrink: 0; +} + +.logo-icon img { + width: 100%; + height: 100%; + object-fit: contain; + border-radius: 50%; +} + +@media (max-width: 480px) { + .logo-icon { + width: 36px; + height: 36px; + } +} + +.logo-text-container { + display: flex; + flex-direction: column; + line-height: 1.2; +} + +.logo-main-text { + font-family: "Space Grotesk", sans-serif; + font-size: clamp(1.3rem, 2vw, 1.6rem); + font-weight: 700; + color: var(--text-primary); + letter-spacing: -0.5px; + white-space: nowrap; +} + +.logo-subtext { + font-family: "Space Grotesk", sans-serif; + font-size: clamp(0.7rem, 1.2vw, 0.85rem); + font-weight: 600; + background: var(--gold-gradient); + -webkit-background-clip: text; + background-clip: text; + color: transparent; + letter-spacing: 5.5px; + margin-top: 1px; + text-transform: uppercase; + white-space: nowrap; +} + +.nav-desktop { + display: flex; + gap: 2px; + background: var(--surface); + padding: 4px; + border-radius: 14px; + backdrop-filter: blur(10px); + border: 1px solid var(--border); +} + +.nav-link { + display: flex; + align-items: center; + gap: 10px; + padding: 12px 24px; + text-decoration: none; + color: var(--text-secondary); + font-weight: 500; + font-size: clamp(0.85rem, 1vw, 0.95rem); + border-radius: 10px; + transition: all var(--transition-fast); + position: relative; +} + +.nav-link::after { + content: ""; + position: absolute; + bottom: 0; + left: 50%; + transform: translateX(-50%); + width: 0; + height: 2px; + background: var(--accent); + transition: width var(--transition-base); + border-radius: 2px; +} + +.nav-link:hover { + color: var(--text-primary); + background: var(--surface-light); +} + +.nav-link:hover::after { + width: 60%; +} + +.nav-link.active { + color: var(--text-primary); + background: var(--surface-light); +} + +.nav-link.active::after { + width: 80%; +} + +.theme-toggle { + width: 44px; + height: 44px; + background: var(--surface); + border: 1px solid var(--border); + border-radius: 12px; + display: flex; + align-items: center; + justify-content: center; + color: var(--text-secondary); + cursor: pointer; + transition: all var(--transition-fast); + font-size: 1.1rem; +} + +@media (max-width: 480px) { + .theme-toggle { + width: 40px; + height: 40px; + font-size: 1rem; + } +} + +.theme-toggle:hover { + background: var(--surface-light); + color: var(--text-primary); + transform: translateY(-2px); + border-color: var(--accent); +} + +.light-mode .theme-toggle .fa-sun { + display: block; +} + +.light-mode .theme-toggle .fa-moon { + display: none; +} + +.theme-toggle .fa-sun { + display: none; +} + +.theme-toggle .fa-moon { + display: block; +} + +.nav-actions { + display: flex; + align-items: center; + gap: 1rem; +} + +@media (max-width: 768px) { + .nav-actions { + gap: 0.75rem; + } +} + +.action-btn { + width: 44px; + height: 44px; + background: var(--surface); + border: 1px solid var(--border); + border-radius: 12px; + display: flex; + align-items: center; + justify-content: center; + color: var(--text-secondary); + cursor: pointer; + transition: all var(--transition-fast); + font-size: 1.1rem; + position: relative; +} + +@media (max-width: 480px) { + .action-btn { + width: 40px; + height: 40px; + font-size: 1rem; + } +} + +.action-btn:hover { + background: var(--surface-light); + color: var(--text-primary); + transform: translateY(-2px); + border-color: var(--accent); +} + +.notification-badge { + position: absolute; + top: -4px; + right: -4px; + background: var(--accent); + color: white; + font-size: 0.7rem; + width: 18px; + height: 18px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-weight: 700; + border: 2px solid var(--primary); +} + +.cta-btn { + display: flex; + align-items: center; + gap: 10px; + padding: 12px 28px; + background: var(--accent-gradient); + color: white; + border: none; + border-radius: 12px; + font-weight: 600; + font-size: 0.95rem; + cursor: pointer; + transition: all var(--transition-base); + text-decoration: none; + box-shadow: var(--shadow-md); +} + +@media (max-width: 480px) { + .cta-btn { + padding: 10px 20px; + font-size: 0.9rem; + } +} + +.cta-btn:hover { + transform: translateY(-3px); + box-shadow: var(--shadow-lg); +} + +.cta-btn:hover i { + transform: translateX(3px); +} + +.hamburger-menu { + display: none; + width: 32px; + height: 32px; + flex-direction: column; + justify-content: center; + align-items: center; + cursor: pointer; + position: relative; + z-index: 1002; +} + +@media (max-width: 1024px) { + .hamburger-menu { + display: flex; + } +} + +@media (max-width: 480px) { + .hamburger-menu { + width: 28px; + height: 28px; + } +} + +.hamburger-line { + width: 22px; + height: 2px; + background: var(--text-primary); + border-radius: 2px; + transition: all var(--transition-base); + position: absolute; +} + +.hamburger-line:nth-child(1) { + transform: translateY(-6px); +} + +.hamburger-line:nth-child(3) { + transform: translateY(6px); +} + +.hamburger-menu.active .hamburger-line:nth-child(1) { + transform: rotate(45deg); +} + +.hamburger-menu.active .hamburger-line:nth-child(2) { + opacity: 0; +} + +.hamburger-menu.active .hamburger-line:nth-child(3) { + transform: rotate(-45deg); +} + +.mobile-menu { + position: fixed; + top: 0; + right: -100%; + width: 100%; + max-width: 320px; + height: 100vh; + background: var(--secondary); + backdrop-filter: blur(30px); + -webkit-backdrop-filter: blur(30px); + border-left: 1px solid var(--border); + transition: right var(--transition-base); + z-index: 1001; + padding: 2rem; + display: flex; + flex-direction: column; + overflow-y: auto; +} + +@media (max-width: 480px) { + .mobile-menu { + padding: 1.5rem; + max-width: 280px; + } +} + +.mobile-menu.active { + right: 0; +} + +.mobile-menu-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 2rem; +} + +.mobile-nav { + display: flex; + flex-direction: column; + gap: 8px; + margin-bottom: 2rem; +} + +.mobile-nav-item { + list-style: none; +} + +.mobile-nav-link { + display: flex; + align-items: center; + gap: 12px; + padding: 16px; + text-decoration: none; + color: var(--text-secondary); + border-radius: 12px; + transition: all var(--transition-fast); + font-weight: 500; +} + +.mobile-nav-link:hover, +.mobile-nav-link.active { + background: var(--surface-light); + color: var(--text-primary); +} + +.mobile-actions { + margin-top: auto; + display: flex; + flex-direction: column; + gap: 1rem; + padding-top: 2rem; + border-top: 1px solid var(--border); +} + +.menu-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.7); + backdrop-filter: blur(5px); + -webkit-backdrop-filter: blur(5px); + opacity: 0; + visibility: hidden; + transition: all var(--transition-base); + z-index: 1000; +} + +.menu-overlay.active { + opacity: 1; + visibility: visible; +} + +/* ==================== */ +/* COMPONENTE: MODAL DE BÚSQUEDA */ +/* ==================== */ +.search-modal { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100vh; + background: var(--primary); + z-index: 2000; + display: none; + flex-direction: column; + opacity: 0; + transition: opacity var(--transition-base); +} + +.search-modal.active { + display: flex; + opacity: 1; +} + +.search-modal-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 1.5rem clamp(1rem, 3vw, 2rem); + border-bottom: 1px solid var(--border); +} + +.search-modal-title { + font-family: "Space Grotesk", sans-serif; + font-size: clamp(1.5rem, 3vw, 2rem); + font-weight: 700; + color: var(--text-primary); +} + +.search-close-btn { + width: 44px; + height: 44px; + background: var(--surface); + border: 1px solid var(--border); + border-radius: 12px; + display: flex; + align-items: center; + justify-content: center; + color: var(--text-secondary); + cursor: pointer; + transition: all var(--transition-fast); + font-size: 1.2rem; +} + +.search-close-btn:hover { + background: var(--surface-light); + color: var(--text-primary); + transform: rotate(90deg); +} + +.search-modal-content { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 2rem; + max-width: 800px; + margin: 0 auto; + width: 100%; +} + +.search-input-container { + width: 100%; + position: relative; + margin-bottom: 3rem; +} + +.search-input { + width: 100%; + height: 70px; + padding: 0 2rem 0 5rem; + background: var(--secondary); + border: 2px solid var(--border); + border-radius: 20px; + color: var(--text-primary); + font-family: "Inter", sans-serif; + font-size: clamp(1.2rem, 2vw, 1.5rem); + transition: all var(--transition-base); + outline: none; +} + +.search-input:focus { + border-color: var(--accent); + box-shadow: 0 0 0 4px rgba(4, 205, 255, 0.15); + transform: translateY(-2px); +} + +.search-icon { + position: absolute; + left: 1.5rem; + top: 50%; + transform: translateY(-50%); + color: var(--accent); + font-size: 1.5rem; +} + +.search-results { + width: 100%; + display: grid; + grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); + gap: 1.5rem; + opacity: 0; + transform: translateY(20px); + transition: all var(--transition-base); +} + +.search-results.active { + opacity: 1; + transform: translateY(0); +} + +.search-result-item { + background: var(--surface); + border: 1px solid var(--border); + border-radius: 16px; + padding: 1.5rem; + transition: all var(--transition-fast); + cursor: pointer; +} + +.search-result-item:hover { + transform: translateY(-5px); + border-color: var(--accent); + box-shadow: var(--shadow-lg); +} + +.search-result-title { + font-size: 1.1rem; + font-weight: 600; + margin-bottom: 0.5rem; + color: var(--text-primary); +} + +.search-result-description { + color: var(--text-secondary); + font-size: 0.9rem; + line-height: 1.5; +} + +/* ==================== */ +/* COMPONENTE: FOOTER PREMIUM */ +/* ==================== */ +.footer { + background: var(--secondary); + border-top: 1px solid var(--border); + padding: clamp(3rem, 6vw, 5rem) clamp(1rem, 3vw, 2rem) 1.5rem; +} + +.footer-container { + max-width: var(--max-width); + margin: 0 auto; +} + +.footer-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: clamp(2rem, 4vw, 3rem); + margin-bottom: 3rem; +} + +.footer-brand { + max-width: 300px; +} + +.footer-logo { + display: flex; + align-items: center; + gap: 12px; + text-decoration: none; + margin-bottom: 1.5rem; +} + +.footer-logo-icon { + width: 45px; + height: 45px; + background: var(--accent-gradient); + border-radius: 12px; + display: flex; + align-items: center; + justify-content: center; + color: white; + font-size: 1.3rem; + font-weight: 800; +} + +.footer-logo-text { + font-family: "Space Grotesk", sans-serif; + font-size: 1.4rem; + font-weight: 700; + color: var(--text-primary); +} + +.footer-logo-sub { + font-size: 0.7rem; + font-weight: 600; + background: var(--gold-gradient); + -webkit-background-clip: text; + background-clip: text; + color: transparent; + letter-spacing: 4px; +} + +.footer-description { + color: var(--text-secondary); + font-size: 0.9rem; + line-height: 1.6; + margin-bottom: 1.5rem; +} + +.footer-social { + display: flex; + gap: 1rem; +} + +.social-link { + width: 40px; + height: 40px; + background: var(--surface-light); + border-radius: 12px; + display: flex; + align-items: center; + justify-content: center; + color: var(--text-secondary); + transition: all var(--transition-fast); + text-decoration: none; + font-size: 1.1rem; +} + +.social-link:hover { + background: var(--accent); + color: white; + transform: translateY(-3px); +} + +.footer-title { + font-size: 1.1rem; + font-weight: 700; + margin-bottom: 1.5rem; + color: var(--text-primary); + position: relative; + display: inline-block; +} + +.footer-title::after { + content: ""; + position: absolute; + bottom: -8px; + left: 0; + width: 40px; + height: 2px; + background: var(--accent); + border-radius: 2px; +} + +.footer-links { + list-style: none; + padding: 0; +} + +.footer-links li { + margin-bottom: 0.75rem; +} + +.footer-links a { + color: var(--text-secondary); + text-decoration: none; + transition: all var(--transition-fast); + font-size: 0.9rem; + display: inline-flex; + align-items: center; + gap: 8px; +} + +.footer-links a:hover { + color: var(--accent); + transform: translateX(5px); +} + +.footer-contact-item { + display: flex; + align-items: center; + gap: 12px; + margin-bottom: 1rem; + color: var(--text-secondary); + font-size: 0.9rem; +} + +.footer-contact-item i { + width: 20px; + color: var(--accent); + font-size: 1rem; +} + +.footer-bottom { + border-top: 1px solid var(--border); + padding-top: 2rem; + display: flex; + justify-content: space-between; + align-items: center; + flex-wrap: wrap; + gap: 1rem; +} + +.copyright { + color: var(--text-tertiary); + font-size: 0.85rem; +} + +.footer-legal { + display: flex; + gap: 1.5rem; + flex-wrap: wrap; +} + +.footer-legal a { + color: var(--text-tertiary); + text-decoration: none; + font-size: 0.85rem; + transition: color var(--transition-fast); +} + +.footer-legal a:hover { + color: var(--accent); +} + +@media (max-width: 768px) { + .footer-bottom { + flex-direction: column; + text-align: center; + } + .footer-legal { + justify-content: center; + } + .footer-brand { + max-width: 100%; + text-align: center; + } + .footer-logo { + justify-content: center; + } + .footer-social { + justify-content: center; + } + .footer-title::after { + left: 50%; + transform: translateX(-50%); + } + .footer-title { + text-align: center; + display: block; + } + .footer-contact-item { + justify-content: center; + } + .footer-links { + text-align: center; + } + .footer-links a { + justify-content: center; + } +} + +/* ==================== */ +/* COMPONENTE: CHATBOT WIDGET */ +/* ==================== */ +.chatbot-widget { + position: fixed; + bottom: 30px; + right: 30px; + z-index: 2000; +} + +.chatbot-toggle { + width: 65px; + height: 65px; + background: var(--accent-gradient); + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + box-shadow: var(--shadow-lg); + transition: all var(--transition-base); + border: 2px solid rgba(255, 255, 255, 0.2); + animation: pulseRing 2s ease-in-out infinite; +} + +.chatbot-toggle:hover { + transform: scale(1.1); + box-shadow: var(--shadow-xl); + animation: none; +} + +@keyframes pulseRing { + 0% { + box-shadow: 0 0 0 0 rgba(4, 205, 255, 0.7); + } + 70% { + box-shadow: 0 0 0 15px rgba(4, 205, 255, 0); + } + 100% { + box-shadow: 0 0 0 0 rgba(4, 205, 255, 0); + } +} + +.chatbot-toggle i { + font-size: 28px; + color: white; +} + +.chatbot-container { + position: absolute; + bottom: 80px; + right: 0; + width: 380px; + height: 550px; + background: var(--secondary); + border-radius: 24px; + overflow: hidden; + box-shadow: var(--shadow-xl); + border: 1px solid var(--border); + display: none; + flex-direction: column; + animation: slideInRight 0.3s ease-out; +} + +.chatbot-container.active { + display: flex; +} + +.chatbot-header { + background: var(--accent-gradient); + padding: 18px 20px; + display: flex; + align-items: center; + justify-content: space-between; + color: white; +} + +.chatbot-header-info { + display: flex; + align-items: center; + gap: 12px; +} + +.chatbot-header-info i { + font-size: 28px; +} + +.chatbot-header-info h3 { + font-size: 16px; + font-weight: 600; + margin: 0; +} + +.chatbot-header-info p { + font-size: 12px; + opacity: 0.8; + margin: 0; +} + +.chatbot-close { + background: rgba(255, 255, 255, 0.2); + border: none; + width: 32px; + height: 32px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + color: white; + transition: all var(--transition-fast); +} + +.chatbot-close:hover { + background: rgba(255, 255, 255, 0.3); + transform: rotate(90deg); +} + +.chatbot-messages { + flex: 1; + overflow-y: auto; + padding: 20px; + display: flex; + flex-direction: column; + gap: 12px; +} + +.chatbot-message { + max-width: 85%; + padding: 12px 16px; + border-radius: 18px; + font-size: 14px; + line-height: 1.5; + animation: fadeInUp 0.3s ease-out; +} + +.chatbot-message.bot { + background: var(--surface-light); + color: var(--text-primary); + align-self: flex-start; + border-bottom-left-radius: 6px; +} + +.chatbot-message.user { + background: var(--accent); + color: white; + align-self: flex-end; + border-bottom-right-radius: 6px; +} + +.chatbot-message.system { + background: rgba(56, 178, 172, 0.2); + color: var(--success); + align-self: center; + font-size: 12px; + padding: 8px 16px; + border-radius: 30px; + max-width: 90%; +} + +.chatbot-input { + padding: 16px; + border-top: 1px solid var(--border); + display: flex; + gap: 12px; + background: var(--secondary); +} + +.chatbot-input input { + flex: 1; + padding: 12px 16px; + background: var(--surface-light); + border: 1px solid var(--border); + border-radius: 30px; + color: var(--text-primary); + font-size: 14px; + outline: none; + transition: all var(--transition-fast); +} + +.chatbot-input input:focus { + border-color: var(--accent); +} + +.chatbot-input input::placeholder { + color: var(--text-tertiary); +} + +.chatbot-input button { + padding: 12px 20px; + background: var(--accent-gradient); + border: none; + border-radius: 30px; + color: white; + font-weight: 600; + cursor: pointer; + transition: all var(--transition-fast); +} + +.chatbot-input button:hover:not(:disabled) { + transform: translateY(-2px); +} + +.chatbot-input button:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +@media (max-width: 480px) { + .chatbot-container { + width: calc(100vw - 40px); + height: 500px; + right: 0; + bottom: 80px; + } + .chatbot-widget { + bottom: 20px; + right: 20px; + } + .chatbot-toggle { + width: 55px; + height: 55px; + } + .chatbot-toggle i { + font-size: 24px; + } +} + +/* ==================== */ +/* COMPONENTE: HERO SECTION */ +/* ==================== */ +.hero-section { + position: relative; + min-height: 100vh; + width: 100%; + overflow: hidden; + display: flex; + align-items: center; +} + +@media (max-width: 768px) { + .hero-section { + min-height: auto; + padding: 2rem 0; + } +} + +.hero-content { + position: relative; + z-index: 2; + width: 100%; + padding: 4rem clamp(1rem, 3vw, 2rem); + max-width: var(--max-width); + margin: 0 auto; +} + +.video-container { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + overflow: hidden; +} + +.hero-video { + width: 100%; + height: 100%; + object-fit: cover; + object-position: center; + filter: brightness(0.7); +} + +.video-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: linear-gradient( + to bottom, + rgba(10, 11, 13, 0.2) 0%, + rgba(10, 11, 13, 0.4) 30%, + rgba(10, 11, 13, 0.7) 70%, + rgba(10, 11, 13, 0.9) 100% + ); + z-index: 1; +} + +.light-mode .video-overlay { + background: linear-gradient( + to bottom, + rgba(248, 250, 252, 0.3) 0%, + rgba(248, 250, 252, 0.5) 30%, + rgba(248, 250, 252, 0.7) 70%, + rgba(248, 250, 252, 0.9) 100% + ); +} + +.hero-text { + max-width: 800px; +} + +.hero-subtitle { + display: inline-block; + background: var(--surface); + backdrop-filter: blur(10px); + padding: clamp(6px, 1.5vw, 8px) clamp(16px, 2.5vw, 20px); + border-radius: 50px; + font-size: clamp(0.8rem, 1vw, 0.9rem); + font-weight: 500; + color: var(--accent); + margin-bottom: 1.5rem; + border: 1px solid var(--border); + animation: fadeInUp 1s ease-out; +} + +.hero-title { + font-family: "Space Grotesk", sans-serif; + font-size: clamp(2rem, 5vw, 4.5rem); + font-weight: 800; + line-height: 1.1; + margin-bottom: clamp(1rem, 2vw, 1.5rem); + background: linear-gradient(to right, var(--text-primary), var(--accent)); + -webkit-background-clip: text; + background-clip: text; + color: transparent; + animation: fadeInUp 1s ease-out 0.2s both; +} + +.hero-description { + font-size: clamp(0.95rem, 2vw, 1.25rem); + color: var(--text-secondary); + max-width: 600px; + margin-bottom: clamp(1.5rem, 3vw, 2rem); + animation: fadeInUp 1s ease-out 0.4s both; +} + +.hero-stats { + display: flex; + gap: clamp(1.5rem, 4vw, 3rem); + margin-top: clamp(2rem, 4vw, 3rem); + animation: fadeInUp 1s ease-out 0.6s both; +} + +@media (max-width: 768px) { + .hero-stats { + flex-wrap: wrap; + justify-content: center; + gap: 2rem; + } +} + +.stat-item { + display: flex; + flex-direction: column; + align-items: center; + text-align: center; +} + +.stat-number { + font-size: clamp(2rem, 4vw, 2.5rem); + font-weight: 800; + background: var(--accent-gradient); + -webkit-background-clip: text; + background-clip: text; + color: transparent; + line-height: 1; + margin-bottom: 0.5rem; +} + +.stat-label { + font-size: clamp(0.8rem, 1vw, 0.9rem); + color: var(--text-tertiary); + text-transform: uppercase; + letter-spacing: 0.5px; +} + +/* ==================== */ +/* COMPONENTE: VUELOS CARDS */ +/* ==================== */ +.vuelos-section { + padding: 5rem clamp(1rem, 3vw, 2rem); + max-width: var(--max-width); + margin: 0 auto; +} + +.block-title { + text-align: center; + margin-bottom: 3rem; +} + +.block-title p { + color: var(--accent); + font-size: 1rem; + text-transform: uppercase; + letter-spacing: 2px; + margin-bottom: 0.5rem; + font-weight: 500; +} + +.block-title h3 { + font-family: "Space Grotesk", sans-serif; + font-size: clamp(2rem, 4vw, 2.8rem); + font-weight: 700; + background: linear-gradient(135deg, var(--text-primary), var(--accent)); + -webkit-background-clip: text; + background-clip: text; + color: transparent; +} + +.row { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); + gap: 2rem; +} + +.tour-one__single { + background: var(--secondary); + border-radius: 24px; + overflow: hidden; + transition: all var(--transition-base); + border: 1px solid var(--border); + position: relative; +} + +.tour-one__single:hover { + transform: translateY(-12px); + border-color: var(--accent); + box-shadow: var(--shadow-xl); +} + +.tour-one__image { + position: relative; + overflow: hidden; + aspect-ratio: 370 / 292; +} + +.tour-one__image img { + width: 100%; + height: 100%; + object-fit: cover; + transition: transform var(--transition-slow); + cursor: pointer; +} + +.tour-one__single:hover .tour-one__image img { + transform: scale(1.05); +} + +.tour-one__content { + padding: 1.5rem; + position: relative; + background: var(--secondary); +} + +.tour-one__content h3 { + font-size: 1.35rem; + font-weight: 700; + margin-bottom: 0.75rem; + line-height: 1.3; +} + +.tour-one__content h3 a { + color: var(--text-primary); + text-decoration: none; + transition: color var(--transition-fast); +} + +.tour-one__content h3 a:hover { + color: var(--accent); +} + +.tour-one__content p { + margin: 0.5rem 0; + color: var(--text-secondary); + font-size: 0.95rem; +} + +.tour-one__content p span { + font-size: 1.6rem; + font-weight: 800; + background: var(--accent-gradient); + -webkit-background-clip: text; + background-clip: text; + color: transparent; + letter-spacing: -0.5px; +} + +.tour-one__meta { + display: flex; + gap: 1.25rem; + list-style: none; + margin-top: 1.25rem; + padding-top: 1rem; + border-top: 1px solid var(--border); +} + +.tour-one__meta li { + display: flex; + align-items: center; +} + +.tour-one__meta a { + display: flex; + align-items: center; + gap: 0.5rem; + color: var(--text-tertiary); + text-decoration: none; + font-size: 0.85rem; + transition: color var(--transition-fast); +} + +.tour-one__meta a:hover { + color: var(--accent); +} + +.tour-one__meta i { + font-size: 0.9rem; + color: var(--accent); +} + +/* ==================== */ +/* COMPONENTE: CONÓCENOS */ +/* ==================== */ +.conocenos-section { + padding: 5rem clamp(1rem, 3vw, 2rem); + max-width: var(--max-width); + margin: 2rem auto; + background: var(--secondary); + border-radius: 40px; +} + +.conocenos-header { + text-align: center; + margin-bottom: 3rem; +} + +.conocenos-subtitle { + color: var(--accent); + font-size: 1rem; + text-transform: uppercase; + letter-spacing: 2px; + font-weight: 500; + margin-bottom: 0.5rem; + display: inline-block; + background: var(--surface-light); + padding: 0.5rem 1.5rem; + border-radius: 50px; +} + +.conocenos-title { + font-family: "Space Grotesk", sans-serif; + font-size: clamp(2rem, 4vw, 2.8rem); + font-weight: 700; + background: linear-gradient(135deg, var(--text-primary), var(--accent)); + -webkit-background-clip: text; + background-clip: text; + color: transparent; + margin-top: 1rem; +} + +.conocenos-grid { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 2rem; + margin-bottom: 4rem; +} + +@media (max-width: 1024px) { + .conocenos-grid { + grid-template-columns: repeat(2, 1fr); + gap: 1.5rem; + } +} + +@media (max-width: 640px) { + .conocenos-grid { + grid-template-columns: 1fr; + gap: 1.5rem; + } +} + +.conocenos-card { + background: var(--surface); + border: 1px solid var(--border); + border-radius: 28px; + padding: 2rem 1.5rem; + text-align: center; + transition: all var(--transition-base); + position: relative; + overflow: hidden; +} + +.conocenos-card::before { + content: ""; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 4px; + background: var(--accent-gradient); + transform: scaleX(0); + transform-origin: left; + transition: transform var(--transition-base); +} + +.conocenos-card:hover { + transform: translateY(-8px); + border-color: var(--accent); + box-shadow: var(--shadow-lg); +} + +.conocenos-card:hover::before { + transform: scaleX(1); +} + +.conocenos-icon { + width: 80px; + height: 80px; + background: var(--accent-gradient); + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + margin: 0 auto 1.5rem; + font-size: 2rem; + color: white; + box-shadow: var(--shadow-md); + transition: transform var(--transition-fast); +} + +.conocenos-card:hover .conocenos-icon { + transform: scale(1.1) rotate(5deg); +} + +.conocenos-card h3 { + font-size: 1.5rem; + font-weight: 700; + margin-bottom: 0.5rem; + color: var(--text-primary); +} + +.conocenos-card h3 span { + display: block; + font-size: 1rem; + font-weight: 400; + color: var(--accent); + margin-top: 0.25rem; +} + +.conocenos-card p { + color: var(--text-secondary); + font-size: 0.9rem; + line-height: 1.5; +} + +.btn-reserva { + display: inline-flex; + align-items: center; + gap: 12px; + padding: 1rem 2.5rem; + background: var(--accent-gradient); + color: white; + text-decoration: none; + border-radius: 50px; + font-weight: 700; + font-size: 1.1rem; + transition: all var(--transition-base); + box-shadow: var(--shadow-md); + border: none; + cursor: pointer; +} + +.btn-reserva:hover { + transform: translateY(-3px); + box-shadow: var(--shadow-lg); +} + +.btn-reserva:hover i { + transform: translateX(5px); +} + +/* ==================== */ +/* COMPONENTE: EXTRAS */ +/* ==================== */ +.extras-section { + padding: 5rem clamp(1rem, 3vw, 2rem); + max-width: var(--max-width); + margin: 2rem auto; + background: var(--primary); + border-radius: 40px; +} + +.extras-header { + text-align: center; + margin-bottom: 3rem; +} + +.extras-subtitle { + display: inline-flex; + align-items: center; + gap: 8px; + background: var(--surface-light); + padding: 0.5rem 1.5rem; + border-radius: 50px; + color: var(--accent); + font-size: 0.9rem; + font-weight: 500; + text-transform: uppercase; + letter-spacing: 2px; + margin-bottom: 1rem; +} + +.extras-title { + font-family: "Space Grotesk", sans-serif; + font-size: clamp(2rem, 4vw, 2.8rem); + font-weight: 700; + background: linear-gradient(135deg, var(--text-primary), var(--accent)); + -webkit-background-clip: text; + background-clip: text; + color: transparent; + margin-bottom: 1rem; +} + +.extras-description { + color: var(--text-secondary); + font-size: 1.1rem; + max-width: 700px; + margin: 0 auto; +} + +.extras-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 2rem; + margin-bottom: 4rem; +} + +@media (max-width: 1024px) { + .extras-grid { + grid-template-columns: repeat(2, 1fr); + gap: 1.5rem; + } +} + +@media (max-width: 640px) { + .extras-grid { + grid-template-columns: 1fr; + gap: 1.5rem; + } +} + +.extra-card { + background: var(--secondary); + border: 1px solid var(--border); + border-radius: 28px; + padding: 2rem 1.5rem; + text-align: center; + transition: all var(--transition-base); + position: relative; + overflow: hidden; + backdrop-filter: blur(10px); +} + +.extra-card::before { + content: ""; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 4px; + background: var(--accent-gradient); + transform: scaleX(0); + transform-origin: left; + transition: transform var(--transition-base); +} + +.extra-card:hover { + transform: translateY(-8px); + border-color: var(--accent); + box-shadow: var(--shadow-lg); +} + +.extra-card:hover::before { + transform: scaleX(1); +} + +.extra-icon { + width: 80px; + height: 80px; + background: var(--accent-gradient); + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + margin: 0 auto 1.5rem; + font-size: 2rem; + color: white; + box-shadow: var(--shadow-md); + transition: transform var(--transition-fast); +} + +.extra-card:hover .extra-icon { + transform: scale(1.1) rotate(5deg); +} + +.extra-card h3 { + font-size: 1.5rem; + font-weight: 700; + margin-bottom: 0.5rem; + color: var(--text-primary); +} + +.extra-card p { + color: var(--text-secondary); + font-size: 0.9rem; + line-height: 1.5; + margin-bottom: 1.5rem; +} + +.extra-link { + display: inline-flex; + align-items: center; + gap: 8px; + color: var(--accent); + font-weight: 600; + text-decoration: none; + font-size: 0.9rem; + transition: gap var(--transition-fast); +} + +.extra-link:hover { + gap: 12px; +} + +.extra-link i { + font-size: 0.8rem; + transition: transform var(--transition-fast); +} + +.extra-link:hover i { + transform: translateX(3px); +} + +.btn-extras { + display: inline-flex; + align-items: center; + gap: 10px; + padding: 1rem 2rem; + background: var(--surface-light); + border: 1px solid var(--border); + border-radius: 50px; + color: var(--text-primary); + font-weight: 600; + text-decoration: none; + transition: all var(--transition-base); +} + +.btn-extras:hover { + background: var(--accent); + color: white; + border-color: var(--accent); + transform: translateY(-3px); + box-shadow: var(--shadow-md); +} + +.btn-extras:hover i { + transform: translateX(5px); +} diff --git a/public/assets/css/contacto.css b/public/assets/css/contacto.css new file mode 100644 index 0000000..8dba1db --- /dev/null +++ b/public/assets/css/contacto.css @@ -0,0 +1,535 @@ +/* ============================================================ + CONTACTO PAGE — UX/UI ENHANCEMENTS + Todos los selectores son específicos a esta página. + No afectan al layout ni a otros blades. + ============================================================ */ + +/* ---------- ENTRADA ANIMADA DE SECCIONES (Intersection Observer) ---------- */ +.contact-cards .contact-card-modern, +.contact-wrapper, +.map-container-modern { + opacity: 0; + transform: translateY(32px); + transition: + opacity 0.55s cubic-bezier(0.22, 1, 0.36, 1), + transform 0.55s cubic-bezier(0.22, 1, 0.36, 1); +} +.contact-cards .contact-card-modern.cpage--visible, +.contact-wrapper.cpage--visible, +.map-container-modern.cpage--visible { + opacity: 1; + transform: translateY(0); +} +/* Stagger de las tarjetas */ +.contact-cards .contact-card-modern:nth-child(1) { + transition-delay: 0s; +} +.contact-cards .contact-card-modern:nth-child(2) { + transition-delay: 0.1s; +} +.contact-cards .contact-card-modern:nth-child(3) { + transition-delay: 0.2s; +} + +/* ---------- TARJETAS DE CONTACTO ---------- */ +.contact-card-modern { + cursor: default; + will-change: transform, box-shadow; +} +.contact-card-modern:hover { + transform: translateY(-8px) scale(1.01); + border-color: var(--accent) !important; + box-shadow: + 0 20px 48px rgba(4, 205, 255, 0.13), + var(--shadow-xl); +} +/* Barra superior animada al hover */ +.contact-card-modern > div:first-child { + transition: transform 0.4s cubic-bezier(0.22, 1, 0.36, 1) !important; +} +.contact-card-modern:hover > div:first-child { + transform: scaleX(1) !important; +} +/* Icono con micro-animación */ +.contact-card-modern:hover div[style*="border-radius: 20px"] { + transform: scale(1.08) rotate(6deg); + box-shadow: 0 8px 24px rgba(4, 205, 255, 0.25); +} +/* Links internos de las tarjetas */ +.contact-card-modern a[href^="tel"], +.contact-card-modern a[href^="https://maps"] { + position: relative; + overflow: hidden; +} +.contact-card-modern a[href^="tel"]:hover, +.contact-card-modern a[href^="https://maps"]:hover { + background: rgba(4, 205, 255, 0.08) !important; + color: var(--accent) !important; + transform: translateY(-2px); + border-radius: 8px; +} + +/* ---------- WRAPPER CONTACTO (formulario + info) ---------- */ +.contact-wrapper { + will-change: transform; +} +.contact-wrapper:hover { + transform: translateY(-4px); + box-shadow: + 0 30px 60px rgba(0, 0, 0, 0.2), + var(--shadow-xl); +} + +/* ---------- INFO ROW ---------- */ +.info-row { + cursor: default; + will-change: transform; +} +.info-row:hover { + transform: translateX(8px); + background: var(--surface) !important; + box-shadow: 0 4px 16px rgba(4, 205, 255, 0.08); +} +/* Icono del info-row gira sutilmente */ +.info-row:hover div[style*="background: var(--accent-gradient)"] { + transform: rotate(-6deg) scale(1.05); + transition: transform 0.25s ease; +} + +/* ---------- REDES SOCIALES ---------- */ +.social-link-modern { + position: relative; + overflow: hidden; +} +.social-link-modern::after { + content: ""; + position: absolute; + inset: 0; + background: var(--accent-gradient); + opacity: 0; + border-radius: 12px; + transition: opacity 0.25s ease; +} +.social-link-modern:hover::after { + opacity: 1; +} +.social-link-modern:hover { + color: white !important; + transform: translateY(-4px) scale(1.08); + box-shadow: 0 8px 20px rgba(4, 205, 255, 0.3); +} +.social-link-modern i { + position: relative; + z-index: 1; +} + +/* ---------- FORMULARIO — INPUTS ---------- */ +.form-group-modern { + position: relative; +} +/* Label flotante simulado mediante placeholder + focus */ +.form-group-modern input, +.form-group-modern textarea, +.form-group-modern select { + outline: none !important; + box-sizing: border-box; +} +.form-group-modern input:focus, +.form-group-modern textarea:focus, +.form-group-modern select:focus { + border-color: var(--accent) !important; + box-shadow: 0 0 0 4px rgba(4, 205, 255, 0.12) !important; + transform: translateY(-2px); + background: var(--surface) !important; +} +/* Estado valid — borde verde sutil */ +.form-group-modern input:not(:placeholder-shown):not(.error):not(:focus), +.form-group-modern textarea:not(:placeholder-shown):not(.error):not(:focus) { + border-color: rgba(0, 255, 136, 0.4) !important; +} + +/* ---------- CHECKBOX PERSONALIZADO ---------- */ +#privacy_checkbox { + appearance: none; + -webkit-appearance: none; + width: 18px !important; + height: 18px !important; + border: 2px solid var(--border); + border-radius: 5px; + background: var(--surface-light); + cursor: pointer; + position: relative; + flex-shrink: 0; + transition: + border-color 0.2s, + background 0.2s, + box-shadow 0.2s; +} +#privacy_checkbox:checked { + background: var(--accent, #04cdff); + border-color: var(--accent, #04cdff); + box-shadow: 0 0 0 3px rgba(4, 205, 255, 0.2); +} +#privacy_checkbox:checked::after { + content: ""; + position: absolute; + top: 2px; + left: 5px; + width: 5px; + height: 9px; + border: 2px solid #000; + border-top: none; + border-left: none; + transform: rotate(45deg); +} +#privacy_checkbox:hover { + border-color: var(--accent); +} + +/* ---------- BOTÓN SUBMIT ---------- */ +.submit-btn-modern { + letter-spacing: 0.02em; + isolation: isolate; +} +.submit-btn-modern::before { + content: ""; + position: absolute; + top: 50%; + left: 50%; + width: 0; + height: 0; + background: rgba(255, 255, 255, 0.25); + border-radius: 50%; + transform: translate(-50%, -50%); + transition: + width 0.55s cubic-bezier(0.22, 1, 0.36, 1), + height 0.55s cubic-bezier(0.22, 1, 0.36, 1); + z-index: 0; +} +.submit-btn-modern:hover::before { + width: 340px; + height: 340px; +} +.submit-btn-modern > * { + position: relative; + z-index: 1; +} +.submit-btn-modern:hover { + transform: translateY(-3px); + box-shadow: 0 12px 32px rgba(4, 205, 255, 0.35); + gap: 16px !important; +} +.submit-btn-modern:active { + transform: translateY(0) scale(0.98); + box-shadow: none; +} +.submit-btn-modern:disabled { + opacity: 0.65; + cursor: not-allowed; + transform: none !important; +} +/* Icono flecha se mueve al hover */ +.submit-btn-modern:hover .fa-arrow-right { + transform: translateX(4px); + transition: transform 0.2s ease; +} + +/* ---------- ICONO PAPER PLANE (header formulario) ---------- */ +.form-panel .fa-paper-plane { + transition: transform 0.35s cubic-bezier(0.22, 1, 0.36, 1); + display: inline-block; +} +.form-panel:hover .fa-paper-plane { + transform: translateX(6px) translateY(-6px) rotate(15deg); +} + +/* ---------- MAPA ---------- */ +.map-container-modern { + will-change: transform; +} +.map-container-modern:hover { + transform: translateY(-5px); + box-shadow: 0 24px 48px rgba(0, 0, 0, 0.18) !important; +} +.map-container-modern:hover iframe { + filter: grayscale(0) !important; +} + +/* ---------- WHATSAPP FLOAT ---------- */ +.whatsapp-float-modern a { + will-change: transform; +} +.whatsapp-float-modern a:hover { + transform: scale(1.05) translateY(-3px); + box-shadow: 0 12px 32px rgba(37, 211, 102, 0.55) !important; + gap: 16px !important; +} +.whatsapp-float-modern a:active { + transform: scale(0.97); +} +/* Animación del pulso */ +@keyframes pulseRing { + 0% { + transform: scale(1); + opacity: 0.5; + } + 100% { + transform: scale(1.6); + opacity: 0; + } +} +/* Badge "en línea" pulsante sobre el botón de WA */ +.whatsapp-float-modern { + position: fixed; + bottom: 30px; + right: 30px; + z-index: 1000; +} +.whatsapp-float-modern::before { + content: ""; + position: absolute; + top: -4px; + right: -4px; + width: 14px; + height: 14px; + background: #00ff88; + border-radius: 50%; + border: 2px solid #fff; + animation: cpage-online-pulse 2s infinite; + z-index: 2; +} +@keyframes cpage-online-pulse { + 0%, + 100% { + transform: scale(1); + box-shadow: 0 0 0 0 rgba(0, 255, 136, 0.6); + } + 50% { + transform: scale(1.15); + box-shadow: 0 0 0 6px rgba(0, 255, 136, 0); + } +} + +/* ---------- ALERTAS ---------- */ +.alert { + padding: 1rem 1.25rem; + border-radius: 14px; + margin-top: 1rem; + animation: cpage-fadeInUp 0.35s cubic-bezier(0.22, 1, 0.36, 1); + display: flex; + align-items: center; + gap: 10px; + font-size: 0.92rem; + font-weight: 500; +} +.alert-success { + background: rgba(0, 255, 136, 0.08); + border: 1px solid rgba(0, 255, 136, 0.5); + color: #00ff88; + box-shadow: 0 4px 16px rgba(0, 255, 136, 0.1); +} +.alert-error { + background: rgba(255, 68, 68, 0.08); + border: 1px solid rgba(255, 68, 68, 0.5); + color: #ff4444; + box-shadow: 0 4px 16px rgba(255, 68, 68, 0.1); +} +.alert-info { + background: rgba(4, 205, 255, 0.08); + border: 1px solid rgba(4, 205, 255, 0.4); + color: var(--accent); +} +/* Icono en alertas */ +.alert i { + font-size: 1.1rem; + flex-shrink: 0; +} + +@keyframes cpage-fadeInUp { + from { + opacity: 0; + transform: translateY(12px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +/* ---------- CAMPO CON ERROR (shake mejorado) ---------- */ +input.error, +textarea.error, +select.error { + border-color: #ff4444 !important; + box-shadow: 0 0 0 3px rgba(255, 68, 68, 0.12) !important; + animation: cpage-shake 0.35s cubic-bezier(0.36, 0.07, 0.19, 0.97); +} +@keyframes cpage-shake { + 0%, + 100% { + transform: translateX(0); + } + 15% { + transform: translateX(-6px); + } + 30% { + transform: translateX(5px); + } + 45% { + transform: translateX(-4px); + } + 60% { + transform: translateX(3px); + } + 75% { + transform: translateX(-2px); + } +} + +/* ---------- CONTADOR DE CARACTERES (indicador de color dinámico) ---------- */ +.char-counter { + font-weight: 500; +} + +/* ---------- SKELETON LOADER para imágenes (progressive) ---------- */ +@keyframes cpage-shimmer { + 0% { + background-position: -400px 0; + } + 100% { + background-position: 400px 0; + } +} + +/* ---------- INTL-TEL-INPUT (alineado al tema) ---------- */ +.iti { + width: 100%; + display: block !important; +} +.iti__flag-container { + border-radius: 12px 0 0 12px; +} +.iti--allow-dropdown .iti__flag-container:hover .iti__selected-flag { + background-color: rgba(4, 205, 255, 0.08); + border-radius: 12px 0 0 12px; +} +.iti__selected-flag { + padding: 0 12px; + border-radius: 12px 0 0 12px; + background: var(--surface-light); +} +.iti__country-list { + background: var(--surface); + border: 1px solid var(--border); + border-radius: 12px; + box-shadow: var(--shadow-lg); + backdrop-filter: blur(12px); +} +.iti__country:hover { + background: var(--surface-light); +} +.iti__highlight { + background: rgba(4, 205, 255, 0.15); +} + +/* ---------- TOOLTIP en links de teléfono ---------- */ +#tel1-contacto, +#tel3-contacto { + position: relative; +} +#tel1-contacto::after { + content: "Abrir WhatsApp"; + position: absolute; + bottom: calc(100% + 8px); + left: 50%; + transform: translateX(-50%) scale(0.85); + background: #25d366; + color: #fff; + font-size: 0.72rem; + font-weight: 600; + padding: 4px 10px; + border-radius: 6px; + white-space: nowrap; + pointer-events: none; + opacity: 0; + transition: + opacity 0.2s, + transform 0.2s; +} +#tel1-contacto:hover::after { + opacity: 1; + transform: translateX(-50%) scale(1); +} +#tel3-contacto::after { + content: "Llamar ahora"; + position: absolute; + bottom: calc(100% + 8px); + left: 50%; + transform: translateX(-50%) scale(0.85); + background: var(--accent, #04cdff); + color: #000; + font-size: 0.72rem; + font-weight: 600; + padding: 4px 10px; + border-radius: 6px; + white-space: nowrap; + pointer-events: none; + opacity: 0; + transition: + opacity 0.2s, + transform 0.2s; +} +#tel3-contacto:hover::after { + opacity: 1; + transform: translateX(-50%) scale(1); +} + +/* ---------- RESPONSIVE ---------- */ +@media (max-width: 992px) { + .contact-wrapper { + grid-template-columns: 1fr !important; + } + .info-panel { + order: 2; + } + .form-panel { + order: 1; + } +} +@media (max-width: 768px) { + .cards-grid { + grid-template-columns: 1fr !important; + } + .form-panel { + padding: 1.5rem !important; + } + .info-panel { + padding: 1.5rem !important; + } + .whatsapp-float-modern span { + display: none !important; + } + .whatsapp-float-modern a { + padding: 1rem !important; + } + .whatsapp-float-modern::before { + top: 2px; + right: 2px; + } +} +@media (max-width: 640px) { + .contact-wrapper div[style*="grid-template-columns: 1fr 1fr"] { + grid-template-columns: 1fr !important; + } +} + +/* ---------- FOCUS VISIBLE (accesibilidad) ---------- */ +.contact-card-modern a:focus-visible, +.submit-btn-modern:focus-visible, +.social-link-modern:focus-visible, +#tel1-contacto:focus-visible, +#tel3-contacto:focus-visible { + outline: 2px solid var(--accent); + outline-offset: 3px; + border-radius: 8px; +} diff --git a/public/assets/css/instalaciones.css b/public/assets/css/instalaciones.css new file mode 100644 index 0000000..f2eaf9b --- /dev/null +++ b/public/assets/css/instalaciones.css @@ -0,0 +1,251 @@ +/* ==================== */ +/* COMPONENTE: GALERÍA PREMIUM MODERNA */ +/* ==================== */ + +.gallery-premium { + padding: 5rem clamp(1rem, 3vw, 2rem); + max-width: var(--max-width); + margin: 0 auto; + background: linear-gradient(135deg, var(--primary) 0%, var(--surface) 100%); + border-radius: 40px; + margin-top: 2rem; + margin-bottom: 2rem; + position: relative; + overflow: hidden; +} + +/* Background decoration */ +.gallery-premium::before { + content: ""; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 100%; + background: radial-gradient( + circle at 20% 50%, + rgba(var(--accent-rgb), 0.03) 0%, + transparent 50% + ); + pointer-events: none; +} + +.gallery-premium-header { + text-align: center; + margin-bottom: 4rem; + position: relative; +} + +.gallery-premium-subtitle { + display: inline-flex; + align-items: center; + gap: 8px; + background: rgba(var(--accent-rgb), 0.1); + backdrop-filter: blur(10px); + padding: 0.5rem 1.5rem; + border-radius: 50px; + color: var(--accent); + font-size: 0.9rem; + font-weight: 500; + text-transform: uppercase; + letter-spacing: 2px; + margin-bottom: 1rem; + border: 1px solid rgba(var(--accent-rgb), 0.2); +} + +.gallery-premium-title { + font-family: "Space Grotesk", sans-serif; + font-size: clamp(2rem, 4vw, 3rem); + font-weight: 700; + margin-bottom: 1rem; + background: linear-gradient( + 135deg, + var(--text-primary) 0%, + var(--accent) 100% + ); + -webkit-background-clip: text; + background-clip: text; + color: transparent; +} + +.gallery-premium-description { + color: var(--text-secondary); + font-size: 1.1rem; + max-width: 600px; + margin: 0 auto; +} + +/* Grid de galería estilo Masonry */ +.gallery-premium-container { + column-count: 4; + column-gap: 1.5rem; + margin-bottom: 3rem; + position: relative; +} + +@media (max-width: 1200px) { + .gallery-premium-container { + column-count: 3; + } +} + +@media (max-width: 768px) { + .gallery-premium-container { + column-count: 2; + } +} + +@media (max-width: 480px) { + .gallery-premium-container { + column-count: 1; + } +} + +/* Items de galería */ +.gallery-item { + break-inside: avoid; + margin-bottom: 1.5rem; + position: relative; + border-radius: 24px; + overflow: hidden; + cursor: pointer; + transform: translateY(0); + transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); +} + +.gallery-item:hover { + transform: translateY(-10px); +} + +.gallery-image-wrapper { + position: relative; + overflow: hidden; + border-radius: 24px; + background: var(--surface-light); +} + +.gallery-image { + width: 100%; + height: auto; + display: block; + transition: transform 0.6s cubic-bezier(0.4, 0, 0.2, 1); +} + +.gallery-item:hover .gallery-image { + transform: scale(1.08); +} + +/* Overlay moderno */ +.gallery-overlay { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: linear-gradient( + 135deg, + rgba(0, 0, 0, 0.85) 0%, + rgba(0, 0, 0, 0.4) 50%, + transparent 100% + ); + display: flex; + flex-direction: column; + justify-content: flex-end; + padding: 1.5rem; + opacity: 0; + transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); +} + +.gallery-item:hover .gallery-overlay { + opacity: 1; +} + +.gallery-info { + transform: translateY(20px); + transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1); +} + +.gallery-item:hover .gallery-info { + transform: translateY(0); +} + +.gallery-title { + color: white; + font-size: 1.1rem; + font-weight: 700; + margin-bottom: 0.25rem; + display: flex; + align-items: center; + gap: 8px; +} + +.gallery-title i { + font-size: 0.9rem; + opacity: 0; + transform: translateX(-10px); + transition: all 0.3s ease; +} + +.gallery-item:hover .gallery-title i { + opacity: 1; + transform: translateX(0); +} + +/* Badge de zoom */ +.zoom-badge { + position: absolute; + top: 1rem; + right: 1rem; + background: rgba(255, 255, 255, 0.2); + backdrop-filter: blur(10px); + padding: 0.5rem; + border-radius: 50%; + width: 35px; + height: 35px; + display: flex; + align-items: center; + justify-content: center; + opacity: 0; + transform: scale(0); + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); +} + +.gallery-item:hover .zoom-badge { + opacity: 1; + transform: scale(1); +} + +.zoom-badge i { + color: white; + font-size: 1rem; +} + +/* Contador de imágenes */ +.gallery-stats { + text-align: center; + margin-top: 2rem; + color: var(--text-secondary); + font-size: 0.9rem; +} + +/* Animaciones de entrada */ +@keyframes fadeInUp { + from { + opacity: 0; + transform: translateY(30px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.gallery-item { + animation: fadeInUp 0.6s ease-out forwards; + opacity: 0; +} + +/* Scroll reveal animation */ +.gallery-item.revealed { + animation: fadeInUp 0.6s ease-out forwards; +} diff --git a/public/assets/css/preguntas-frecuentes.css b/public/assets/css/preguntas-frecuentes.css new file mode 100644 index 0000000..3560418 --- /dev/null +++ b/public/assets/css/preguntas-frecuentes.css @@ -0,0 +1,243 @@ +/* ==================== */ +/* COMPONENTE: ACORDEÓN FAQ PREMIUM */ +/* ==================== */ + +.faq-premium { + padding: 5rem clamp(1rem, 3vw, 2rem); + max-width: var(--max-width); + margin: 0 auto; + background: linear-gradient(135deg, var(--surface) 0%, var(--primary) 100%); + border-radius: 40px; + margin-top: 2rem; + margin-bottom: 2rem; + position: relative; + overflow: hidden; +} + +/* Background decoration */ +.faq-premium::before { + content: ""; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 100%; + background: radial-gradient( + circle at 80% 30%, + rgba(var(--accent-rgb), 0.03) 0%, + transparent 60% + ); + pointer-events: none; +} + +.faq-premium-header { + text-align: center; + margin-bottom: 4rem; + position: relative; +} + +.faq-premium-subtitle { + display: inline-flex; + align-items: center; + gap: 8px; + background: rgba(var(--accent-rgb), 0.1); + backdrop-filter: blur(10px); + padding: 0.5rem 1.5rem; + border-radius: 50px; + color: var(--accent); + font-size: 0.9rem; + font-weight: 500; + text-transform: uppercase; + letter-spacing: 2px; + margin-bottom: 1rem; + border: 1px solid rgba(var(--accent-rgb), 0.2); +} + +.faq-premium-title { + font-family: "Space Grotesk", sans-serif; + font-size: clamp(2rem, 4vw, 3rem); + font-weight: 700; + margin-bottom: 1rem; + background: linear-gradient( + 135deg, + var(--text-primary) 0%, + var(--accent) 100% + ); + -webkit-background-clip: text; + background-clip: text; + color: transparent; +} + +.faq-premium-description { + color: var(--text-secondary); + font-size: 1.1rem; + max-width: 600px; + margin: 0 auto; +} + +/* Layout del acordeón - Una sola columna */ +.faq-premium-container { + max-width: 900px; + margin: 0 auto; + position: relative; +} + +/* Estilos del acordeón */ +.faq-accordion { + background: rgba(var(--surface-rgb), 0.6); + backdrop-filter: blur(10px); + border-radius: 24px; + overflow: hidden; + transition: all 0.3s ease; + border: 1px solid rgba(var(--accent-rgb), 0.1); + margin-bottom: 1rem; +} + +.faq-accordion:hover { + border-color: rgba(var(--accent-rgb), 0.3); + transform: translateY(-2px); +} + +.faq-accordion.active { + border-color: rgba(var(--accent-rgb), 0.5); + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1); +} + +.faq-accordion-title { + padding: 1.25rem 1.5rem; + cursor: pointer; + display: flex; + align-items: center; + justify-content: space-between; + gap: 1rem; + transition: all 0.3s ease; + background: linear-gradient( + 135deg, + rgba(var(--surface-rgb), 0.8) 0%, + rgba(var(--surface-rgb), 0.4) 100% + ); +} + +.faq-accordion-title h4 { + font-size: 1rem; + font-weight: 600; + margin: 0; + color: var(--text-primary); + line-height: 1.4; + flex: 1; +} + +.faq-accordion-icon { + width: 32px; + height: 32px; + min-width: 32px; + display: flex; + align-items: center; + justify-content: center; + background: rgba(var(--accent-rgb), 0.1); + border-radius: 50%; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); +} + +.faq-accordion.active .faq-accordion-icon { + background: var(--accent); + transform: rotate(180deg); +} + +.faq-accordion-icon i { + color: var(--accent); + font-size: 0.9rem; + transition: all 0.3s ease; +} + +.faq-accordion.active .faq-accordion-icon i { + color: white; +} + +.faq-accordion-content { + max-height: 0; + overflow: hidden; + transition: max-height 0.5s cubic-bezier(0.4, 0, 0.2, 1); + background: rgba(var(--surface-rgb), 0.3); +} + +.faq-accordion.active .faq-accordion-content { + max-height: 2000px; +} + +.faq-accordion-inner { + padding: 0 1.5rem 1.5rem 1.5rem; + color: var(--text-secondary); + line-height: 1.7; + font-size: 0.95rem; +} + +.faq-accordion-inner p { + margin-bottom: 1rem; +} + +.faq-accordion-inner p:last-child { + margin-bottom: 0; +} + +.faq-accordion-inner a { + color: var(--accent); + text-decoration: none; + transition: all 0.3s ease; +} + +.faq-accordion-inner a:hover { + text-decoration: underline; +} + +/* Estadísticas FAQ */ +.faq-stats { + text-align: center; + margin-top: 3rem; + padding-top: 2rem; + border-top: 1px solid rgba(var(--accent-rgb), 0.1); + color: var(--text-secondary); + font-size: 0.9rem; +} + +.faq-stats i { + color: var(--accent); + margin-right: 0.5rem; +} + +/* Animaciones de entrada */ +@keyframes fadeInUp { + from { + opacity: 0; + transform: translateY(30px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.faq-accordion { + animation: fadeInUp 0.6s ease-out forwards; + opacity: 0; +} + +.faq-accordion.revealed { + animation: fadeInUp 0.6s ease-out forwards; +} + +/* Responsive */ +@media (max-width: 768px) { + .faq-accordion-title { + padding: 1rem; + } + + .faq-accordion-title h4 { + font-size: 0.9rem; + } + + .faq-accordion-inner { + padding: 0 1rem 1rem 1rem; + font-size: 0.9rem; + } +} diff --git a/public/assets/css/style.css b/public/assets/css/style.css new file mode 100644 index 0000000..1cc1c2e --- /dev/null +++ b/public/assets/css/style.css @@ -0,0 +1,2745 @@ + /* ==================== */ + /* VARIABLES GLOBALES */ + /* ==================== */ + :root { + --primary: #0a0b0d; + --secondary: #1a1d23; + --accent: #04cdff; + --accent-gradient: linear-gradient(135deg, #04cdff, #0099cc); + --gold: #FFD700; + --gold-gradient: linear-gradient(135deg, #FFD700, #B8860B); + --text-primary: #ffffff; + --text-secondary: #a0aec0; + --text-tertiary: #718096; + --surface: rgba(26, 29, 35, 0.85); + --surface-light: rgba(255, 255, 255, 0.08); + --border: rgba(255, 255, 255, 0.12); + --success: #38b2ac; + --error: #f56565; + --shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.25); + --shadow-md: 0 8px 32px rgba(0, 0, 0, 0.4); + --shadow-lg: 0 20px 60px rgba(0, 0, 0, 0.5); + --shadow-xl: 0 40px 80px rgba(0, 0, 0, 0.6); + --transition-fast: 200ms cubic-bezier(0.4, 0, 0.2, 1); + --transition-base: 400ms cubic-bezier(0.4, 0, 0.2, 1); + --transition-slow: 600ms cubic-bezier(0.4, 0, 0.2, 1); + --navbar-height: 80px; + --navbar-height-mobile: 70px; + --max-width: 1440px; + } + + body.light-mode { + --primary: #f8fafc; + --secondary: #ffffff; + --accent: #04cdff; + --accent-gradient: linear-gradient(135deg, #04cdff, #0099cc); + --gold: #FFD700; + --gold-gradient: linear-gradient(135deg, #FFD700, #B8860B); + --text-primary: #1a202c; + --text-secondary: #4a5568; + --text-tertiary: #718096; + --surface: rgba(255, 255, 255, 0.95); + --surface-light: rgba(0, 0, 0, 0.04); + --border: rgba(0, 0, 0, 0.12); + --shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.08); + --shadow-md: 0 8px 32px rgba(0, 0, 0, 0.12); + --shadow-lg: 0 20px 60px rgba(0, 0, 0, 0.15); + --shadow-xl: 0 40px 80px rgba(0, 0, 0, 0.2); + } + + * { + margin: 0; + padding: 0; + box-sizing: border-box; + } + + html { + scroll-behavior: smooth; + } + + body { + font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; + background: var(--primary); + color: var(--text-primary); + overflow-x: hidden; + line-height: 1.6; + transition: background-color var(--transition-base), color var(--transition-base); + } + + /* Utilidades */ + .hide-mobile { + display: none; + } + + @media (min-width: 769px) { + .hide-mobile { + display: flex; + } + + .hide-desktop { + display: none !important; + } + } + + ::-webkit-scrollbar { + width: 10px; + } + + ::-webkit-scrollbar-track { + background: var(--secondary); + } + + ::-webkit-scrollbar-thumb { + background: var(--surface); + border-radius: 5px; + } + + ::-webkit-scrollbar-thumb:hover { + background: var(--surface-light); + } + + /* ==================== */ + /* ANIMACIONES GLOBALES */ + /* ==================== */ + @keyframes fadeInUp { + from { + opacity: 0; + transform: translateY(30px); + } + + to { + opacity: 1; + transform: translateY(0); + } + } + + @keyframes slideInRight { + from { + transform: translateX(100%); + opacity: 0; + } + + to { + transform: translateX(0); + opacity: 1; + } + } + + @keyframes slideOutRight { + from { + transform: translateX(0); + opacity: 1; + } + + to { + transform: translateX(100%); + opacity: 0; + } + } + + /* + + + +*/ + + #preloader { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: var(--primary); + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + z-index: 9999; + transition: opacity var(--transition-slow), visibility var(--transition-slow); + } + + #preloader.loaded { + opacity: 0; + visibility: hidden; + } + + .preloader-container { + display: flex; + flex-direction: column; + align-items: center; + gap: 2rem; + text-align: center; + max-width: 300px; + width: 90%; + } + + .preloader-logo { + width: 120px; + height: 120px; + position: relative; + animation: float 3s ease-in-out infinite; + display: flex; + align-items: center; + justify-content: center; + } + + .preloader-logo img { + width: 100%; + height: 100%; + object-fit: contain; + filter: drop-shadow(0 0 20px rgba(4, 205, 255, 0.3)); + } + + .preloader-logo::before { + content: ''; + position: absolute; + top: -10px; + left: -10px; + right: -10px; + bottom: -10px; + background: rgba(4, 205, 255, 0.1); + border-radius: 50%; + animation: pulse 2s ease-in-out infinite; + z-index: -1; + } + + .preloader-text { + font-family: 'Space Grotesk', sans-serif; + font-size: clamp(1.2rem, 2vw, 1.5rem); + font-weight: 600; + color: var(--text-primary); + margin-top: 1rem; + position: relative; + overflow: hidden; + } + + .preloader-text::after { + content: 'cargando.'; + animation: dots 3s steps(4, end) infinite; + } + + .preloader-progress { + width: 100%; + height: 4px; + background: rgba(255, 255, 255, 0.1); + border-radius: 2px; + overflow: hidden; + margin-top: 1rem; + } + + .preloader-progress-bar { + width: 0%; + height: 100%; + background: var(--accent-gradient); + border-radius: 2px; + transition: width 0.3s ease; + } + + .preloader-subtext { + color: var(--text-tertiary); + font-size: clamp(0.85rem, 1.5vw, 0.9rem); + margin-top: 0.5rem; + opacity: 0.8; + } + + @keyframes float { + + 0%, + 100% { + transform: translateY(0); + } + + 50% { + transform: translateY(-20px); + } + } + + @keyframes pulse { + + 0%, + 100% { + opacity: 0.5; + transform: scale(1); + } + + 50% { + opacity: 1; + transform: scale(1.1); + } + } + + @keyframes dots { + + 0%, + 20% { + content: 'cargando.'; + } + + 40%, + 60% { + content: 'cargando..'; + } + + 80%, + 100% { + content: 'cargando...'; + } + } + + + /* + + + +*/ + .navbar-premium { + position: fixed; + top: 0; + left: 0; + right: 0; + height: var(--navbar-height); + background: rgba(10, 11, 13, 0.85); + backdrop-filter: blur(20px); + -webkit-backdrop-filter: blur(20px); + border-bottom: 1px solid var(--border); + z-index: 1000; + transition: all var(--transition-base); + padding: 0 clamp(1rem, 3vw, 2rem); + } + + @media (max-width: 768px) { + .navbar-premium { + height: var(--navbar-height-mobile); + padding: 0 1rem; + } + + .nav-desktop { + display: none !important; + } + + .nav-actions .profile-btn, + .nav-actions .cta-btn { + display: none !important; + } + + .hamburger-menu { + display: flex !important; + } + } + + @media (min-width: 769px) and (max-width: 1024px) { + .navbar-premium { + padding: 0 1.5rem; + } + + .nav-desktop { + display: none !important; + } + + .hamburger-menu { + display: flex !important; + } + } + + .light-mode .navbar-premium { + background: rgba(248, 250, 252, 0.85); + } + + .navbar-container { + max-width: var(--max-width); + height: 100%; + margin: 0 auto; + display: flex; + align-items: center; + justify-content: space-between; + } + + .navbar-logo { + display: flex; + align-items: center; + gap: 12px; + text-decoration: none; + transition: transform var(--transition-fast); + } + + .navbar-logo:hover { + transform: translateY(-2px); + } + + .logo-icon { + width: 42px; + height: 42px; + background: var(--accent-gradient); + border-radius: 12px; + display: flex; + align-items: center; + justify-content: center; + color: white; + font-size: 20px; + font-weight: 800; + box-shadow: var(--shadow-md); + flex-shrink: 0; + } + + .logo-icon img { + width: 100%; + height: 100%; + object-fit: contain; + border-radius: 12px; + } + + @media (max-width: 480px) { + .logo-icon { + width: 36px; + height: 36px; + } + } + + .logo-text-container { + display: flex; + flex-direction: column; + line-height: 1.2; + } + + .logo-main-text { + font-family: 'Space Grotesk', sans-serif; + font-size: clamp(1.3rem, 2vw, 1.6rem); + font-weight: 700; + color: var(--text-primary); + letter-spacing: -0.5px; + white-space: nowrap; + } + + .logo-subtext { + font-family: 'Space Grotesk', sans-serif; + font-size: clamp(0.7rem, 1.2vw, 0.85rem); + font-weight: 600; + background: var(--gold-gradient); + -webkit-background-clip: text; + background-clip: text; + color: transparent; + letter-spacing: 5.5px; + margin-top: 1px; + text-transform: uppercase; + white-space: nowrap; + } + + .logo-gradient { + background: var(--accent-gradient); + -webkit-background-clip: text; + background-clip: text; + color: transparent; + } + + .nav-desktop { + display: flex; + gap: 2px; + background: var(--surface); + padding: 4px; + border-radius: 14px; + backdrop-filter: blur(10px); + border: 1px solid var(--border); + } + + .nav-item { + position: relative; + } + + .nav-link { + display: flex; + align-items: center; + gap: 10px; + padding: 12px 24px; + text-decoration: none; + color: var(--text-secondary); + font-weight: 500; + font-size: clamp(0.85rem, 1vw, 0.95rem); + border-radius: 10px; + transition: all var(--transition-fast); + position: relative; + } + + .nav-link::after { + content: ''; + position: absolute; + bottom: 0; + left: 50%; + transform: translateX(-50%); + width: 0; + height: 2px; + background: var(--accent); + transition: width var(--transition-base); + border-radius: 2px; + } + + .nav-link:hover { + color: var(--text-primary); + background: var(--surface-light); + } + + .nav-link:hover::after { + width: 60%; + } + + .nav-link.active { + color: var(--text-primary); + background: var(--surface-light); + } + + .nav-link.active::after { + width: 80%; + } + + .nav-icon { + font-size: 1.1rem; + } + + .theme-toggle { + position: relative; + width: 44px; + height: 44px; + background: var(--surface); + border: 1px solid var(--border); + border-radius: 12px; + display: flex; + align-items: center; + justify-content: center; + color: var(--text-secondary); + cursor: pointer; + transition: all var(--transition-fast); + font-size: 1.1rem; + } + + @media (max-width: 480px) { + .theme-toggle { + width: 40px; + height: 40px; + font-size: 1rem; + } + } + + .theme-toggle:hover { + background: var(--surface-light); + color: var(--text-primary); + transform: translateY(-2px); + border-color: var(--accent); + } + + .light-mode .theme-toggle .fa-sun { + display: none; + } + + .light-mode .theme-toggle .fa-moon { + display: block; + } + + .fa-moon { + display: none; + } + + .nav-actions { + display: flex; + align-items: center; + gap: 1rem; + } + + @media (max-width: 768px) { + .nav-actions { + gap: 0.75rem; + } + } + + .action-btn { + position: relative; + width: 44px; + height: 44px; + background: var(--surface); + border: 1px solid var(--border); + border-radius: 12px; + display: flex; + align-items: center; + justify-content: center; + color: var(--text-secondary); + cursor: pointer; + transition: all var(--transition-fast); + font-size: 1.1rem; + } + + @media (max-width: 480px) { + .action-btn { + width: 40px; + height: 40px; + font-size: 1rem; + } + } + + .action-btn:hover { + background: var(--surface-light); + color: var(--text-primary); + transform: translateY(-2px); + border-color: var(--accent); + } + + .notification-badge { + position: absolute; + top: -4px; + right: -4px; + background: var(--accent); + color: white; + font-size: 0.7rem; + width: 18px; + height: 18px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-weight: 700; + border: 2px solid var(--primary); + } + + .cta-btn { + display: flex; + align-items: center; + gap: 10px; + padding: 12px 28px; + background: var(--accent-gradient); + color: white; + border: none; + border-radius: 12px; + font-weight: 600; + font-size: 0.95rem; + cursor: pointer; + transition: all var(--transition-base); + text-decoration: none; + box-shadow: var(--shadow-md); + } + + @media (max-width: 480px) { + .cta-btn { + padding: 10px 20px; + font-size: 0.9rem; + } + } + + .cta-btn:hover { + transform: translateY(-3px); + box-shadow: var(--shadow-lg); + } + + .cta-btn i { + transition: transform var(--transition-fast); + } + + .cta-btn:hover i { + transform: translateX(3px); + } + + .hamburger-menu { + display: none; + width: 32px; + height: 32px; + flex-direction: column; + justify-content: center; + align-items: center; + cursor: pointer; + position: relative; + z-index: 1002; + } + + @media (max-width: 1024px) { + .hamburger-menu { + display: flex; + } + } + + @media (max-width: 480px) { + .hamburger-menu { + width: 28px; + height: 28px; + } + } + + .hamburger-line { + width: 22px; + height: 2px; + background: var(--text-primary); + border-radius: 2px; + transition: all var(--transition-base); + position: absolute; + } + + .hamburger-line:nth-child(1) { + transform: translateY(-6px); + } + + .hamburger-line:nth-child(3) { + transform: translateY(6px); + } + + .hamburger-menu.active .hamburger-line:nth-child(1) { + transform: rotate(45deg); + } + + .hamburger-menu.active .hamburger-line:nth-child(2) { + opacity: 0; + } + + .hamburger-menu.active .hamburger-line:nth-child(3) { + transform: rotate(-45deg); + } + + .mobile-menu { + position: fixed; + top: 0; + right: -100%; + width: 100%; + max-width: 320px; + height: 100vh; + background: var(--secondary); + backdrop-filter: blur(30px); + -webkit-backdrop-filter: blur(30px); + border-left: 1px solid var(--border); + transition: right var(--transition-base); + z-index: 1001; + padding: 2rem; + display: flex; + flex-direction: column; + overflow-y: auto; + } + + @media (max-width: 480px) { + .mobile-menu { + padding: 1.5rem; + max-width: 280px; + } + } + + .mobile-menu.active { + right: 0; + } + + .mobile-menu-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 2rem; + } + + .mobile-nav { + display: flex; + flex-direction: column; + gap: 8px; + margin-bottom: 2rem; + } + + .mobile-nav-item { + list-style: none; + } + + .mobile-nav-link { + display: flex; + align-items: center; + gap: 12px; + padding: 16px; + text-decoration: none; + color: var(--text-secondary); + border-radius: 12px; + transition: all var(--transition-fast); + font-weight: 500; + } + + .mobile-nav-link:hover, + .mobile-nav-link.active { + background: var(--surface-light); + color: var(--text-primary); + } + + .mobile-actions { + margin-top: auto; + display: flex; + flex-direction: column; + gap: 1rem; + padding-top: 2rem; + border-top: 1px solid var(--border); + } + + .menu-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.7); + backdrop-filter: blur(5px); + -webkit-backdrop-filter: blur(5px); + opacity: 0; + visibility: hidden; + transition: all var(--transition-base); + z-index: 1000; + } + + .menu-overlay.active { + opacity: 1; + visibility: visible; + } + + + + /* + + + +*/ + + .navbar-premium { + position: fixed; + top: 0; + left: 0; + right: 0; + height: var(--navbar-height); + background: rgba(10, 11, 13, 0.85); + backdrop-filter: blur(20px); + -webkit-backdrop-filter: blur(20px); + border-bottom: 1px solid var(--border); + z-index: 1000; + transition: all var(--transition-base); + padding: 0 clamp(1rem, 3vw, 2rem); + } + + @media (max-width: 768px) { + .navbar-premium { + height: var(--navbar-height-mobile); + padding: 0 1rem; + } + + .nav-desktop { + display: none !important; + } + + .nav-actions .profile-btn, + .nav-actions .cta-btn { + display: none !important; + } + + .hamburger-menu { + display: flex !important; + } + } + + @media (min-width: 769px) and (max-width: 1024px) { + .navbar-premium { + padding: 0 1.5rem; + } + + .nav-desktop { + display: none !important; + } + + .hamburger-menu { + display: flex !important; + } + } + + .light-mode .navbar-premium { + background: rgba(248, 250, 252, 0.85); + } + + .navbar-container { + max-width: var(--max-width); + height: 100%; + margin: 0 auto; + display: flex; + align-items: center; + justify-content: space-between; + } + + .navbar-logo { + display: flex; + align-items: center; + gap: 12px; + text-decoration: none; + transition: transform var(--transition-fast); + } + + .navbar-logo:hover { + transform: translateY(-2px); + } + + .logo-icon { + width: 42px; + height: 42px; + background: var(--accent-gradient); + border-radius: 12px; + display: flex; + align-items: center; + justify-content: center; + color: white; + font-size: 20px; + font-weight: 800; + box-shadow: var(--shadow-md); + flex-shrink: 0; + } + + .logo-icon img { + width: 100%; + height: 100%; + object-fit: contain; + border-radius: 50%; + } + + @media (max-width: 480px) { + .logo-icon { + width: 36px; + height: 36px; + } + } + + .logo-text-container { + display: flex; + flex-direction: column; + line-height: 1.2; + } + + .logo-main-text { + font-family: 'Space Grotesk', sans-serif; + font-size: clamp(1.3rem, 2vw, 1.6rem); + font-weight: 700; + color: var(--text-primary); + letter-spacing: -0.5px; + white-space: nowrap; + } + + .logo-subtext { + font-family: 'Space Grotesk', sans-serif; + font-size: clamp(0.7rem, 1.2vw, 0.85rem); + font-weight: 600; + background: var(--gold-gradient); + -webkit-background-clip: text; + background-clip: text; + color: transparent; + letter-spacing: 5.5px; + margin-top: 1px; + text-transform: uppercase; + white-space: nowrap; + } + + .logo-gradient { + background: var(--accent-gradient); + -webkit-background-clip: text; + background-clip: text; + color: transparent; + } + + .nav-desktop { + display: flex; + gap: 2px; + background: var(--surface); + padding: 4px; + border-radius: 14px; + backdrop-filter: blur(10px); + border: 1px solid var(--border); + } + + .nav-item { + position: relative; + } + + .nav-link { + display: flex; + align-items: center; + gap: 10px; + padding: 12px 24px; + text-decoration: none; + color: var(--text-secondary); + font-weight: 500; + font-size: clamp(0.85rem, 1vw, 0.95rem); + border-radius: 10px; + transition: all var(--transition-fast); + position: relative; + } + + .nav-link::after { + content: ''; + position: absolute; + bottom: 0; + left: 50%; + transform: translateX(-50%); + width: 0; + height: 2px; + background: var(--accent); + transition: width var(--transition-base); + border-radius: 2px; + } + + .nav-link:hover { + color: var(--text-primary); + background: var(--surface-light); + } + + .nav-link:hover::after { + width: 60%; + } + + .nav-link.active { + color: var(--text-primary); + background: var(--surface-light); + } + + .nav-link.active::after { + width: 80%; + } + + .nav-icon { + font-size: 1.1rem; + } + + .theme-toggle { + position: relative; + width: 44px; + height: 44px; + background: var(--surface); + border: 1px solid var(--border); + border-radius: 12px; + display: flex; + align-items: center; + justify-content: center; + color: var(--text-secondary); + cursor: pointer; + transition: all var(--transition-fast); + font-size: 1.1rem; + } + + @media (max-width: 480px) { + .theme-toggle { + width: 40px; + height: 40px; + font-size: 1rem; + } + } + + .theme-toggle:hover { + background: var(--surface-light); + color: var(--text-primary); + transform: translateY(-2px); + border-color: var(--accent); + } + + .light-mode .theme-toggle .fa-sun { + display: none; + } + + .light-mode .theme-toggle .fa-moon { + display: block; + } + + .fa-moon { + display: none; + } + + .nav-actions { + display: flex; + align-items: center; + gap: 1rem; + } + + @media (max-width: 768px) { + .nav-actions { + gap: 0.75rem; + } + } + + .action-btn { + position: relative; + width: 44px; + height: 44px; + background: var(--surface); + border: 1px solid var(--border); + border-radius: 12px; + display: flex; + align-items: center; + justify-content: center; + color: var(--text-secondary); + cursor: pointer; + transition: all var(--transition-fast); + font-size: 1.1rem; + } + + @media (max-width: 480px) { + .action-btn { + width: 40px; + height: 40px; + font-size: 1rem; + } + } + + .action-btn:hover { + background: var(--surface-light); + color: var(--text-primary); + transform: translateY(-2px); + border-color: var(--accent); + } + + .notification-badge { + position: absolute; + top: -4px; + right: -4px; + background: var(--accent); + color: white; + font-size: 0.7rem; + width: 18px; + height: 18px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-weight: 700; + border: 2px solid var(--primary); + } + + .cta-btn { + display: flex; + align-items: center; + gap: 10px; + padding: 12px 28px; + background: var(--accent-gradient); + color: white; + border: none; + border-radius: 12px; + font-weight: 600; + font-size: 0.95rem; + cursor: pointer; + transition: all var(--transition-base); + text-decoration: none; + box-shadow: var(--shadow-md); + } + + @media (max-width: 480px) { + .cta-btn { + padding: 10px 20px; + font-size: 0.9rem; + } + } + + .cta-btn:hover { + transform: translateY(-3px); + box-shadow: var(--shadow-lg); + } + + .cta-btn i { + transition: transform var(--transition-fast); + } + + .cta-btn:hover i { + transform: translateX(3px); + } + + .hamburger-menu { + display: none; + width: 32px; + height: 32px; + flex-direction: column; + justify-content: center; + align-items: center; + cursor: pointer; + position: relative; + z-index: 1002; + } + + @media (max-width: 1024px) { + .hamburger-menu { + display: flex; + } + } + + @media (max-width: 480px) { + .hamburger-menu { + width: 28px; + height: 28px; + } + } + + .hamburger-line { + width: 22px; + height: 2px; + background: var(--text-primary); + border-radius: 2px; + transition: all var(--transition-base); + position: absolute; + } + + .hamburger-line:nth-child(1) { + transform: translateY(-6px); + } + + .hamburger-line:nth-child(3) { + transform: translateY(6px); + } + + .hamburger-menu.active .hamburger-line:nth-child(1) { + transform: rotate(45deg); + } + + .hamburger-menu.active .hamburger-line:nth-child(2) { + opacity: 0; + } + + .hamburger-menu.active .hamburger-line:nth-child(3) { + transform: rotate(-45deg); + } + + .mobile-menu { + position: fixed; + top: 0; + right: -100%; + width: 100%; + max-width: 320px; + height: 100vh; + background: var(--secondary); + backdrop-filter: blur(30px); + -webkit-backdrop-filter: blur(30px); + border-left: 1px solid var(--border); + transition: right var(--transition-base); + z-index: 1001; + padding: 2rem; + display: flex; + flex-direction: column; + overflow-y: auto; + } + + @media (max-width: 480px) { + .mobile-menu { + padding: 1.5rem; + max-width: 280px; + } + } + + .mobile-menu.active { + right: 0; + } + + .mobile-menu-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 2rem; + } + + .mobile-nav { + display: flex; + flex-direction: column; + gap: 8px; + margin-bottom: 2rem; + } + + .mobile-nav-item { + list-style: none; + } + + .mobile-nav-link { + display: flex; + align-items: center; + gap: 12px; + padding: 16px; + text-decoration: none; + color: var(--text-secondary); + border-radius: 12px; + transition: all var(--transition-fast); + font-weight: 500; + } + + .mobile-nav-link:hover, + .mobile-nav-link.active { + background: var(--surface-light); + color: var(--text-primary); + } + + .mobile-actions { + margin-top: auto; + display: flex; + flex-direction: column; + gap: 1rem; + padding-top: 2rem; + border-top: 1px solid var(--border); + } + + .menu-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.7); + backdrop-filter: blur(5px); + -webkit-backdrop-filter: blur(5px); + opacity: 0; + visibility: hidden; + transition: all var(--transition-base); + z-index: 1000; + } + + .menu-overlay.active { + opacity: 1; + visibility: visible; + } + + + /* + + + +*/ + .search-modal { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100vh; + background: var(--primary); + z-index: 2000; + display: none; + flex-direction: column; + opacity: 0; + transition: opacity var(--transition-base); + } + + .search-modal.active { + display: flex; + opacity: 1; + } + + .search-modal-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 1.5rem clamp(1rem, 3vw, 2rem); + border-bottom: 1px solid var(--border); + } + + .search-modal-title { + font-family: 'Space Grotesk', sans-serif; + font-size: clamp(1.5rem, 3vw, 2rem); + font-weight: 700; + color: var(--text-primary); + } + + .search-close-btn { + width: 44px; + height: 44px; + background: var(--surface); + border: 1px solid var(--border); + border-radius: 12px; + display: flex; + align-items: center; + justify-content: center; + color: var(--text-secondary); + cursor: pointer; + transition: all var(--transition-fast); + font-size: 1.2rem; + } + + .search-close-btn:hover { + background: var(--surface-light); + color: var(--text-primary); + transform: rotate(90deg); + } + + .search-modal-content { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 2rem; + max-width: 800px; + margin: 0 auto; + width: 100%; + } + + .search-input-container { + width: 100%; + position: relative; + margin-bottom: 3rem; + } + + .search-input { + width: 100%; + height: 70px; + padding: 0 2rem 0 5rem; + background: var(--secondary); + border: 2px solid var(--border); + border-radius: 20px; + color: var(--text-primary); + font-family: 'Inter', sans-serif; + font-size: clamp(1.2rem, 2vw, 1.5rem); + transition: all var(--transition-base); + outline: none; + } + + .search-input:focus { + border-color: var(--accent); + box-shadow: 0 0 0 4px rgba(4, 205, 255, 0.15); + transform: translateY(-2px); + } + + .search-icon { + position: absolute; + left: 1.5rem; + top: 50%; + transform: translateY(-50%); + color: var(--accent); + font-size: 1.5rem; + } + + .search-results { + width: 100%; + display: grid; + grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); + gap: 1.5rem; + opacity: 0; + transform: translateY(20px); + transition: all var(--transition-base); + } + + .search-results.active { + opacity: 1; + transform: translateY(0); + } + + .search-result-item { + background: var(--surface); + border: 1px solid var(--border); + border-radius: 16px; + padding: 1.5rem; + transition: all var(--transition-fast); + cursor: pointer; + } + + .search-result-item:hover { + transform: translateY(-5px); + border-color: var(--accent); + box-shadow: var(--shadow-lg); + } + + .search-result-title { + font-size: 1.1rem; + font-weight: 600; + margin-bottom: 0.5rem; + color: var(--text-primary); + } + + .search-result-description { + color: var(--text-secondary); + font-size: 0.9rem; + line-height: 1.5; + } + + .search-suggestions { + display: flex; + flex-wrap: wrap; + gap: 1rem; + justify-content: center; + margin-top: 2rem; + } + + .search-suggestion { + padding: 0.5rem 1rem; + background: var(--surface); + border: 1px solid var(--border); + border-radius: 50px; + color: var(--text-secondary); + font-size: 0.9rem; + cursor: pointer; + transition: all var(--transition-fast); + } + + .search-suggestion:hover { + background: var(--accent); + color: white; + transform: translateY(-2px); + } + + + /* + + + +*/ + .hero-section { + position: relative; + height: 100vh; + min-height: 600px; + width: 100%; + overflow: hidden; + margin-top: var(--navbar-height); + } + + @media (max-width: 768px) { + .hero-section { + margin-top: var(--navbar-height-mobile); + min-height: 700px; + } + } + + @media (max-width: 480px) { + .hero-section { + min-height: 800px; + height: auto; + } + } + + .video-container { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + overflow: hidden; + } + + .hero-video { + width: 100%; + height: 100%; + object-fit: cover; + object-position: center; + filter: brightness(0.7); + } + + .light-mode .hero-video { + filter: brightness(0.9); + } + + .video-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: linear-gradient(to bottom, + rgba(10, 11, 13, 0.2) 0%, + rgba(10, 11, 13, 0.4) 30%, + rgba(10, 11, 13, 0.7) 70%, + rgba(10, 11, 13, 0.9) 100%); + z-index: 1; + } + + .light-mode .video-overlay { + background: linear-gradient(to bottom, + rgba(248, 250, 252, 0.3) 0%, + rgba(248, 250, 252, 0.5) 30%, + rgba(248, 250, 252, 0.7) 70%, + rgba(248, 250, 252, 0.9) 100%); + } + + .hero-content { + position: relative; + z-index: 2; + height: 100%; + display: flex; + flex-direction: column; + justify-content: space-between; + padding: 0 clamp(1rem, 3vw, 2rem); + max-width: var(--max-width); + margin: 0 auto; + } + + .hero-text { + padding-top: 10vh; + max-width: 800px; + } + + @media (max-width: 768px) { + .hero-text { + padding-top: 5vh; + } + } + + .hero-subtitle { + display: inline-block; + background: var(--surface); + backdrop-filter: blur(10px); + padding: clamp(6px, 1.5vw, 8px) clamp(16px, 2.5vw, 20px); + border-radius: 50px; + font-size: clamp(0.8rem, 1vw, 0.9rem); + font-weight: 500; + color: var(--accent); + margin-bottom: 1.5rem; + border: 1px solid var(--border); + animation: fadeInUp 1s ease-out; + } + + .hero-title { + font-family: 'Space Grotesk', sans-serif; + font-size: clamp(2rem, 5vw, 4.5rem); + font-weight: 800; + line-height: 1.1; + margin-bottom: clamp(1rem, 2vw, 1.5rem); + background: linear-gradient(to right, var(--text-primary), var(--accent)); + -webkit-background-clip: text; + background-clip: text; + color: transparent; + animation: fadeInUp 1s ease-out 0.2s both; + } + + @media (max-width: 480px) { + .hero-title { + font-size: 2.2rem; + line-height: 1.2; + } + } + + .hero-description { + font-size: clamp(0.95rem, 2vw, 1.25rem); + color: var(--text-secondary); + max-width: 600px; + margin-bottom: clamp(1.5rem, 3vw, 2rem); + animation: fadeInUp 1s ease-out 0.4s both; + } + + .hero-stats { + display: flex; + gap: clamp(1.5rem, 4vw, 3rem); + margin-top: clamp(2rem, 4vw, 3rem); + animation: fadeInUp 1s ease-out 0.6s both; + } + + @media (max-width: 768px) { + .hero-stats { + flex-wrap: wrap; + justify-content: center; + gap: 2rem; + } + } + + .stat-item { + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + } + + .stat-number { + font-size: clamp(2rem, 4vw, 2.5rem); + font-weight: 800; + background: var(--accent-gradient); + -webkit-background-clip: text; + background-clip: text; + color: transparent; + line-height: 1; + margin-bottom: 0.5rem; + } + + .stat-label { + font-size: clamp(0.8rem, 1vw, 0.9rem); + color: var(--text-tertiary); + text-transform: uppercase; + letter-spacing: 0.5px; + } + + + /* + + + */ + .vuelos-section { + padding: 5rem clamp(1rem, 3vw, 2rem); + max-width: var(--max-width); + margin: 0 auto; + } + + .block-title { + text-align: center; + margin-bottom: 3rem; + } + + .block-title p { + color: var(--accent); + font-size: 1rem; + text-transform: uppercase; + letter-spacing: 2px; + margin-bottom: 0.5rem; + font-weight: 500; + } + + .block-title h3 { + font-family: 'Space Grotesk', sans-serif; + font-size: clamp(2rem, 4vw, 2.8rem); + font-weight: 700; + background: linear-gradient(135deg, var(--text-primary), var(--accent)); + -webkit-background-clip: text; + background-clip: text; + color: transparent; + } + + .row { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); + gap: 2rem; + } + + .tour-one__single { + background: var(--secondary); + border-radius: 24px; + overflow: hidden; + transition: all var(--transition-base); + border: 1px solid var(--border); + position: relative; + } + + .tour-one__single:hover { + transform: translateY(-12px); + border-color: var(--accent); + box-shadow: var(--shadow-xl); + } + + .tour-one__image { + position: relative; + overflow: hidden; + aspect-ratio: 370 / 292; + } + + .tour-one__image img { + width: 100%; + height: 100%; + object-fit: cover; + transition: transform var(--transition-slow); + cursor: pointer; + } + + .tour-one__single:hover .tour-one__image img { + transform: scale(1.05); + } + + .tour-one__content { + padding: 1.5rem; + position: relative; + background: var(--secondary); + } + + .tour-one__content h3 { + font-size: 1.35rem; + font-weight: 700; + margin-bottom: 0.75rem; + line-height: 1.3; + } + + .tour-one__content h3 a { + color: var(--text-primary); + text-decoration: none; + transition: color var(--transition-fast); + } + + .tour-one__content h3 a:hover { + color: var(--accent); + } + + .tour-one__content p { + margin: 0.5rem 0; + color: var(--text-secondary); + font-size: 0.95rem; + } + + .tour-one__content p span { + font-size: 1.6rem; + font-weight: 800; + background: var(--accent-gradient); + -webkit-background-clip: text; + background-clip: text; + color: transparent; + letter-spacing: -0.5px; + } + + .tour-one__meta { + display: flex; + gap: 1.25rem; + list-style: none; + margin-top: 1.25rem; + padding-top: 1rem; + border-top: 1px solid var(--border); + } + + .tour-one__meta li { + display: flex; + align-items: center; + } + + .tour-one__meta a { + display: flex; + align-items: center; + gap: 0.5rem; + color: var(--text-tertiary); + text-decoration: none; + font-size: 0.85rem; + transition: color var(--transition-fast); + } + + .tour-one__meta a:hover { + color: var(--accent); + } + + .tour-one__meta i { + font-size: 0.9rem; + color: var(--accent); + } + + /* < !--====================--> + < !-- COMPONENTE: FOOTER PREMIUM --> + < !--====================--> */ + + .footer { + background: var(--secondary); + border-top: 1px solid var(--border); + padding: clamp(3rem, 6vw, 5rem) clamp(1rem, 3vw, 2rem) 1.5rem; + margin-top: 2rem; + } + + .footer-container { + max-width: var(--max-width); + margin: 0 auto; + } + + .footer-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: clamp(2rem, 4vw, 3rem); + margin-bottom: 3rem; + } + + .footer-brand { + max-width: 300px; + } + + .footer-logo { + display: flex; + align-items: center; + gap: 12px; + text-decoration: none; + margin-bottom: 1.5rem; + } + + .footer-logo-icon { + width: 45px; + height: 45px; + background: var(--accent-gradient); + border-radius: 12px; + display: flex; + align-items: center; + justify-content: center; + color: white; + font-size: 1.3rem; + font-weight: 800; + } + + .footer-logo-text { + font-family: 'Space Grotesk', sans-serif; + font-size: 1.4rem; + font-weight: 700; + color: var(--text-primary); + } + + .footer-logo-sub { + font-size: 0.7rem; + font-weight: 600; + background: var(--gold-gradient); + -webkit-background-clip: text; + background-clip: text; + color: transparent; + letter-spacing: 4px; + } + + .footer-description { + color: var(--text-secondary); + font-size: 0.9rem; + line-height: 1.6; + margin-bottom: 1.5rem; + } + + .footer-social { + display: flex; + gap: 1rem; + } + + .social-link { + width: 40px; + height: 40px; + background: var(--surface-light); + border-radius: 12px; + display: flex; + align-items: center; + justify-content: center; + color: var(--text-secondary); + transition: all var(--transition-fast); + text-decoration: none; + font-size: 1.1rem; + } + + .social-link:hover { + background: var(--accent); + color: white; + transform: translateY(-3px); + } + + .footer-title { + font-size: 1.1rem; + font-weight: 700; + margin-bottom: 1.5rem; + color: var(--text-primary); + position: relative; + display: inline-block; + } + + .footer-title::after { + content: ''; + position: absolute; + bottom: -8px; + left: 0; + width: 40px; + height: 2px; + background: var(--accent); + border-radius: 2px; + } + + .footer-links { + list-style: none; + padding: 0; + } + + .footer-links li { + margin-bottom: 0.75rem; + } + + .footer-links a { + color: var(--text-secondary); + text-decoration: none; + transition: all var(--transition-fast); + font-size: 0.9rem; + display: inline-flex; + align-items: center; + gap: 8px; + } + + .footer-links a:hover { + color: var(--accent); + transform: translateX(5px); + } + + .footer-contact-item { + display: flex; + align-items: center; + gap: 12px; + margin-bottom: 1rem; + color: var(--text-secondary); + font-size: 0.9rem; + } + + .footer-contact-item i { + width: 20px; + color: var(--accent); + font-size: 1rem; + } + + .footer-newsletter { + margin-top: 0.5rem; + } + + .newsletter-text { + color: var(--text-secondary); + font-size: 0.85rem; + margin-bottom: 1rem; + line-height: 1.5; + } + + .newsletter-form { + display: flex; + gap: 0.5rem; + flex-wrap: wrap; + } + + .newsletter-input { + flex: 1; + min-width: 140px; + padding: 0.75rem 1rem; + background: var(--surface-light); + border: 1px solid var(--border); + border-radius: 12px; + color: var(--text-primary); + font-size: 0.85rem; + outline: none; + transition: all var(--transition-fast); + } + + .newsletter-input:focus { + border-color: var(--accent); + box-shadow: 0 0 0 3px rgba(4, 205, 255, 0.1); + } + + .newsletter-btn { + padding: 0.75rem 1.25rem; + background: var(--accent-gradient); + border: none; + border-radius: 12px; + color: white; + font-weight: 600; + font-size: 0.85rem; + cursor: pointer; + transition: all var(--transition-fast); + white-space: nowrap; + } + + .newsletter-btn:hover { + transform: translateY(-2px); + box-shadow: var(--shadow-sm); + } + + .footer-bottom { + border-top: 1px solid var(--border); + padding-top: 2rem; + display: flex; + justify-content: space-between; + align-items: center; + flex-wrap: wrap; + gap: 1rem; + } + + .copyright { + color: var(--text-tertiary); + font-size: 0.85rem; + } + + .footer-legal { + display: flex; + gap: 1.5rem; + flex-wrap: wrap; + } + + .footer-legal a { + color: var(--text-tertiary); + text-decoration: none; + font-size: 0.85rem; + transition: color var(--transition-fast); + } + + .footer-legal a:hover { + color: var(--accent); + } + + @media (max-width: 768px) { + .footer-bottom { + flex-direction: column; + text-align: center; + } + + .footer-legal { + justify-content: center; + } + + .footer-brand { + max-width: 100%; + text-align: center; + } + + .footer-logo { + justify-content: center; + } + + .footer-social { + justify-content: center; + } + + .footer-title::after { + left: 50%; + transform: translateX(-50%); + } + + .footer-title { + text-align: center; + display: block; + } + + .footer-contact-item { + justify-content: center; + } + + .footer-links { + text-align: center; + } + + .footer-links a { + justify-content: center; + } + } + + + /* + + */ + + .chatbot-widget { + position: fixed; + bottom: 30px; + right: 30px; + z-index: 2000; + } + + .chatbot-toggle { + width: 65px; + height: 65px; + background: var(--accent-gradient); + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + box-shadow: var(--shadow-lg); + transition: all var(--transition-base); + border: 2px solid rgba(255, 255, 255, 0.2); + animation: pulseRing 2s ease-in-out infinite; + } + + .chatbot-toggle:hover { + transform: scale(1.1); + box-shadow: var(--shadow-xl); + animation: none; + } + + @keyframes pulseRing { + 0% { + box-shadow: 0 0 0 0 rgba(4, 205, 255, 0.7); + } + + 70% { + box-shadow: 0 0 0 15px rgba(4, 205, 255, 0); + } + + 100% { + box-shadow: 0 0 0 0 rgba(4, 205, 255, 0); + } + } + + .chatbot-toggle i { + font-size: 28px; + color: white; + } + + .chatbot-container { + position: absolute; + bottom: 80px; + right: 0; + width: 380px; + height: 550px; + background: var(--secondary); + border-radius: 24px; + overflow: hidden; + box-shadow: var(--shadow-xl); + border: 1px solid var(--border); + display: none; + flex-direction: column; + animation: slideInRight 0.3s ease-out; + } + + .chatbot-container.active { + display: flex; + } + + .chatbot-header { + background: var(--accent-gradient); + padding: 18px 20px; + display: flex; + align-items: center; + justify-content: space-between; + color: white; + } + + .chatbot-header-info { + display: flex; + align-items: center; + gap: 12px; + } + + .chatbot-header-info i { + font-size: 28px; + } + + .chatbot-header-info h3 { + font-size: 16px; + font-weight: 600; + margin: 0; + } + + .chatbot-header-info p { + font-size: 12px; + opacity: 0.8; + margin: 0; + } + + .chatbot-close { + background: rgba(255, 255, 255, 0.2); + border: none; + width: 32px; + height: 32px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + color: white; + transition: all var(--transition-fast); + } + + .chatbot-close:hover { + background: rgba(255, 255, 255, 0.3); + transform: rotate(90deg); + } + + .chatbot-messages { + flex: 1; + overflow-y: auto; + padding: 20px; + display: flex; + flex-direction: column; + gap: 12px; + } + + .chatbot-message { + max-width: 85%; + padding: 12px 16px; + border-radius: 18px; + font-size: 14px; + line-height: 1.5; + animation: fadeInUp 0.3s ease-out; + } + + .chatbot-message.bot { + background: var(--surface-light); + color: var(--text-primary); + align-self: flex-start; + border-bottom-left-radius: 6px; + } + + .chatbot-message.user { + background: var(--accent); + color: white; + align-self: flex-end; + border-bottom-right-radius: 6px; + } + + .chatbot-message.system { + background: rgba(56, 178, 172, 0.2); + color: var(--success); + align-self: center; + font-size: 12px; + padding: 8px 16px; + border-radius: 30px; + max-width: 90%; + } + + .chatbot-message.error { + background: rgba(245, 101, 101, 0.2); + color: var(--error); + align-self: center; + font-size: 12px; + padding: 8px 16px; + border-radius: 30px; + } + + .chatbot-loading { + display: flex; + gap: 4px; + padding: 12px 16px; + background: var(--surface-light); + border-radius: 18px; + align-self: flex-start; + } + + .chatbot-loading span { + width: 8px; + height: 8px; + background: var(--text-tertiary); + border-radius: 50%; + animation: loadingBounce 1.4s infinite ease-in-out both; + } + + .chatbot-loading span:nth-child(1) { + animation-delay: -0.32s; + } + + .chatbot-loading span:nth-child(2) { + animation-delay: -0.16s; + } + + @keyframes loadingBounce { + + 0%, + 80%, + 100% { + transform: scale(0); + } + + 40% { + transform: scale(1); + } + } + + .chatbot-input { + padding: 16px; + border-top: 1px solid var(--border); + display: flex; + gap: 12px; + background: var(--secondary); + } + + .chatbot-input input { + flex: 1; + padding: 12px 16px; + background: var(--surface-light); + border: 1px solid var(--border); + border-radius: 30px; + color: var(--text-primary); + font-size: 14px; + outline: none; + transition: all var(--transition-fast); + } + + .chatbot-input input:focus { + border-color: var(--accent); + } + + .chatbot-input input::placeholder { + color: var(--text-tertiary); + } + + .chatbot-input button { + padding: 12px 20px; + background: var(--accent-gradient); + border: none; + border-radius: 30px; + color: white; + font-weight: 600; + cursor: pointer; + transition: all var(--transition-fast); + } + + .chatbot-input button:hover:not(:disabled) { + transform: translateY(-2px); + } + + .chatbot-input button:disabled { + opacity: 0.5; + cursor: not-allowed; + } + + .chatbot-status { + text-align: center; + font-size: 11px; + padding: 8px; + color: var(--text-tertiary); + background: var(--surface-light); + border-top: 1px solid var(--border); + } + + @media (max-width: 480px) { + .chatbot-container { + width: calc(100vw - 40px); + height: 500px; + right: 0; + bottom: 80px; + } + + .chatbot-widget { + bottom: 20px; + right: 20px; + } + + .chatbot-toggle { + width: 55px; + height: 55px; + } + + .chatbot-toggle i { + font-size: 24px; + } + } + + /* componente informativo */ + /* ==================== */ + /* COMPONENTE: CONÓCENOS */ + /* ==================== */ + .conocenos-section { + padding: 5rem clamp(1rem, 3vw, 2rem); + max-width: var(--max-width); + margin: 0 auto; + background: var(--secondary); + border-radius: 40px; + margin-top: 2rem; + margin-bottom: 2rem; + } + + .conocenos-header { + text-align: center; + margin-bottom: 3rem; + } + + .conocenos-subtitle { + color: var(--accent); + font-size: 1rem; + text-transform: uppercase; + letter-spacing: 2px; + font-weight: 500; + margin-bottom: 0.5rem; + display: inline-block; + background: var(--surface-light); + padding: 0.5rem 1.5rem; + border-radius: 50px; + } + + .conocenos-title { + font-family: 'Space Grotesk', sans-serif; + font-size: clamp(2rem, 4vw, 2.8rem); + font-weight: 700; + background: linear-gradient(135deg, var(--text-primary), var(--accent)); + -webkit-background-clip: text; + background-clip: text; + color: transparent; + margin-top: 1rem; + } + + .conocenos-grid { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 2rem; + margin-bottom: 4rem; + } + + @media (max-width: 1024px) { + .conocenos-grid { + grid-template-columns: repeat(2, 1fr); + gap: 1.5rem; + } + } + + @media (max-width: 640px) { + .conocenos-grid { + grid-template-columns: 1fr; + gap: 1.5rem; + } + } + + .conocenos-card { + background: var(--surface); + border: 1px solid var(--border); + border-radius: 28px; + padding: 2rem 1.5rem; + text-align: center; + transition: all var(--transition-base); + position: relative; + overflow: hidden; + } + + .conocenos-card::before { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 4px; + background: var(--accent-gradient); + transform: scaleX(0); + transform-origin: left; + transition: transform var(--transition-base); + } + + .conocenos-card:hover { + transform: translateY(-8px); + border-color: var(--accent); + box-shadow: var(--shadow-lg); + } + + .conocenos-card:hover::before { + transform: scaleX(1); + } + + .conocenos-icon { + width: 80px; + height: 80px; + background: var(--accent-gradient); + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + margin: 0 auto 1.5rem; + font-size: 2rem; + color: white; + box-shadow: var(--shadow-md); + transition: transform var(--transition-fast); + } + + .conocenos-card:hover .conocenos-icon { + transform: scale(1.1) rotate(5deg); + } + + .conocenos-card h3 { + font-size: 1.5rem; + font-weight: 700; + margin-bottom: 0.5rem; + color: var(--text-primary); + } + + .conocenos-card h3 span { + display: block; + font-size: 1rem; + font-weight: 400; + color: var(--accent); + margin-top: 0.25rem; + } + + .conocenos-card p { + color: var(--text-secondary); + font-size: 0.9rem; + line-height: 1.5; + } + + .conocenos-cta { + text-align: center; + margin-top: 2rem; + } + + .btn-reserva { + display: inline-flex; + align-items: center; + gap: 12px; + padding: 1rem 2.5rem; + background: var(--accent-gradient); + color: white; + text-decoration: none; + border-radius: 50px; + font-weight: 700; + font-size: 1.1rem; + transition: all var(--transition-base); + box-shadow: var(--shadow-md); + border: none; + cursor: pointer; + } + + .btn-reserva:hover { + transform: translateY(-3px); + box-shadow: var(--shadow-lg); + } + + .btn-reserva i { + transition: transform var(--transition-fast); + } + + .btn-reserva:hover i { + transform: translateX(5px); + } + + + /* ==================== */ +/* COMPONENTE: EXTRAS (Complementa tu viaje) */ +/* ==================== */ + +.extras-section { + padding: 5rem clamp(1rem, 3vw, 2rem); + max-width: var(--max-width); + margin: 0 auto; + background: var(--primary); + border-radius: 40px; + margin-top: 2rem; + margin-bottom: 2rem; +} + +.extras-header { + text-align: center; + margin-bottom: 3rem; +} + +.extras-subtitle { + display: inline-flex; + align-items: center; + gap: 8px; + background: var(--surface-light); + padding: 0.5rem 1.5rem; + border-radius: 50px; + color: var(--accent); + font-size: 0.9rem; + font-weight: 500; + text-transform: uppercase; + letter-spacing: 2px; + margin-bottom: 1rem; +} + +.extras-subtitle i { + font-size: 0.9rem; +} + +.extras-title { + font-family: 'Space Grotesk', sans-serif; + font-size: clamp(2rem, 4vw, 2.8rem); + font-weight: 700; + background: linear-gradient(135deg, var(--text-primary), var(--accent)); + -webkit-background-clip: text; + background-clip: text; + color: transparent; + margin-bottom: 1rem; +} + +.extras-description { + color: var(--text-secondary); + font-size: 1.1rem; + max-width: 700px; + margin: 0 auto; +} + +.extras-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 2rem; + margin-bottom: 4rem; +} + +@media (max-width: 1024px) { + .extras-grid { + grid-template-columns: repeat(2, 1fr); + gap: 1.5rem; + } +} + +@media (max-width: 640px) { + .extras-grid { + grid-template-columns: 1fr; + gap: 1.5rem; + } +} + +.extra-card { + background: var(--secondary); + border: 1px solid var(--border); + border-radius: 28px; + padding: 2rem 1.5rem; + text-align: center; + transition: all var(--transition-base); + position: relative; + overflow: hidden; + backdrop-filter: blur(10px); +} + +.extra-card::before { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 4px; + background: var(--accent-gradient); + transform: scaleX(0); + transform-origin: left; + transition: transform var(--transition-base); +} + +.extra-card:hover { + transform: translateY(-8px); + border-color: var(--accent); + box-shadow: var(--shadow-lg); +} + +.extra-card:hover::before { + transform: scaleX(1); +} + +.extra-icon { + width: 80px; + height: 80px; + background: var(--accent-gradient); + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + margin: 0 auto 1.5rem; + font-size: 2rem; + color: white; + box-shadow: var(--shadow-md); + transition: transform var(--transition-fast); +} + +.extra-card:hover .extra-icon { + transform: scale(1.1) rotate(5deg); +} + +.extra-card h3 { + font-size: 1.5rem; + font-weight: 700; + margin-bottom: 0.5rem; + color: var(--text-primary); +} + +.extra-card h3 span { + display: block; + font-size: 1rem; + font-weight: 400; + color: var(--accent); + margin-top: 0.25rem; +} + +.extra-card p { + color: var(--text-secondary); + font-size: 0.9rem; + line-height: 1.5; + margin-bottom: 1.5rem; +} + +.extra-link { + display: inline-flex; + align-items: center; + gap: 8px; + color: var(--accent); + font-weight: 600; + text-decoration: none; + font-size: 0.9rem; + transition: gap var(--transition-fast); +} + +.extra-link:hover { + gap: 12px; +} + +.extra-link i { + font-size: 0.8rem; + transition: transform var(--transition-fast); +} + +.extra-link:hover i { + transform: translateX(3px); +} + +.extras-cta { + text-align: center; +} + +.btn-extras { + display: inline-flex; + align-items: center; + gap: 10px; + padding: 1rem 2rem; + background: var(--surface-light); + border: 1px solid var(--border); + border-radius: 50px; + color: var(--text-primary); + font-weight: 600; + text-decoration: none; + transition: all var(--transition-base); +} + +.btn-extras:hover { + background: var(--accent); + color: white; + border-color: var(--accent); + transform: translateY(-3px); + box-shadow: var(--shadow-md); +} + +.btn-extras i { + transition: transform var(--transition-fast); +} + +.btn-extras:hover i { + transform: translateX(5px); +} \ No newline at end of file diff --git a/public/assets/css/style.min.css b/public/assets/css/style.min.css new file mode 100644 index 0000000..a9cd299 --- /dev/null +++ b/public/assets/css/style.min.css @@ -0,0 +1,3681 @@ +/* ==================== */ +/* VARIABLES GLOBALES (MEJORADAS) */ +/* ==================== */ +:root { + /* Colores principales */ + --color-primary: #4A63D2; + --color-primary-light: #B7C1F0; + --color-dark: #2B2F5B; + --color-sky: #6ECFF6; + --color-accent: #FFC107; + + /* Colores secundarios */ + --color-cyan: #00B4D8; + --color-blue-deep: #0077B6; + --color-mint: #80E0D0; + --color-orange: #FF8A65; + --color-light: #F7F9FC; + + /* Gradientes */ + --gradient-primary: linear-gradient(135deg, #4A63D2, #6ECFF6); + --gradient-sunset: linear-gradient(135deg, #FFC107, #FF8A65); + --gradient-ocean: linear-gradient(135deg, #0077B6, #00B4D8); + + /* Estados UI */ + --color-success: #80E0D0; + --color-warning: #FFC107; + --color-info: #6ECFF6; + --color-danger: #FF8A65; + + /* Fondos */ + --bg-main: #4A63D2; + --bg-soft: #B7C1F0; + --bg-white: #F7F9FC; + + /* Texto */ + --text-dark: #2B2F5B; + --text-mid: #4A63D2; + --text-light: #F7F9FC; + + /* Tema oscuro (predeterminado) */ + --primary: #1a1d3a; + --secondary: #25284a; + --accent: #6ECFF6; + --accent-gradient: linear-gradient(135deg, #6ECFF6, #4A63D2); + --gold: #FFC107; + --gold-gradient: linear-gradient(135deg, #FFC107, #FF8A65); + --text-primary: #F7F9FC; + --text-secondary: #B7C1F0; + --text-tertiary: #94a3b8; + --surface: rgba(43, 47, 91, 0.85); + --surface-light: rgba(255, 255, 255, 0.08); + --border: rgba(255, 255, 255, 0.12); + --success: #80E0D0; + --error: #FF8A65; + + /* Sombras mejoradas con color variable */ + --shadow-color: 0, 0, 0; + --shadow-sm: 0 2px 8px rgba(var(--shadow-color), 0.25); + --shadow-md: 0 8px 32px rgba(var(--shadow-color), 0.4); + --shadow-lg: 0 20px 60px rgba(var(--shadow-color), 0.5); + --shadow-xl: 0 40px 80px rgba(var(--shadow-color), 0.6); + + --transition-fast: 200ms cubic-bezier(0.4, 0, 0.2, 1); + --transition-base: 400ms cubic-bezier(0.4, 0, 0.2, 1); + --transition-slow: 600ms cubic-bezier(0.4, 0, 0.2, 1); + --navbar-height: 80px; + --navbar-height-mobile: 70px; + --max-width: 1440px; +} + +body.light-mode { + --primary: #F7F9FC; + --secondary: #ffffff; + --accent: #4A63D2; + --accent-gradient: linear-gradient(135deg, #4A63D2, #6ECFF6); + --gold: #FFC107; + --gold-gradient: linear-gradient(135deg, #FFC107, #FF8A65); + --text-primary: #2B2F5B; + --text-secondary: #4A63D2; /* más oscuro para mejor contraste */ + --text-tertiary: #64748b; /* antes #718096, ahora más oscuro */ + --surface: rgba(255, 255, 255, 0.95); + --surface-light: rgba(0, 0, 0, 0.04); + --border: rgba(0, 0, 0, 0.12); + + --shadow-color: 0, 0, 0; + --shadow-sm: 0 2px 8px rgba(var(--shadow-color), 0.06); + --shadow-md: 0 8px 32px rgba(var(--shadow-color), 0.10); + --shadow-lg: 0 20px 60px rgba(var(--shadow-color), 0.12); + --shadow-xl: 0 40px 80px rgba(var(--shadow-color), 0.15); +} + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +html { + scroll-behavior: smooth; +} + +body { + font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; + background: var(--primary); + color: var(--text-primary); + overflow-x: hidden; + line-height: 1.6; + transition: background-color var(--transition-base), color var(--transition-base); +} + +/* Utilidades */ +.hide-mobile { + display: none; +} + +@media (min-width: 769px) { + .hide-mobile { + display: flex; + } + .hide-desktop { + display: none !important; + } +} + +::-webkit-scrollbar { + width: 10px; +} +::-webkit-scrollbar-track { + background: var(--secondary); +} +::-webkit-scrollbar-thumb { + background: var(--text-tertiary); + border-radius: 5px; +} +::-webkit-scrollbar-thumb:hover { + background: var(--accent); +} + +/* ==================== */ +/* ANIMACIONES GLOBALES */ +/* ==================== */ +@keyframes fadeInUp { + from { + opacity: 0; + transform: translateY(30px); + } + to { + opacity: 1; + transform: translateY(0); + } +} +@keyframes slideInRight { + from { + transform: translateX(100%); + opacity: 0; + } + to { + transform: translateX(0); + opacity: 1; + } +} +@keyframes slideOutRight { + from { + transform: translateX(0); + opacity: 1; + } + to { + transform: translateX(100%); + opacity: 0; + } +} + +/* ==================== */ +/* COMPONENTE: PRELOADER */ +/* ==================== */ +#preloader { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: var(--primary); + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + z-index: 9999; + transition: opacity var(--transition-slow), visibility var(--transition-slow); +} +#preloader.loaded { + opacity: 0; + visibility: hidden; +} +.preloader-container { + display: flex; + flex-direction: column; + align-items: center; + gap: 2rem; + text-align: center; + max-width: 300px; + width: 90%; +} +.preloader-logo { + width: 120px; + height: 120px; + position: relative; + animation: float 3s ease-in-out infinite; + display: flex; + align-items: center; + justify-content: center; +} +.preloader-logo img { + width: 100%; + height: 100%; + object-fit: contain; + filter: drop-shadow(0 0 20px rgba(110, 207, 246, 0.3)); + border-radius: 50%; +} +.preloader-logo::before { + content: ''; + position: absolute; + top: -10px; + left: -10px; + right: -10px; + bottom: -10px; + background: rgba(110, 207, 246, 0.1); + border-radius: 50%; + animation: pulse 2s ease-in-out infinite; + z-index: -1; +} +.preloader-text { + font-family: 'Space Grotesk', sans-serif; + font-size: clamp(1.2rem, 2vw, 1.5rem); + font-weight: 600; + color: var(--text-primary); + margin-top: 1rem; + position: relative; + overflow: hidden; +} +.preloader-text::after { + content: 'cargando.'; + animation: dots 3s steps(4, end) infinite; +} +.preloader-progress { + width: 100%; + height: 4px; + background: rgba(255, 255, 255, 0.1); + border-radius: 2px; + overflow: hidden; + margin-top: 1rem; +} +.preloader-progress-bar { + width: 0%; + height: 100%; + background: var(--accent-gradient); + border-radius: 2px; + transition: width 0.3s ease; +} +.preloader-subtext { + color: var(--text-tertiary); + font-size: clamp(0.85rem, 1.5vw, 0.9rem); + margin-top: 0.5rem; + opacity: 0.8; +} + +@keyframes float { + 0%, 100% { transform: translateY(0); } + 50% { transform: translateY(-20px); } +} +@keyframes pulse { + 0%, 100% { opacity: 0.5; transform: scale(1); } + 50% { opacity: 1; transform: scale(1.1); } +} +@keyframes dots { + 0%, 20% { content: 'cargando.'; } + 40%, 60% { content: 'cargando..'; } + 80%, 100% { content: 'cargando...'; } +} + +/* ==================== */ +/* COMPONENTE: NAVBAR PREMIUM */ +/* ==================== */ +.navbar-premium { + position: fixed; + top: 0; + left: 0; + right: 0; + height: var(--navbar-height); + background: rgba(26, 29, 58, 0.85); + backdrop-filter: blur(20px); + -webkit-backdrop-filter: blur(20px); + border-bottom: 1px solid var(--border); + z-index: 1000; + transition: all var(--transition-base); + padding: 0 clamp(1rem, 3vw, 2rem); +} +@media (max-width: 768px) { + .navbar-premium { + height: var(--navbar-height-mobile); + padding: 0 1rem; + } + .nav-desktop { + display: none !important; + } + .nav-actions .profile-btn, + .nav-actions .cta-btn { + display: none !important; + } + .hamburger-menu { + display: flex !important; + } +} +@media (min-width: 769px) and (max-width: 1024px) { + .navbar-premium { + padding: 0 1.5rem; + } + .nav-desktop { + display: none !important; + } + .hamburger-menu { + display: flex !important; + } +} +.light-mode .navbar-premium { + background: rgba(247, 249, 252, 0.85); +} +.navbar-container { + max-width: var(--max-width); + height: 100%; + margin: 0 auto; + display: flex; + align-items: center; + justify-content: space-between; +} +.navbar-logo { + display: flex; + align-items: center; + gap: 12px; + text-decoration: none; + transition: transform var(--transition-fast); +} +.navbar-logo:hover { + transform: translateY(-2px); +} +.logo-icon { + width: 42px; + height: 42px; + background: var(--accent-gradient); + border-radius: 12px; + display: flex; + align-items: center; + justify-content: center; + color: white; + font-size: 20px; + font-weight: 800; + box-shadow: var(--shadow-md); + flex-shrink: 0; +} +.logo-icon img { + width: 100%; + height: 100%; + object-fit: contain; + border-radius: 12px; +} +@media (max-width: 480px) { + .logo-icon { + width: 36px; + height: 36px; + } +} +.logo-text-container { + display: flex; + flex-direction: column; + line-height: 1.2; +} +.logo-main-text { + font-family: 'Space Grotesk', sans-serif; + font-size: clamp(1.3rem, 2vw, 1.6rem); + font-weight: 700; + color: var(--text-primary); + letter-spacing: -0.5px; + white-space: nowrap; +} +.logo-subtext { + font-family: 'Space Grotesk', sans-serif; + font-size: clamp(0.7rem, 1.2vw, 0.85rem); + font-weight: 600; + background: var(--gold-gradient); + -webkit-background-clip: text; + background-clip: text; + color: transparent; + letter-spacing: 5.5px; + margin-top: 1px; + text-transform: uppercase; + white-space: nowrap; +} +.logo-gradient { + background: var(--accent-gradient); + -webkit-background-clip: text; + background-clip: text; + color: transparent; +} +.nav-desktop { + display: flex; + gap: 2px; + background: var(--surface); + padding: 4px; + border-radius: 14px; + backdrop-filter: blur(10px); + border: 1px solid var(--border); +} +.nav-item { + position: relative; +} +.nav-link { + display: flex; + align-items: center; + gap: 10px; + padding: 12px 24px; + text-decoration: none; + color: var(--text-secondary); + font-weight: 500; + font-size: clamp(0.85rem, 1vw, 0.95rem); + border-radius: 10px; + transition: all var(--transition-fast); + position: relative; +} +.nav-link::after { + content: ''; + position: absolute; + bottom: 0; + left: 50%; + transform: translateX(-50%); + width: 0; + height: 2px; + background: var(--accent); + transition: width var(--transition-base); + border-radius: 2px; +} +.nav-link:hover { + color: var(--text-primary); + background: var(--surface-light); +} +.nav-link:hover::after { + width: 60%; +} +.nav-link.active { + color: var(--text-primary); + background: var(--surface-light); +} +.nav-link.active::after { + width: 80%; +} +.nav-icon { + font-size: 1.1rem; +} +.theme-toggle { + position: relative; + width: 44px; + height: 44px; + background: var(--surface); + border: 1px solid var(--border); + border-radius: 12px; + display: flex; + align-items: center; + justify-content: center; + color: var(--text-secondary); + cursor: pointer; + transition: all var(--transition-fast); + font-size: 1.1rem; +} +@media (max-width: 480px) { + .theme-toggle { + width: 40px; + height: 40px; + font-size: 1rem; + } +} +.theme-toggle:hover { + background: var(--surface-light); + color: var(--text-primary); + transform: translateY(-2px); + border-color: var(--accent); +} +.light-mode .theme-toggle .fa-sun { + display: none; +} +.light-mode .theme-toggle .fa-moon { + display: block; +} +.fa-moon { + display: none; +} +.nav-actions { + display: flex; + align-items: center; + gap: 1rem; +} +@media (max-width: 768px) { + .nav-actions { + gap: 0.75rem; + } +} +.action-btn { + position: relative; + width: 44px; + height: 44px; + background: var(--surface); + border: 1px solid var(--border); + border-radius: 12px; + display: flex; + align-items: center; + justify-content: center; + color: var(--text-secondary); + cursor: pointer; + transition: all var(--transition-fast); + font-size: 1.1rem; +} +@media (max-width: 480px) { + .action-btn { + width: 40px; + height: 40px; + font-size: 1rem; + } +} +.action-btn:hover { + background: var(--surface-light); + color: var(--text-primary); + transform: translateY(-2px); + border-color: var(--accent); +} +.notification-badge { + position: absolute; + top: -4px; + right: -4px; + background: var(--accent); + color: #2B2F5B; + font-size: 0.7rem; + width: 18px; + height: 18px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-weight: 700; + border: 2px solid var(--primary); +} +.cta-btn { + display: flex; + align-items: center; + gap: 10px; + padding: 12px 28px; + background: var(--accent-gradient); + color: white; + border: none; + border-radius: 12px; + font-weight: 600; + font-size: 0.95rem; + cursor: pointer; + transition: all var(--transition-base); + text-decoration: none; + box-shadow: var(--shadow-md); +} +@media (max-width: 480px) { + .cta-btn { + padding: 10px 20px; + font-size: 0.9rem; + } +} +.cta-btn:hover { + transform: translateY(-3px); + box-shadow: var(--shadow-lg); +} +.cta-btn i { + transition: transform var(--transition-fast); +} +.cta-btn:hover i { + transform: translateX(3px); +} +.hamburger-menu { + display: none; + width: 32px; + height: 32px; + flex-direction: column; + justify-content: center; + align-items: center; + cursor: pointer; + position: relative; + z-index: 1002; +} +@media (max-width: 1024px) { + .hamburger-menu { + display: flex; + } +} +@media (max-width: 480px) { + .hamburger-menu { + width: 28px; + height: 28px; + } +} +.hamburger-line { + width: 22px; + height: 2px; + background: var(--text-primary); + border-radius: 2px; + transition: all var(--transition-base); + position: absolute; +} +.hamburger-line:nth-child(1) { + transform: translateY(-6px); +} +.hamburger-line:nth-child(3) { + transform: translateY(6px); +} +.hamburger-menu.active .hamburger-line:nth-child(1) { + transform: rotate(45deg); +} +.hamburger-menu.active .hamburger-line:nth-child(2) { + opacity: 0; +} +.hamburger-menu.active .hamburger-line:nth-child(3) { + transform: rotate(-45deg); +} +.mobile-menu { + position: fixed; + top: 0; + right: -100%; + width: 100%; + max-width: 320px; + height: 100vh; + background: var(--secondary); + backdrop-filter: blur(30px); + -webkit-backdrop-filter: blur(30px); + border-left: 1px solid var(--border); + transition: right var(--transition-base); + z-index: 1001; + padding: 2rem; + display: flex; + flex-direction: column; + overflow-y: auto; +} +@media (max-width: 480px) { + .mobile-menu { + padding: 1.5rem; + max-width: 280px; + } +} +.mobile-menu.active { + right: 0; +} +.mobile-menu-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 2rem; +} +.mobile-nav { + display: flex; + flex-direction: column; + gap: 8px; + margin-bottom: 2rem; +} +.mobile-nav-item { + list-style: none; +} +.mobile-nav-link { + display: flex; + align-items: center; + gap: 12px; + padding: 16px; + text-decoration: none; + color: var(--text-secondary); + border-radius: 12px; + transition: all var(--transition-fast); + font-weight: 500; +} +.mobile-nav-link:hover, +.mobile-nav-link.active { + background: var(--surface-light); + color: var(--text-primary); +} +.mobile-actions { + margin-top: auto; + display: flex; + flex-direction: column; + gap: 1rem; + padding-top: 2rem; + border-top: 1px solid var(--border); +} +.menu-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.7); + backdrop-filter: blur(5px); + -webkit-backdrop-filter: blur(5px); + opacity: 0; + visibility: hidden; + transition: all var(--transition-base); + z-index: 1000; +} +.menu-overlay.active { + opacity: 1; + visibility: visible; +} + +/* ==================== */ +/* COMPONENTE: MODAL DE BÚSQUEDA */ +/* ==================== */ +.search-modal { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100vh; + background: var(--primary); + z-index: 2000; + display: none; + flex-direction: column; + opacity: 0; + transition: opacity var(--transition-base); +} +.search-modal.active { + display: flex; + opacity: 1; +} +.search-modal-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 1.5rem clamp(1rem, 3vw, 2rem); + border-bottom: 1px solid var(--border); +} +.search-modal-title { + font-family: 'Space Grotesk', sans-serif; + font-size: clamp(1.5rem, 3vw, 2rem); + font-weight: 700; + color: var(--text-primary); +} +.search-close-btn { + width: 44px; + height: 44px; + background: var(--surface); + border: 1px solid var(--border); + border-radius: 12px; + display: flex; + align-items: center; + justify-content: center; + color: var(--text-secondary); + cursor: pointer; + transition: all var(--transition-fast); + font-size: 1.2rem; +} +.search-close-btn:hover { + background: var(--surface-light); + color: var(--text-primary); + transform: rotate(90deg); +} +.search-modal-content { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 2rem; + max-width: 800px; + margin: 0 auto; + width: 100%; +} +.search-input-container { + width: 100%; + position: relative; + margin-bottom: 3rem; +} +.search-input { + width: 100%; + height: 70px; + padding: 0 2rem 0 5rem; + background: var(--secondary); + border: 2px solid var(--border); + border-radius: 20px; + color: var(--text-primary); + font-family: 'Inter', sans-serif; + font-size: clamp(1.2rem, 2vw, 1.5rem); + transition: all var(--transition-base); + outline: none; +} +.search-input:focus { + border-color: var(--accent); + box-shadow: 0 0 0 4px rgba(110, 207, 246, 0.15); + transform: translateY(-2px); +} +.search-icon { + position: absolute; + left: 1.5rem; + top: 50%; + transform: translateY(-50%); + color: var(--accent); + font-size: 1.5rem; +} +.search-results { + width: 100%; + display: grid; + grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); + gap: 1.5rem; + opacity: 0; + transform: translateY(20px); + transition: all var(--transition-base); +} +.search-results.active { + opacity: 1; + transform: translateY(0); +} +.search-result-item { + background: var(--surface); + border: 1px solid var(--border); + border-radius: 16px; + padding: 1.5rem; + transition: all var(--transition-fast); + cursor: pointer; +} +.search-result-item:hover { + transform: translateY(-5px); + border-color: var(--accent); + box-shadow: var(--shadow-lg); +} +.search-result-title { + font-size: 1.1rem; + font-weight: 600; + margin-bottom: 0.5rem; + color: var(--text-primary); +} +.search-result-description { + color: var(--text-secondary); + font-size: 0.9rem; + line-height: 1.5; +} +.search-suggestions { + display: flex; + flex-wrap: wrap; + gap: 1rem; + justify-content: center; + margin-top: 2rem; +} +.search-suggestion { + padding: 0.5rem 1rem; + background: var(--surface); + border: 1px solid var(--border); + border-radius: 50px; + color: var(--text-secondary); + font-size: 0.9rem; + cursor: pointer; + transition: all var(--transition-fast); +} +.search-suggestion:hover { + background: var(--accent); + color: #2B2F5B; + transform: translateY(-2px); +} + +/* ==================== */ +/* COMPONENTE: HERO SECTION */ +/* ==================== */ +.hero-section { + position: relative; + height: 100vh; + min-height: 600px; + width: 100%; + overflow: hidden; + margin-top: var(--navbar-height); +} +@media (max-width: 768px) { + .hero-section { + margin-top: var(--navbar-height-mobile); + min-height: 700px; + } +} +@media (max-width: 480px) { + .hero-section { + min-height: 800px; + height: auto; + } +} +.video-container { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + overflow: hidden; +} +.hero-video { + width: 100%; + height: 100%; + object-fit: cover; + object-position: center; + filter: brightness(0.7); +} +.light-mode .hero-video { + filter: brightness(0.9); +} +.video-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: linear-gradient(to bottom, + rgba(26, 29, 58, 0.2) 0%, + rgba(26, 29, 58, 0.4) 30%, + rgba(26, 29, 58, 0.7) 70%, + rgba(26, 29, 58, 0.9) 100%); + z-index: 1; +} +.light-mode .video-overlay { + background: linear-gradient(to bottom, + rgba(247, 249, 252, 0.3) 0%, + rgba(247, 249, 252, 0.5) 30%, + rgba(247, 249, 252, 0.7) 70%, + rgba(247, 249, 252, 0.9) 100%); +} +.hero-content { + position: relative; + z-index: 2; + height: 100%; + display: flex; + flex-direction: column; + justify-content: space-between; + padding: 0 clamp(1rem, 3vw, 2rem); + max-width: var(--max-width); + margin: 0 auto; +} +.hero-text { + padding-top: 10vh; + max-width: 800px; +} +@media (max-width: 768px) { + .hero-text { + padding-top: 5vh; + } +} +.hero-subtitle { + display: inline-block; + background: var(--surface); + backdrop-filter: blur(10px); + padding: clamp(6px, 1.5vw, 8px) clamp(16px, 2.5vw, 20px); + border-radius: 50px; + font-size: clamp(0.8rem, 1vw, 0.9rem); + font-weight: 500; + color: var(--accent); + margin-bottom: 1.5rem; + border: 1px solid var(--border); + animation: fadeInUp 1s ease-out; +} +.hero-title { + font-family: 'Space Grotesk', sans-serif; + font-size: clamp(2rem, 5vw, 4.5rem); + font-weight: 800; + line-height: 1.1; + margin-bottom: clamp(1rem, 2vw, 1.5rem); + background: linear-gradient(to right, var(--text-primary), var(--accent)); + -webkit-background-clip: text; + background-clip: text; + color: transparent; + animation: fadeInUp 1s ease-out 0.2s both; +} +@media (max-width: 480px) { + .hero-title { + font-size: 2.2rem; + line-height: 1.2; + } +} +.hero-description { + font-size: clamp(0.95rem, 2vw, 1.25rem); + color: var(--text-secondary); + max-width: 600px; + margin-bottom: clamp(1.5rem, 3vw, 2rem); + animation: fadeInUp 1s ease-out 0.4s both; +} +.hero-stats { + display: flex; + gap: clamp(1.5rem, 4vw, 3rem); + margin-top: clamp(2rem, 4vw, 3rem); + animation: fadeInUp 1s ease-out 0.6s both; +} +@media (max-width: 768px) { + .hero-stats { + flex-wrap: wrap; + justify-content: center; + gap: 2rem; + } +} +.stat-item { + display: flex; + flex-direction: column; + align-items: center; + text-align: center; +} +.stat-number { + font-size: clamp(2rem, 4vw, 2.5rem); + font-weight: 800; + background: var(--accent-gradient); + -webkit-background-clip: text; + background-clip: text; + color: transparent; + line-height: 1; + margin-bottom: 0.5rem; +} +.stat-label { + font-size: clamp(0.8rem, 1vw, 0.9rem); + color: var(--text-tertiary); + text-transform: uppercase; + letter-spacing: 0.5px; +} + +/* ==================== */ +/* COMPONENTE: VUELOS (Tours original) */ +/* ==================== */ +.vuelos-section { + padding: 5rem clamp(1rem, 3vw, 2rem); + max-width: var(--max-width); + margin: 0 auto; +} +.block-title { + text-align: center; + margin-bottom: 3rem; +} +.block-title p { + color: var(--accent); + font-size: 1rem; + text-transform: uppercase; + letter-spacing: 2px; + margin-bottom: 0.5rem; + font-weight: 500; +} +.block-title h3 { + font-family: 'Space Grotesk', sans-serif; + font-size: clamp(2rem, 4vw, 2.8rem); + font-weight: 700; + background: linear-gradient(135deg, var(--text-primary), var(--accent)); + -webkit-background-clip: text; + background-clip: text; + color: transparent; +} +.row { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); + gap: 2rem; +} +.tour-one__single { + background: var(--secondary); + border-radius: 24px; + overflow: hidden; + transition: all var(--transition-base); + border: 1px solid var(--border); + position: relative; +} +.tour-one__single:hover { + transform: translateY(-12px); + border-color: var(--accent); + box-shadow: var(--shadow-xl); +} +.tour-one__image { + position: relative; + overflow: hidden; + aspect-ratio: 370 / 292; +} +.tour-one__image img { + width: 100%; + height: 100%; + object-fit: cover; + transition: transform var(--transition-slow); + cursor: pointer; +} +.tour-one__single:hover .tour-one__image img { + transform: scale(1.05); +} +.tour-one__content { + padding: 1.5rem; + position: relative; + background: var(--secondary); +} +.tour-one__content h3 { + font-size: 1.35rem; + font-weight: 700; + margin-bottom: 0.75rem; + line-height: 1.3; +} +.tour-one__content h3 a { + color: var(--text-primary); + text-decoration: none; + transition: color var(--transition-fast); +} +.tour-one__content h3 a:hover { + color: var(--accent); +} +.tour-one__content p { + margin: 0.5rem 0; + color: var(--text-secondary); + font-size: 0.95rem; +} +.tour-one__content p span { + font-size: 1.6rem; + font-weight: 800; + background: var(--accent-gradient); + -webkit-background-clip: text; + background-clip: text; + color: transparent; + letter-spacing: -0.5px; +} +.tour-one__meta { + display: flex; + gap: 1.25rem; + list-style: none; + margin-top: 1.25rem; + padding-top: 1rem; + border-top: 1px solid var(--border); +} +.tour-one__meta li { + display: flex; + align-items: center; +} +.tour-one__meta a { + display: flex; + align-items: center; + gap: 0.5rem; + color: var(--text-tertiary); + text-decoration: none; + font-size: 0.85rem; + transition: color var(--transition-fast); +} +.tour-one__meta a:hover { + color: var(--accent); +} +.tour-one__meta i { + font-size: 0.9rem; + color: var(--accent); +} + +/* ==================== */ +/* COMPONENTE: FLIGHTS PREMIUM (Tarjetas mejoradas) */ +/* ==================== */ +.flights-premium { + padding: 5rem clamp(1rem, 3vw, 2rem); + max-width: var(--max-width); + margin: 0 auto; +} +.flights-premium .section-badge { + display: inline-block; + font-size: 0.85rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 3px; + color: var(--accent); + background: rgba(110, 207, 246, 0.1); + padding: 6px 16px; + border-radius: 50px; + margin-bottom: 16px; +} +.flights-premium .section-title { + font-size: clamp(2rem, 4vw, 3rem); + font-weight: 800; + background: linear-gradient(135deg, var(--text-primary), var(--accent)); + -webkit-background-clip: text; + background-clip: text; + color: transparent; + margin-bottom: 16px; +} +.flights-premium .section-description { + font-size: 1.1rem; + color: var(--text-secondary); + max-width: 600px; + margin: 0 auto 3rem auto; + text-align: center; +} +.flights-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(380px, 1fr)); + gap: 32px; +} +.flight-card { + position: relative; + background: var(--secondary); + border-radius: 28px; + overflow: hidden; + transition: all 0.4s cubic-bezier(0.2, 0.9, 0.4, 1.1); + box-shadow: var(--shadow-md); + border: 1px solid var(--border); +} +.flight-card:hover { + transform: translateY(-12px); + box-shadow: var(--shadow-xl); + border-color: var(--accent); +} +.flight-card__badge { + position: absolute; + top: 20px; + left: 20px; + z-index: 10; + display: flex; + align-items: center; + gap: 8px; + padding: 8px 16px; + border-radius: 50px; + color: white; + font-size: 0.8rem; + font-weight: 600; + backdrop-filter: blur(10px); + box-shadow: var(--shadow-sm); + animation: slideInLeft 0.4s ease; +} +.flight-card__badge i { + font-size: 0.75rem; +} +.flight-card__discount { + position: absolute; + top: 20px; + right: 20px; + z-index: 10; + background: linear-gradient(135deg, #FF8A65, #FFC107); + border-radius: 50%; + width: 65px; + height: 65px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + color: #2B2F5B; + font-weight: 800; + box-shadow: var(--shadow-md); + animation: bounceIn 0.5s ease; +} +.flight-card__discount span { + font-size: 1.2rem; + line-height: 1; +} +.flight-card__discount small { + font-size: 0.7rem; + opacity: 0.9; +} +.flight-card__image { + position: relative; + height: 260px; + overflow: hidden; + cursor: pointer; + background: linear-gradient(90deg, #25284a 25%, #3a3f6a 50%, #25284a 75%); + background-size: 200% 100%; + animation: skeleton-pulse 1.5s infinite; +} +@keyframes skeleton-pulse { + 0% { background-position: -200% 0; } + 100% { background-position: 200% 0; } +} +.flight-card__image img { + width: 100%; + height: 100%; + object-fit: cover; + transition: transform 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94); + opacity: 0; +} +.flight-card__image img.loaded { + opacity: 1; + animation: none; +} +.flight-card:hover .flight-card__image img { + transform: scale(1.08); +} +.flight-card__overlay { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.6); + backdrop-filter: blur(5px); + display: flex; + align-items: center; + justify-content: center; + opacity: 0; + transition: all 0.3s ease; +} +.flight-card:hover .flight-card__overlay { + opacity: 1; +} +.flight-card__quick-view { + background: linear-gradient(135deg, var(--accent), #4A63D2); + border: none; + padding: 12px 24px; + border-radius: 50px; + color: white; + font-size: 0.9rem; + font-weight: 600; + cursor: pointer; + transition: all 0.3s ease; + transform: translateY(20px); + opacity: 0; +} +.flight-card:hover .flight-card__quick-view { + transform: translateY(0); + opacity: 1; +} +.flight-card__quick-view:hover { + transform: translateY(-3px); + box-shadow: 0 5px 20px rgba(110, 207, 246, 0.4); +} +.flight-card__rating { + position: absolute; + bottom: 15px; + right: 15px; + background: rgba(0, 0, 0, 0.8); + backdrop-filter: blur(10px); + padding: 6px 12px; + border-radius: 50px; + display: flex; + align-items: center; + gap: 6px; + font-size: 0.85rem; + z-index: 5; +} +.flight-card__rating i { + color: #FFC107; +} +.flight-card__rating span { + color: white; + font-weight: 600; +} +.flight-card__rating small { + color: rgba(255, 255, 255, 0.7); + font-size: 0.7rem; +} +.light-mode .flight-card__rating { + background: rgba(0, 0, 0, 0.5); /* más suave en modo claro */ +} +.flight-card__content { + padding: 24px; +} +.flight-card__category { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 16px; +} +.category-tag { + display: inline-flex; + align-items: center; + gap: 6px; + background: rgba(110, 207, 246, 0.15); /* sutilmente más opaco */ + padding: 5px 12px; + border-radius: 50px; + font-size: 0.75rem; + font-weight: 600; + color: var(--accent); +} +.duration-tag { + display: inline-flex; + align-items: center; + gap: 6px; + background: var(--surface-light); + padding: 5px 12px; + border-radius: 50px; + font-size: 0.75rem; + color: var(--text-secondary); +} +.flight-card__title { + font-size: 1.35rem; + font-weight: 700; + margin-bottom: 12px; + line-height: 1.3; +} +.flight-card__title a { + color: var(--text-primary); + text-decoration: none; + transition: color 0.2s ease; +} +.flight-card__title a:hover { + color: var(--accent); +} +.flight-card__description { + color: var(--text-secondary); + font-size: 0.9rem; + line-height: 1.5; + margin-bottom: 16px; +} +.flight-card__schedule { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 16px; + padding: 8px 12px; + background: var(--surface-light); + border-radius: 12px; + font-size: 0.85rem; + color: var(--text-secondary); +} +.flight-card__schedule i { + color: var(--accent); +} +.flight-card__includes { + display: flex; + flex-wrap: wrap; + gap: 8px; + margin-bottom: 20px; +} +.include-item { + display: inline-flex; + align-items: center; + gap: 6px; + background: var(--surface-light); + padding: 5px 10px; + border-radius: 50px; + font-size: 0.7rem; + color: var(--text-secondary); + transition: all 0.2s ease; +} +.include-item i { + color: #80E0D0; + font-size: 0.65rem; +} +.include-item:hover { + background: var(--accent); + color: #2B2F5B; + transform: translateY(-2px); +} +.include-item:hover i { + color: #2B2F5B; +} +.flight-card__prices { + background: linear-gradient(135deg, rgba(110, 207, 246, 0.08), rgba(74, 99, 210, 0.08)); + border-radius: 16px; + padding: 16px; + margin-bottom: 20px; + border: 1px solid var(--border); +} +.prices-wrapper { + display: flex; + align-items: baseline; + justify-content: space-between; + flex-wrap: wrap; + gap: 10px; + margin-bottom: 8px; +} +.price-old { + text-decoration: line-through; + color: #FF8A65; + font-size: 0.9rem; +} +.price-current { + display: flex; + align-items: baseline; + gap: 4px; +} +.price-current .currency { + font-size: 1rem; + font-weight: 600; + color: var(--accent); +} +.price-current .amount { + font-size: 2rem; + font-weight: 800; + background: linear-gradient(135deg, var(--accent), #4A63D2); + -webkit-background-clip: text; + background-clip: text; + color: transparent; +} +.price-current .period { + font-size: 0.8rem; + color: var(--text-secondary); +} +.price-savings { + display: flex; + align-items: center; + gap: 6px; + font-size: 0.8rem; + color: #80E0D0; + padding-top: 8px; + border-top: 1px solid var(--border); +} +.flight-card__meta { + display: flex; + flex-wrap: wrap; + gap: 16px; + margin-bottom: 20px; + padding: 12px 0; + border-top: 1px solid var(--border); + border-bottom: 1px solid var(--border); +} +.meta-item { + display: flex; + align-items: center; + gap: 8px; + font-size: 0.8rem; + color: var(--text-secondary); +} +.meta-item i { + color: var(--accent); + width: 16px; +} +.flight-card__actions { + display: flex; + gap: 12px; +} +.btn-inquire { + flex: 1; + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + padding: 12px 16px; + background: var(--surface-light); + border: 1px solid var(--border); + border-radius: 50px; + color: var(--text-primary); + font-size: 0.85rem; + font-weight: 600; + cursor: pointer; + transition: all 0.3s ease; +} +.btn-inquire:hover { + background: var(--surface); + border-color: var(--accent); + transform: translateY(-2px); +} +.btn-book { + flex: 1.5; + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + padding: 12px 20px; + background: linear-gradient(135deg, var(--accent), #4A63D2); + border: none; + border-radius: 50px; + color: white; + font-size: 0.85rem; + font-weight: 600; + text-decoration: none; + transition: all 0.3s ease; + position: relative; + overflow: hidden; +} +.btn-book::before { + content: ""; + position: absolute; + top: 50%; + left: 50%; + width: 0; + height: 0; + border-radius: 50%; + background: rgba(255, 255, 255, 0.3); + transform: translate(-50%, -50%); + transition: width 0.5s, height 0.5s; +} +.btn-book:active::before { + width: 200px; + height: 200px; +} +.btn-book:hover { + transform: translateY(-2px); + box-shadow: 0 8px 20px rgba(110, 207, 246, 0.4); + gap: 12px; +} +.btn-book i:last-child { + transition: transform 0.2s ease; +} +.btn-book:hover i:last-child { + transform: translateX(4px); +} +.no-flights { + text-align: center; + padding: 60px 20px; + background: var(--secondary); + border-radius: 28px; +} +.no-flights__icon { + font-size: 4rem; + color: var(--accent); + margin-bottom: 20px; + opacity: 0.5; +} +.no-flights h3 { + font-size: 1.5rem; + margin-bottom: 12px; + color: var(--text-primary); +} +.no-flights p { + color: var(--text-secondary); + margin-bottom: 24px; +} +.btn-contact { + display: inline-flex; + align-items: center; + gap: 10px; + padding: 12px 28px; + background: linear-gradient(135deg, var(--accent), #4A63D2); + border: none; + border-radius: 50px; + color: white; + font-weight: 600; + cursor: pointer; + transition: all 0.3s ease; +} +.btn-contact:hover { + transform: translateY(-2px); + box-shadow: 0 8px 20px rgba(110, 207, 246, 0.4); +} +@keyframes slideInLeft { + from { + opacity: 0; + transform: translateX(-30px); + } + to { + opacity: 1; + transform: translateX(0); + } +} +@keyframes bounceIn { + 0% { + opacity: 0; + transform: scale(0); + } + 50% { + transform: scale(1.1); + } + 100% { + opacity: 1; + transform: scale(1); + } +} + +/* ==================== */ +/* COMPONENTE: FOOTER PREMIUM */ +/* ==================== */ +.footer { + background: var(--secondary); + border-top: 1px solid var(--border); + padding: clamp(3rem, 6vw, 5rem) clamp(1rem, 3vw, 2rem) 1.5rem; + margin-top: 2rem; +} +.footer-container { + max-width: var(--max-width); + margin: 0 auto; +} +.footer-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: clamp(2rem, 4vw, 3rem); + margin-bottom: 3rem; +} +.footer-brand { + max-width: 300px; +} +.footer-logo { + display: flex; + align-items: center; + gap: 12px; + text-decoration: none; + margin-bottom: 1.5rem; +} +.footer-logo-icon { + width: 45px; + height: 45px; + background: var(--accent-gradient); + border-radius: 12px; + display: flex; + align-items: center; + justify-content: center; + color: white; + font-size: 1.3rem; + font-weight: 800; +} +.footer-logo-text { + font-family: 'Space Grotesk', sans-serif; + font-size: 1.4rem; + font-weight: 700; + color: var(--text-primary); +} +.footer-logo-sub { + font-size: 0.7rem; + font-weight: 600; + background: var(--gold-gradient); + -webkit-background-clip: text; + background-clip: text; + color: transparent; + letter-spacing: 4px; +} +.footer-description { + color: var(--text-secondary); + font-size: 0.9rem; + line-height: 1.6; + margin-bottom: 1.5rem; +} +.footer-social { + display: flex; + gap: 1rem; +} +.social-link { + width: 40px; + height: 40px; + background: var(--surface-light); + border-radius: 12px; + display: flex; + align-items: center; + justify-content: center; + color: var(--text-secondary); + transition: all var(--transition-fast); + text-decoration: none; + font-size: 1.1rem; +} +.social-link:hover { + background: var(--accent); + color: #2B2F5B; + transform: translateY(-3px); +} +.footer-title { + font-size: 1.1rem; + font-weight: 700; + margin-bottom: 1.5rem; + color: var(--text-primary); + position: relative; + display: inline-block; +} +.footer-title::after { + content: ''; + position: absolute; + bottom: -8px; + left: 0; + width: 40px; + height: 2px; + background: var(--accent); + border-radius: 2px; +} +.footer-links { + list-style: none; + padding: 0; +} +.footer-links li { + margin-bottom: 0.75rem; +} +.footer-links a { + color: var(--text-secondary); + text-decoration: none; + transition: all var(--transition-fast); + font-size: 0.9rem; + display: inline-flex; + align-items: center; + gap: 8px; +} +.footer-links a:hover { + color: var(--accent); + transform: translateX(5px); +} +.footer-contact-item { + display: flex; + align-items: center; + gap: 12px; + margin-bottom: 1rem; + color: var(--text-secondary); + font-size: 0.9rem; +} +.footer-contact-item i { + width: 20px; + color: var(--accent); + font-size: 1rem; +} +.footer-newsletter { + margin-top: 0.5rem; +} +.newsletter-text { + color: var(--text-secondary); + font-size: 0.85rem; + margin-bottom: 1rem; + line-height: 1.5; +} +.newsletter-form { + display: flex; + gap: 0.5rem; + flex-wrap: wrap; +} +.newsletter-input { + flex: 1; + min-width: 140px; + padding: 0.75rem 1rem; + background: var(--surface-light); + border: 1px solid var(--border); + border-radius: 12px; + color: var(--text-primary); + font-size: 0.85rem; + outline: none; + transition: all var(--transition-fast); +} +.newsletter-input:focus { + border-color: var(--accent); + box-shadow: 0 0 0 3px rgba(110, 207, 246, 0.1); +} +.newsletter-btn { + padding: 0.75rem 1.25rem; + background: var(--accent-gradient); + border: none; + border-radius: 12px; + color: white; + font-weight: 600; + font-size: 0.85rem; + cursor: pointer; + transition: all var(--transition-fast); + white-space: nowrap; +} +.newsletter-btn:hover { + transform: translateY(-2px); + box-shadow: var(--shadow-sm); +} +.footer-bottom { + border-top: 1px solid var(--border); + padding-top: 2rem; + display: flex; + justify-content: space-between; + align-items: center; + flex-wrap: wrap; + gap: 1rem; +} +.copyright { + color: var(--text-tertiary); + font-size: 0.85rem; +} +.footer-legal { + display: flex; + gap: 1.5rem; + flex-wrap: wrap; +} +.footer-legal a { + color: var(--text-tertiary); + text-decoration: none; + font-size: 0.85rem; + transition: color var(--transition-fast); +} +.footer-legal a:hover { + color: var(--accent); +} +@media (max-width: 768px) { + .footer-bottom { + flex-direction: column; + text-align: center; + } + .footer-legal { + justify-content: center; + } + .footer-brand { + max-width: 100%; + text-align: center; + } + .footer-logo { + justify-content: center; + } + .footer-social { + justify-content: center; + } + .footer-title::after { + left: 50%; + transform: translateX(-50%); + } + .footer-title { + text-align: center; + display: block; + } + .footer-contact-item { + justify-content: center; + } + .footer-links { + text-align: center; + } + .footer-links a { + justify-content: center; + } +} + +/* ==================== */ +/* COMPONENTE: CHATBOT WIDGET (mejoras de contraste) */ +/* ==================== */ +.chatbot-widget { + position: fixed; + bottom: 30px; + right: 30px; + z-index: 2000; +} +.chatbot-toggle { + width: 65px; + height: 65px; + background: var(--accent-gradient); + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + box-shadow: var(--shadow-lg); + transition: all var(--transition-base); + border: 2px solid rgba(255, 255, 255, 0.2); + animation: pulseRing 2s ease-in-out infinite; +} +.chatbot-toggle:hover { + transform: scale(1.1); + box-shadow: var(--shadow-xl); + animation: none; +} +@keyframes pulseRing { + 0% { + box-shadow: 0 0 0 0 rgba(110, 207, 246, 0.7); + } + 70% { + box-shadow: 0 0 0 15px rgba(110, 207, 246, 0); + } + 100% { + box-shadow: 0 0 0 0 rgba(110, 207, 246, 0); + } +} +.chatbot-toggle i { + font-size: 28px; + color: white; +} +.chatbot-container { + position: absolute; + bottom: 80px; + right: 0; + width: 380px; + height: 550px; + background: var(--secondary); + border-radius: 24px; + overflow: hidden; + box-shadow: var(--shadow-xl); + border: 1px solid var(--border); + display: none; + flex-direction: column; + animation: slideInRight 0.3s ease-out; +} +.chatbot-container.active { + display: flex; +} +.chatbot-header { + background: var(--accent-gradient); + padding: 18px 20px; + display: flex; + align-items: center; + justify-content: space-between; + color: white; +} +.chatbot-header-info { + display: flex; + align-items: center; + gap: 12px; +} +.chatbot-header-info i { + font-size: 28px; +} +.chatbot-header-info h3 { + font-size: 16px; + font-weight: 600; + margin: 0; +} +.chatbot-header-info p { + font-size: 12px; + opacity: 0.8; + margin: 0; +} +.chatbot-close { + background: rgba(255, 255, 255, 0.2); + border: none; + width: 32px; + height: 32px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + color: white; + transition: all var(--transition-fast); +} +.chatbot-close:hover { + background: rgba(255, 255, 255, 0.3); + transform: rotate(90deg); +} +.chatbot-messages { + flex: 1; + overflow-y: auto; + padding: 20px; + display: flex; + flex-direction: column; + gap: 12px; +} +.chatbot-message { + max-width: 85%; + padding: 12px 16px; + border-radius: 18px; + font-size: 14px; + line-height: 1.5; + animation: fadeInUp 0.3s ease-out; +} +.chatbot-message.bot { + background: var(--surface-light); + color: var(--text-primary); + align-self: flex-start; + border-bottom-left-radius: 6px; +} +.light-mode .chatbot-message.bot { + background: var(--surface); /* blanco casi opaco */ + border: 1px solid var(--border); /* borde para separar del fondo */ +} +.chatbot-message.user { + background: var(--accent); + color: #2B2F5B; + align-self: flex-end; + border-bottom-right-radius: 6px; +} +.light-mode .chatbot-message.user { + background: var(--accent); + color: white; /* blanco sobre acento azul (#4A63D2) */ +} +.chatbot-message.system { + background: rgba(128, 224, 208, 0.2); + color: var(--success); + align-self: center; + font-size: 12px; + padding: 8px 16px; + border-radius: 30px; + max-width: 90%; +} +.chatbot-message.error { + background: rgba(255, 138, 101, 0.2); + color: var(--error); + align-self: center; + font-size: 12px; + padding: 8px 16px; + border-radius: 30px; +} +.chatbot-loading { + display: flex; + gap: 4px; + padding: 12px 16px; + background: var(--surface-light); + border-radius: 18px; + align-self: flex-start; +} +.chatbot-loading span { + width: 8px; + height: 8px; + background: var(--text-tertiary); + border-radius: 50%; + animation: loadingBounce 1.4s infinite ease-in-out both; +} +.chatbot-loading span:nth-child(1) { + animation-delay: -0.32s; +} +.chatbot-loading span:nth-child(2) { + animation-delay: -0.16s; +} +@keyframes loadingBounce { + 0%, 80%, 100% { + transform: scale(0); + } + 40% { + transform: scale(1); + } +} +.chatbot-input { + padding: 16px; + border-top: 1px solid var(--border); + display: flex; + gap: 12px; + background: var(--secondary); +} +.chatbot-input input { + flex: 1; + padding: 12px 16px; + background: var(--surface-light); + border: 1px solid var(--border); + border-radius: 30px; + color: var(--text-primary); + font-size: 14px; + outline: none; + transition: all var(--transition-fast); +} +.chatbot-input input:focus { + border-color: var(--accent); +} +.chatbot-input input::placeholder { + color: var(--text-tertiary); +} +.chatbot-input button { + padding: 12px 20px; + background: var(--accent-gradient); + border: none; + border-radius: 30px; + color: white; + font-weight: 600; + cursor: pointer; + transition: all var(--transition-fast); +} +.chatbot-input button:hover:not(:disabled) { + transform: translateY(-2px); +} +.chatbot-input button:disabled { + opacity: 0.5; + cursor: not-allowed; +} +.chatbot-status { + text-align: center; + font-size: 11px; + padding: 8px; + color: var(--text-tertiary); + background: var(--surface-light); + border-top: 1px solid var(--border); +} +@media (max-width: 480px) { + .chatbot-container { + width: calc(100vw - 40px); + height: 500px; + right: 0; + bottom: 80px; + } + .chatbot-widget { + bottom: 20px; + right: 20px; + } + .chatbot-toggle { + width: 55px; + height: 55px; + } + .chatbot-toggle i { + font-size: 24px; + } +} + +/* ==================== */ +/* COMPONENTE: CONÓCENOS */ +/* ==================== */ +.conocenos-section { + padding: 5rem clamp(1rem, 3vw, 2rem); + max-width: var(--max-width); + margin: 0 auto; + background: var(--secondary); + border-radius: 40px; + margin-top: 2rem; + margin-bottom: 2rem; +} +.conocenos-header { + text-align: center; + margin-bottom: 3rem; +} +.conocenos-subtitle { + color: var(--accent); + font-size: 1rem; + text-transform: uppercase; + letter-spacing: 2px; + font-weight: 500; + margin-bottom: 0.5rem; + display: inline-block; + background: var(--surface-light); + padding: 0.5rem 1.5rem; + border-radius: 50px; +} +.conocenos-title { + font-family: 'Space Grotesk', sans-serif; + font-size: clamp(2rem, 4vw, 2.8rem); + font-weight: 700; + background: linear-gradient(135deg, var(--text-primary), var(--accent)); + -webkit-background-clip: text; + background-clip: text; + color: transparent; + margin-top: 1rem; +} +.conocenos-grid { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 2rem; + margin-bottom: 4rem; +} +@media (max-width: 1024px) { + .conocenos-grid { + grid-template-columns: repeat(2, 1fr); + gap: 1.5rem; + } +} +@media (max-width: 640px) { + .conocenos-grid { + grid-template-columns: 1fr; + gap: 1.5rem; + } +} +.conocenos-card { + background: var(--surface); + border: 1px solid var(--border); + border-radius: 28px; + padding: 2rem 1.5rem; + text-align: center; + transition: all var(--transition-base); + position: relative; + overflow: hidden; + box-shadow: var(--shadow-sm); +} +.conocenos-card::before { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 4px; + background: var(--accent-gradient); + transform: scaleX(0); + transform-origin: left; + transition: transform var(--transition-base); +} +.conocenos-card:hover { + transform: translateY(-8px); + border-color: var(--accent); + box-shadow: var(--shadow-lg); +} +.conocenos-card:hover::before { + transform: scaleX(1); +} +.conocenos-icon { + width: 80px; + height: 80px; + background: var(--accent-gradient); + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + margin: 0 auto 1.5rem; + font-size: 2rem; + color: white; + box-shadow: var(--shadow-md); + transition: transform var(--transition-fast); +} +.conocenos-card:hover .conocenos-icon { + transform: scale(1.1) rotate(5deg); +} +.conocenos-card h3 { + font-size: 1.5rem; + font-weight: 700; + margin-bottom: 0.5rem; + color: var(--text-primary); +} +.conocenos-card h3 span { + display: block; + font-size: 1rem; + font-weight: 400; + color: var(--accent); + margin-top: 0.25rem; +} +.conocenos-card p { + color: var(--text-secondary); + font-size: 0.9rem; + line-height: 1.5; +} +.conocenos-cta { + text-align: center; + margin-top: 2rem; +} +.btn-reserva { + display: inline-flex; + align-items: center; + gap: 12px; + padding: 1rem 2.5rem; + background: var(--accent-gradient); + color: white; + text-decoration: none; + border-radius: 50px; + font-weight: 700; + font-size: 1.1rem; + transition: all var(--transition-base); + box-shadow: var(--shadow-md); + border: none; + cursor: pointer; +} +.btn-reserva:hover { + transform: translateY(-3px); + box-shadow: var(--shadow-lg); +} +.btn-reserva i { + transition: transform var(--transition-fast); +} +.btn-reserva:hover i { + transform: translateX(5px); +} + +/* ==================== */ +/* COMPONENTE: EXTRAS (Complementa tu viaje) */ +/* ==================== */ +.extras-section { + padding: 5rem clamp(1rem, 3vw, 2rem); + max-width: var(--max-width); + margin: 0 auto; + background: var(--primary); + border-radius: 40px; + margin-top: 2rem; + margin-bottom: 2rem; +} +.extras-header { + text-align: center; + margin-bottom: 3rem; +} +.extras-subtitle { + display: inline-flex; + align-items: center; + gap: 8px; + background: var(--surface-light); + padding: 0.5rem 1.5rem; + border-radius: 50px; + color: var(--accent); + font-size: 0.9rem; + font-weight: 500; + text-transform: uppercase; + letter-spacing: 2px; + margin-bottom: 1rem; +} +.extras-subtitle i { + font-size: 0.9rem; +} +.extras-title { + font-family: 'Space Grotesk', sans-serif; + font-size: clamp(2rem, 4vw, 2.8rem); + font-weight: 700; + background: linear-gradient(135deg, var(--text-primary), var(--accent)); + -webkit-background-clip: text; + background-clip: text; + color: transparent; + margin-bottom: 1rem; +} +.extras-description { + color: var(--text-secondary); + font-size: 1.1rem; + max-width: 700px; + margin: 0 auto; +} +.extras-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 2rem; + margin-bottom: 4rem; +} +@media (max-width: 1024px) { + .extras-grid { + grid-template-columns: repeat(2, 1fr); + gap: 1.5rem; + } +} +@media (max-width: 640px) { + .extras-grid { + grid-template-columns: 1fr; + gap: 1.5rem; + } +} +.extra-card { + background: var(--secondary); + border: 1px solid var(--border); + border-radius: 28px; + padding: 2rem 1.5rem; + text-align: center; + transition: all var(--transition-base); + position: relative; + overflow: hidden; + backdrop-filter: blur(10px); + box-shadow: var(--shadow-sm); +} +.extra-card::before { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 4px; + background: var(--accent-gradient); + transform: scaleX(0); + transform-origin: left; + transition: transform var(--transition-base); +} +.extra-card:hover { + transform: translateY(-8px); + border-color: var(--accent); + box-shadow: var(--shadow-lg); +} +.extra-card:hover::before { + transform: scaleX(1); +} +.extra-icon { + width: 80px; + height: 80px; + background: var(--accent-gradient); + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + margin: 0 auto 1.5rem; + font-size: 2rem; + color: white; + box-shadow: var(--shadow-md); + transition: transform var(--transition-fast); +} +.extra-card:hover .extra-icon { + transform: scale(1.1) rotate(5deg); +} +.extra-card h3 { + font-size: 1.5rem; + font-weight: 700; + margin-bottom: 0.5rem; + color: var(--text-primary); +} +.extra-card h3 span { + display: block; + font-size: 1rem; + font-weight: 400; + color: var(--accent); + margin-top: 0.25rem; +} +.extra-card p { + color: var(--text-secondary); + font-size: 0.9rem; + line-height: 1.5; + margin-bottom: 1.5rem; +} +.extra-link { + display: inline-flex; + align-items: center; + gap: 8px; + color: var(--accent); + font-weight: 600; + text-decoration: none; + font-size: 0.9rem; + transition: gap var(--transition-fast); +} +.extra-link:hover { + gap: 12px; +} +.extra-link i { + font-size: 0.8rem; + transition: transform var(--transition-fast); +} +.extra-link:hover i { + transform: translateX(3px); +} +.extras-cta { + text-align: center; +} +.btn-extras { + display: inline-flex; + align-items: center; + gap: 10px; + padding: 1rem 2rem; + background: var(--surface-light); + border: 1px solid var(--border); + border-radius: 50px; + color: var(--text-primary); + font-weight: 600; + text-decoration: none; + transition: all var(--transition-base); +} +.btn-extras:hover { + background: var(--accent); + color: #2B2F5B; + border-color: var(--accent); + transform: translateY(-3px); + box-shadow: var(--shadow-md); +} +.btn-extras i { + transition: transform var(--transition-fast); +} +.btn-extras:hover i { + transform: translateX(5px); +} + +/* ==================== */ +/* RESPONSIVE ADICIONAL */ +/* ==================== */ +@media (max-width: 1024px) { + .flights-grid { + grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); + gap: 24px; + } +} +@media (max-width: 768px) { + .flights-premium { + padding: 60px 16px; + } + .flights-grid { + grid-template-columns: 1fr; + gap: 24px; + } + .flight-card__image { + height: 220px; + } + .flight-card__content { + padding: 20px; + } + .flight-card__title { + font-size: 1.2rem; + } + .flight-card__actions { + flex-direction: column; + } + .btn-inquire, + .btn-book { + width: 100%; + } +} +@media (max-width: 480px) { + .flight-card__discount { + width: 55px; + height: 55px; + } + .flight-card__discount span { + font-size: 1rem; + } + .price-current .amount { + font-size: 1.6rem; + } +} +/* Modo claro para flight-card (ya integrado en variables, sombra general) */ +.light-mode .flight-card { + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.08); +} +.light-mode .flight-card:hover { + box-shadow: 0 25px 50px rgba(0, 0, 0, 0.15); +} +@media (prefers-reduced-motion: reduce) { + .flight-card, + .flight-card__image img, + .btn-inquire, + .btn-book { + transition: none; + } + .flight-card:hover { + transform: none; + } +} + +/* ==================== */ +/* COMPONENTE: FLIGHT DETAILS (mejoras puntuales) */ +/* ==================== */ +.flight-details { + padding: 120px 0 80px; + background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%); + min-height: 100vh; +} + +.flight-details .container { + max-width: 1280px; + margin: 0 auto; + padding: 0 24px; +} + +@media (max-width: 768px) { + .flight-details { + padding: 100px 0 60px; + } + .flight-details .container { + padding: 0 16px; + } +} + +.flight-details .details-grid { + display: grid; + grid-template-columns: 1fr 380px; + gap: 32px; +} + +@media (max-width: 1024px) { + .flight-details .details-grid { + grid-template-columns: 1fr 340px; + gap: 24px; + } +} + +@media (max-width: 992px) { + .flight-details .details-grid { + grid-template-columns: 1fr; + gap: 32px; + } +} + +.flight-details .main-card { + background: var(--secondary); + border-radius: 28px; + box-shadow: var(--shadow-xl); + overflow: hidden; + transition: var(--transition-base); + border: 1px solid var(--border); +} + +.flight-details .main-card:hover { + transform: translateY(-4px); + box-shadow: var(--shadow-xl); +} + +.flight-details .card-body { + padding: 32px; +} + +@media (max-width: 768px) { + .flight-details .card-body { + padding: 24px; + } +} + +.flight-details .flight-header { + display: flex; + justify-content: space-between; + align-items: flex-start; + margin-bottom: 24px; + padding-bottom: 24px; + border-bottom: 2px solid var(--border); + flex-wrap: wrap; + gap: 20px; +} + +.flight-details .flight-title { + flex: 1; +} + +.flight-details .flight-title h1 { + font-size: clamp(24px, 5vw, 32px); + color: var(--text-primary); + margin-bottom: 12px; + font-weight: 700; + line-height: 1.2; +} + +.flight-details .flight-title .subtitle { + font-size: 16px; + color: var(--text-secondary); + margin-bottom: 12px; + line-height: 1.5; +} + +.flight-details .badge { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 6px 16px; + background: var(--accent-gradient); + color: white; + border-radius: 50px; + font-size: 13px; + font-weight: 600; + box-shadow: var(--shadow-sm); +} + +.flight-details .price-box { + text-align: right; + background: var(--surface-light); + padding: 16px 24px; + border-radius: 20px; + position: relative; + min-width: 160px; + border: 1px solid var(--border); +} + +@media (max-width: 480px) { + .flight-details .price-box { + text-align: left; + width: 100%; + } +} + +.flight-details .price-badge { + position: absolute; + top: -10px; + right: 16px; + background: var(--error); + color: white; + padding: 4px 12px; + border-radius: 50px; + font-size: 12px; + font-weight: 700; + animation: pulse 2s infinite; +} + +@keyframes pulse { + 0%, 100% { transform: scale(1); } + 50% { transform: scale(1.05); } +} + +.flight-details .price-old { + text-decoration: line-through; + color: var(--text-tertiary); + font-size: 14px; + margin-bottom: 4px; +} + +.flight-details .price-current { + font-size: 36px; + font-weight: 800; + background: var(--accent-gradient); + -webkit-background-clip: text; + background-clip: text; + color: transparent; + line-height: 1; +} + +.flight-details .price-label { + font-size: 12px; + color: var(--text-tertiary); + margin-top: 4px; + font-weight: 500; +} + +.flight-details .meta-info { + display: flex; + gap: 12px; + flex-wrap: wrap; + margin-bottom: 32px; +} + +.flight-details .meta-item { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 8px 20px; + background: var(--surface-light); + border-radius: 50px; + font-size: 14px; + color: var(--text-secondary); + font-weight: 500; + transition: var(--transition-fast); + border: 1px solid var(--border); +} + +.flight-details .meta-item:hover { + background: var(--surface); + transform: translateY(-2px); + border-color: var(--accent); +} + +.flight-details .meta-item i { + color: var(--accent); +} + +.flight-details .rating { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 8px 20px; + background: var(--surface-light); + border-radius: 50px; + border: 1px solid var(--border); +} + +.flight-details .stars { + color: #FFC107; + font-size: 13px; + letter-spacing: 2px; +} + +.flight-details .rating-value { + font-weight: 600; + color: var(--text-primary); +} + +.flight-details .gallery-section { + margin-bottom: 40px; +} + +.flight-details .main-swiper { + border-radius: 20px; + overflow: hidden; + margin-bottom: 16px; + box-shadow: var(--shadow-lg); +} + +.flight-details .main-swiper .swiper-slide { + aspect-ratio: 16/9; + background: var(--surface); + position: relative; +} + +.flight-details .main-swiper .swiper-slide img { + width: 100%; + height: 100%; + object-fit: cover; + transition: transform 0.3s ease; +} + +.flight-details .main-swiper .swiper-slide:hover img { + transform: scale(1.05); +} + +.flight-details .thumbs-swiper { + margin-top: 16px; + padding: 0 4px; +} + +.flight-details .thumbs-swiper .swiper-slide { + width: 100px; + height: 70px; + border-radius: 12px; + overflow: hidden; + cursor: pointer; + opacity: 0.6; + transition: all var(--transition-fast); + border: 2px solid transparent; +} + +.flight-details .thumbs-swiper .swiper-slide-thumb-active { + opacity: 1; + border-color: var(--accent); + transform: scale(1.05); +} + +.flight-details .thumbs-swiper .swiper-slide:hover { + opacity: 0.9; + transform: scale(1.03); +} + +.flight-details .thumbs-swiper .swiper-slide img { + width: 100%; + height: 100%; + object-fit: cover; +} + +.flight-details .swiper-button-next, +.flight-details .swiper-button-prev { + color: white; + background: rgba(0, 0, 0, 0.6); + width: 44px; + height: 44px; + border-radius: 50%; + transition: all var(--transition-fast); +} + +.flight-details .swiper-button-next:hover, +.flight-details .swiper-button-prev:hover { + background: var(--accent); + transform: scale(1.1); +} + +.flight-details .swiper-button-next:after, +.flight-details .swiper-button-prev:after { + font-size: 18px; +} + +.flight-details .swiper-pagination-bullet { + background: white; + opacity: 0.8; + transition: all var(--transition-fast); +} + +.flight-details .swiper-pagination-bullet-active { + background: var(--accent); + transform: scale(1.2); +} + +.flight-details .section { + margin-bottom: 40px; +} + +.flight-details .section-title { + font-size: 24px; + color: var(--text-primary); + margin-bottom: 20px; + padding-left: 16px; + border-left: 4px solid var(--accent); + font-weight: 700; +} + +@media (max-width: 768px) { + .flight-details .section-title { + font-size: 20px; + } +} + +.flight-details .section-subtitle { + font-size: 20px; + color: var(--text-primary); + margin-bottom: 16px; + display: flex; + align-items: center; + gap: 12px; + font-weight: 600; +} + +.flight-details .section-subtitle i { + color: var(--success); +} + +.flight-details .description { + color: var(--text-secondary); + line-height: 1.8; + font-size: 15px; +} + +.flight-details .description p { + margin-bottom: 16px; +} + +.flight-details .includes-list { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); + gap: 12px; + list-style: none; + margin: 0; + padding: 0; +} + +.flight-details .includes-list li { + display: flex; + align-items: center; + gap: 12px; + padding: 12px 16px; + background: var(--surface-light); + border-radius: 16px; + color: var(--text-secondary); + transition: all var(--transition-fast); + border: 1px solid var(--border); +} + +.flight-details .includes-list li:hover { + transform: translateX(6px); + border-color: var(--accent); + box-shadow: var(--shadow-md); +} + +.flight-details .includes-list li i { + color: var(--success); + font-size: 18px; +} + +.flight-details .note { + padding: 20px 24px; + background: rgba(110, 207, 246, 0.1); + border-radius: 20px; + color: var(--accent); + display: flex; + align-items: center; + gap: 12px; + font-size: 14px; + font-weight: 500; + border-left: 4px solid var(--accent); +} + +.flight-details .note i { + font-size: 20px; +} + +.flight-details .sidebar { + position: sticky; + top: 100px; + height: fit-content; +} + +@media (max-width: 992px) { + .flight-details .sidebar { + position: static; + margin-top: 0; + } +} + +.flight-details .sidebar-card { + background: var(--secondary); + border-radius: 28px; + box-shadow: var(--shadow-lg); + padding: 24px; + margin-bottom: 24px; + transition: var(--transition-base); + border: 1px solid var(--border); +} + +.flight-details .sidebar-card:hover { + transform: translateY(-3px); + box-shadow: var(--shadow-xl); +} + +.flight-details .card-title { + font-size: 20px; + color: var(--text-primary); + margin-bottom: 20px; + text-align: center; + font-weight: 700; + display: flex; + align-items: center; + justify-content: center; + gap: 10px; +} + +.flight-details .card-title i { + color: var(--accent); +} + +.flight-details .form-group { + margin-bottom: 20px; +} + +.flight-details .form-group label { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 8px; + color: var(--text-primary); + font-weight: 500; + font-size: 14px; +} + +.flight-details .form-group label i { + color: var(--accent); + width: 16px; +} + +.flight-details .form-control { + width: 100%; + padding: 12px 16px; + border: 2px solid var(--border); + border-radius: 16px; + font-size: 14px; + transition: all var(--transition-fast); + font-family: inherit; + background: var(--surface-light); + color: var(--text-primary); +} + +.light-mode .flight-details .form-control { + background: var(--secondary); /* blanco puro */ + border-color: var(--border); /* borde gris claro */ +} + +.flight-details .form-control:hover { + border-color: var(--text-tertiary); +} + +.flight-details .form-control:focus { + outline: none; + border-color: var(--accent); + background: var(--secondary); + box-shadow: 0 0 0 4px rgba(110, 207, 246, 0.2); +} + +.flight-details .form-control.error { + border-color: var(--error); + background: rgba(255, 138, 101, 0.1); +} + +.flight-details .error-message { + color: var(--error); + font-size: 12px; + margin-top: 6px; + display: none; + animation: slideDown 0.3s ease; +} + +@keyframes slideDown { + from { + opacity: 0; + transform: translateY(-10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.flight-details .error-message.show { + display: block; +} + +.flight-details .iti { + width: 100%; +} + +.flight-details .iti__flag-container { + padding: 2px; +} + +.flight-details .iti__selected-flag { + border-radius: 16px 0 0 16px; + background: var(--surface); +} + +.flight-details .iti__country-list { + border-radius: 16px; + box-shadow: var(--shadow-lg); + background: var(--secondary); + border: 1px solid var(--border); +} + +.flight-details .btn-primary { + width: 100%; + padding: 14px 20px; + background: var(--accent-gradient); + color: white; + border: none; + border-radius: 16px; + font-size: 16px; + font-weight: 600; + cursor: pointer; + transition: all var(--transition-base); + display: flex; + align-items: center; + justify-content: center; + gap: 10px; + position: relative; + overflow: hidden; +} + +.flight-details .btn-primary::before { + content: ""; + position: absolute; + top: 0; + left: -100%; + width: 100%; + height: 100%; + background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); + transition: left 0.5s; +} + +.flight-details .btn-primary:hover::before { + left: 100%; +} + +.flight-details .btn-primary:hover { + transform: translateY(-2px); + box-shadow: var(--shadow-lg); +} + +.flight-details .btn-primary:active { + transform: translateY(0); +} + +.flight-details .btn-primary:disabled { + opacity: 0.6; + cursor: not-allowed; + transform: none; +} + +.flight-details .btn-secondary { + width: 100%; + padding: 14px 20px; + background: transparent; + color: var(--accent); + border: 2px solid var(--accent); + border-radius: 16px; + font-size: 16px; + font-weight: 600; + cursor: pointer; + transition: all var(--transition-base); + text-decoration: none; + display: flex; + align-items: center; + justify-content: center; + gap: 10px; +} + +.flight-details .btn-secondary:hover { + background: var(--accent); + color: white; + transform: translateY(-2px); + box-shadow: var(--shadow-lg); +} + +.flight-details .info-text { + text-align: center; + font-size: 12px; + color: var(--text-tertiary); + margin-top: 12px; +} + +.flight-details .info-list { + list-style: none; + margin: 0; + padding: 0; +} + +.flight-details .info-list li { + display: flex; + align-items: center; + gap: 12px; + padding: 12px 0; + color: var(--text-secondary); + font-size: 14px; + border-bottom: 1px solid var(--border); + transition: all var(--transition-fast); +} + +.flight-details .info-list li:last-child { + border-bottom: none; +} + +.flight-details .info-list li:hover { + transform: translateX(6px); + color: var(--text-primary); +} + +.flight-details .info-list li i { + color: var(--accent); + width: 20px; + font-size: 14px; +} + +.flight-details .share-buttons { + display: flex; + justify-content: center; + gap: 12px; + flex-wrap: wrap; +} + +.flight-details .share-btn { + width: 44px; + height: 44px; + border-radius: 50%; + border: none; + cursor: pointer; + transition: all var(--transition-base); + display: flex; + align-items: center; + justify-content: center; + font-size: 18px; +} + +.flight-details .share-btn.facebook { + background: #1877f2; + color: white; +} + +.flight-details .share-btn.twitter { + background: #1da1f2; + color: white; +} + +.flight-details .share-btn.whatsapp { + background: #25d366; + color: white; +} + +.flight-details .share-btn.copy { + background: var(--text-tertiary); + color: white; +} + +.flight-details .share-btn:hover { + transform: translateY(-3px) scale(1.05); +} + +.flight-details .share-btn:active { + transform: translateY(0) scale(1); +} + +.flight-details .btn-loading { + position: relative; + pointer-events: none; +} + +.flight-details .btn-loading span { + opacity: 0; +} + +.flight-details .btn-loading::after { + content: ""; + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + width: 22px; + height: 22px; + border: 2px solid white; + border-radius: 50%; + border-top-color: transparent; + animation: spin 0.6s linear infinite; +} + +@keyframes spin { + to { + transform: translate(-50%, -50%) rotate(360deg); + } +} + +.flight-details .alert { + background: var(--secondary); + padding: 60px 40px; + border-radius: 28px; + text-align: center; + box-shadow: var(--shadow-xl); + border: 1px solid var(--border); +} + +.flight-details .alert i { + font-size: 64px; + color: var(--color-warning); + margin-bottom: 20px; +} + +.flight-details .alert h3 { + margin-bottom: 16px; + color: var(--text-primary); + font-size: 28px; +} + +.flight-details .alert p { + margin-bottom: 24px; + color: var(--text-secondary); +} + +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.flight-details .main-card, +.flight-details .sidebar-card { + animation: fadeIn 0.5s ease-out; +} + +.flight-details .sidebar-card:nth-child(2) { + animation-delay: 0.1s; +} + +.flight-details .sidebar-card:nth-child(3) { + animation-delay: 0.2s; +} + +@media (max-width: 768px) { + .flight-details .includes-list { + grid-template-columns: 1fr; + } + .flight-details .sidebar-card { + padding: 20px; + } + .flight-details .swiper-button-next, + .flight-details .swiper-button-prev { + width: 36px; + height: 36px; + } + .flight-details .thumbs-swiper .swiper-slide { + width: 80px; + height: 56px; + } +} + +@media (max-width: 480px) { + .flight-details .flight-header { + flex-direction: column; + } + .flight-details .price-box { + width: 100%; + } + .flight-details .meta-info { + gap: 8px; + } + .flight-details .meta-item, + .flight-details .rating { + padding: 6px 14px; + font-size: 12px; + } +} + +@media print { + .flight-details .sidebar, + .flight-details .swiper-button-next, + .flight-details .swiper-button-prev, + .flight-details .share-card { + display: none; + } + .flight-details { + padding: 20px; + } + .flight-details .main-card { + box-shadow: none; + } +} + +/* ==================== */ +/* NUEVO: PROCESO DE RESERVA (STEP-BY-STEP) INTEGRADO */ +/* ==================== */ +.booking-flow .step-indicator { + display: flex; + justify-content: space-between; + margin-bottom: 2rem; + position: relative; + border-bottom: 2px solid var(--border); + padding-bottom: 1rem; +} +.booking-flow .step-item { + flex: 1; + text-align: center; + position: relative; + cursor: pointer; +} +.booking-flow .step-number { + width: 40px; + height: 40px; + background: var(--surface-light); + border-radius: 50%; + display: inline-flex; + align-items: center; + justify-content: center; + font-weight: bold; + color: var(--text-tertiary); + transition: all var(--transition-fast); + border: 2px solid transparent; +} +.booking-flow .step-item.active .step-number { + background: var(--accent-gradient); + color: white; + box-shadow: 0 0 0 3px rgba(110, 207, 246, 0.3); + border-color: var(--accent); +} +.booking-flow .step-item.completed .step-number { + background: var(--success); + color: white; + border-color: var(--success); +} +.booking-flow .step-label { + display: block; + font-size: 0.875rem; + margin-top: 0.5rem; + color: var(--text-tertiary); + transition: color var(--transition-fast); +} +.booking-flow .step-item.active .step-label { + color: var(--accent); + font-weight: 600; +} +.booking-flow .step-content { + animation: fadeIn 0.3s ease; +} +@keyframes fadeIn { + from { opacity: 0; transform: translateY(10px); } + to { opacity: 1; transform: translateY(0); } +} +.booking-flow .form-group { + margin-bottom: 1.5rem; +} +.booking-flow .form-group label { + display: block; + margin-bottom: 0.5rem; + font-weight: 500; + color: var(--text-primary); + font-size: 0.95rem; +} +.booking-flow .form-control { + width: 100%; + padding: 0.75rem 1rem; + border: 1px solid var(--border); + border-radius: 0.5rem; + background: var(--surface-light); + color: var(--text-primary); + transition: border-color var(--transition-fast), box-shadow var(--transition-fast); + font-family: inherit; +} +.light-mode .booking-flow .form-control { + background: var(--secondary); + border-color: var(--border); +} +.booking-flow .form-control:focus { + outline: none; + border-color: var(--accent); + box-shadow: 0 0 0 3px rgba(110, 207, 246, 0.15); + background: var(--secondary); +} +.booking-flow .error-message { + color: var(--error); + font-size: 0.875rem; + margin-top: 0.25rem; + display: none; +} +.booking-flow .error-message.show { + display: block; +} +.booking-flow .btn-primary, +.booking-flow .btn-secondary, +.booking-flow .btn-pay { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 0.5rem; + padding: 0.75rem 1.5rem; + border-radius: 0.75rem; + font-weight: 600; + transition: all var(--transition-base); + cursor: pointer; + border: none; + width: 100%; + font-size: 0.95rem; + text-decoration: none; +} +.booking-flow .btn-primary { + background: var(--accent-gradient); + color: white; +} +.booking-flow .btn-primary:hover { + transform: translateY(-2px); + box-shadow: var(--shadow-md); +} +.booking-flow .btn-secondary { + background: var(--surface-light); + color: var(--text-primary); + border: 1px solid var(--border); +} +.booking-flow .btn-secondary:hover { + background: var(--surface); + border-color: var(--accent); + transform: translateY(-2px); +} +.booking-flow .btn-pay { + background: var(--success); + color: #2B2F5B; + font-size: 1.1rem; + font-weight: 700; +} +.booking-flow .btn-pay:hover { + filter: brightness(1.1); + transform: translateY(-2px); + box-shadow: var(--shadow-md); +} +.booking-flow .summary-item { + display: flex; + justify-content: space-between; + margin-bottom: 1rem; + font-size: 0.95rem; + color: var(--text-secondary); +} +.booking-flow .summary-total { + border-top: 2px solid var(--border); + margin-top: 1rem; + padding-top: 1rem; + font-weight: bold; + font-size: 1.2rem; + color: var(--text-primary); +} +.booking-flow .iti { + width: 100%; +} +.booking-flow .passenger-card { + background: var(--surface-light); + border-radius: 0.75rem; + padding: 1rem; + margin-bottom: 1rem; + border: 1px solid var(--border); +} +.booking-flow .passenger-title { + font-weight: 600; + margin-bottom: 1rem; + color: var(--text-primary); + border-left: 3px solid var(--accent); + padding-left: 0.75rem; + font-size: 1rem; +} +.booking-flow .inline-fields { + display: flex; + gap: 1rem; + flex-wrap: wrap; +} +.booking-flow .inline-fields .form-group { + flex: 1; + min-width: 150px; +} +.booking-flow hr { + margin: 1.5rem 0; + border-color: var(--border); +} +.booking-flow .extras-category { + margin-bottom: 1rem; +} +.booking-flow .category-title { + font-weight: 600; + margin: 1rem 0 0.5rem; + font-size: 1.1rem; + position: sticky; + top: 0; + background: var(--secondary); + z-index: 1; + padding: 0.25rem 0; + color: var(--text-primary); +} +.booking-flow .btn-decrement, +.booking-flow .btn-increment { + width: 32px; + height: 32px; + border: 1px solid var(--border); + background: var(--surface-light); + border-radius: 4px; + cursor: pointer; + font-weight: bold; + transition: background var(--transition-fast); + color: var(--text-primary); +} +.booking-flow .btn-decrement:hover, +.booking-flow .btn-increment:hover { + background: var(--surface); + color: var(--accent); +} +.booking-flow .service-qty-display { + min-width: 20px; + text-align: center; + color: var(--text-primary); +} +#extras-container { + max-height: 500px; + overflow-y: auto; + border: 1px solid var(--border); + border-radius: 0.5rem; + scrollbar-width: thin; + scrollbar-color: var(--text-tertiary) var(--surface-light); +} +#extras-container::-webkit-scrollbar { + width: 6px; +} +#extras-container::-webkit-scrollbar-thumb { + background: var(--text-tertiary); + border-radius: 3px; +} +.booking-flow .sidebar-card { + position: sticky; + top: 1rem; + background: var(--secondary); + border-radius: 1rem; + padding: 1.5rem; + border: 1px solid var(--border); + box-shadow: var(--shadow-md); +} +.booking-flow .edit-link { + font-size: 0.8rem; + color: var(--accent); + cursor: pointer; + margin-left: 0.5rem; + text-decoration: underline; + transition: color var(--transition-fast); +} +.booking-flow .edit-link:hover { + color: var(--text-primary); +} +.booking-flow .trust-badges { + display: flex; + justify-content: center; + gap: 1.5rem; + margin-top: 1.5rem; + flex-wrap: wrap; +} +.booking-flow .trust-badge { + display: flex; + align-items: center; + gap: 0.3rem; + color: var(--text-tertiary); + font-size: 0.85rem; +} +.booking-flow .trust-badge i { + color: var(--success); + font-size: 1rem; +} +.booking-flow .discount-toggle { + font-size: 0.9rem; + color: var(--accent); + cursor: pointer; + margin-top: 0.5rem; + display: inline-block; +} +.booking-flow .discount-input { + display: none; + margin-top: 0.5rem; +} +.booking-flow .discount-input.active { + display: flex; + gap: 0.5rem; +} +.booking-flow .discount-input input { + flex: 1; +} +@media (max-width: 768px) { + .booking-flow .sidebar-card { + position: static; + } +} + +/* La regla final que probablemente se duplica; la mantenemos para consistencia con el diseño deseado */ +.flight-details .btn-secondary { + background: var(--accent); + color: white; + transform: translateY(-2px); + box-shadow: var(--shadow-lg); +} \ No newline at end of file diff --git a/public/assets/css/terminos.css b/public/assets/css/terminos.css new file mode 100644 index 0000000..d872c91 --- /dev/null +++ b/public/assets/css/terminos.css @@ -0,0 +1,292 @@ +/* ==================== */ +/* COMPONENTE: TÉRMINOS Y CONDICIONES PREMIUM */ +/* ==================== */ + +.terminos-premium { + padding: 5rem clamp(1rem, 3vw, 2rem); + max-width: var(--max-width); + margin: 0 auto; + background: linear-gradient(135deg, var(--primary) 0%, var(--surface) 100%); + border-radius: 40px; + margin-top: 2rem; + margin-bottom: 2rem; + position: relative; + overflow: hidden; +} + +.terminos-premium::before { + content: ""; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 100%; + background: radial-gradient( + circle at 20% 50%, + rgba(var(--accent-rgb), 0.03) 0%, + transparent 50% + ); + pointer-events: none; +} + +.terminos-premium-header { + text-align: center; + margin-bottom: 4rem; + position: relative; +} + +.terminos-premium-subtitle { + display: inline-flex; + align-items: center; + gap: 8px; + background: rgba(var(--accent-rgb), 0.1); + backdrop-filter: blur(10px); + padding: 0.5rem 1.5rem; + border-radius: 50px; + color: var(--accent); + font-size: 0.9rem; + font-weight: 500; + text-transform: uppercase; + letter-spacing: 2px; + margin-bottom: 1rem; + border: 1px solid rgba(var(--accent-rgb), 0.2); +} + +.terminos-premium-title { + font-family: "Space Grotesk", sans-serif; + font-size: clamp(2rem, 4vw, 3rem); + font-weight: 700; + margin-bottom: 1rem; + background: linear-gradient( + 135deg, + var(--text-primary) 0%, + var(--accent) 100% + ); + -webkit-background-clip: text; + background-clip: text; + color: transparent; +} + +.terminos-premium-description { + color: var(--text-secondary); + font-size: 1.1rem; + max-width: 600px; + margin: 0 auto; +} + +.terminos-container { + max-width: 900px; + margin: 0 auto; + position: relative; +} + +.terminos-accordion { + background: rgba(var(--surface-rgb), 0.6); + backdrop-filter: blur(10px); + border-radius: 24px; + overflow: hidden; + transition: all 0.3s ease; + border: 1px solid rgba(var(--accent-rgb), 0.1); + margin-bottom: 1rem; +} + +.terminos-accordion:hover { + border-color: rgba(var(--accent-rgb), 0.3); + transform: translateY(-2px); +} + +.terminos-accordion.active { + border-color: rgba(var(--accent-rgb), 0.5); + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1); +} + +.terminos-accordion-title { + padding: 1.25rem 1.5rem; + cursor: pointer; + display: flex; + align-items: center; + justify-content: space-between; + gap: 1rem; + transition: all 0.3s ease; + background: linear-gradient( + 135deg, + rgba(var(--surface-rgb), 0.8) 0%, + rgba(var(--surface-rgb), 0.4) 100% + ); +} + +.terminos-accordion-title h4 { + font-size: 1.1rem; + font-weight: 700; + margin: 0; + color: var(--text-primary); + line-height: 1.4; + flex: 1; + display: flex; + align-items: center; + gap: 10px; +} + +.terminos-accordion-title h4 i { + color: var(--accent); + font-size: 1.2rem; +} + +.terminos-accordion-icon { + width: 32px; + height: 32px; + min-width: 32px; + display: flex; + align-items: center; + justify-content: center; + background: rgba(var(--accent-rgb), 0.1); + border-radius: 50%; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); +} + +.terminos-accordion.active .terminos-accordion-icon { + background: var(--accent); + transform: rotate(180deg); +} + +.terminos-accordion-icon i { + color: var(--accent); + font-size: 0.9rem; + transition: all 0.3s ease; +} + +.terminos-accordion.active .terminos-accordion-icon i { + color: white; +} + +.terminos-accordion-content { + max-height: 0; + overflow: hidden; + transition: max-height 0.5s cubic-bezier(0.4, 0, 0.2, 1); + background: rgba(var(--surface-rgb), 0.3); +} + +.terminos-accordion.active .terminos-accordion-content { + max-height: 3000px; +} + +.terminos-accordion-inner { + padding: 0 1.5rem 1.5rem 1.5rem; + color: var(--text-secondary); + line-height: 1.7; + font-size: 0.95rem; +} + +.terminos-accordion-inner ol, +.terminos-accordion-inner ul { + margin: 0; + padding-left: 1.5rem; +} + +.terminos-accordion-inner ol li, +.terminos-accordion-inner ul li { + margin-bottom: 0.75rem; +} + +.terminos-accordion-inner ol li:last-child, +.terminos-accordion-inner ul li:last-child { + margin-bottom: 0; +} + +.terminos-accordion-inner ul ul { + margin-top: 0.5rem; + margin-bottom: 0.5rem; +} + +.terminos-accordion-inner .list-disc { + list-style-type: disc; +} + +.terminos-accordion-inner .list-outside { + list-style-position: outside; +} + +.terminos-accordion-inner a { + color: var(--accent); + text-decoration: none; + transition: all 0.3s ease; +} + +.terminos-accordion-inner a:hover { + text-decoration: underline; +} + +.terminos-accordion-inner strong { + color: var(--accent); + font-weight: 700; +} + +.terminos-nota { + font-size: 0.85rem; + font-style: italic; + color: #ff6b6b; + margin-top: 0.5rem; + display: block; +} + +.terminos-nota-dark { + font-size: 0.85rem; + font-style: italic; + color: #ffaa00; + margin-top: 0.5rem; + display: block; +} + +@keyframes fadeInUp { + from { + opacity: 0; + transform: translateY(30px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.terminos-accordion { + animation: fadeInUp 0.6s ease-out forwards; + opacity: 0; +} + +.terminos-accordion.revealed { + animation: fadeInUp 0.6s ease-out forwards; +} + +.terminos-stats { + text-align: center; + margin-top: 3rem; + padding-top: 2rem; + border-top: 1px solid rgba(var(--accent-rgb), 0.1); + color: var(--text-secondary); + font-size: 0.9rem; +} + +.terminos-stats i { + color: var(--accent); + margin-right: 0.5rem; +} + +@media (max-width: 768px) { + .terminos-accordion-title { + padding: 1rem; + } + + .terminos-accordion-title h4 { + font-size: 0.95rem; + } + + .terminos-accordion-inner { + padding: 0 1rem 1rem 1rem; + font-size: 0.9rem; + } + + .terminos-accordion-inner ol, + .terminos-accordion-inner ul { + padding-left: 1rem; + } +} diff --git a/public/assets/css/theme.css b/public/assets/css/theme.css new file mode 100644 index 0000000..863aaab --- /dev/null +++ b/public/assets/css/theme.css @@ -0,0 +1,4847 @@ +/* ==================== */ +/* VARIABLES GLOBALES */ +/* ==================== */ +:root { + --primary: #0a0b0d; + --secondary: #1a1d23; + --accent: #04cdff; + --accent-gradient: linear-gradient(135deg, #04cdff, #0099cc); + --gold: #ffd700; + --gold-gradient: linear-gradient(135deg, #ffd700, #b8860b); + --text-primary: #ffffff; + --text-secondary: #a0aec0; + --text-tertiary: #718096; + --surface: rgba(26, 29, 35, 0.85); + --surface-light: rgba(255, 255, 255, 0.08); + --border: rgba(255, 255, 255, 0.12); + --success: #38b2ac; + --error: #f56565; + --shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.25); + --shadow-md: 0 8px 32px rgba(0, 0, 0, 0.4); + --shadow-lg: 0 20px 60px rgba(0, 0, 0, 0.5); + --shadow-xl: 0 40px 80px rgba(0, 0, 0, 0.6); + --transition-fast: 200ms cubic-bezier(0.4, 0, 0.2, 1); + --transition-base: 400ms cubic-bezier(0.4, 0, 0.2, 1); + --transition-slow: 600ms cubic-bezier(0.4, 0, 0.2, 1); + --navbar-height: 80px; + --navbar-height-mobile: 70px; + --max-width: 1440px; +} + +body.light-mode { + --primary: #f8fafc; + --secondary: #ffffff; + --text-primary: #1a202c; + --text-secondary: #4a5568; + --text-tertiary: #718096; + --surface: rgba(255, 255, 255, 0.95); + --surface-light: rgba(0, 0, 0, 0.04); + --border: rgba(0, 0, 0, 0.12); + --shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.08); + --shadow-md: 0 8px 32px rgba(0, 0, 0, 0.12); + --shadow-lg: 0 20px 60px rgba(0, 0, 0, 0.15); + --shadow-xl: 0 40px 80px rgba(0, 0, 0, 0.2); +} + +/* ==================== */ +/* RESET & BASE */ +/* ==================== */ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +html { + scroll-behavior: smooth; +} + +body { + font-family: + "Inter", + -apple-system, + BlinkMacSystemFont, + sans-serif; + background: var(--primary); + color: var(--text-primary); + overflow-x: hidden; + line-height: 1.6; + transition: + background-color var(--transition-base), + color var(--transition-base); +} + +/* Utilidades */ +.hide-mobile { + display: none; +} + +@media (min-width: 769px) { + .hide-mobile { + display: flex; + } + .hide-desktop { + display: none !important; + } +} + +/* Scrollbar */ +::-webkit-scrollbar { + width: 10px; +} +::-webkit-scrollbar-track { + background: var(--secondary); +} +::-webkit-scrollbar-thumb { + background: var(--surface); + border-radius: 5px; +} +::-webkit-scrollbar-thumb:hover { + background: var(--surface-light); +} + +/* ==================== */ +/* ANIMACIONES GLOBALES */ +/* ==================== */ +@keyframes fadeInUp { + from { + opacity: 0; + transform: translateY(30px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes slideInRight { + from { + transform: translateX(100%); + opacity: 0; + } + to { + transform: translateX(0); + opacity: 1; + } +} + +@keyframes slideOutRight { + from { + transform: translateX(0); + opacity: 1; + } + to { + transform: translateX(100%); + opacity: 0; + } +} + +/* ==================== */ +/* PRELOADER */ +/* ==================== */ +#preloader { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: var(--primary); + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + z-index: 9999; + transition: + opacity var(--transition-slow), + visibility var(--transition-slow); +} +#preloader.loaded { + opacity: 0; + visibility: hidden; +} + +.preloader-container { + display: flex; + flex-direction: column; + align-items: center; + gap: 2rem; + text-align: center; + max-width: 300px; + width: 90%; +} + +.preloader-logo { + width: 120px; + height: 120px; + position: relative; + animation: float 3s ease-in-out infinite; + display: flex; + align-items: center; + justify-content: center; +} +.preloader-logo img { + width: 100%; + height: 100%; + object-fit: contain; + filter: drop-shadow(0 0 20px rgba(4, 205, 255, 0.3)); +} +.preloader-logo::before { + content: ""; + position: absolute; + top: -10px; + left: -10px; + right: -10px; + bottom: -10px; + background: rgba(4, 205, 255, 0.1); + border-radius: 50%; + animation: pulse 2s ease-in-out infinite; + z-index: -1; +} + +.preloader-text { + font-family: "Space Grotesk", sans-serif; + font-size: clamp(1.2rem, 2vw, 1.5rem); + font-weight: 600; + color: var(--text-primary); + margin-top: 1rem; + position: relative; + overflow: hidden; +} +.preloader-text::after { + content: "cargando."; + animation: dots 3s steps(4, end) infinite; +} + +.preloader-progress { + width: 100%; + height: 4px; + background: rgba(255, 255, 255, 0.1); + border-radius: 2px; + overflow: hidden; + margin-top: 1rem; +} +.preloader-progress-bar { + width: 0%; + height: 100%; + background: var(--accent-gradient); + border-radius: 2px; + transition: width 0.3s ease; +} + +.preloader-subtext { + color: var(--text-tertiary); + font-size: clamp(0.85rem, 1.5vw, 0.9rem); + margin-top: 0.5rem; + opacity: 0.8; +} + +@keyframes float { + 0%, + 100% { + transform: translateY(0); + } + 50% { + transform: translateY(-20px); + } +} +@keyframes pulse { + 0%, + 100% { + opacity: 0.5; + transform: scale(1); + } + 50% { + opacity: 1; + transform: scale(1.1); + } +} +@keyframes dots { + 0%, + 20% { + content: "cargando."; + } + 40%, + 60% { + content: "cargando.."; + } + 80%, + 100% { + content: "cargando..."; + } +} + +/* ==================== */ +/* NAVBAR PREMIUM */ +/* ==================== */ +.navbar-premium { + position: fixed; + top: 0; + left: 0; + right: 0; + height: var(--navbar-height); + background: rgba(10, 11, 13, 0.85); + backdrop-filter: blur(20px); + -webkit-backdrop-filter: blur(20px); + border-bottom: 1px solid var(--border); + z-index: 1000; + transition: all var(--transition-base); + padding: 0 clamp(1rem, 3vw, 2rem); +} + +@media (max-width: 768px) { + .navbar-premium { + height: var(--navbar-height-mobile); + padding: 0 1rem; + } + .nav-desktop, + .nav-actions .profile-btn, + .nav-actions .cta-btn { + display: none !important; + } + .hamburger-menu { + display: flex !important; + } +} + +@media (min-width: 769px) and (max-width: 1024px) { + .navbar-premium { + padding: 0 1.5rem; + } + .nav-desktop { + display: none !important; + } + .hamburger-menu { + display: flex !important; + } +} + +.light-mode .navbar-premium { + background: rgba(248, 250, 252, 0.85); +} + +.navbar-container { + max-width: var(--max-width); + height: 100%; + margin: 0 auto; + display: flex; + align-items: center; + justify-content: space-between; +} + +.navbar-logo { + display: flex; + align-items: center; + gap: 12px; + text-decoration: none; + transition: transform var(--transition-fast); +} +.navbar-logo:hover { + transform: translateY(-2px); +} + +.logo-icon { + width: 42px; + height: 42px; + background: var(--accent-gradient); + border-radius: 12px; + display: flex; + align-items: center; + justify-content: center; + color: white; + font-size: 20px; + font-weight: 800; + box-shadow: var(--shadow-md); + flex-shrink: 0; +} +.logo-icon img { + width: 100%; + height: 100%; + object-fit: contain; + border-radius: 12px; +} +@media (max-width: 480px) { + .logo-icon { + width: 36px; + height: 36px; + } +} + +.logo-text-container { + display: flex; + flex-direction: column; + line-height: 1.2; +} +.logo-main-text { + font-family: "Space Grotesk", sans-serif; + font-size: clamp(1.3rem, 2vw, 1.6rem); + font-weight: 700; + color: var(--text-primary); + letter-spacing: -0.5px; + white-space: nowrap; +} +.logo-subtext { + font-family: "Space Grotesk", sans-serif; + font-size: clamp(0.7rem, 1.2vw, 0.85rem); + font-weight: 600; + background: var(--gold-gradient); + -webkit-background-clip: text; + background-clip: text; + color: transparent; + letter-spacing: 5.5px; + margin-top: 1px; + text-transform: uppercase; + white-space: nowrap; +} +.logo-gradient { + background: var(--accent-gradient); + -webkit-background-clip: text; + background-clip: text; + color: transparent; +} + +.nav-desktop { + display: flex; + gap: 2px; + background: var(--surface); + padding: 4px; + border-radius: 14px; + backdrop-filter: blur(10px); + border: 1px solid var(--border); +} +.nav-item { + position: relative; +} +.nav-link { + display: flex; + align-items: center; + gap: 10px; + padding: 12px 24px; + text-decoration: none; + color: var(--text-secondary); + font-weight: 500; + font-size: clamp(0.85rem, 1vw, 0.95rem); + border-radius: 10px; + transition: all var(--transition-fast); + position: relative; +} +.nav-link::after { + content: ""; + position: absolute; + bottom: 0; + left: 50%; + transform: translateX(-50%); + width: 0; + height: 2px; + background: var(--accent); + transition: width var(--transition-base); + border-radius: 2px; +} +.nav-link:hover { + color: var(--text-primary); + background: var(--surface-light); +} +.nav-link:hover::after { + width: 60%; +} +.nav-link.active { + color: var(--text-primary); + background: var(--surface-light); +} +.nav-link.active::after { + width: 80%; +} +.nav-icon { + font-size: 1.1rem; +} + +.theme-toggle { + position: relative; + width: 44px; + height: 44px; + background: var(--surface); + border: 1px solid var(--border); + border-radius: 12px; + display: flex; + align-items: center; + justify-content: center; + color: var(--text-secondary); + cursor: pointer; + transition: all var(--transition-fast); + font-size: 1.1rem; +} +@media (max-width: 480px) { + .theme-toggle { + width: 40px; + height: 40px; + font-size: 1rem; + } +} +.theme-toggle:hover { + background: var(--surface-light); + color: var(--text-primary); + transform: translateY(-2px); + border-color: var(--accent); +} +.light-mode .theme-toggle .fa-sun { + display: none; +} +.light-mode .theme-toggle .fa-moon { + display: block; +} +.fa-moon { + display: none; +} + +.nav-actions { + display: flex; + align-items: center; + gap: 1rem; +} +@media (max-width: 768px) { + .nav-actions { + gap: 0.75rem; + } +} + +.action-btn { + position: relative; + width: 44px; + height: 44px; + background: var(--surface); + border: 1px solid var(--border); + border-radius: 12px; + display: flex; + align-items: center; + justify-content: center; + color: var(--text-secondary); + cursor: pointer; + transition: all var(--transition-fast); + font-size: 1.1rem; +} +@media (max-width: 480px) { + .action-btn { + width: 40px; + height: 40px; + font-size: 1rem; + } +} +.action-btn:hover { + background: var(--surface-light); + color: var(--text-primary); + transform: translateY(-2px); + border-color: var(--accent); +} +.notification-badge { + position: absolute; + top: -4px; + right: -4px; + background: var(--accent); + color: white; + font-size: 0.7rem; + width: 18px; + height: 18px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-weight: 700; + border: 2px solid var(--primary); +} + +.cta-btn { + display: flex; + align-items: center; + gap: 10px; + padding: 12px 28px; + background: var(--accent-gradient); + color: white; + border: none; + border-radius: 12px; + font-weight: 600; + font-size: 0.95rem; + cursor: pointer; + transition: all var(--transition-base); + text-decoration: none; + box-shadow: var(--shadow-md); +} +@media (max-width: 480px) { + .cta-btn { + padding: 10px 20px; + font-size: 0.9rem; + } +} +.cta-btn:hover { + transform: translateY(-3px); + box-shadow: var(--shadow-lg); +} +.cta-btn i { + transition: transform var(--transition-fast); +} +.cta-btn:hover i { + transform: translateX(3px); +} + +.hamburger-menu { + display: none; + width: 32px; + height: 32px; + flex-direction: column; + justify-content: center; + align-items: center; + cursor: pointer; + position: relative; + z-index: 1002; +} +@media (max-width: 1024px) { + .hamburger-menu { + display: flex; + } +} +@media (max-width: 480px) { + .hamburger-menu { + width: 28px; + height: 28px; + } +} +.hamburger-line { + width: 22px; + height: 2px; + background: var(--text-primary); + border-radius: 2px; + transition: all var(--transition-base); + position: absolute; +} +.hamburger-line:nth-child(1) { + transform: translateY(-6px); +} +.hamburger-line:nth-child(3) { + transform: translateY(6px); +} +.hamburger-menu.active .hamburger-line:nth-child(1) { + transform: rotate(45deg); +} +.hamburger-menu.active .hamburger-line:nth-child(2) { + opacity: 0; +} +.hamburger-menu.active .hamburger-line:nth-child(3) { + transform: rotate(-45deg); +} + +.mobile-menu { + position: fixed; + top: 0; + right: -100%; + width: 100%; + max-width: 320px; + height: 100vh; + background: var(--secondary); + backdrop-filter: blur(30px); + -webkit-backdrop-filter: blur(30px); + border-left: 1px solid var(--border); + transition: right var(--transition-base); + z-index: 1001; + padding: 2rem; + display: flex; + flex-direction: column; + overflow-y: auto; +} +@media (max-width: 480px) { + .mobile-menu { + padding: 1.5rem; + max-width: 280px; + } +} +.mobile-menu.active { + right: 0; +} +.mobile-menu-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 2rem; +} +.mobile-nav { + display: flex; + flex-direction: column; + gap: 8px; + margin-bottom: 2rem; +} +.mobile-nav-item { + list-style: none; +} +.mobile-nav-link { + display: flex; + align-items: center; + gap: 12px; + padding: 16px; + text-decoration: none; + color: var(--text-secondary); + border-radius: 12px; + transition: all var(--transition-fast); + font-weight: 500; +} +.mobile-nav-link:hover, +.mobile-nav-link.active { + background: var(--surface-light); + color: var(--text-primary); +} +.mobile-actions { + margin-top: auto; + display: flex; + flex-direction: column; + gap: 1rem; + padding-top: 2rem; + border-top: 1px solid var(--border); +} + +.menu-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.7); + backdrop-filter: blur(5px); + -webkit-backdrop-filter: blur(5px); + opacity: 0; + visibility: hidden; + transition: all var(--transition-base); + z-index: 1000; +} +.menu-overlay.active { + opacity: 1; + visibility: visible; +} + +/* ==================== */ +/* MODAL DE BÚSQUEDA */ +/* ==================== */ +.search-modal { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100vh; + background: var(--primary); + z-index: 2000; + display: none; + flex-direction: column; + opacity: 0; + transition: opacity var(--transition-base); +} +.search-modal.active { + display: flex; + opacity: 1; +} +.search-modal-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 1.5rem clamp(1rem, 3vw, 2rem); + border-bottom: 1px solid var(--border); +} +.search-modal-title { + font-family: "Space Grotesk", sans-serif; + font-size: clamp(1.5rem, 3vw, 2rem); + font-weight: 700; + color: var(--text-primary); +} +.search-close-btn { + width: 44px; + height: 44px; + background: var(--surface); + border: 1px solid var(--border); + border-radius: 12px; + display: flex; + align-items: center; + justify-content: center; + color: var(--text-secondary); + cursor: pointer; + transition: all var(--transition-fast); + font-size: 1.2rem; +} +.search-close-btn:hover { + background: var(--surface-light); + color: var(--text-primary); + transform: rotate(90deg); +} +.search-modal-content { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 2rem; + max-width: 800px; + margin: 0 auto; + width: 100%; +} +.search-input-container { + width: 100%; + position: relative; + margin-bottom: 3rem; +} +.search-input { + width: 100%; + height: 70px; + padding: 0 2rem 0 5rem; + background: var(--secondary); + border: 2px solid var(--border); + border-radius: 20px; + color: var(--text-primary); + font-family: "Inter", sans-serif; + font-size: clamp(1.2rem, 2vw, 1.5rem); + transition: all var(--transition-base); + outline: none; +} +.search-input:focus { + border-color: var(--accent); + box-shadow: 0 0 0 4px rgba(4, 205, 255, 0.15); + transform: translateY(-2px); +} +.search-icon { + position: absolute; + left: 1.5rem; + top: 50%; + transform: translateY(-50%); + color: var(--accent); + font-size: 1.5rem; +} +.search-results { + width: 100%; + display: grid; + grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); + gap: 1.5rem; + opacity: 0; + transform: translateY(20px); + transition: all var(--transition-base); +} +.search-results.active { + opacity: 1; + transform: translateY(0); +} +.search-result-item { + background: var(--surface); + border: 1px solid var(--border); + border-radius: 16px; + padding: 1.5rem; + transition: all var(--transition-fast); + cursor: pointer; +} +.search-result-item:hover { + transform: translateY(-5px); + border-color: var(--accent); + box-shadow: var(--shadow-lg); +} +.search-result-title { + font-size: 1.1rem; + font-weight: 600; + margin-bottom: 0.5rem; + color: var(--text-primary); +} +.search-result-description { + color: var(--text-secondary); + font-size: 0.9rem; + line-height: 1.5; +} +.search-suggestions { + display: flex; + flex-wrap: wrap; + gap: 1rem; + justify-content: center; + margin-top: 2rem; +} +.search-suggestion { + padding: 0.5rem 1rem; + background: var(--surface); + border: 1px solid var(--border); + border-radius: 50px; + color: var(--text-secondary); + font-size: 0.9rem; + cursor: pointer; + transition: all var(--transition-fast); +} +.search-suggestion:hover { + background: var(--accent); + color: white; + transform: translateY(-2px); +} + +/* ==================== */ +/* HERO SECTION */ +/* ==================== */ +.hero-section { + position: relative; + height: 100vh; + min-height: 600px; + width: 100%; + overflow: hidden; + margin-top: var(--navbar-height); +} +@media (max-width: 768px) { + .hero-section { + margin-top: var(--navbar-height-mobile); + min-height: 700px; + } +} +@media (max-width: 480px) { + .hero-section { + min-height: 800px; + height: auto; + } +} + +.video-container { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + overflow: hidden; +} +.hero-video { + width: 100%; + height: 100%; + object-fit: cover; + object-position: center; + filter: brightness(0.7); +} +.light-mode .hero-video { + filter: brightness(0.9); +} +.video-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: linear-gradient( + to bottom, + rgba(10, 11, 13, 0.2) 0%, + rgba(10, 11, 13, 0.4) 30%, + rgba(10, 11, 13, 0.7) 70%, + rgba(10, 11, 13, 0.9) 100% + ); + z-index: 1; +} +.light-mode .video-overlay { + background: linear-gradient( + to bottom, + rgba(3, 41, 80, 0.25) 0%, + rgba(36, 73, 111, 0.15) 30%, + rgba(248, 250, 252, 0.6) 65%, + rgba(248, 250, 252, 0.95) 100% + ); + backdrop-filter: blur(5px); + -webkit-backdrop-filter: blur(5px); +} + +.hero-content { + position: relative; + z-index: 2; + height: 100%; + display: flex; + flex-direction: column; + justify-content: space-between; + padding: 0 clamp(1rem, 3vw, 2rem); + max-width: var(--max-width); + margin: 0 auto; +} +.hero-text { + padding-top: 10vh; + max-width: 800px; +} +@media (max-width: 768px) { + .hero-text { + padding-top: 5vh; + } +} +.hero-subtitle { + display: inline-block; + background: var(--surface); + backdrop-filter: blur(10px); + padding: clamp(6px, 1.5vw, 8px) clamp(16px, 2.5vw, 20px); + border-radius: 50px; + font-size: clamp(0.8rem, 1vw, 0.9rem); + font-weight: 500; + color: var(--accent); + margin-bottom: 1.5rem; + border: 1px solid var(--border); + animation: fadeInUp 1s ease-out; +} +.hero-title { + font-family: "Space Grotesk", sans-serif; + font-size: clamp(2rem, 5vw, 4.5rem); + font-weight: 800; + line-height: 1.1; + margin-bottom: clamp(1rem, 2vw, 1.5rem); + background: linear-gradient(to right, var(--text-primary), var(--accent)); + -webkit-background-clip: text; + background-clip: text; + color: transparent; + animation: fadeInUp 1s ease-out 0.2s both; +} +@media (max-width: 480px) { + .hero-title { + font-size: 2.2rem; + line-height: 1.2; + } +} +.hero-description { + font-size: clamp(0.95rem, 2vw, 1.25rem); + color: var(--text-secondary); + max-width: 600px; + margin-bottom: clamp(1.5rem, 3vw, 2rem); + animation: fadeInUp 1s ease-out 0.4s both; +} +.hero-stats { + display: flex; + gap: clamp(1.5rem, 4vw, 3rem); + margin-top: clamp(2rem, 4vw, 3rem); + animation: fadeInUp 1s ease-out 0.6s both; +} +@media (max-width: 768px) { + .hero-stats { + flex-wrap: wrap; + justify-content: center; + gap: 2rem; + } +} +.stat-item { + display: flex; + flex-direction: column; + align-items: center; + text-align: center; +} +.stat-number { + font-size: clamp(2rem, 4vw, 2.5rem); + font-weight: 800; + background: var(--accent-gradient); + -webkit-background-clip: text; + background-clip: text; + color: transparent; + line-height: 1; + margin-bottom: 0.5rem; +} +.stat-label { + font-size: clamp(0.8rem, 1vw, 0.9rem); + color: var(--text-tertiary); + text-transform: uppercase; + letter-spacing: 0.5px; +} + +/* ==================== */ +/* VUELOS CARDS */ +/* ==================== */ +.vuelos-section { + padding: 5rem clamp(1rem, 3vw, 2rem); + max-width: var(--max-width); + margin: 0 auto; +} +.block-title { + text-align: center; + margin-bottom: 3rem; +} +.block-title p { + color: var(--accent); + font-size: 1rem; + text-transform: uppercase; + letter-spacing: 2px; + margin-bottom: 0.5rem; + font-weight: 500; +} +.block-title h3 { + font-family: "Space Grotesk", sans-serif; + font-size: clamp(2rem, 4vw, 2.8rem); + font-weight: 700; + background: linear-gradient(135deg, var(--text-primary), var(--accent)); + -webkit-background-clip: text; + background-clip: text; + color: transparent; +} +.row { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); + gap: 2rem; +} +.tour-one__single { + background: var(--secondary); + border-radius: 24px; + overflow: hidden; + transition: all var(--transition-base); + border: 1px solid var(--border); + position: relative; +} +.tour-one__single:hover { + transform: translateY(-12px); + border-color: var(--accent); + box-shadow: var(--shadow-xl); +} +.tour-one__image { + position: relative; + overflow: hidden; + aspect-ratio: 370 / 292; +} +.tour-one__image img { + width: 100%; + height: 100%; + object-fit: cover; + transition: transform var(--transition-slow); + cursor: pointer; +} +.tour-one__single:hover .tour-one__image img { + transform: scale(1.05); +} +.tour-one__content { + padding: 1.5rem; + position: relative; + background: var(--secondary); +} +.tour-one__content h3 { + font-size: 1.35rem; + font-weight: 700; + margin-bottom: 0.75rem; + line-height: 1.3; +} +.tour-one__content h3 a { + color: var(--text-primary); + text-decoration: none; + transition: color var(--transition-fast); +} +.tour-one__content h3 a:hover { + color: var(--accent); +} +.tour-one__content p { + margin: 0.5rem 0; + color: var(--text-secondary); + font-size: 0.95rem; +} +.tour-one__content p span { + font-size: 1.6rem; + font-weight: 800; + background: var(--accent-gradient); + -webkit-background-clip: text; + background-clip: text; + color: transparent; + letter-spacing: -0.5px; +} +.tour-one__meta { + display: flex; + gap: 1.25rem; + list-style: none; + margin-top: 1.25rem; + padding-top: 1rem; + border-top: 1px solid var(--border); +} +.tour-one__meta li { + display: flex; + align-items: center; +} +.tour-one__meta a { + display: flex; + align-items: center; + gap: 0.5rem; + color: var(--text-tertiary); + text-decoration: none; + font-size: 0.85rem; + transition: color var(--transition-fast); +} +.tour-one__meta a:hover { + color: var(--accent); +} +.tour-one__meta i { + font-size: 0.9rem; + color: var(--accent); +} + +/* ==================== */ +/* FOOTER PREMIUM */ +/* ==================== */ +.footer { + background: var(--secondary); + border-top: 1px solid var(--border); + padding: clamp(3rem, 6vw, 5rem) clamp(1rem, 3vw, 2rem) 1.5rem; + margin-top: 2rem; +} +.footer-container { + max-width: var(--max-width); + margin: 0 auto; +} +.footer-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: clamp(2rem, 4vw, 3rem); + margin-bottom: 3rem; +} +.footer-brand { + max-width: 300px; +} +.footer-logo { + display: flex; + align-items: center; + gap: 12px; + text-decoration: none; + margin-bottom: 1.5rem; +} +.footer-logo-icon { + width: 45px; + height: 45px; + background: var(--accent-gradient); + border-radius: 12px; + display: flex; + align-items: center; + justify-content: center; + color: white; + font-size: 1.3rem; + font-weight: 800; +} +.footer-logo-text { + font-family: "Space Grotesk", sans-serif; + font-size: 1.4rem; + font-weight: 700; + color: var(--text-primary); +} +.footer-logo-sub { + font-size: 0.7rem; + font-weight: 600; + background: var(--gold-gradient); + -webkit-background-clip: text; + background-clip: text; + color: transparent; + letter-spacing: 4px; +} +.footer-description { + color: var(--text-secondary); + font-size: 0.9rem; + line-height: 1.6; + margin-bottom: 1.5rem; +} +.footer-social { + display: flex; + gap: 1rem; +} +.social-link { + width: 40px; + height: 40px; + background: var(--surface-light); + border-radius: 12px; + display: flex; + align-items: center; + justify-content: center; + color: var(--text-secondary); + transition: all var(--transition-fast); + text-decoration: none; + font-size: 1.1rem; +} +.social-link:hover { + background: var(--accent); + color: white; + transform: translateY(-3px); +} +.footer-title { + font-size: 1.1rem; + font-weight: 700; + margin-bottom: 1.5rem; + color: var(--text-primary); + position: relative; + display: inline-block; +} +.footer-title::after { + content: ""; + position: absolute; + bottom: -8px; + left: 0; + width: 40px; + height: 2px; + background: var(--accent); + border-radius: 2px; +} +.footer-links { + list-style: none; + padding: 0; +} +.footer-links li { + margin-bottom: 0.75rem; +} +.footer-links a { + color: var(--text-secondary); + text-decoration: none; + transition: all var(--transition-fast); + font-size: 0.9rem; + display: inline-flex; + align-items: center; + gap: 8px; +} +.footer-links a:hover { + color: var(--accent); + transform: translateX(5px); +} +.footer-contact-item { + display: flex; + align-items: center; + gap: 12px; + margin-bottom: 1rem; + color: var(--text-secondary); + font-size: 0.9rem; +} +.footer-contact-item i { + width: 20px; + color: var(--accent); + font-size: 1rem; +} +.footer-newsletter { + margin-top: 0.5rem; +} +.newsletter-text { + color: var(--text-secondary); + font-size: 0.85rem; + margin-bottom: 1rem; + line-height: 1.5; +} +.newsletter-form { + display: flex; + gap: 0.5rem; + flex-wrap: wrap; +} +.newsletter-input { + flex: 1; + min-width: 140px; + padding: 0.75rem 1rem; + background: var(--surface-light); + border: 1px solid var(--border); + border-radius: 12px; + color: var(--text-primary); + font-size: 0.85rem; + outline: none; + transition: all var(--transition-fast); +} +.newsletter-input:focus { + border-color: var(--accent); + box-shadow: 0 0 0 3px rgba(4, 205, 255, 0.1); +} +.newsletter-btn { + padding: 0.75rem 1.25rem; + background: var(--accent-gradient); + border: none; + border-radius: 12px; + color: white; + font-weight: 600; + font-size: 0.85rem; + cursor: pointer; + transition: all var(--transition-fast); + white-space: nowrap; +} +.newsletter-btn:hover { + transform: translateY(-2px); + box-shadow: var(--shadow-sm); +} +.footer-bottom { + border-top: 1px solid var(--border); + padding-top: 2rem; + display: flex; + justify-content: space-between; + align-items: center; + flex-wrap: wrap; + gap: 1rem; +} +.copyright { + color: var(--text-tertiary); + font-size: 0.85rem; +} +.footer-legal { + display: flex; + gap: 1.5rem; + flex-wrap: wrap; +} +.footer-legal a { + color: var(--text-tertiary); + text-decoration: none; + font-size: 0.85rem; + transition: color var(--transition-fast); +} +.footer-legal a:hover { + color: var(--accent); +} + +@media (max-width: 768px) { + .footer-bottom { + flex-direction: column; + text-align: center; + } + .footer-legal { + justify-content: center; + } + .footer-brand { + max-width: 100%; + text-align: center; + } + .footer-logo { + justify-content: center; + } + .footer-social { + justify-content: center; + } + .footer-title::after { + left: 50%; + transform: translateX(-50%); + } + .footer-title { + text-align: center; + display: block; + } + .footer-contact-item { + justify-content: center; + } + .footer-links { + text-align: center; + } + .footer-links a { + justify-content: center; + } +} + +/* ==================== */ +/* CHATBOT WIDGET */ +/* ==================== */ +.chatbot-widget { + position: fixed; + bottom: 30px; + right: 30px; + z-index: 2000; +} +.chatbot-toggle { + width: 65px; + height: 65px; + background: var(--accent-gradient); + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + box-shadow: var(--shadow-lg); + transition: all var(--transition-base); + border: 2px solid rgba(255, 255, 255, 0.2); + animation: pulseRing 2s ease-in-out infinite; +} +.chatbot-toggle:hover { + transform: scale(1.1); + box-shadow: var(--shadow-xl); + animation: none; +} +@keyframes pulseRing { + 0% { + box-shadow: 0 0 0 0 rgba(4, 205, 255, 0.7); + } + 70% { + box-shadow: 0 0 0 15px rgba(4, 205, 255, 0); + } + 100% { + box-shadow: 0 0 0 0 rgba(4, 205, 255, 0); + } +} +.chatbot-toggle i { + font-size: 28px; + color: white; +} +.chatbot-container { + position: absolute; + bottom: 80px; + right: 0; + width: 380px; + height: 550px; + background: var(--secondary); + border-radius: 24px; + overflow: hidden; + box-shadow: var(--shadow-xl); + border: 1px solid var(--border); + display: none; + flex-direction: column; + animation: slideInRight 0.3s ease-out; +} +.chatbot-container.active { + display: flex; +} +.chatbot-header { + background: var(--accent-gradient); + padding: 18px 20px; + display: flex; + align-items: center; + justify-content: space-between; + color: white; +} +.chatbot-header-info { + display: flex; + align-items: center; + gap: 12px; +} +.chatbot-header-info i { + font-size: 28px; +} +.chatbot-header-info h3 { + font-size: 16px; + font-weight: 600; + margin: 0; +} +.chatbot-header-info p { + font-size: 12px; + opacity: 0.8; + margin: 0; +} +.chatbot-close { + background: rgba(255, 255, 255, 0.2); + border: none; + width: 32px; + height: 32px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + color: white; + transition: all var(--transition-fast); +} +.chatbot-close:hover { + background: rgba(255, 255, 255, 0.3); + transform: rotate(90deg); +} +.chatbot-messages { + flex: 1; + overflow-y: auto; + padding: 20px; + display: flex; + flex-direction: column; + gap: 12px; +} +.chatbot-message { + max-width: 85%; + padding: 12px 16px; + border-radius: 18px; + font-size: 14px; + line-height: 1.5; + animation: fadeInUp 0.3s ease-out; +} +.chatbot-message.bot { + background: var(--surface-light); + color: var(--text-primary); + align-self: flex-start; + border-bottom-left-radius: 6px; +} +.chatbot-message.user { + background: var(--accent); + color: white; + align-self: flex-end; + border-bottom-right-radius: 6px; +} +.chatbot-message.system { + background: rgba(56, 178, 172, 0.2); + color: var(--success); + align-self: center; + font-size: 12px; + padding: 8px 16px; + border-radius: 30px; + max-width: 90%; +} +.chatbot-message.error { + background: rgba(245, 101, 101, 0.2); + color: var(--error); + align-self: center; + font-size: 12px; + padding: 8px 16px; + border-radius: 30px; +} +.chatbot-loading { + display: flex; + gap: 4px; + padding: 12px 16px; + background: var(--surface-light); + border-radius: 18px; + align-self: flex-start; +} +.chatbot-loading span { + width: 8px; + height: 8px; + background: var(--text-tertiary); + border-radius: 50%; + animation: loadingBounce 1.4s infinite ease-in-out both; +} +.chatbot-loading span:nth-child(1) { + animation-delay: -0.32s; +} +.chatbot-loading span:nth-child(2) { + animation-delay: -0.16s; +} +@keyframes loadingBounce { + 0%, + 80%, + 100% { + transform: scale(0); + } + 40% { + transform: scale(1); + } +} +.chatbot-input { + padding: 16px; + border-top: 1px solid var(--border); + display: flex; + gap: 12px; + background: var(--secondary); +} +.chatbot-input input { + flex: 1; + padding: 12px 16px; + background: var(--surface-light); + border: 1px solid var(--border); + border-radius: 30px; + color: var(--text-primary); + font-size: 14px; + outline: none; + transition: all var(--transition-fast); +} +.chatbot-input input:focus { + border-color: var(--accent); +} +.chatbot-input input::placeholder { + color: var(--text-tertiary); +} +.chatbot-input button { + padding: 12px 20px; + background: var(--accent-gradient); + border: none; + border-radius: 30px; + color: white; + font-weight: 600; + cursor: pointer; + transition: all var(--transition-fast); +} +.chatbot-input button:hover:not(:disabled) { + transform: translateY(-2px); +} +.chatbot-input button:disabled { + opacity: 0.5; + cursor: not-allowed; +} +.chatbot-status { + text-align: center; + font-size: 11px; + padding: 8px; + color: var(--text-tertiary); + background: var(--surface-light); + border-top: 1px solid var(--border); +} + +@media (max-width: 480px) { + .chatbot-container { + width: calc(100vw - 40px); + height: 500px; + right: 0; + bottom: 80px; + } + .chatbot-widget { + bottom: 20px; + right: 20px; + } + .chatbot-toggle { + width: 55px; + height: 55px; + } + .chatbot-toggle i { + font-size: 24px; + } +} + +/* ==================== */ +/* CONÓCENOS */ +/* ==================== */ +.conocenos-section { + padding: 5rem clamp(1rem, 3vw, 2rem); + max-width: var(--max-width); + margin: 0 auto; + background: var(--secondary); + border-radius: 40px; + margin-top: 2rem; + margin-bottom: 2rem; +} +.conocenos-header { + text-align: center; + margin-bottom: 3rem; +} +.conocenos-subtitle { + color: var(--accent); + font-size: 1rem; + text-transform: uppercase; + letter-spacing: 2px; + font-weight: 500; + margin-bottom: 0.5rem; + display: inline-block; + background: var(--surface-light); + padding: 0.5rem 1.5rem; + border-radius: 50px; +} +.conocenos-title { + font-family: "Space Grotesk", sans-serif; + font-size: clamp(2rem, 4vw, 2.8rem); + font-weight: 700; + background: linear-gradient(135deg, var(--text-primary), var(--accent)); + -webkit-background-clip: text; + background-clip: text; + color: transparent; + margin-top: 1rem; +} +.conocenos-grid { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 2rem; + margin-bottom: 4rem; +} +@media (max-width: 1024px) { + .conocenos-grid { + grid-template-columns: repeat(2, 1fr); + gap: 1.5rem; + } +} +@media (max-width: 640px) { + .conocenos-grid { + grid-template-columns: 1fr; + gap: 1.5rem; + } +} +.conocenos-card { + background: var(--surface); + border: 1px solid var(--border); + border-radius: 28px; + padding: 2rem 1.5rem; + text-align: center; + transition: all var(--transition-base); + position: relative; + overflow: hidden; +} +.conocenos-card::before { + content: ""; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 4px; + background: var(--accent-gradient); + transform: scaleX(0); + transform-origin: left; + transition: transform var(--transition-base); +} +.conocenos-card:hover { + transform: translateY(-8px); + border-color: var(--accent); + box-shadow: var(--shadow-lg); +} +.conocenos-card:hover::before { + transform: scaleX(1); +} +.conocenos-icon { + width: 80px; + height: 80px; + background: var(--accent-gradient); + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + margin: 0 auto 1.5rem; + font-size: 2rem; + color: white; + box-shadow: var(--shadow-md); + transition: transform var(--transition-fast); +} +.conocenos-card:hover .conocenos-icon { + transform: scale(1.1) rotate(5deg); +} +.conocenos-card h3 { + font-size: 1.5rem; + font-weight: 700; + margin-bottom: 0.5rem; + color: var(--text-primary); +} +.conocenos-card h3 span { + display: block; + font-size: 1rem; + font-weight: 400; + color: var(--accent); + margin-top: 0.25rem; +} +.conocenos-card p { + color: var(--text-secondary); + font-size: 0.9rem; + line-height: 1.5; +} +.conocenos-cta { + text-align: center; + margin-top: 2rem; +} +.btn-reserva { + display: inline-flex; + align-items: center; + gap: 12px; + padding: 1rem 2.5rem; + background: var(--accent-gradient); + color: white; + text-decoration: none; + border-radius: 50px; + font-weight: 700; + font-size: 1.1rem; + transition: all var(--transition-base); + box-shadow: var(--shadow-md); + border: none; + cursor: pointer; +} +.btn-reserva:hover { + transform: translateY(-3px); + box-shadow: var(--shadow-lg); +} +.btn-reserva i { + transition: transform var(--transition-fast); +} +.btn-reserva:hover i { + transform: translateX(5px); +} + +/* ==================== */ +/* EXTRAS (Complementa tu viaje) */ +/* ==================== */ +.extras-section { + padding: 5rem clamp(1rem, 3vw, 2rem); + max-width: var(--max-width); + margin: 0 auto; + background: var(--primary); + border-radius: 40px; + margin-top: 2rem; + margin-bottom: 2rem; +} +.extras-header { + text-align: center; + margin-bottom: 3rem; +} +.extras-subtitle { + display: inline-flex; + align-items: center; + gap: 8px; + background: var(--surface-light); + padding: 0.5rem 1.5rem; + border-radius: 50px; + color: var(--accent); + font-size: 0.9rem; + font-weight: 500; + text-transform: uppercase; + letter-spacing: 2px; + margin-bottom: 1rem; +} +.extras-subtitle i { + font-size: 0.9rem; +} +.extras-title { + font-family: "Space Grotesk", sans-serif; + font-size: clamp(2rem, 4vw, 2.8rem); + font-weight: 700; + background: linear-gradient(135deg, var(--text-primary), var(--accent)); + -webkit-background-clip: text; + background-clip: text; + color: transparent; + margin-bottom: 1rem; +} +.extras-description { + color: var(--text-secondary); + font-size: 1.1rem; + max-width: 700px; + margin: 0 auto; +} +.extras-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 2rem; + margin-bottom: 4rem; +} +@media (max-width: 1024px) { + .extras-grid { + grid-template-columns: repeat(2, 1fr); + gap: 1.5rem; + } +} +@media (max-width: 640px) { + .extras-grid { + grid-template-columns: 1fr; + gap: 1.5rem; + } +} +.extra-card { + background: var(--secondary); + border: 1px solid var(--border); + border-radius: 28px; + padding: 2rem 1.5rem; + text-align: center; + transition: all var(--transition-base); + position: relative; + overflow: hidden; + backdrop-filter: blur(10px); +} +.extra-card::before { + content: ""; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 4px; + background: var(--accent-gradient); + transform: scaleX(0); + transform-origin: left; + transition: transform var(--transition-base); +} +.extra-card:hover { + transform: translateY(-8px); + border-color: var(--accent); + box-shadow: var(--shadow-lg); +} +.extra-card:hover::before { + transform: scaleX(1); +} +.extra-icon { + width: 80px; + height: 80px; + background: var(--accent-gradient); + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + margin: 0 auto 1.5rem; + font-size: 2rem; + color: white; + box-shadow: var(--shadow-md); + transition: transform var(--transition-fast); +} +.extra-card:hover .extra-icon { + transform: scale(1.1) rotate(5deg); +} +.extra-card h3 { + font-size: 1.5rem; + font-weight: 700; + margin-bottom: 0.5rem; + color: var(--text-primary); +} +.extra-card h3 span { + display: block; + font-size: 1rem; + font-weight: 400; + color: var(--accent); + margin-top: 0.25rem; +} +.extra-card p { + color: var(--text-secondary); + font-size: 0.9rem; + line-height: 1.5; + margin-bottom: 1.5rem; +} +.extra-link { + display: inline-flex; + align-items: center; + gap: 8px; + color: var(--accent); + font-weight: 600; + text-decoration: none; + font-size: 0.9rem; + transition: gap var(--transition-fast); +} +.extra-link:hover { + gap: 12px; +} +.extra-link i { + font-size: 0.8rem; + transition: transform var(--transition-fast); +} +.extra-link:hover i { + transform: translateX(3px); +} +.extras-cta { + text-align: center; +} +.btn-extras { + display: inline-flex; + align-items: center; + gap: 10px; + padding: 1rem 2rem; + background: var(--surface-light); + border: 1px solid var(--border); + border-radius: 50px; + color: var(--text-primary); + font-weight: 600; + text-decoration: none; + transition: all var(--transition-base); +} +.btn-extras:hover { + background: var(--accent); + color: white; + border-color: var(--accent); + transform: translateY(-3px); + box-shadow: var(--shadow-md); +} +.btn-extras i { + transition: transform var(--transition-fast); +} +.btn-extras:hover i { + transform: translateX(5px); +} + +/* ==================== */ +/* CONTACTO PAGE — UX/UI ENHANCEMENTS */ +/* ==================== */ +.contact-cards .contact-card-modern, +.contact-wrapper, +.map-container-modern { + opacity: 0; + transform: translateY(32px); + transition: + opacity 0.55s cubic-bezier(0.22, 1, 0.36, 1), + transform 0.55s cubic-bezier(0.22, 1, 0.36, 1); +} +.contact-cards .contact-card-modern.cpage--visible, +.contact-wrapper.cpage--visible, +.map-container-modern.cpage--visible { + opacity: 1; + transform: translateY(0); +} +.contact-cards .contact-card-modern:nth-child(1) { + transition-delay: 0s; +} +.contact-cards .contact-card-modern:nth-child(2) { + transition-delay: 0.1s; +} +.contact-cards .contact-card-modern:nth-child(3) { + transition-delay: 0.2s; +} + +.contact-card-modern { + cursor: default; + will-change: transform, box-shadow; +} +.contact-card-modern:hover { + transform: translateY(-8px) scale(1.01); + border-color: var(--accent) !important; + box-shadow: + 0 20px 48px rgba(4, 205, 255, 0.13), + var(--shadow-xl); +} +.contact-card-modern > div:first-child { + transition: transform 0.4s cubic-bezier(0.22, 1, 0.36, 1) !important; +} +.contact-card-modern:hover > div:first-child { + transform: scaleX(1) !important; +} +.contact-card-modern:hover div[style*="border-radius: 20px"] { + transform: scale(1.08) rotate(6deg); + box-shadow: 0 8px 24px rgba(4, 205, 255, 0.25); +} +.contact-card-modern a[href^="tel"], +.contact-card-modern a[href^="https://maps"] { + position: relative; + overflow: hidden; +} +.contact-card-modern a[href^="tel"]:hover, +.contact-card-modern a[href^="https://maps"]:hover { + background: rgba(4, 205, 255, 0.08) !important; + color: var(--accent) !important; + transform: translateY(-2px); + border-radius: 8px; +} + +.contact-wrapper { + will-change: transform; +} +.contact-wrapper:hover { + transform: translateY(-4px); + box-shadow: + 0 30px 60px rgba(0, 0, 0, 0.2), + var(--shadow-xl); +} + +.info-row { + cursor: default; + will-change: transform; +} +.info-row:hover { + transform: translateX(8px); + background: var(--surface) !important; + box-shadow: 0 4px 16px rgba(4, 205, 255, 0.08); +} +.info-row:hover div[style*="background: var(--accent-gradient)"] { + transform: rotate(-6deg) scale(1.05); + transition: transform 0.25s ease; +} + +.social-link-modern { + position: relative; + overflow: hidden; +} +.social-link-modern::after { + content: ""; + position: absolute; + inset: 0; + background: var(--accent-gradient); + opacity: 0; + border-radius: 12px; + transition: opacity 0.25s ease; +} +.social-link-modern:hover::after { + opacity: 1; +} +.social-link-modern:hover { + color: white !important; + transform: translateY(-4px) scale(1.08); + box-shadow: 0 8px 20px rgba(4, 205, 255, 0.3); +} +.social-link-modern i { + position: relative; + z-index: 1; +} + +.form-group-modern { + position: relative; +} +.form-group-modern input, +.form-group-modern textarea, +.form-group-modern select { + outline: none !important; + box-sizing: border-box; +} +.form-group-modern input:focus, +.form-group-modern textarea:focus, +.form-group-modern select:focus { + border-color: var(--accent) !important; + box-shadow: 0 0 0 4px rgba(4, 205, 255, 0.12) !important; + transform: translateY(-2px); + background: var(--surface) !important; +} +.form-group-modern input:not(:placeholder-shown):not(.error):not(:focus), +.form-group-modern textarea:not(:placeholder-shown):not(.error):not(:focus) { + border-color: rgba(0, 255, 136, 0.4) !important; +} + +#privacy_checkbox { + appearance: none; + -webkit-appearance: none; + width: 18px !important; + height: 18px !important; + border: 2px solid var(--border); + border-radius: 5px; + background: var(--surface-light); + cursor: pointer; + position: relative; + flex-shrink: 0; + transition: + border-color 0.2s, + background 0.2s, + box-shadow 0.2s; +} +#privacy_checkbox:checked { + background: var(--accent, #04cdff); + border-color: var(--accent, #04cdff); + box-shadow: 0 0 0 3px rgba(4, 205, 255, 0.2); +} +#privacy_checkbox:checked::after { + content: ""; + position: absolute; + top: 2px; + left: 5px; + width: 5px; + height: 9px; + border: 2px solid #000; + border-top: none; + border-left: none; + transform: rotate(45deg); +} +#privacy_checkbox:hover { + border-color: var(--accent); +} + +.submit-btn-modern { + letter-spacing: 0.02em; + isolation: isolate; +} +.submit-btn-modern::before { + content: ""; + position: absolute; + top: 50%; + left: 50%; + width: 0; + height: 0; + background: rgba(255, 255, 255, 0.25); + border-radius: 50%; + transform: translate(-50%, -50%); + transition: + width 0.55s cubic-bezier(0.22, 1, 0.36, 1), + height 0.55s cubic-bezier(0.22, 1, 0.36, 1); + z-index: 0; +} +.submit-btn-modern:hover::before { + width: 340px; + height: 340px; +} +.submit-btn-modern > * { + position: relative; + z-index: 1; +} +.submit-btn-modern:hover { + transform: translateY(-3px); + box-shadow: 0 12px 32px rgba(4, 205, 255, 0.35); + gap: 16px !important; +} +.submit-btn-modern:active { + transform: translateY(0) scale(0.98); + box-shadow: none; +} +.submit-btn-modern:disabled { + opacity: 0.65; + cursor: not-allowed; + transform: none !important; +} +.submit-btn-modern:hover .fa-arrow-right { + transform: translateX(4px); + transition: transform 0.2s ease; +} + +.form-panel .fa-paper-plane { + transition: transform 0.35s cubic-bezier(0.22, 1, 0.36, 1); + display: inline-block; +} +.form-panel:hover .fa-paper-plane { + transform: translateX(6px) translateY(-6px) rotate(15deg); +} + +.map-container-modern { + will-change: transform; +} +.map-container-modern:hover { + transform: translateY(-5px); + box-shadow: 0 24px 48px rgba(0, 0, 0, 0.18) !important; +} +.map-container-modern:hover iframe { + filter: grayscale(0) !important; +} + +.whatsapp-float-modern a { + will-change: transform; +} +.whatsapp-float-modern a:hover { + transform: scale(1.05) translateY(-3px); + box-shadow: 0 12px 32px rgba(37, 211, 102, 0.55) !important; + gap: 16px !important; +} +.whatsapp-float-modern a:active { + transform: scale(0.97); +} +.whatsapp-float-modern { + position: fixed; + bottom: 30px; + right: 30px; + z-index: 1000; +} +.whatsapp-float-modern::before { + content: ""; + position: absolute; + top: -4px; + right: -4px; + width: 14px; + height: 14px; + background: #00ff88; + border-radius: 50%; + border: 2px solid #fff; + animation: cpage-online-pulse 2s infinite; + z-index: 2; +} +@keyframes cpage-online-pulse { + 0%, + 100% { + transform: scale(1); + box-shadow: 0 0 0 0 rgba(0, 255, 136, 0.6); + } + 50% { + transform: scale(1.15); + box-shadow: 0 0 0 6px rgba(0, 255, 136, 0); + } +} + +.alert { + padding: 1rem 1.25rem; + border-radius: 14px; + margin-top: 1rem; + animation: cpage-fadeInUp 0.35s cubic-bezier(0.22, 1, 0.36, 1); + display: flex; + align-items: center; + gap: 10px; + font-size: 0.92rem; + font-weight: 500; +} +.alert-success { + background: rgba(0, 255, 136, 0.08); + border: 1px solid rgba(0, 255, 136, 0.5); + color: #00ff88; + box-shadow: 0 4px 16px rgba(0, 255, 136, 0.1); +} +.alert-error { + background: rgba(255, 68, 68, 0.08); + border: 1px solid rgba(255, 68, 68, 0.5); + color: #ff4444; + box-shadow: 0 4px 16px rgba(255, 68, 68, 0.1); +} +.alert-info { + background: rgba(4, 205, 255, 0.08); + border: 1px solid rgba(4, 205, 255, 0.4); + color: var(--accent); +} +.alert i { + font-size: 1.1rem; + flex-shrink: 0; +} +@keyframes cpage-fadeInUp { + from { + opacity: 0; + transform: translateY(12px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +input.error, +textarea.error, +select.error { + border-color: #ff4444 !important; + box-shadow: 0 0 0 3px rgba(255, 68, 68, 0.12) !important; + animation: cpage-shake 0.35s cubic-bezier(0.36, 0.07, 0.19, 0.97); +} +@keyframes cpage-shake { + 0%, + 100% { + transform: translateX(0); + } + 15% { + transform: translateX(-6px); + } + 30% { + transform: translateX(5px); + } + 45% { + transform: translateX(-4px); + } + 60% { + transform: translateX(3px); + } + 75% { + transform: translateX(-2px); + } +} + +.char-counter { + font-weight: 500; +} + +.iti { + width: 100%; + display: block !important; +} +.iti__flag-container { + border-radius: 12px 0 0 12px; +} +.iti--allow-dropdown .iti__flag-container:hover .iti__selected-flag { + background-color: rgba(4, 205, 255, 0.08); + border-radius: 12px 0 0 12px; +} +.iti__selected-flag { + padding: 0 12px; + border-radius: 12px 0 0 12px; + background: var(--surface-light); +} +.iti__country-list { + background: var(--surface); + border: 1px solid var(--border); + border-radius: 12px; + box-shadow: var(--shadow-lg); + backdrop-filter: blur(12px); +} +.iti__country:hover { + background: var(--surface-light); +} +.iti__highlight { + background: rgba(4, 205, 255, 0.15); +} + +#tel1-contacto, +#tel3-contacto { + position: relative; +} +#tel1-contacto::after { + content: "Abrir WhatsApp"; + position: absolute; + bottom: calc(100% + 8px); + left: 50%; + transform: translateX(-50%) scale(0.85); + background: #25d366; + color: #fff; + font-size: 0.72rem; + font-weight: 600; + padding: 4px 10px; + border-radius: 6px; + white-space: nowrap; + pointer-events: none; + opacity: 0; + transition: + opacity 0.2s, + transform 0.2s; +} +#tel1-contacto:hover::after { + opacity: 1; + transform: translateX(-50%) scale(1); +} +#tel3-contacto::after { + content: "Llamar ahora"; + position: absolute; + bottom: calc(100% + 8px); + left: 50%; + transform: translateX(-50%) scale(0.85); + background: var(--accent, #04cdff); + color: #000; + font-size: 0.72rem; + font-weight: 600; + padding: 4px 10px; + border-radius: 6px; + white-space: nowrap; + pointer-events: none; + opacity: 0; + transition: + opacity 0.2s, + transform 0.2s; +} +#tel3-contacto:hover::after { + opacity: 1; + transform: translateX(-50%) scale(1); +} + +@media (max-width: 992px) { + .contact-wrapper { + grid-template-columns: 1fr !important; + } + .info-panel { + order: 2; + } + .form-panel { + order: 1; + } +} +@media (max-width: 768px) { + .cards-grid { + grid-template-columns: 1fr !important; + } + .form-panel { + padding: 1.5rem !important; + } + .info-panel { + padding: 1.5rem !important; + } + .whatsapp-float-modern span { + display: none !important; + } + .whatsapp-float-modern a { + padding: 1rem !important; + } + .whatsapp-float-modern::before { + top: 2px; + right: 2px; + } +} +@media (max-width: 640px) { + .contact-wrapper div[style*="grid-template-columns: 1fr 1fr"] { + grid-template-columns: 1fr !important; + } +} + +.contact-card-modern a:focus-visible, +.submit-btn-modern:focus-visible, +.social-link-modern:focus-visible, +#tel1-contacto:focus-visible, +#tel3-contacto:focus-visible { + outline: 2px solid var(--accent); + outline-offset: 3px; + border-radius: 8px; +} + +/* ==================== */ +/* GALERÍA PREMIUM MODERNA */ +/* ==================== */ +.gallery-premium { + padding: 5rem clamp(1rem, 3vw, 2rem); + max-width: var(--max-width); + margin: 0 auto; + background: linear-gradient(135deg, var(--primary) 0%, var(--surface) 100%); + border-radius: 40px; + margin-top: 2rem; + margin-bottom: 2rem; + position: relative; + overflow: hidden; +} +.gallery-premium::before { + content: ""; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 100%; + background: radial-gradient( + circle at 20% 50%, + rgba(var(--accent-rgb), 0.03) 0%, + transparent 50% + ); + pointer-events: none; +} +.gallery-premium-header { + text-align: center; + margin-bottom: 4rem; + position: relative; +} +.gallery-premium-subtitle { + display: inline-flex; + align-items: center; + gap: 8px; + background: rgba(var(--accent-rgb), 0.1); + backdrop-filter: blur(10px); + padding: 0.5rem 1.5rem; + border-radius: 50px; + color: var(--accent); + font-size: 0.9rem; + font-weight: 500; + text-transform: uppercase; + letter-spacing: 2px; + margin-bottom: 1rem; + border: 1px solid rgba(var(--accent-rgb), 0.2); +} +.gallery-premium-title { + font-family: "Space Grotesk", sans-serif; + font-size: clamp(2rem, 4vw, 3rem); + font-weight: 700; + margin-bottom: 1rem; + background: linear-gradient( + 135deg, + var(--text-primary) 0%, + var(--accent) 100% + ); + -webkit-background-clip: text; + background-clip: text; + color: transparent; +} +.gallery-premium-description { + color: var(--text-secondary); + font-size: 1.1rem; + max-width: 600px; + margin: 0 auto; +} +.gallery-premium-container { + column-count: 4; + column-gap: 1.5rem; + margin-bottom: 3rem; + position: relative; +} +@media (max-width: 1200px) { + .gallery-premium-container { + column-count: 3; + } +} +@media (max-width: 768px) { + .gallery-premium-container { + column-count: 2; + } +} +@media (max-width: 480px) { + .gallery-premium-container { + column-count: 1; + } +} +.gallery-item { + break-inside: avoid; + margin-bottom: 1.5rem; + position: relative; + border-radius: 24px; + overflow: hidden; + cursor: pointer; + transform: translateY(0); + transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); +} +.gallery-item:hover { + transform: translateY(-10px); +} +.gallery-image-wrapper { + position: relative; + overflow: hidden; + border-radius: 24px; + background: var(--surface-light); +} +.gallery-image { + width: 100%; + height: auto; + display: block; + transition: transform 0.6s cubic-bezier(0.4, 0, 0.2, 1); +} +.gallery-item:hover .gallery-image { + transform: scale(1.08); +} +.gallery-overlay { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: linear-gradient( + 135deg, + rgba(0, 0, 0, 0.85) 0%, + rgba(0, 0, 0, 0.4) 50%, + transparent 100% + ); + display: flex; + flex-direction: column; + justify-content: flex-end; + padding: 1.5rem; + opacity: 0; + transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); +} +.gallery-item:hover .gallery-overlay { + opacity: 1; +} +.gallery-info { + transform: translateY(20px); + transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1); +} +.gallery-item:hover .gallery-info { + transform: translateY(0); +} +.gallery-title { + color: white; + font-size: 1.1rem; + font-weight: 700; + margin-bottom: 0.25rem; + display: flex; + align-items: center; + gap: 8px; +} +.gallery-title i { + font-size: 0.9rem; + opacity: 0; + transform: translateX(-10px); + transition: all 0.3s ease; +} +.gallery-item:hover .gallery-title i { + opacity: 1; + transform: translateX(0); +} +.zoom-badge { + position: absolute; + top: 1rem; + right: 1rem; + background: rgba(255, 255, 255, 0.2); + backdrop-filter: blur(10px); + padding: 0.5rem; + border-radius: 50%; + width: 35px; + height: 35px; + display: flex; + align-items: center; + justify-content: center; + opacity: 0; + transform: scale(0); + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); +} +.gallery-item:hover .zoom-badge { + opacity: 1; + transform: scale(1); +} +.zoom-badge i { + color: white; + font-size: 1rem; +} +.gallery-stats { + text-align: center; + margin-top: 2rem; + color: var(--text-secondary); + font-size: 0.9rem; +} +.gallery-item { + animation: fadeInUp 0.6s ease-out forwards; + opacity: 0; +} +.gallery-item.revealed { + animation: fadeInUp 0.6s ease-out forwards; +} + +/* ==================== */ +/* ACORDEÓN FAQ PREMIUM */ +/* ==================== */ +.faq-premium { + padding: 5rem clamp(1rem, 3vw, 2rem); + max-width: var(--max-width); + margin: 0 auto; + background: linear-gradient(135deg, var(--surface) 0%, var(--primary) 100%); + border-radius: 40px; + margin-top: 2rem; + margin-bottom: 2rem; + position: relative; + overflow: hidden; +} +.faq-premium::before { + content: ""; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 100%; + background: radial-gradient( + circle at 80% 30%, + rgba(var(--accent-rgb), 0.03) 0%, + transparent 60% + ); + pointer-events: none; +} +.faq-premium-header { + text-align: center; + margin-bottom: 4rem; + position: relative; +} +.faq-premium-subtitle { + display: inline-flex; + align-items: center; + gap: 8px; + background: rgba(var(--accent-rgb), 0.1); + backdrop-filter: blur(10px); + padding: 0.5rem 1.5rem; + border-radius: 50px; + color: var(--accent); + font-size: 0.9rem; + font-weight: 500; + text-transform: uppercase; + letter-spacing: 2px; + margin-bottom: 1rem; + border: 1px solid rgba(var(--accent-rgb), 0.2); +} +.faq-premium-title { + font-family: "Space Grotesk", sans-serif; + font-size: clamp(2rem, 4vw, 3rem); + font-weight: 700; + margin-bottom: 1rem; + background: linear-gradient( + 135deg, + var(--text-primary) 0%, + var(--accent) 100% + ); + -webkit-background-clip: text; + background-clip: text; + color: transparent; +} +.faq-premium-description { + color: var(--text-secondary); + font-size: 1.1rem; + max-width: 600px; + margin: 0 auto; +} +.faq-premium-container { + max-width: 900px; + margin: 0 auto; + position: relative; +} +.faq-accordion { + background: rgba(var(--surface-rgb), 0.6); + backdrop-filter: blur(10px); + border-radius: 24px; + overflow: hidden; + transition: all 0.3s ease; + border: 1px solid rgba(var(--accent-rgb), 0.1); + margin-bottom: 1rem; +} +.faq-accordion:hover { + border-color: rgba(var(--accent-rgb), 0.3); + transform: translateY(-2px); +} +.faq-accordion.active { + border-color: rgba(var(--accent-rgb), 0.5); + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1); +} +.faq-accordion-title { + padding: 1.25rem 1.5rem; + cursor: pointer; + display: flex; + align-items: center; + justify-content: space-between; + gap: 1rem; + transition: all 0.3s ease; + background: linear-gradient( + 135deg, + rgba(var(--surface-rgb), 0.8) 0%, + rgba(var(--surface-rgb), 0.4) 100% + ); +} +.faq-accordion-title h4 { + font-size: 1rem; + font-weight: 600; + margin: 0; + color: var(--text-primary); + line-height: 1.4; + flex: 1; +} +.faq-accordion-icon { + width: 32px; + height: 32px; + min-width: 32px; + display: flex; + align-items: center; + justify-content: center; + background: rgba(var(--accent-rgb), 0.1); + border-radius: 50%; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); +} +.faq-accordion.active .faq-accordion-icon { + background: var(--accent); + transform: rotate(180deg); +} +.faq-accordion-icon i { + color: var(--accent); + font-size: 0.9rem; + transition: all 0.3s ease; +} +.faq-accordion.active .faq-accordion-icon i { + color: white; +} +.faq-accordion-content { + max-height: 0; + overflow: hidden; + transition: max-height 0.5s cubic-bezier(0.4, 0, 0.2, 1); + background: rgba(var(--surface-rgb), 0.3); +} +.faq-accordion.active .faq-accordion-content { + max-height: 2000px; +} +.faq-accordion-inner { + padding: 0 1.5rem 1.5rem 1.5rem; + color: var(--text-secondary); + line-height: 1.7; + font-size: 0.95rem; +} +.faq-accordion-inner p { + margin-bottom: 1rem; +} +.faq-accordion-inner p:last-child { + margin-bottom: 0; +} +.faq-accordion-inner a { + color: var(--accent); + text-decoration: none; + transition: all 0.3s ease; +} +.faq-accordion-inner a:hover { + text-decoration: underline; +} +.faq-stats { + text-align: center; + margin-top: 3rem; + padding-top: 2rem; + border-top: 1px solid rgba(var(--accent-rgb), 0.1); + color: var(--text-secondary); + font-size: 0.9rem; +} +.faq-stats i { + color: var(--accent); + margin-right: 0.5rem; +} +.faq-accordion { + animation: fadeInUp 0.6s ease-out forwards; + opacity: 0; +} +.faq-accordion.revealed { + animation: fadeInUp 0.6s ease-out forwards; +} +@media (max-width: 768px) { + .faq-accordion-title { + padding: 1rem; + } + .faq-accordion-title h4 { + font-size: 0.9rem; + } + .faq-accordion-inner { + padding: 0 1rem 1rem 1rem; + font-size: 0.9rem; + } +} + +/* ==================== */ +/* TÉRMINOS Y CONDICIONES PREMIUM */ +/* ==================== */ +.terminos-premium { + padding: 5rem clamp(1rem, 3vw, 2rem); + max-width: var(--max-width); + margin: 0 auto; + background: linear-gradient(135deg, var(--primary) 0%, var(--surface) 100%); + border-radius: 40px; + margin-top: 2rem; + margin-bottom: 2rem; + position: relative; + overflow: hidden; +} +.terminos-premium::before { + content: ""; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 100%; + background: radial-gradient( + circle at 20% 50%, + rgba(var(--accent-rgb), 0.03) 0%, + transparent 50% + ); + pointer-events: none; +} +.terminos-premium-header { + text-align: center; + margin-bottom: 4rem; + position: relative; +} +.terminos-premium-subtitle { + display: inline-flex; + align-items: center; + gap: 8px; + background: rgba(var(--accent-rgb), 0.1); + backdrop-filter: blur(10px); + padding: 0.5rem 1.5rem; + border-radius: 50px; + color: var(--accent); + font-size: 0.9rem; + font-weight: 500; + text-transform: uppercase; + letter-spacing: 2px; + margin-bottom: 1rem; + border: 1px solid rgba(var(--accent-rgb), 0.2); +} +.terminos-premium-title { + font-family: "Space Grotesk", sans-serif; + font-size: clamp(2rem, 4vw, 3rem); + font-weight: 700; + margin-bottom: 1rem; + background: linear-gradient( + 135deg, + var(--text-primary) 0%, + var(--accent) 100% + ); + -webkit-background-clip: text; + background-clip: text; + color: transparent; +} +.terminos-premium-description { + color: var(--text-secondary); + font-size: 1.1rem; + max-width: 600px; + margin: 0 auto; +} +.terminos-container { + max-width: 900px; + margin: 0 auto; + position: relative; +} +.terminos-accordion { + background: rgba(var(--surface-rgb), 0.6); + backdrop-filter: blur(10px); + border-radius: 24px; + overflow: hidden; + transition: all 0.3s ease; + border: 1px solid rgba(var(--accent-rgb), 0.1); + margin-bottom: 1rem; +} +.terminos-accordion:hover { + border-color: rgba(var(--accent-rgb), 0.3); + transform: translateY(-2px); +} +.terminos-accordion.active { + border-color: rgba(var(--accent-rgb), 0.5); + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1); +} +.terminos-accordion-title { + padding: 1.25rem 1.5rem; + cursor: pointer; + display: flex; + align-items: center; + justify-content: space-between; + gap: 1rem; + transition: all 0.3s ease; + background: linear-gradient( + 135deg, + rgba(var(--surface-rgb), 0.8) 0%, + rgba(var(--surface-rgb), 0.4) 100% + ); +} +.terminos-accordion-title h4 { + font-size: 1.1rem; + font-weight: 700; + margin: 0; + color: var(--text-primary); + line-height: 1.4; + flex: 1; + display: flex; + align-items: center; + gap: 10px; +} +.terminos-accordion-title h4 i { + color: var(--accent); + font-size: 1.2rem; +} +.terminos-accordion-icon { + width: 32px; + height: 32px; + min-width: 32px; + display: flex; + align-items: center; + justify-content: center; + background: rgba(var(--accent-rgb), 0.1); + border-radius: 50%; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); +} +.terminos-accordion.active .terminos-accordion-icon { + background: var(--accent); + transform: rotate(180deg); +} +.terminos-accordion-icon i { + color: var(--accent); + font-size: 0.9rem; + transition: all 0.3s ease; +} +.terminos-accordion.active .terminos-accordion-icon i { + color: white; +} +.terminos-accordion-content { + max-height: 0; + overflow: hidden; + transition: max-height 0.5s cubic-bezier(0.4, 0, 0.2, 1); + background: rgba(var(--surface-rgb), 0.3); +} +.terminos-accordion.active .terminos-accordion-content { + max-height: 3000px; +} +.terminos-accordion-inner { + padding: 0 1.5rem 1.5rem 1.5rem; + color: var(--text-secondary); + line-height: 1.7; + font-size: 0.95rem; +} +.terminos-accordion-inner ol, +.terminos-accordion-inner ul { + margin: 0; + padding-left: 1.5rem; +} +.terminos-accordion-inner ol li, +.terminos-accordion-inner ul li { + margin-bottom: 0.75rem; +} +.terminos-accordion-inner ol li:last-child, +.terminos-accordion-inner ul li:last-child { + margin-bottom: 0; +} +.terminos-accordion-inner ul ul { + margin-top: 0.5rem; + margin-bottom: 0.5rem; +} +.terminos-accordion-inner .list-disc { + list-style-type: disc; +} +.terminos-accordion-inner .list-outside { + list-style-position: outside; +} +.terminos-accordion-inner a { + color: var(--accent); + text-decoration: none; + transition: all 0.3s ease; +} +.terminos-accordion-inner a:hover { + text-decoration: underline; +} +.terminos-accordion-inner strong { + color: var(--accent); + font-weight: 700; +} +.terminos-nota { + font-size: 0.85rem; + font-style: italic; + color: #ff6b6b; + margin-top: 0.5rem; + display: block; +} +.terminos-nota-dark { + font-size: 0.85rem; + font-style: italic; + color: #ffaa00; + margin-top: 0.5rem; + display: block; +} +.terminos-accordion { + animation: fadeInUp 0.6s ease-out forwards; + opacity: 0; +} +.terminos-accordion.revealed { + animation: fadeInUp 0.6s ease-out forwards; +} +.terminos-stats { + text-align: center; + margin-top: 3rem; + padding-top: 2rem; + border-top: 1px solid rgba(var(--accent-rgb), 0.1); + color: var(--text-secondary); + font-size: 0.9rem; +} +.terminos-stats i { + color: var(--accent); + margin-right: 0.5rem; +} +@media (max-width: 768px) { + .terminos-accordion-title { + padding: 1rem; + } + .terminos-accordion-title h4 { + font-size: 0.95rem; + } + .terminos-accordion-inner { + padding: 0 1rem 1rem 1rem; + font-size: 0.9rem; + } + .terminos-accordion-inner ol, + .terminos-accordion-inner ul { + padding-left: 1rem; + } +} + +/* ==================== */ +/* CHECKOUT / RESERVA STYLES */ +/* ==================== */ +.grid-template-columns { + display: grid; + grid-template-columns: 1fr; + gap: 1.5rem; +} + +.options-section { + padding: 2rem; + background: rgba(26, 29, 35, 0.6); + backdrop-filter: blur(20px); + transition: all var(--transition-base); + position: relative; +} +@media (max-width: 640px) { + .options-section { + padding: 1.5rem; + } +} +.options-section h3 { + font-family: "Space Grotesk", sans-serif; + font-size: 1.8rem; + font-weight: 700; + margin-bottom: 1.8rem; + background: linear-gradient(135deg, var(--text-primary), var(--accent)); + -webkit-background-clip: text; + background-clip: text; + color: transparent; + position: relative; + display: inline-block; +} +.options-section h3::after { + content: ""; + position: absolute; + bottom: -8px; + left: 0; + width: 60px; + height: 3px; + background: var(--accent-gradient); + border-radius: 3px; +} + +.step-indicator { + display: flex; + align-items: center; + justify-content: space-between; + background: var(--surface); + padding: 0.8rem 1.5rem; + border-radius: 60px; + margin-bottom: 2.5rem; + border: 1px solid var(--border); + gap: 0.5rem; + position: relative; +} +.step-item { + flex: 1; + text-align: center; + display: flex; + flex-direction: column; + align-items: center; + gap: 8px; + font-size: 0.75rem; + font-weight: 600; + color: var(--text-tertiary); + transition: var(--transition-base); + cursor: pointer; + position: relative; +} +.step-number { + width: 40px; + height: 40px; + background: var(--surface-light); + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-weight: 800; + font-size: 1rem; + border: 2px solid var(--border); + transition: all var(--transition-base); + position: relative; + z-index: 1; +} +.step-item.active .step-number { + background: var(--accent-gradient); + color: white; + border-color: var(--accent); + box-shadow: 0 0 20px rgba(4, 205, 255, 0.5); + transform: scale(1.1); +} +.step-item.active .step-label { + color: var(--accent); + font-weight: 700; + text-shadow: 0 0 10px rgba(4, 205, 255, 0.3); +} +.step-item.completed .step-number { + background: var(--success); + border-color: var(--success); + color: white; + animation: pulse-success 0.5s ease-out; +} +@keyframes pulse-success { + 0% { + transform: scale(1); + box-shadow: 0 0 0 0 rgba(56, 178, 172, 0.7); + } + 70% { + transform: scale(1.05); + box-shadow: 0 0 0 10px rgba(56, 178, 172, 0); + } + 100% { + transform: scale(1); + box-shadow: 0 0 0 0 rgba(56, 178, 172, 0); + } +} +.step-item.completed .step-number::before { + content: "✓"; + font-size: 1rem; + font-weight: bold; +} +.step-item.completed .step-number span { + display: none; +} +.step-indicator::before { + content: ""; + position: absolute; + top: 28px; + left: 0; + right: 0; + height: 2px; + background: var(--border); + z-index: 0; +} + +.step-content { + animation: fadeInUp 0.5s ease-out; +} + +.input-group { + margin-bottom: 2rem; +} +.input-group label { + display: block; + margin-bottom: 0.5rem; + font-weight: 500; + color: var(--text-secondary); + font-size: 0.9rem; +} +.custom-input { + display: flex; + align-items: center; + background: var(--surface-light); + border: 2px solid var(--border); + border-radius: 20px; + overflow: hidden; + transition: all 0.3s ease; +} +.custom-input:focus-within { + border-color: var(--accent); + box-shadow: 0 0 0 4px rgba(4, 205, 255, 0.15); + transform: translateY(-2px); +} +.custom-input .icon { + width: 60px; + display: flex; + align-items: center; + justify-content: center; + background: var(--surface); + color: var(--accent); + font-size: 1.2rem; + border-right: 2px solid var(--border); + flex-shrink: 0; + height: 64px; + transition: all var(--transition-fast); +} +.custom-input:hover .icon { + color: var(--accent); + background: var(--surface-light); +} +.custom-input input, +.custom-input select { + flex: 1; + border: none; + background: transparent; + padding: 0 1.2rem; + height: 64px; + color: var(--text-primary); + font-size: 1rem; + outline: none; + font-family: "Inter", sans-serif; +} +.custom-input input::placeholder { + color: var(--text-tertiary); +} +.custom-input select { + cursor: pointer; + appearance: none; + background-image: url("data:image/svg+xml;utf8,"); + background-repeat: no-repeat; + background-position: right 1.2rem center; +} + +.custom-input.number { + display: flex; + align-items: center; + justify-content: space-between; + gap: 0.5rem; + padding: 0 0.2rem; +} +.custom-input.number button { + width: 52px; + height: 64px; + background: transparent; + border: none; + color: var(--accent); + cursor: pointer; + font-size: 1.2rem; + transition: all 0.2s; + border-radius: 20px; + font-weight: bold; +} +.custom-input.number button:hover { + background: var(--surface); + color: var(--accent); + transform: scale(1.05); +} +.custom-input.number button:active { + transform: scale(0.95); +} +.custom-input.number input { + width: 80px; + text-align: center; + font-weight: 700; + font-size: 1.3rem; + padding: 0; +} + +.btn-sig, +.btn-prev, +.btn-pay-clip { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 12px; + padding: 1rem 2rem; + background: var(--accent-gradient); + color: white; + border: none; + border-radius: 60px; + font-weight: 700; + font-size: 1rem; + cursor: pointer; + width: 100%; + transition: all var(--transition-base); + box-shadow: var(--shadow-md); + position: relative; + overflow: hidden; +} +.btn-sig::before, +.btn-pay-clip::before { + content: ""; + position: absolute; + top: 50%; + left: 50%; + width: 0; + height: 0; + border-radius: 50%; + background: rgba(255, 255, 255, 0.3); + transform: translate(-50%, -50%); + transition: + width 0.6s, + height 0.6s; +} +.btn-sig:hover::before, +.btn-pay-clip:hover::before { + width: 300px; + height: 300px; +} +.btn-sig:hover, +.btn-pay-clip:hover { + transform: translateY(-3px); + box-shadow: var(--shadow-lg); + filter: brightness(1.05); +} +.btn-sig:active, +.btn-pay-clip:active { + transform: translateY(0); +} +.btn-prev { + background: var(--surface-light); + color: var(--text-primary); + box-shadow: none; + border: 1px solid var(--border); +} +.btn-prev:hover { + background: var(--surface); + transform: translateY(-2px); + border-color: var(--accent); +} + +.table-adicionales { + width: 100%; + border-collapse: collapse; +} +.table-adicionales th, +.table-adicionales td { + padding: 1rem 0.8rem; + border-bottom: 1px solid var(--border); + text-align: center; + vertical-align: middle; +} +.table-adicionales th { + background: var(--surface); + color: var(--accent); + font-weight: 700; + font-size: 0.9rem; +} +.table-adicionales tr:hover { + background: var(--surface-light); + transition: background var(--transition-fast); +} +.table-adicionales img { + width: 60px; + height: 60px; + object-fit: cover; + border-radius: 16px; + box-shadow: var(--shadow-sm); + transition: transform var(--transition-fast); +} +.table-adicionales img:hover { + transform: scale(1.1); +} +@media (max-width: 640px) { + .table-adicionales img { + width: 45px; + height: 45px; + } + .table-adicionales th, + .table-adicionales td { + font-size: 0.8rem; + padding: 0.8rem 0.3rem; + } + .custom-input .icon { + width: 50px; + height: 56px; + } + .custom-input input, + .custom-input select { + height: 56px; + } + .custom-input.number button { + height: 56px; + width: 45px; + } +} + +.details-section { + padding: 2rem; + background: linear-gradient( + 135deg, + rgba(17, 24, 39, 0.95), + rgba(26, 29, 35, 0.95) + ); + backdrop-filter: blur(20px); + border-left: 1px solid var(--border); + border-radius: 32px; + position: sticky; + top: calc(var(--navbar-height) + 2rem); + height: fit-content; + transition: all var(--transition-base); +} +@media (max-width: 992px) { + .details-section { + position: static; + border-left: none; + border-top: 1px solid var(--border); + border-radius: 32px; + } +} +.details-section h3 { + font-family: "Space Grotesk", sans-serif; + font-size: 1.4rem; + font-weight: 700; + margin-bottom: 1rem; + color: var(--text-primary); + position: relative; + display: inline-block; +} +.details-section h3::after { + content: ""; + position: absolute; + bottom: -6px; + left: 0; + width: 40px; + height: 2px; + background: var(--accent); + border-radius: 2px; +} +.hr { + height: 1px; + background: var(--border); + margin: 1rem 0; +} +.row-details { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.8rem 0; + border-bottom: 1px dashed var(--border); + font-weight: 500; + transition: all var(--transition-fast); +} +.row-details:hover { + padding-left: 8px; + border-bottom-color: var(--accent); +} +.row-details label { + color: var(--text-secondary); +} +.row-details p { + color: var(--text-primary); + font-weight: 600; +} +.row-details .quantity { + font-weight: 800; + color: var(--accent); +} + +.tour-sidebar__sorter-inputs p { + display: flex; + align-items: center; + gap: 12px; + margin: 0; +} +input[type="checkbox"] { + width: 20px; + height: 20px; + accent-color: var(--accent); + cursor: pointer; + transition: transform var(--transition-fast); +} +input[type="checkbox"]:hover { + transform: scale(1.1); +} + +.custom-input-phone { + display: flex; + flex: 1; + gap: 8px; + padding-right: 8px; +} +.custom-input-phone select { + width: 120px; + border: none; + background: transparent; + color: var(--text-primary); + cursor: pointer; +} +.custom-input-phone select option { + background: var(--secondary); +} + +#checkout-clip iframe { + width: 100%; + min-height: 350px; + border-radius: 24px; + background: var(--surface-light); + border: 1px solid var(--border); + transition: all var(--transition-base); +} +#checkout-clip iframe:hover { + border-color: var(--accent); + box-shadow: var(--shadow-md); +} + +#reserva_datos_alerta { + background: linear-gradient( + 135deg, + rgba(245, 101, 101, 0.15), + rgba(245, 101, 101, 0.05) + ); + padding: 1rem; + border-radius: 20px; + margin-bottom: 1.5rem; + font-size: 0.85rem; + text-align: center; + border-left: 3px solid var(--error); + color: var(--error); + font-weight: 500; +} + +.offer-badge { + position: absolute; + top: 20px; + right: 20px; + background: var(--gold-gradient); + padding: 6px 12px; + border-radius: 20px; + font-size: 0.8rem; + font-weight: 700; + color: var(--primary); + z-index: 10; + box-shadow: var(--shadow-sm); +} + +.info-tooltip { + position: relative; + display: inline-flex; + align-items: center; + margin-left: 8px; + cursor: help; +} +.info-tooltip i { + color: var(--accent); + font-size: 0.8rem; + opacity: 0.7; + transition: opacity var(--transition-fast); +} +.info-tooltip:hover i { + opacity: 1; +} +.info-tooltip .tooltip-text { + visibility: hidden; + width: 200px; + background: var(--secondary); + color: var(--text-primary); + text-align: center; + padding: 8px 12px; + border-radius: 12px; + position: absolute; + z-index: 1; + bottom: 125%; + left: 50%; + margin-left: -100px; + opacity: 0; + transition: opacity var(--transition-fast); + font-size: 0.8rem; + font-weight: normal; + border: 1px solid var(--border); + pointer-events: none; +} +.info-tooltip:hover .tooltip-text { + visibility: visible; + opacity: 1; +} + +.input-group::-webkit-scrollbar { + width: 6px; +} +.input-group::-webkit-scrollbar-track { + background: var(--surface); + border-radius: 3px; +} +.input-group::-webkit-scrollbar-thumb { + background: var(--accent); + border-radius: 3px; +} + +@keyframes fadeInUp { + from { + opacity: 0; + transform: translateY(20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} +@keyframes slideInRight { + from { + transform: translateX(30px); + opacity: 0; + } + to { + transform: translateX(0); + opacity: 1; + } +} + +@media (max-width: 480px) { + .step-indicator { + padding: 0.5rem 1rem; + } + .step-number { + width: 32px; + height: 32px; + font-size: 0.8rem; + } + .step-label { + font-size: 0.65rem; + } + .step-indicator::before { + top: 24px; + } + .options-section h3 { + font-size: 1.4rem; + } + .btn-sig, + .btn-prev, + .btn-pay-clip { + padding: 0.8rem 1.5rem; + font-size: 0.9rem; + } +} + +.btn-loading { + position: relative; + pointer-events: none; + opacity: 0.7; +} +.btn-loading::after { + content: ""; + position: absolute; + width: 20px; + height: 20px; + border: 2px solid white; + border-top-color: transparent; + border-radius: 50%; + animation: spin 0.6s linear infinite; + margin-left: 10px; +} +@keyframes spin { + to { + transform: rotate(360deg); + } +} + +.custom-input input:focus-visible, +.custom-input select:focus-visible, +button:focus-visible { + outline: 2px solid var(--accent); + outline-offset: 2px; +} + +/* ==================== */ +/* FLIGHT DETAILS PAGE */ +/* ==================== */ +.flight-details { + --primary: #2563eb; + --primary-dark: #1d4ed8; + --primary-light: #3b82f6; + --secondary: #64748b; + --success: #10b981; + --danger: #ef4444; + --warning: #f59e0b; + --dark: #1e293b; + --light: #f8fafc; + --gray: #94a3b8; + --border: #e2e8f0; + --shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1); + --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1); + --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1); + --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1); + --radius: 12px; + --radius-lg: 16px; + --radius-xl: 20px; + padding: 120px 0 60px; + background: linear-gradient(135deg, var(--light) 0%, #f1f5f9 100%); + min-height: 100vh; +} +.flight-details .container { + max-width: 1280px; + margin: 0 auto; + padding: 0 20px; +} +.flight-details .details-grid { + display: grid; + grid-template-columns: 1fr 400px; + gap: 30px; +} +@media (max-width: 992px) { + .flight-details .details-grid { + grid-template-columns: 1fr; + } +} +.flight-details .main-card { + background: white; + border-radius: var(--radius-xl); + box-shadow: var(--shadow-xl); + overflow: hidden; + transition: + transform 0.3s ease, + box-shadow 0.3s ease; +} +.flight-details .main-card:hover { + transform: translateY(-5px); + box-shadow: 0 25px 35px -12px rgba(0, 0, 0, 0.15); +} +.flight-details .card-body { + padding: 35px; +} +@media (max-width: 768px) { + .flight-details .card-body { + padding: 20px; + } +} +.flight-details .flight-header { + display: flex; + justify-content: space-between; + align-items: flex-start; + margin-bottom: 25px; + padding-bottom: 25px; + border-bottom: 2px solid var(--border); + flex-wrap: wrap; + gap: 15px; +} +.flight-details .flight-title h1 { + font-size: 32px; + color: var(--dark); + margin-bottom: 12px; + font-weight: 700; + line-height: 1.2; +} +@media (max-width: 768px) { + .flight-details .flight-title h1 { + font-size: 24px; + } +} +.flight-details .badge { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 6px 14px; + background: linear-gradient(135deg, var(--primary), var(--primary-dark)); + color: white; + border-radius: 25px; + font-size: 12px; + font-weight: 600; + box-shadow: var(--shadow); +} +.flight-details .price-box { + text-align: right; + background: linear-gradient(135deg, #fef3c7, #fde68a); + padding: 12px 20px; + border-radius: var(--radius-lg); +} +@media (max-width: 480px) { + .flight-details .price-box { + text-align: left; + width: 100%; + } +} +.flight-details .price-old { + text-decoration: line-through; + color: var(--secondary); + font-size: 14px; +} +.flight-details .price-current { + font-size: 36px; + font-weight: bold; + color: var(--primary-dark); + line-height: 1; +} +.flight-details .price-label { + font-size: 12px; + color: var(--secondary); + margin-top: 4px; +} +.flight-details .meta-info { + display: flex; + gap: 12px; + flex-wrap: wrap; + margin-bottom: 30px; +} +.flight-details .meta-item { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 8px 16px; + background: white; + border-radius: 30px; + font-size: 13px; + color: var(--secondary); + box-shadow: var(--shadow); + border: 1px solid var(--border); +} +.flight-details .meta-item i { + color: var(--primary); +} +.flight-details .rating { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 8px 16px; + background: white; + border-radius: 30px; + box-shadow: var(--shadow); + border: 1px solid var(--border); +} +.flight-details .stars { + color: #fbbf24; + font-size: 13px; +} +.flight-details .gallery-section { + margin-bottom: 35px; +} +.flight-details .main-swiper { + border-radius: var(--radius-lg); + overflow: hidden; + margin-bottom: 15px; + box-shadow: var(--shadow-lg); +} +.flight-details .main-swiper .swiper-slide { + aspect-ratio: 16/9; + background: var(--gray); +} +.flight-details .main-swiper .swiper-slide img { + width: 100%; + height: 100%; + object-fit: cover; +} +.flight-details .thumbs-swiper { + margin-top: 15px; +} +.flight-details .thumbs-swiper .swiper-slide { + width: 100px; + height: 70px; + border-radius: 10px; + overflow: hidden; + cursor: pointer; + opacity: 0.5; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + border: 2px solid transparent; +} +.flight-details .thumbs-swiper .swiper-slide-thumb-active { + opacity: 1; + border-color: var(--primary); + transform: scale(1.05); +} +.flight-details .thumbs-swiper .swiper-slide:hover { + opacity: 0.8; + transform: scale(1.05); +} +.flight-details .thumbs-swiper .swiper-slide img { + width: 100%; + height: 100%; + object-fit: cover; +} +.flight-details .swiper-button-next, +.flight-details .swiper-button-prev { + color: white; + background: rgba(0, 0, 0, 0.6); + width: 44px; + height: 44px; + border-radius: 50%; + transition: all 0.3s; +} +.flight-details .swiper-button-next:hover, +.flight-details .swiper-button-prev:hover { + background: var(--primary); + transform: scale(1.1); +} +.flight-details .swiper-button-next:after, +.flight-details .swiper-button-prev:after { + font-size: 18px; +} +.flight-details .swiper-pagination-bullet { + background: white; + opacity: 0.8; +} +.flight-details .swiper-pagination-bullet-active { + background: var(--primary); + transform: scale(1.2); +} +.flight-details .section { + margin-bottom: 35px; +} +.flight-details .section-title { + font-size: 24px; + color: var(--dark); + margin-bottom: 18px; + padding-left: 15px; + border-left: 4px solid var(--primary); + font-weight: 700; +} +@media (max-width: 768px) { + .flight-details .section-title { + font-size: 20px; + } +} +.flight-details .section-subtitle { + font-size: 20px; + color: var(--dark); + margin-bottom: 15px; + display: flex; + align-items: center; + gap: 10px; + font-weight: 600; +} +.flight-details .description { + color: var(--secondary); + line-height: 1.8; + font-size: 15px; +} +.flight-details .includes-list { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); + gap: 12px; + list-style: none; + margin: 0; + padding: 0; +} +.flight-details .includes-list li { + display: flex; + align-items: center; + gap: 12px; + padding: 12px 15px; + background: linear-gradient(135deg, var(--light), white); + border-radius: 12px; + color: var(--secondary); + transition: all 0.3s; + border: 1px solid var(--border); +} +.flight-details .includes-list li:hover { + transform: translateX(5px); + border-color: var(--primary-light); + box-shadow: var(--shadow); +} +.flight-details .includes-list li i { + color: var(--success); + font-size: 18px; +} +.flight-details .note { + padding: 18px; + background: linear-gradient(135deg, #dbeafe, #bfdbfe); + border-radius: var(--radius-lg); + color: var(--primary-dark); + text-align: center; + font-size: 14px; + font-weight: 500; + border-left: 4px solid var(--primary); +} +.flight-details .map-container { + border-radius: var(--radius-lg); + overflow: hidden; + box-shadow: var(--shadow-lg); +} +.flight-details .map-container iframe { + width: 100%; + height: 350px; + border: none; +} +.flight-details .sidebar { + position: sticky; + top: 100px; +} +@media (max-width: 992px) { + .flight-details .sidebar { + position: static; + margin-top: 30px; + } +} +.flight-details .sidebar-card { + background: white; + border-radius: var(--radius-xl); + box-shadow: var(--shadow-xl); + padding: 25px; + margin-bottom: 25px; + transition: transform 0.3s ease; +} +.flight-details .sidebar-card:hover { + transform: translateY(-3px); +} +.flight-details .card-title { + font-size: 22px; + color: var(--dark); + margin-bottom: 20px; + text-align: center; + font-weight: 700; + display: flex; + align-items: center; + justify-content: center; + gap: 10px; +} +.flight-details .card-title i { + color: var(--primary); +} +.flight-details .form-group { + margin-bottom: 20px; +} +.flight-details .form-group label { + display: block; + margin-bottom: 8px; + color: var(--dark); + font-weight: 500; + font-size: 14px; +} +.flight-details .form-control { + width: 100%; + padding: 12px 15px; + border: 2px solid var(--border); + border-radius: 10px; + font-size: 14px; + transition: all 0.3s; + font-family: inherit; +} +.flight-details .form-control:focus { + outline: none; + border-color: var(--primary); + box-shadow: 0 0 0 4px rgba(37, 99, 235, 0.1); +} +.flight-details .form-control.error { + border-color: var(--danger); +} +.flight-details .error-message { + color: var(--danger); + font-size: 12px; + margin-top: 5px; + display: none; +} +.flight-details .error-message.show { + display: block; +} +.flight-details .iti { + width: 100%; +} +.flight-details .iti__flag-container { + padding: 2px; +} +.flight-details .iti__selected-flag { + border-radius: 8px 0 0 8px; + background: var(--light); +} +.flight-details .iti__country-list { + border-radius: 10px; + box-shadow: var(--shadow-lg); +} +.flight-details .btn-primary { + width: 100%; + padding: 14px; + background: linear-gradient(135deg, var(--primary), var(--primary-dark)); + color: white; + border: none; + border-radius: 10px; + font-size: 16px; + font-weight: 600; + cursor: pointer; + transition: all 0.3s; + position: relative; + overflow: hidden; +} +.flight-details .btn-primary::before { + content: ""; + position: absolute; + top: 0; + left: -100%; + width: 100%; + height: 100%; + background: linear-gradient( + 90deg, + transparent, + rgba(255, 255, 255, 0.2), + transparent + ); + transition: left 0.5s; +} +.flight-details .btn-primary:hover::before { + left: 100%; +} +.flight-details .btn-primary:hover { + transform: translateY(-2px); + box-shadow: var(--shadow-lg); +} +.flight-details .btn-primary:disabled { + opacity: 0.6; + cursor: not-allowed; + transform: none; +} +.flight-details .btn-secondary { + width: 100%; + padding: 14px; + background: white; + color: var(--primary); + border: 2px solid var(--primary); + border-radius: 10px; + font-size: 16px; + font-weight: 600; + cursor: pointer; + transition: all 0.3s; + text-decoration: none; + display: inline-block; + text-align: center; +} +.flight-details .btn-secondary:hover { + background: var(--primary); + color: white; + transform: translateY(-2px); + box-shadow: var(--shadow-lg); +} +.flight-details .info-list { + list-style: none; + margin: 0; + padding: 0; +} +.flight-details .info-list li { + display: flex; + align-items: center; + gap: 12px; + padding: 12px 0; + color: var(--secondary); + font-size: 14px; + border-bottom: 1px solid var(--border); + transition: all 0.3s; +} +.flight-details .info-list li:last-child { + border-bottom: none; +} +.flight-details .info-list li:hover { + transform: translateX(5px); + color: var(--dark); +} +.flight-details .info-list li i { + color: var(--primary); + width: 24px; + font-size: 16px; +} +.flight-details .alert { + background: white; + padding: 50px; + border-radius: var(--radius-xl); + text-align: center; + box-shadow: var(--shadow-xl); +} +.flight-details .alert i { + font-size: 64px; + color: var(--warning); + margin-bottom: 20px; +} +.flight-details .alert h3 { + margin-bottom: 15px; + color: var(--dark); + font-size: 28px; +} +.flight-details .alert p { + margin-bottom: 25px; + color: var(--secondary); +} +.flight-details .btn-loading { + position: relative; + pointer-events: none; +} +.flight-details .btn-loading span { + opacity: 0; +} +.flight-details .btn-loading::after { + content: ""; + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + width: 22px; + height: 22px; + border: 2px solid white; + border-radius: 50%; + border-top-color: transparent; + animation: spin 0.6s linear infinite; +} +@keyframes spin { + to { + transform: translate(-50%, -50%) rotate(360deg); + } +} +@media (max-width: 768px) { + .flight-details { + padding: 100px 0 40px; + } + .flight-details .includes-list { + grid-template-columns: 1fr; + } + .flight-details .sidebar-card { + padding: 20px; + } +} + +/* ==================== */ +/* TARJETAS DE VUELOS MEJORADAS */ +/* ==================== */ +.vuelos-section { + padding: 120px 24px; + max-width: 1440px; + margin: 0 auto; +} +.section-badge { + display: inline-block; + font-size: 0.85rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 3px; + color: var(--accent); + background: rgba(4, 205, 255, 0.1); + padding: 6px 16px; + border-radius: 50px; + margin-bottom: 16px; +} +.section-title { + font-size: clamp(2rem, 4vw, 3rem); + font-weight: 800; + background: linear-gradient(135deg, var(--text-primary), var(--accent)); + -webkit-background-clip: text; + background-clip: text; + color: transparent; + margin-bottom: 16px; +} +.section-description { + font-size: 1.1rem; + color: var(--text-secondary); + max-width: 600px; + margin: 0 auto; +} +.flights-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(380px, 1fr)); + gap: 32px; +} +.flight-card { + position: relative; + background: var(--secondary); + border-radius: 28px; + overflow: hidden; + transition: all 0.4s cubic-bezier(0.2, 0.9, 0.4, 1.1); + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2); + border: 1px solid var(--border); +} +.flight-card:hover { + transform: translateY(-12px); + box-shadow: 0 25px 50px rgba(0, 0, 0, 0.3); + border-color: var(--accent); +} +.flight-card__badge { + position: absolute; + top: 20px; + left: 20px; + z-index: 10; + display: flex; + align-items: center; + gap: 8px; + padding: 8px 16px; + border-radius: 50px; + color: white; + font-size: 0.8rem; + font-weight: 600; + backdrop-filter: blur(10px); + box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2); + animation: slideInLeft 0.4s ease; +} +.flight-card__badge i { + font-size: 0.75rem; +} +.flight-card__discount { + position: absolute; + top: 20px; + right: 20px; + z-index: 10; + background: linear-gradient(135deg, #ff4757, #ff6b81); + border-radius: 50%; + width: 65px; + height: 65px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + color: white; + font-weight: 800; + box-shadow: 0 4px 15px rgba(255, 71, 87, 0.4); + animation: bounceIn 0.5s ease; +} +.flight-card__discount span { + font-size: 1.2rem; + line-height: 1; +} +.flight-card__discount small { + font-size: 0.7rem; + opacity: 0.9; +} +.flight-card__image { + position: relative; + height: 260px; + overflow: hidden; + cursor: pointer; + background: linear-gradient(90deg, #1a1d23 25%, #2a2e38 50%, #1a1d23 75%); + background-size: 200% 100%; + animation: skeleton-pulse 1.5s infinite; +} +@keyframes skeleton-pulse { + 0% { + background-position: -200% 0; + } + 100% { + background-position: 200% 0; + } +} +.flight-card__image img { + width: 100%; + height: 100%; + object-fit: cover; + transition: transform 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94); + opacity: 0; +} +.flight-card__image img.loaded { + opacity: 1; + animation: none; +} +.flight-card:hover .flight-card__image img { + transform: scale(1.08); +} +.flight-card__overlay { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.6); + backdrop-filter: blur(5px); + display: flex; + align-items: center; + justify-content: center; + opacity: 0; + transition: all 0.3s ease; +} +.flight-card:hover .flight-card__overlay { + opacity: 1; +} +.flight-card__quick-view { + background: linear-gradient(135deg, var(--accent), #0099cc); + border: none; + padding: 12px 24px; + border-radius: 50px; + color: white; + font-size: 0.9rem; + font-weight: 600; + cursor: pointer; + transition: all 0.3s ease; + transform: translateY(20px); + opacity: 0; +} +.flight-card:hover .flight-card__quick-view { + transform: translateY(0); + opacity: 1; +} +.flight-card__quick-view:hover { + transform: translateY(-3px); + box-shadow: 0 5px 20px rgba(4, 205, 255, 0.4); +} +.flight-card__rating { + position: absolute; + bottom: 15px; + right: 15px; + background: rgba(0, 0, 0, 0.8); + backdrop-filter: blur(10px); + padding: 6px 12px; + border-radius: 50px; + display: flex; + align-items: center; + gap: 6px; + font-size: 0.85rem; + z-index: 5; +} +.flight-card__rating i { + color: #ffc107; +} +.flight-card__rating span { + color: white; + font-weight: 600; +} +.flight-card__rating small { + color: rgba(255, 255, 255, 0.7); + font-size: 0.7rem; +} +.flight-card__content { + padding: 24px; +} +.flight-card__category { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 16px; +} +.category-tag { + display: inline-flex; + align-items: center; + gap: 6px; + background: rgba(4, 205, 255, 0.1); + padding: 5px 12px; + border-radius: 50px; + font-size: 0.75rem; + font-weight: 600; + color: var(--accent); +} +.duration-tag { + display: inline-flex; + align-items: center; + gap: 6px; + background: var(--surface-light); + padding: 5px 12px; + border-radius: 50px; + font-size: 0.75rem; + color: var(--text-secondary); +} +.flight-card__title { + font-size: 1.35rem; + font-weight: 700; + margin-bottom: 12px; + line-height: 1.3; +} +.flight-card__title a { + color: var(--text-primary); + text-decoration: none; + transition: color 0.2s ease; +} +.flight-card__title a:hover { + color: var(--accent); +} +.flight-card__description { + color: var(--text-secondary); + font-size: 0.9rem; + line-height: 1.5; + margin-bottom: 16px; +} +.flight-card__schedule { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 16px; + padding: 8px 12px; + background: var(--surface-light); + border-radius: 12px; + font-size: 0.85rem; + color: var(--text-secondary); +} +.flight-card__schedule i { + color: var(--accent); +} +.flight-card__includes { + display: flex; + flex-wrap: wrap; + gap: 8px; + margin-bottom: 20px; +} +.include-item { + display: inline-flex; + align-items: center; + gap: 6px; + background: var(--surface-light); + padding: 5px 10px; + border-radius: 50px; + font-size: 0.7rem; + color: var(--text-secondary); + transition: all 0.2s ease; +} +.include-item i { + color: #4caf50; + font-size: 0.65rem; +} +.include-item:hover { + background: var(--accent); + color: white; + transform: translateY(-2px); +} +.include-item:hover i { + color: white; +} +.flight-card__prices { + background: linear-gradient( + 135deg, + rgba(4, 205, 255, 0.05), + rgba(0, 136, 204, 0.05) + ); + border-radius: 16px; + padding: 16px; + margin-bottom: 20px; + border: 1px solid var(--border); +} +.prices-wrapper { + display: flex; + align-items: baseline; + justify-content: space-between; + flex-wrap: wrap; + gap: 10px; + margin-bottom: 8px; +} +.price-old { + text-decoration: line-through; + color: #ff6b81; + font-size: 0.9rem; +} +.price-current { + display: flex; + align-items: baseline; + gap: 4px; +} +.price-current .currency { + font-size: 1rem; + font-weight: 600; + color: var(--accent); +} +.price-current .amount { + font-size: 2rem; + font-weight: 800; + background: linear-gradient(135deg, var(--accent), #0099cc); + -webkit-background-clip: text; + background-clip: text; + color: transparent; +} +.price-current .period { + font-size: 0.8rem; + color: var(--text-secondary); +} +.price-savings { + display: flex; + align-items: center; + gap: 6px; + font-size: 0.8rem; + color: #4caf50; + padding-top: 8px; + border-top: 1px solid var(--border); +} +.flight-card__meta { + display: flex; + flex-wrap: wrap; + gap: 16px; + margin-bottom: 20px; + padding: 12px 0; + border-top: 1px solid var(--border); + border-bottom: 1px solid var(--border); +} +.meta-item { + display: flex; + align-items: center; + gap: 8px; + font-size: 0.8rem; + color: var(--text-secondary); +} +.meta-item i { + color: var(--accent); + width: 16px; +} +.flight-card__actions { + display: flex; + gap: 12px; +} +.btn-inquire { + flex: 1; + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + padding: 12px 16px; + background: var(--surface-light); + border: 1px solid var(--border); + border-radius: 50px; + color: var(--text-primary); + font-size: 0.85rem; + font-weight: 600; + cursor: pointer; + transition: all 0.3s ease; +} +.btn-inquire:hover { + background: var(--surface); + border-color: var(--accent); + transform: translateY(-2px); +} +.btn-book { + flex: 1.5; + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + padding: 12px 20px; + background: linear-gradient(135deg, var(--accent), #0099cc); + border: none; + border-radius: 50px; + color: white; + font-size: 0.85rem; + font-weight: 600; + text-decoration: none; + transition: all 0.3s ease; + position: relative; + overflow: hidden; +} +.btn-book::before { + content: ""; + position: absolute; + top: 50%; + left: 50%; + width: 0; + height: 0; + border-radius: 50%; + background: rgba(255, 255, 255, 0.3); + transform: translate(-50%, -50%); + transition: + width 0.5s, + height 0.5s; +} +.btn-book:active::before { + width: 200px; + height: 200px; +} +.btn-book:hover { + transform: translateY(-2px); + box-shadow: 0 8px 20px rgba(4, 205, 255, 0.4); + gap: 12px; +} +.btn-book i:last-child { + transition: transform 0.2s ease; +} +.btn-book:hover i:last-child { + transform: translateX(4px); +} +.no-flights { + text-align: center; + padding: 60px 20px; + background: var(--secondary); + border-radius: 28px; +} +.no-flights__icon { + font-size: 4rem; + color: var(--accent); + margin-bottom: 20px; + opacity: 0.5; +} +.no-flights h3 { + font-size: 1.5rem; + margin-bottom: 12px; + color: var(--text-primary); +} +.no-flights p { + color: var(--text-secondary); + margin-bottom: 24px; +} +.btn-contact { + display: inline-flex; + align-items: center; + gap: 10px; + padding: 12px 28px; + background: linear-gradient(135deg, var(--accent), #0099cc); + border: none; + border-radius: 50px; + color: white; + font-weight: 600; + cursor: pointer; + transition: all 0.3s ease; +} +.btn-contact:hover { + transform: translateY(-2px); + box-shadow: 0 8px 20px rgba(4, 205, 255, 0.4); +} +@keyframes slideInLeft { + from { + opacity: 0; + transform: translateX(-30px); + } + to { + opacity: 1; + transform: translateX(0); + } +} +@keyframes bounceIn { + 0% { + opacity: 0; + transform: scale(0); + } + 50% { + transform: scale(1.1); + } + 100% { + opacity: 1; + transform: scale(1); + } +} +@media (max-width: 1024px) { + .flights-grid { + grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); + gap: 24px; + } +} +@media (max-width: 768px) { + .vuelos-section { + padding: 60px 16px; + } + .flights-grid { + grid-template-columns: 1fr; + gap: 24px; + } + .flight-card__image { + height: 220px; + } + .flight-card__content { + padding: 20px; + } + .flight-card__title { + font-size: 1.2rem; + } + .flight-card__actions { + flex-direction: column; + } + .btn-inquire, + .btn-book { + width: 100%; + } +} +@media (max-width: 480px) { + .flight-card__discount { + width: 55px; + height: 55px; + } + .flight-card__discount span { + font-size: 1rem; + } + .price-current .amount { + font-size: 1.6rem; + } +} +.light-mode .flight-card { + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.08); +} +.light-mode .flight-card:hover { + box-shadow: 0 25px 50px rgba(0, 0, 0, 0.15); +} +@media (prefers-reduced-motion: reduce) { + .flight-card, + .flight-card__image img, + .btn-inquire, + .btn-book { + transition: none; + } + .flight-card:hover { + transform: none; + } +} diff --git a/public/assets/css/vuelo-seleccionado.css b/public/assets/css/vuelo-seleccionado.css new file mode 100644 index 0000000..6b5b8fd --- /dev/null +++ b/public/assets/css/vuelo-seleccionado.css @@ -0,0 +1,937 @@ +/* ============================================ + VARIABLES Y RESET + ============================================ */ +.flight-details { + --primary: #2563eb; + --primary-dark: #1d4ed8; + --primary-light: #3b82f6; + --primary-bg: #eff6ff; + --secondary: #64748b; + --success: #10b981; + --danger: #ef4444; + --danger-dark: #dc2626; + --warning: #f59e0b; + --dark: #1e293b; + --light: #f8fafc; + --gray-50: #f9fafb; + --gray-100: #f3f4f6; + --gray-200: #e5e7eb; + --gray-300: #d1d5db; + --border: #e2e8f0; + + /* Sombras */ + --shadow-xs: 0 1px 2px 0 rgba(0, 0, 0, 0.05); + --shadow-sm: 0 1px 3px 0 rgba(0, 0, 0, 0.1); + --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1); + --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1); + --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1); + --shadow-2xl: 0 25px 50px -12px rgba(0, 0, 0, 0.25); + + /* Bordes */ + --radius-sm: 6px; + --radius-md: 8px; + --radius-lg: 12px; + --radius-xl: 16px; + --radius-2xl: 20px; + + /* Transiciones */ + --transition-fast: 150ms cubic-bezier(0.4, 0, 0.2, 1); + --transition-base: 300ms cubic-bezier(0.4, 0, 0.2, 1); + --transition-slow: 500ms cubic-bezier(0.4, 0, 0.2, 1); +} + +/* ============================================ + LAYOUT PRINCIPAL + ============================================ */ +.flight-details { + padding: 120px 0 80px; + background: linear-gradient(135deg, var(--gray-50) 0%, #ffffff 100%); + min-height: 100vh; +} + +.flight-details .container { + max-width: 1280px; + margin: 0 auto; + padding: 0 24px; +} + +@media (max-width: 768px) { + .flight-details { + padding: 100px 0 60px; + } + .flight-details .container { + padding: 0 16px; + } +} + +/* ============================================ + GRID + ============================================ */ +.flight-details .details-grid { + display: grid; + grid-template-columns: 1fr 380px; + gap: 32px; +} + +@media (max-width: 1024px) { + .flight-details .details-grid { + grid-template-columns: 1fr 340px; + gap: 24px; + } +} + +@media (max-width: 992px) { + .flight-details .details-grid { + grid-template-columns: 1fr; + gap: 32px; + } +} + +/* ============================================ + TARJETA PRINCIPAL + ============================================ */ +.flight-details .main-card { + background: white; + border-radius: var(--radius-2xl); + box-shadow: var(--shadow-xl); + overflow: hidden; + transition: var(--transition-base); +} + +.flight-details .main-card:hover { + transform: translateY(-4px); + box-shadow: var(--shadow-2xl); +} + +.flight-details .card-body { + padding: 32px; +} + +@media (max-width: 768px) { + .flight-details .card-body { + padding: 24px; + } +} + +/* ============================================ + HEADER DEL VUELO + ============================================ */ +.flight-details .flight-header { + display: flex; + justify-content: space-between; + align-items: flex-start; + margin-bottom: 24px; + padding-bottom: 24px; + border-bottom: 2px solid var(--gray-200); + flex-wrap: wrap; + gap: 20px; +} + +.flight-details .flight-title { + flex: 1; +} + +.flight-details .flight-title h1 { + font-size: clamp(24px, 5vw, 32px); + color: var(--dark); + margin-bottom: 12px; + font-weight: 700; + line-height: 1.2; +} + +.flight-details .flight-title .subtitle { + font-size: 16px; + color: var(--secondary); + margin-bottom: 12px; + line-height: 1.5; +} + +.flight-details .badge { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 6px 16px; + background: linear-gradient(135deg, var(--primary), var(--primary-dark)); + color: white; + border-radius: 50px; + font-size: 13px; + font-weight: 600; + box-shadow: var(--shadow-sm); +} + +/* ============================================ + CAJA DE PRECIO + ============================================ */ +.flight-details .price-box { + text-align: right; + background: linear-gradient(135deg, #fef3c7, #fde68a); + padding: 16px 24px; + border-radius: var(--radius-xl); + position: relative; + min-width: 160px; +} + +@media (max-width: 480px) { + .flight-details .price-box { + text-align: left; + width: 100%; + } +} + +.flight-details .price-badge { + position: absolute; + top: -10px; + right: 16px; + background: var(--danger); + color: white; + padding: 4px 12px; + border-radius: 50px; + font-size: 12px; + font-weight: 700; + animation: pulse 2s infinite; +} + +@keyframes pulse { + 0%, + 100% { + transform: scale(1); + } + 50% { + transform: scale(1.05); + } +} + +.flight-details .price-old { + text-decoration: line-through; + color: var(--secondary); + font-size: 14px; + margin-bottom: 4px; +} + +.flight-details .price-current { + font-size: 36px; + font-weight: 800; + color: var(--primary-dark); + line-height: 1; +} + +.flight-details .price-label { + font-size: 12px; + color: var(--secondary); + margin-top: 4px; + font-weight: 500; +} + +/* ============================================ + META INFO + ============================================ */ +.flight-details .meta-info { + display: flex; + gap: 12px; + flex-wrap: wrap; + margin-bottom: 32px; +} + +.flight-details .meta-item { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 8px 20px; + background: var(--gray-50); + border-radius: 50px; + font-size: 14px; + color: var(--secondary); + font-weight: 500; + transition: var(--transition-fast); +} + +.flight-details .meta-item:hover { + background: var(--gray-100); + transform: translateY(-2px); +} + +.flight-details .meta-item i { + color: var(--primary); +} + +.flight-details .rating { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 8px 20px; + background: var(--gray-50); + border-radius: 50px; +} + +.flight-details .stars { + color: #fbbf24; + font-size: 13px; + letter-spacing: 2px; +} + +.flight-details .rating-value { + font-weight: 600; + color: var(--dark); +} + +/* ============================================ + GALERÍA + ============================================ */ +.flight-details .gallery-section { + margin-bottom: 40px; +} + +.flight-details .main-swiper { + border-radius: var(--radius-xl); + overflow: hidden; + margin-bottom: 16px; + box-shadow: var(--shadow-lg); +} + +.flight-details .main-swiper .swiper-slide { + aspect-ratio: 16/9; + background: var(--gray-200); + position: relative; +} + +.flight-details .main-swiper .swiper-slide img { + width: 100%; + height: 100%; + object-fit: cover; + transition: transform 0.3s ease; +} + +.flight-details .main-swiper .swiper-slide:hover img { + transform: scale(1.05); +} + +.flight-details .thumbs-swiper { + margin-top: 16px; + padding: 0 4px; +} + +.flight-details .thumbs-swiper .swiper-slide { + width: 100px; + height: 70px; + border-radius: var(--radius-md); + overflow: hidden; + cursor: pointer; + opacity: 0.6; + transition: all var(--transition-fast); + border: 2px solid transparent; +} + +.flight-details .thumbs-swiper .swiper-slide-thumb-active { + opacity: 1; + border-color: var(--primary); + transform: scale(1.05); +} + +.flight-details .thumbs-swiper .swiper-slide:hover { + opacity: 0.9; + transform: scale(1.03); +} + +.flight-details .thumbs-swiper .swiper-slide img { + width: 100%; + height: 100%; + object-fit: cover; +} + +/* Botones de navegación */ +.flight-details .swiper-button-next, +.flight-details .swiper-button-prev { + color: white; + background: rgba(0, 0, 0, 0.6); + width: 44px; + height: 44px; + border-radius: 50%; + transition: all var(--transition-fast); +} + +.flight-details .swiper-button-next:hover, +.flight-details .swiper-button-prev:hover { + background: var(--primary); + transform: scale(1.1); +} + +.flight-details .swiper-button-next:after, +.flight-details .swiper-button-prev:after { + font-size: 18px; +} + +.flight-details .swiper-pagination-bullet { + background: white; + opacity: 0.8; + transition: all var(--transition-fast); +} + +.flight-details .swiper-pagination-bullet-active { + background: var(--primary); + transform: scale(1.2); +} + +/* ============================================ + SECCIONES DE CONTENIDO + ============================================ */ +.flight-details .section { + margin-bottom: 40px; +} + +.flight-details .section-title { + font-size: 24px; + color: var(--dark); + margin-bottom: 20px; + padding-left: 16px; + border-left: 4px solid var(--primary); + font-weight: 700; +} + +@media (max-width: 768px) { + .flight-details .section-title { + font-size: 20px; + } +} + +.flight-details .section-subtitle { + font-size: 20px; + color: var(--dark); + margin-bottom: 16px; + display: flex; + align-items: center; + gap: 12px; + font-weight: 600; +} + +.flight-details .section-subtitle i { + color: var(--success); +} + +.flight-details .description { + color: var(--secondary); + line-height: 1.8; + font-size: 15px; +} + +.flight-details .description p { + margin-bottom: 16px; +} + +/* ============================================ + LISTA DE INCLUYE + ============================================ */ +.flight-details .includes-list { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); + gap: 12px; + list-style: none; + margin: 0; + padding: 0; +} + +.flight-details .includes-list li { + display: flex; + align-items: center; + gap: 12px; + padding: 12px 16px; + background: linear-gradient(135deg, var(--gray-50), white); + border-radius: var(--radius-lg); + color: var(--secondary); + transition: all var(--transition-fast); + border: 1px solid var(--gray-200); +} + +.flight-details .includes-list li:hover { + transform: translateX(6px); + border-color: var(--primary-light); + box-shadow: var(--shadow-md); +} + +.flight-details .includes-list li i { + color: var(--success); + font-size: 18px; +} + +/* ============================================ + NOTA DESTACADA + ============================================ */ +.flight-details .note { + padding: 20px 24px; + background: linear-gradient(135deg, var(--primary-bg), #dbeafe); + border-radius: var(--radius-lg); + color: var(--primary-dark); + display: flex; + align-items: center; + gap: 12px; + font-size: 14px; + font-weight: 500; + border-left: 4px solid var(--primary); +} + +.flight-details .note i { + font-size: 20px; +} + +/* ============================================ + SIDEBAR + ============================================ */ +.flight-details .sidebar { + position: sticky; + top: 100px; + height: fit-content; +} + +@media (max-width: 992px) { + .flight-details .sidebar { + position: static; + margin-top: 0; + } +} + +.flight-details .sidebar-card { + background: white; + border-radius: var(--radius-2xl); + box-shadow: var(--shadow-lg); + padding: 24px; + margin-bottom: 24px; + transition: var(--transition-base); + border: 1px solid var(--gray-200); +} + +.flight-details .sidebar-card:hover { + transform: translateY(-3px); + box-shadow: var(--shadow-xl); +} + +.flight-details .card-title { + font-size: 20px; + color: var(--dark); + margin-bottom: 20px; + text-align: center; + font-weight: 700; + display: flex; + align-items: center; + justify-content: center; + gap: 10px; +} + +.flight-details .card-title i { + color: var(--primary); +} + +/* ============================================ + FORMULARIO + ============================================ */ +.flight-details .form-group { + margin-bottom: 20px; +} + +.flight-details .form-group label { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 8px; + color: var(--dark); + font-weight: 500; + font-size: 14px; +} + +.flight-details .form-group label i { + color: var(--primary); + width: 16px; +} + +.flight-details .form-control { + width: 100%; + padding: 12px 16px; + border: 2px solid var(--gray-200); + border-radius: var(--radius-lg); + font-size: 14px; + transition: all var(--transition-fast); + font-family: inherit; + background: var(--gray-50); +} + +.flight-details .form-control:hover { + border-color: var(--gray-300); +} + +.flight-details .form-control:focus { + outline: none; + border-color: var(--primary); + background: white; + box-shadow: 0 0 0 4px rgba(37, 99, 235, 0.1); +} + +.flight-details .form-control.error { + border-color: var(--danger); + background: #fef2f2; +} + +.flight-details .error-message { + color: var(--danger); + font-size: 12px; + margin-top: 6px; + display: none; + animation: slideDown 0.3s ease; +} + +@keyframes slideDown { + from { + opacity: 0; + transform: translateY(-10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.flight-details .error-message.show { + display: block; +} + +/* Estilos para intl-tel-input */ +.flight-details .iti { + width: 100%; +} + +.flight-details .iti__flag-container { + padding: 2px; +} + +.flight-details .iti__selected-flag { + border-radius: var(--radius-lg) 0 0 var(--radius-lg); + background: var(--gray-100); +} + +.flight-details .iti__country-list { + border-radius: var(--radius-lg); + box-shadow: var(--shadow-lg); +} + +/* ============================================ + BOTONES + ============================================ */ +.flight-details .btn-primary { + width: 100%; + padding: 14px 20px; + background: linear-gradient(135deg, var(--primary), var(--primary-dark)); + color: white; + border: none; + border-radius: var(--radius-lg); + font-size: 16px; + font-weight: 600; + cursor: pointer; + transition: all var(--transition-base); + display: flex; + align-items: center; + justify-content: center; + gap: 10px; + position: relative; + overflow: hidden; +} + +.flight-details .btn-primary::before { + content: ""; + position: absolute; + top: 0; + left: -100%; + width: 100%; + height: 100%; + background: linear-gradient( + 90deg, + transparent, + rgba(255, 255, 255, 0.2), + transparent + ); + transition: left 0.5s; +} + +.flight-details .btn-primary:hover::before { + left: 100%; +} + +.flight-details .btn-primary:hover { + transform: translateY(-2px); + box-shadow: var(--shadow-lg); +} + +.flight-details .btn-primary:active { + transform: translateY(0); +} + +.flight-details .btn-primary:disabled { + opacity: 0.6; + cursor: not-allowed; + transform: none; +} + +.flight-details .btn-secondary { + width: 100%; + padding: 14px 20px; + background: white; + color: var(--primary); + border: 2px solid var(--primary); + border-radius: var(--radius-lg); + font-size: 16px; + font-weight: 600; + cursor: pointer; + transition: all var(--transition-base); + text-decoration: none; + display: flex; + align-items: center; + justify-content: center; + gap: 10px; +} + +.flight-details .btn-secondary:hover { + background: var(--primary); + color: white; + transform: translateY(-2px); + box-shadow: var(--shadow-lg); +} + +.flight-details .info-text { + text-align: center; + font-size: 12px; + color: var(--secondary); + margin-top: 12px; +} + +/* ============================================ + LISTA DE INFORMACIÓN + ============================================ */ +.flight-details .info-list { + list-style: none; + margin: 0; + padding: 0; +} + +.flight-details .info-list li { + display: flex; + align-items: center; + gap: 12px; + padding: 12px 0; + color: var(--secondary); + font-size: 14px; + border-bottom: 1px solid var(--gray-200); + transition: all var(--transition-fast); +} + +.flight-details .info-list li:last-child { + border-bottom: none; +} + +.flight-details .info-list li:hover { + transform: translateX(6px); + color: var(--dark); +} + +.flight-details .info-list li i { + color: var(--primary); + width: 20px; + font-size: 14px; +} + +/* ============================================ + BOTONES DE COMPARTIR + ============================================ */ +.share-buttons { + display: flex; + justify-content: center; + gap: 12px; + flex-wrap: wrap; +} + +.share-btn { + width: 44px; + height: 44px; + border-radius: 50%; + border: none; + cursor: pointer; + transition: all var(--transition-base); + display: flex; + align-items: center; + justify-content: center; + font-size: 18px; +} + +.share-btn.facebook { + background: #1877f2; + color: white; +} + +.share-btn.twitter { + background: #1da1f2; + color: white; +} + +.share-btn.whatsapp { + background: #25d366; + color: white; +} + +.share-btn.copy { + background: var(--secondary); + color: white; +} + +.share-btn:hover { + transform: translateY(-3px) scale(1.05); +} + +.share-btn:active { + transform: translateY(0) scale(1); +} + +/* ============================================ + LOADING SPINNER + ============================================ */ +.flight-details .btn-loading { + position: relative; + pointer-events: none; +} + +.flight-details .btn-loading span { + opacity: 0; +} + +.flight-details .btn-loading::after { + content: ""; + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + width: 22px; + height: 22px; + border: 2px solid white; + border-radius: 50%; + border-top-color: transparent; + animation: spin 0.6s linear infinite; +} + +@keyframes spin { + to { + transform: translate(-50%, -50%) rotate(360deg); + } +} + +/* ============================================ + ALERTAS + ============================================ */ +.flight-details .alert { + background: white; + padding: 60px 40px; + border-radius: var(--radius-2xl); + text-align: center; + box-shadow: var(--shadow-xl); +} + +.flight-details .alert i { + font-size: 64px; + color: var(--warning); + margin-bottom: 20px; +} + +.flight-details .alert h3 { + margin-bottom: 16px; + color: var(--dark); + font-size: 28px; +} + +.flight-details .alert p { + margin-bottom: 24px; + color: var(--secondary); +} + +/* ============================================ + ANIMACIONES + ============================================ */ +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.flight-details .main-card, +.flight-details .sidebar-card { + animation: fadeIn 0.5s ease-out; +} + +.flight-details .sidebar-card:nth-child(2) { + animation-delay: 0.1s; +} + +.flight-details .sidebar-card:nth-child(3) { + animation-delay: 0.2s; +} + +/* ============================================ + RESPONSIVE + ============================================ */ +@media (max-width: 768px) { + .flight-details .includes-list { + grid-template-columns: 1fr; + } + + .flight-details .sidebar-card { + padding: 20px; + } + + .flight-details .swiper-button-next, + .flight-details .swiper-button-prev { + width: 36px; + height: 36px; + } + + .flight-details .thumbs-swiper .swiper-slide { + width: 80px; + height: 56px; + } +} + +@media (max-width: 480px) { + .flight-details .flight-header { + flex-direction: column; + } + + .flight-details .price-box { + width: 100%; + } + + .flight-details .meta-info { + gap: 8px; + } + + .flight-details .meta-item, + .flight-details .rating { + padding: 6px 14px; + font-size: 12px; + } +} + +/* ============================================ + PRINT STYLES + ============================================ */ +@media print { + .flight-details .sidebar, + .flight-details .swiper-button-next, + .flight-details .swiper-button-prev, + .flight-details .share-card { + display: none; + } + + .flight-details { + padding: 20px; + } + + .flight-details .main-card { + box-shadow: none; + } +} diff --git a/public/assets/css/vuelos-en-globo.css b/public/assets/css/vuelos-en-globo.css new file mode 100644 index 0000000..fdeeeaf --- /dev/null +++ b/public/assets/css/vuelos-en-globo.css @@ -0,0 +1,669 @@ +/* ============================================ + ESTILOS MEJORADOS PARA TARJETAS DE VUELOS + ============================================ */ + +.vuelos-section { + padding: 120px 24px; + max-width: 1440px; + margin: 0 auto; +} + +/* Títulos de sección */ +.block-title { + text-align: center; + margin-bottom: 60px; +} + +.section-badge { + display: inline-block; + font-size: 0.85rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 3px; + color: var(--accent); + background: rgba(4, 205, 255, 0.1); + padding: 6px 16px; + border-radius: 50px; + margin-bottom: 16px; +} + +.section-title { + font-size: clamp(2rem, 4vw, 3rem); + font-weight: 800; + background: linear-gradient(135deg, var(--text-primary), var(--accent)); + -webkit-background-clip: text; + background-clip: text; + color: transparent; + margin-bottom: 16px; +} + +.section-description { + font-size: 1.1rem; + color: var(--text-secondary); + max-width: 600px; + margin: 0 auto; +} + +/* Grid de tarjetas */ +.flights-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(380px, 1fr)); + gap: 32px; +} + +/* Tarjeta principal */ +.flight-card { + position: relative; + background: var(--secondary); + border-radius: 28px; + overflow: hidden; + transition: all 0.4s cubic-bezier(0.2, 0.9, 0.4, 1.1); + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2); + border: 1px solid var(--border); +} + +.flight-card:hover { + transform: translateY(-12px); + box-shadow: 0 25px 50px rgba(0, 0, 0, 0.3); + border-color: var(--accent); +} + +/* Badges */ +.flight-card__badge { + position: absolute; + top: 20px; + left: 20px; + z-index: 10; + display: flex; + align-items: center; + gap: 8px; + padding: 8px 16px; + border-radius: 50px; + color: white; + font-size: 0.8rem; + font-weight: 600; + backdrop-filter: blur(10px); + box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2); + animation: slideInLeft 0.4s ease; +} + +.flight-card__badge i { + font-size: 0.75rem; +} + +.flight-card__discount { + position: absolute; + top: 20px; + right: 20px; + z-index: 10; + background: linear-gradient(135deg, #ff4757, #ff6b81); + border-radius: 50%; + width: 65px; + height: 65px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + color: white; + font-weight: 800; + box-shadow: 0 4px 15px rgba(255, 71, 87, 0.4); + animation: bounceIn 0.5s ease; +} + +.flight-card__discount span { + font-size: 1.2rem; + line-height: 1; +} + +.flight-card__discount small { + font-size: 0.7rem; + opacity: 0.9; +} + +/* Imagen */ +.flight-card__image { + position: relative; + height: 260px; + overflow: hidden; + cursor: pointer; + background: linear-gradient(90deg, #1a1d23 25%, #2a2e38 50%, #1a1d23 75%); + background-size: 200% 100%; + animation: skeleton-pulse 1.5s infinite; +} + +@keyframes skeleton-pulse { + 0% { + background-position: -200% 0; + } + + 100% { + background-position: 200% 0; + } +} + +.flight-card__image img { + width: 100%; + height: 100%; + object-fit: cover; + transition: transform 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94); + opacity: 0; +} + +.flight-card__image img.loaded { + opacity: 1; + animation: none; +} + +.flight-card:hover .flight-card__image img { + transform: scale(1.08); +} + +/* Overlay */ +.flight-card__overlay { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.6); + backdrop-filter: blur(5px); + display: flex; + align-items: center; + justify-content: center; + opacity: 0; + transition: all 0.3s ease; +} + +.flight-card:hover .flight-card__overlay { + opacity: 1; +} + +.flight-card__quick-view { + background: linear-gradient(135deg, var(--accent), #0099cc); + border: none; + padding: 12px 24px; + border-radius: 50px; + color: white; + font-size: 0.9rem; + font-weight: 600; + cursor: pointer; + transition: all 0.3s ease; + transform: translateY(20px); + opacity: 0; +} + +.flight-card:hover .flight-card__quick-view { + transform: translateY(0); + opacity: 1; +} + +.flight-card__quick-view:hover { + transform: translateY(-3px); + box-shadow: 0 5px 20px rgba(4, 205, 255, 0.4); +} + +/* Rating */ +.flight-card__rating { + position: absolute; + bottom: 15px; + right: 15px; + background: rgba(0, 0, 0, 0.8); + backdrop-filter: blur(10px); + padding: 6px 12px; + border-radius: 50px; + display: flex; + align-items: center; + gap: 6px; + font-size: 0.85rem; + z-index: 5; +} + +.flight-card__rating i { + color: #ffc107; +} + +.flight-card__rating span { + color: white; + font-weight: 600; +} + +.flight-card__rating small { + color: rgba(255, 255, 255, 0.7); + font-size: 0.7rem; +} + +/* Contenido */ +.flight-card__content { + padding: 24px; +} + +/* Categoría */ +.flight-card__category { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 16px; +} + +.category-tag { + display: inline-flex; + align-items: center; + gap: 6px; + background: rgba(4, 205, 255, 0.1); + padding: 5px 12px; + border-radius: 50px; + font-size: 0.75rem; + font-weight: 600; + color: var(--accent); +} + +.duration-tag { + display: inline-flex; + align-items: center; + gap: 6px; + background: var(--surface-light); + padding: 5px 12px; + border-radius: 50px; + font-size: 0.75rem; + color: var(--text-secondary); +} + +/* Título */ +.flight-card__title { + font-size: 1.35rem; + font-weight: 700; + margin-bottom: 12px; + line-height: 1.3; +} + +.flight-card__title a { + color: var(--text-primary); + text-decoration: none; + transition: color 0.2s ease; +} + +.flight-card__title a:hover { + color: var(--accent); +} + +/* Descripción */ +.flight-card__description { + color: var(--text-secondary); + font-size: 0.9rem; + line-height: 1.5; + margin-bottom: 16px; +} + +/* Horario */ +.flight-card__schedule { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 16px; + padding: 8px 12px; + background: var(--surface-light); + border-radius: 12px; + font-size: 0.85rem; + color: var(--text-secondary); +} + +.flight-card__schedule i { + color: var(--accent); +} + +/* Incluye */ +.flight-card__includes { + display: flex; + flex-wrap: wrap; + gap: 8px; + margin-bottom: 20px; +} + +.include-item { + display: inline-flex; + align-items: center; + gap: 6px; + background: var(--surface-light); + padding: 5px 10px; + border-radius: 50px; + font-size: 0.7rem; + color: var(--text-secondary); + transition: all 0.2s ease; +} + +.include-item i { + color: #4caf50; + font-size: 0.65rem; +} + +.include-item:hover { + background: var(--accent); + color: white; + transform: translateY(-2px); +} + +.include-item:hover i { + color: white; +} + +/* Precios */ +.flight-card__prices { + background: linear-gradient( + 135deg, + rgba(4, 205, 255, 0.05), + rgba(0, 136, 204, 0.05) + ); + border-radius: 16px; + padding: 16px; + margin-bottom: 20px; + border: 1px solid var(--border); +} + +.prices-wrapper { + display: flex; + align-items: baseline; + justify-content: space-between; + flex-wrap: wrap; + gap: 10px; + margin-bottom: 8px; +} + +.price-old { + text-decoration: line-through; + color: #ff6b81; + font-size: 0.9rem; +} + +.price-current { + display: flex; + align-items: baseline; + gap: 4px; +} + +.price-current .currency { + font-size: 1rem; + font-weight: 600; + color: var(--accent); +} + +.price-current .amount { + font-size: 2rem; + font-weight: 800; + background: linear-gradient(135deg, var(--accent), #0099cc); + -webkit-background-clip: text; + background-clip: text; + color: transparent; +} + +.price-current .period { + font-size: 0.8rem; + color: var(--text-secondary); +} + +.price-savings { + display: flex; + align-items: center; + gap: 6px; + font-size: 0.8rem; + color: #4caf50; + padding-top: 8px; + border-top: 1px solid var(--border); +} + +/* Meta información */ +.flight-card__meta { + display: flex; + flex-wrap: wrap; + gap: 16px; + margin-bottom: 20px; + padding: 12px 0; + border-top: 1px solid var(--border); + border-bottom: 1px solid var(--border); +} + +.meta-item { + display: flex; + align-items: center; + gap: 8px; + font-size: 0.8rem; + color: var(--text-secondary); +} + +.meta-item i { + color: var(--accent); + width: 16px; +} + +/* Botones de acción */ +.flight-card__actions { + display: flex; + gap: 12px; +} + +.btn-inquire { + flex: 1; + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + padding: 12px 16px; + background: var(--surface-light); + border: 1px solid var(--border); + border-radius: 50px; + color: var(--text-primary); + font-size: 0.85rem; + font-weight: 600; + cursor: pointer; + transition: all 0.3s ease; +} + +.btn-inquire:hover { + background: var(--surface); + border-color: var(--accent); + transform: translateY(-2px); +} + +.btn-book { + flex: 1.5; + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + padding: 12px 20px; + background: linear-gradient(135deg, var(--accent), #0099cc); + border: none; + border-radius: 50px; + color: white; + font-size: 0.85rem; + font-weight: 600; + text-decoration: none; + transition: all 0.3s ease; + position: relative; + overflow: hidden; +} + +.btn-book::before { + content: ""; + position: absolute; + top: 50%; + left: 50%; + width: 0; + height: 0; + border-radius: 50%; + background: rgba(255, 255, 255, 0.3); + transform: translate(-50%, -50%); + transition: + width 0.5s, + height 0.5s; +} + +.btn-book:active::before { + width: 200px; + height: 200px; +} + +.btn-book:hover { + transform: translateY(-2px); + box-shadow: 0 8px 20px rgba(4, 205, 255, 0.4); + gap: 12px; +} + +.btn-book i:last-child { + transition: transform 0.2s ease; +} + +.btn-book:hover i:last-child { + transform: translateX(4px); +} + +/* No flights */ +.no-flights { + text-align: center; + padding: 60px 20px; + background: var(--secondary); + border-radius: 28px; +} + +.no-flights__icon { + font-size: 4rem; + color: var(--accent); + margin-bottom: 20px; + opacity: 0.5; +} + +.no-flights h3 { + font-size: 1.5rem; + margin-bottom: 12px; + color: var(--text-primary); +} + +.no-flights p { + color: var(--text-secondary); + margin-bottom: 24px; +} + +.btn-contact { + display: inline-flex; + align-items: center; + gap: 10px; + padding: 12px 28px; + background: linear-gradient(135deg, var(--accent), #0099cc); + border: none; + border-radius: 50px; + color: white; + font-weight: 600; + cursor: pointer; + transition: all 0.3s ease; +} + +.btn-contact:hover { + transform: translateY(-2px); + box-shadow: 0 8px 20px rgba(4, 205, 255, 0.4); +} + +/* Animaciones */ +@keyframes slideInLeft { + from { + opacity: 0; + transform: translateX(-30px); + } + + to { + opacity: 1; + transform: translateX(0); + } +} + +@keyframes bounceIn { + 0% { + opacity: 0; + transform: scale(0); + } + + 50% { + transform: scale(1.1); + } + + 100% { + opacity: 1; + transform: scale(1); + } +} + +/* Responsive */ +@media (max-width: 1024px) { + .flights-grid { + grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); + gap: 24px; + } +} + +@media (max-width: 768px) { + .vuelos-section { + padding: 60px 16px; + } + + .flights-grid { + grid-template-columns: 1fr; + gap: 24px; + } + + .flight-card__image { + height: 220px; + } + + .flight-card__content { + padding: 20px; + } + + .flight-card__title { + font-size: 1.2rem; + } + + .flight-card__actions { + flex-direction: column; + } + + .btn-inquire, + .btn-book { + width: 100%; + } +} + +@media (max-width: 480px) { + .flight-card__discount { + width: 55px; + height: 55px; + } + + .flight-card__discount span { + font-size: 1rem; + } + + .price-current .amount { + font-size: 1.6rem; + } +} + +/* Modo oscuro/claro */ +.light-mode .flight-card { + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.08); +} + +.light-mode .flight-card:hover { + box-shadow: 0 25px 50px rgba(0, 0, 0, 0.15); +} + +/* Reducción de movimiento */ +@media (prefers-reduced-motion: reduce) { + .flight-card, + .flight-card__image img, + .btn-inquire, + .btn-book { + transition: none; + } + + .flight-card:hover { + transform: none; + } +} diff --git a/public/assets/img/Logo_globopuerto.png b/public/assets/img/Logo_globopuerto.png new file mode 100644 index 0000000..017fe04 Binary files /dev/null and b/public/assets/img/Logo_globopuerto.png differ diff --git a/public/assets/img/banner-informativo/banner-01.jpeg b/public/assets/img/banner-informativo/banner-01.jpeg new file mode 100644 index 0000000..d4afa28 Binary files /dev/null and b/public/assets/img/banner-informativo/banner-01.jpeg differ diff --git a/public/assets/img/banner-informativo/banner-02.jpeg b/public/assets/img/banner-informativo/banner-02.jpeg new file mode 100644 index 0000000..602f335 Binary files /dev/null and b/public/assets/img/banner-informativo/banner-02.jpeg differ diff --git a/public/assets/img/banner-informativo/banner-03.jpeg b/public/assets/img/banner-informativo/banner-03.jpeg new file mode 100644 index 0000000..e321f9d Binary files /dev/null and b/public/assets/img/banner-informativo/banner-03.jpeg differ diff --git a/public/assets/img/banner/globopuertoteotihuacan-video.jpg b/public/assets/img/banner/globopuertoteotihuacan-video.jpg new file mode 100644 index 0000000..4af15c0 Binary files /dev/null and b/public/assets/img/banner/globopuertoteotihuacan-video.jpg differ diff --git a/public/assets/img/ciclismo/ciclismo.png b/public/assets/img/ciclismo/ciclismo.png new file mode 100644 index 0000000..63c9e04 Binary files /dev/null and b/public/assets/img/ciclismo/ciclismo.png differ diff --git a/public/assets/img/cuatrimoto/1778268306.png b/public/assets/img/cuatrimoto/1778268306.png new file mode 100644 index 0000000..1d27223 Binary files /dev/null and b/public/assets/img/cuatrimoto/1778268306.png differ diff --git a/public/assets/img/cuatrimoto/Portada.png b/public/assets/img/cuatrimoto/Portada.png new file mode 100644 index 0000000..f504b7e Binary files /dev/null and b/public/assets/img/cuatrimoto/Portada.png differ diff --git a/public/assets/img/cuatrimoto/cuatri-piramide.png b/public/assets/img/cuatrimoto/cuatri-piramide.png new file mode 100644 index 0000000..926e031 Binary files /dev/null and b/public/assets/img/cuatrimoto/cuatri-piramide.png differ diff --git a/public/assets/img/hoteles/Hospedaje-Villa-las-Campanas.jpg b/public/assets/img/hoteles/Hospedaje-Villa-las-Campanas.jpg new file mode 100644 index 0000000..5e84d27 Binary files /dev/null and b/public/assets/img/hoteles/Hospedaje-Villa-las-Campanas.jpg differ diff --git a/public/assets/img/hoteles/Hospedaje-en-Globopuerto-PRINCIPAL.jpg b/public/assets/img/hoteles/Hospedaje-en-Globopuerto-PRINCIPAL.jpg new file mode 100644 index 0000000..8c02769 Binary files /dev/null and b/public/assets/img/hoteles/Hospedaje-en-Globopuerto-PRINCIPAL.jpg differ diff --git a/public/assets/img/hoteles/Hotel-Quinto-Sol.jpg b/public/assets/img/hoteles/Hotel-Quinto-Sol.jpg new file mode 100644 index 0000000..29f0a41 Binary files /dev/null and b/public/assets/img/hoteles/Hotel-Quinto-Sol.jpg differ diff --git a/public/assets/img/hoteles/Hotel-Villas-Teotihuacan.jpg b/public/assets/img/hoteles/Hotel-Villas-Teotihuacan.jpg new file mode 100644 index 0000000..ea183ca Binary files /dev/null and b/public/assets/img/hoteles/Hotel-Villas-Teotihuacan.jpg differ diff --git a/public/assets/img/hoteles/Posada-Jatzriri-Globopuerto.jpg b/public/assets/img/hoteles/Posada-Jatzriri-Globopuerto.jpg new file mode 100644 index 0000000..6997d2c Binary files /dev/null and b/public/assets/img/hoteles/Posada-Jatzriri-Globopuerto.jpg differ diff --git a/public/assets/img/instalaciones/Galeria.png b/public/assets/img/instalaciones/Galeria.png new file mode 100644 index 0000000..750112b Binary files /dev/null and b/public/assets/img/instalaciones/Galeria.png differ diff --git a/public/assets/img/instalaciones/Galeria1.png b/public/assets/img/instalaciones/Galeria1.png new file mode 100644 index 0000000..577a1e5 Binary files /dev/null and b/public/assets/img/instalaciones/Galeria1.png differ diff --git a/public/assets/img/instalaciones/Galeria2.png b/public/assets/img/instalaciones/Galeria2.png new file mode 100644 index 0000000..dfbe179 Binary files /dev/null and b/public/assets/img/instalaciones/Galeria2.png differ diff --git a/public/assets/img/instalaciones/Galeriaa.png b/public/assets/img/instalaciones/Galeriaa.png new file mode 100644 index 0000000..937dac4 Binary files /dev/null and b/public/assets/img/instalaciones/Galeriaa.png differ diff --git a/public/assets/img/instalaciones/Instalaciones.png b/public/assets/img/instalaciones/Instalaciones.png new file mode 100644 index 0000000..70cbe8b Binary files /dev/null and b/public/assets/img/instalaciones/Instalaciones.png differ diff --git a/public/assets/img/instalaciones/Instalaciones1.png b/public/assets/img/instalaciones/Instalaciones1.png new file mode 100644 index 0000000..9602d07 Binary files /dev/null and b/public/assets/img/instalaciones/Instalaciones1.png differ diff --git a/public/assets/img/instalaciones/Instalaciones2.png b/public/assets/img/instalaciones/Instalaciones2.png new file mode 100644 index 0000000..5547fb7 Binary files /dev/null and b/public/assets/img/instalaciones/Instalaciones2.png differ diff --git a/public/assets/img/instalaciones/Instalaciones3.png b/public/assets/img/instalaciones/Instalaciones3.png new file mode 100644 index 0000000..7a1322b Binary files /dev/null and b/public/assets/img/instalaciones/Instalaciones3.png differ diff --git a/public/assets/img/instalaciones/Seguridad3.png b/public/assets/img/instalaciones/Seguridad3.png new file mode 100644 index 0000000..bfed1a6 Binary files /dev/null and b/public/assets/img/instalaciones/Seguridad3.png differ diff --git a/public/assets/img/logo.png b/public/assets/img/logo.png new file mode 100644 index 0000000..e84473b Binary files /dev/null and b/public/assets/img/logo.png differ diff --git a/public/assets/img/restaurante-el-cielo/IMG_7647.webp b/public/assets/img/restaurante-el-cielo/IMG_7647.webp new file mode 100644 index 0000000..0aa2b6b Binary files /dev/null and b/public/assets/img/restaurante-el-cielo/IMG_7647.webp differ diff --git a/public/assets/img/restaurante-el-cielo/Nueva-perspectiva.png b/public/assets/img/restaurante-el-cielo/Nueva-perspectiva.png new file mode 100644 index 0000000..721f767 Binary files /dev/null and b/public/assets/img/restaurante-el-cielo/Nueva-perspectiva.png differ diff --git a/public/assets/img/restaurante-el-cielo/Platillos-únicos.png b/public/assets/img/restaurante-el-cielo/Platillos-únicos.png new file mode 100644 index 0000000..3c34d74 Binary files /dev/null and b/public/assets/img/restaurante-el-cielo/Platillos-únicos.png differ diff --git a/public/assets/img/restaurante-el-cielo/Ubicación-exepcional.png b/public/assets/img/restaurante-el-cielo/Ubicación-exepcional.png new file mode 100644 index 0000000..14f8a44 Binary files /dev/null and b/public/assets/img/restaurante-el-cielo/Ubicación-exepcional.png differ diff --git a/public/assets/img/restaurante-el-cielo/Vista-espectacular.png b/public/assets/img/restaurante-el-cielo/Vista-espectacular.png new file mode 100644 index 0000000..cf97363 Binary files /dev/null and b/public/assets/img/restaurante-el-cielo/Vista-espectacular.png differ diff --git a/public/assets/img/restaurante-el-cielo/nueva-perspectiva.webp b/public/assets/img/restaurante-el-cielo/nueva-perspectiva.webp new file mode 100644 index 0000000..6b114c0 Binary files /dev/null and b/public/assets/img/restaurante-el-cielo/nueva-perspectiva.webp differ diff --git a/public/assets/img/restaurante-el-cielo/restaurante-cielo.jpg b/public/assets/img/restaurante-el-cielo/restaurante-cielo.jpg new file mode 100644 index 0000000..2478463 Binary files /dev/null and b/public/assets/img/restaurante-el-cielo/restaurante-cielo.jpg differ diff --git a/public/assets/img/restaurante-el-cielo/restaurante-el-cielo.webp b/public/assets/img/restaurante-el-cielo/restaurante-el-cielo.webp new file mode 100644 index 0000000..72aa956 Binary files /dev/null and b/public/assets/img/restaurante-el-cielo/restaurante-el-cielo.webp differ diff --git a/public/assets/img/restaurante-el-cielo/vista-panoramica.png b/public/assets/img/restaurante-el-cielo/vista-panoramica.png new file mode 100644 index 0000000..9509b2f Binary files /dev/null and b/public/assets/img/restaurante-el-cielo/vista-panoramica.png differ diff --git a/public/assets/img/seguridad/Seguridad.png b/public/assets/img/seguridad/Seguridad.png new file mode 100644 index 0000000..7cb4eb5 Binary files /dev/null and b/public/assets/img/seguridad/Seguridad.png differ diff --git a/public/assets/img/seguridad/Seguridad1.png b/public/assets/img/seguridad/Seguridad1.png new file mode 100644 index 0000000..702ea96 Binary files /dev/null and b/public/assets/img/seguridad/Seguridad1.png differ diff --git a/public/assets/img/seguridad/Seguridad2.png b/public/assets/img/seguridad/Seguridad2.png new file mode 100644 index 0000000..c5310ae Binary files /dev/null and b/public/assets/img/seguridad/Seguridad2.png differ diff --git a/public/assets/img/tour-bici/tour-bici.png b/public/assets/img/tour-bici/tour-bici.png new file mode 100644 index 0000000..0e30309 Binary files /dev/null and b/public/assets/img/tour-bici/tour-bici.png differ diff --git a/public/assets/img/transporte/Chevrolet.png b/public/assets/img/transporte/Chevrolet.png new file mode 100644 index 0000000..d7d82db Binary files /dev/null and b/public/assets/img/transporte/Chevrolet.png differ diff --git a/public/assets/img/transporte/Toyota.png b/public/assets/img/transporte/Toyota.png new file mode 100644 index 0000000..a5e3299 Binary files /dev/null and b/public/assets/img/transporte/Toyota.png differ diff --git a/public/assets/img/vueloenglobo/logo-vueloenglobo.jpeg b/public/assets/img/vueloenglobo/logo-vueloenglobo.jpeg new file mode 100644 index 0000000..10fbb80 Binary files /dev/null and b/public/assets/img/vueloenglobo/logo-vueloenglobo.jpeg differ diff --git a/public/assets/img/vueloenglobo/logo-vueloenglobo.png b/public/assets/img/vueloenglobo/logo-vueloenglobo.png new file mode 100644 index 0000000..00835e6 Binary files /dev/null and b/public/assets/img/vueloenglobo/logo-vueloenglobo.png differ diff --git a/public/assets/img/vuelos/entrega-de-anillo-vip/0.jpg b/public/assets/img/vuelos/entrega-de-anillo-vip/0.jpg new file mode 100644 index 0000000..43d9518 Binary files /dev/null and b/public/assets/img/vuelos/entrega-de-anillo-vip/0.jpg differ diff --git a/public/assets/img/vuelos/entrega-de-anillo-vip/0.png b/public/assets/img/vuelos/entrega-de-anillo-vip/0.png new file mode 100644 index 0000000..bb0822b Binary files /dev/null and b/public/assets/img/vuelos/entrega-de-anillo-vip/0.png differ diff --git a/public/assets/img/vuelos/entrega-de-anillo-vip/1.jpg b/public/assets/img/vuelos/entrega-de-anillo-vip/1.jpg new file mode 100644 index 0000000..bf77ae4 Binary files /dev/null and b/public/assets/img/vuelos/entrega-de-anillo-vip/1.jpg differ diff --git a/public/assets/img/vuelos/entrega-de-anillo-vip/1.png b/public/assets/img/vuelos/entrega-de-anillo-vip/1.png new file mode 100644 index 0000000..8ec97ce Binary files /dev/null and b/public/assets/img/vuelos/entrega-de-anillo-vip/1.png differ diff --git a/public/assets/img/vuelos/entrega-de-anillo-vip/2.jpg b/public/assets/img/vuelos/entrega-de-anillo-vip/2.jpg new file mode 100644 index 0000000..d669f8e Binary files /dev/null and b/public/assets/img/vuelos/entrega-de-anillo-vip/2.jpg differ diff --git a/public/assets/img/vuelos/entrega-de-anillo-vip/2.png b/public/assets/img/vuelos/entrega-de-anillo-vip/2.png new file mode 100644 index 0000000..2c139e7 Binary files /dev/null and b/public/assets/img/vuelos/entrega-de-anillo-vip/2.png differ diff --git a/public/assets/img/vuelos/entrega-de-anillo-vip/3.jpg b/public/assets/img/vuelos/entrega-de-anillo-vip/3.jpg new file mode 100644 index 0000000..1a28c64 Binary files /dev/null and b/public/assets/img/vuelos/entrega-de-anillo-vip/3.jpg differ diff --git a/public/assets/img/vuelos/entrega-de-anillo-vip/3.png b/public/assets/img/vuelos/entrega-de-anillo-vip/3.png new file mode 100644 index 0000000..da487e3 Binary files /dev/null and b/public/assets/img/vuelos/entrega-de-anillo-vip/3.png differ diff --git a/public/assets/img/vuelos/entrega-de-anillo-vip/4.jpg b/public/assets/img/vuelos/entrega-de-anillo-vip/4.jpg new file mode 100644 index 0000000..9966253 Binary files /dev/null and b/public/assets/img/vuelos/entrega-de-anillo-vip/4.jpg differ diff --git a/public/assets/img/vuelos/entrega-de-anillo-vip/4.png b/public/assets/img/vuelos/entrega-de-anillo-vip/4.png new file mode 100644 index 0000000..9fa2ece Binary files /dev/null and b/public/assets/img/vuelos/entrega-de-anillo-vip/4.png differ diff --git a/public/assets/img/vuelos/entrega-de-anillo-vip/Vuelo entrega de anillo.jpg b/public/assets/img/vuelos/entrega-de-anillo-vip/Vuelo entrega de anillo.jpg new file mode 100644 index 0000000..f9441d9 Binary files /dev/null and b/public/assets/img/vuelos/entrega-de-anillo-vip/Vuelo entrega de anillo.jpg differ diff --git a/public/assets/img/vuelos/entrega-de-anillo-vip/entrega-de-anillo-vip-2-invertido.png b/public/assets/img/vuelos/entrega-de-anillo-vip/entrega-de-anillo-vip-2-invertido.png new file mode 100644 index 0000000..13f6b6f Binary files /dev/null and b/public/assets/img/vuelos/entrega-de-anillo-vip/entrega-de-anillo-vip-2-invertido.png differ diff --git a/public/assets/img/vuelos/entrega-de-anillo-vip/entrega-de-anillo-vip-2.png b/public/assets/img/vuelos/entrega-de-anillo-vip/entrega-de-anillo-vip-2.png new file mode 100644 index 0000000..94e4ef5 Binary files /dev/null and b/public/assets/img/vuelos/entrega-de-anillo-vip/entrega-de-anillo-vip-2.png differ diff --git a/public/assets/img/vuelos/entrega-de-anillo-vip/entrega-de-anillo-vip-2.webp b/public/assets/img/vuelos/entrega-de-anillo-vip/entrega-de-anillo-vip-2.webp new file mode 100644 index 0000000..62ed985 Binary files /dev/null and b/public/assets/img/vuelos/entrega-de-anillo-vip/entrega-de-anillo-vip-2.webp differ diff --git a/public/assets/img/vuelos/entrega-de-anillo-vip/entrega-de-anillo-vip.jpg b/public/assets/img/vuelos/entrega-de-anillo-vip/entrega-de-anillo-vip.jpg new file mode 100644 index 0000000..0b5a037 Binary files /dev/null and b/public/assets/img/vuelos/entrega-de-anillo-vip/entrega-de-anillo-vip.jpg differ diff --git a/public/assets/img/vuelos/entrega-de-anillo-vip/entrega-de-anillo-vip.png b/public/assets/img/vuelos/entrega-de-anillo-vip/entrega-de-anillo-vip.png new file mode 100644 index 0000000..43c3811 Binary files /dev/null and b/public/assets/img/vuelos/entrega-de-anillo-vip/entrega-de-anillo-vip.png differ diff --git a/public/assets/img/vuelos/entrega-de-anillo-vip/entrega-de-anillo-vip.webp b/public/assets/img/vuelos/entrega-de-anillo-vip/entrega-de-anillo-vip.webp new file mode 100644 index 0000000..3afa4a8 Binary files /dev/null and b/public/assets/img/vuelos/entrega-de-anillo-vip/entrega-de-anillo-vip.webp differ diff --git a/public/assets/img/vuelos/entrega-de-anillo-vip/entrega-de-anillo-vip_temp.png b/public/assets/img/vuelos/entrega-de-anillo-vip/entrega-de-anillo-vip_temp.png new file mode 100644 index 0000000..b862fe5 Binary files /dev/null and b/public/assets/img/vuelos/entrega-de-anillo-vip/entrega-de-anillo-vip_temp.png differ diff --git a/public/assets/img/vuelos/vuelo-compartido/0.jpg b/public/assets/img/vuelos/vuelo-compartido/0.jpg new file mode 100644 index 0000000..a38af47 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-compartido/0.jpg differ diff --git a/public/assets/img/vuelos/vuelo-compartido/1.jpg b/public/assets/img/vuelos/vuelo-compartido/1.jpg new file mode 100644 index 0000000..4a67903 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-compartido/1.jpg differ diff --git a/public/assets/img/vuelos/vuelo-compartido/2.jpg b/public/assets/img/vuelos/vuelo-compartido/2.jpg new file mode 100644 index 0000000..c8ae5f5 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-compartido/2.jpg differ diff --git a/public/assets/img/vuelos/vuelo-compartido/3.jpg b/public/assets/img/vuelos/vuelo-compartido/3.jpg new file mode 100644 index 0000000..7aed982 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-compartido/3.jpg differ diff --git a/public/assets/img/vuelos/vuelo-compartido/4.jpg b/public/assets/img/vuelos/vuelo-compartido/4.jpg new file mode 100644 index 0000000..ce88075 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-compartido/4.jpg differ diff --git a/public/assets/img/vuelos/vuelo-compartido/5.jpg b/public/assets/img/vuelos/vuelo-compartido/5.jpg new file mode 100644 index 0000000..b3eb987 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-compartido/5.jpg differ diff --git a/public/assets/img/vuelos/vuelo-compartido/6.jpg b/public/assets/img/vuelos/vuelo-compartido/6.jpg new file mode 100644 index 0000000..78ec2a8 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-compartido/6.jpg differ diff --git a/public/assets/img/vuelos/vuelo-compartido/vuelo-compartido-2.jpg b/public/assets/img/vuelos/vuelo-compartido/vuelo-compartido-2.jpg new file mode 100644 index 0000000..5822bcb Binary files /dev/null and b/public/assets/img/vuelos/vuelo-compartido/vuelo-compartido-2.jpg differ diff --git a/public/assets/img/vuelos/vuelo-compartido/vuelo-compartido.jpg b/public/assets/img/vuelos/vuelo-compartido/vuelo-compartido.jpg new file mode 100644 index 0000000..0b37987 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-compartido/vuelo-compartido.jpg differ diff --git a/public/assets/img/vuelos/vuelo-compartido_/0.jpg b/public/assets/img/vuelos/vuelo-compartido_/0.jpg new file mode 100644 index 0000000..9f81f49 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-compartido_/0.jpg differ diff --git a/public/assets/img/vuelos/vuelo-compartido_/0.png b/public/assets/img/vuelos/vuelo-compartido_/0.png new file mode 100644 index 0000000..c69c287 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-compartido_/0.png differ diff --git a/public/assets/img/vuelos/vuelo-compartido_/1.jpg b/public/assets/img/vuelos/vuelo-compartido_/1.jpg new file mode 100644 index 0000000..70ada71 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-compartido_/1.jpg differ diff --git a/public/assets/img/vuelos/vuelo-compartido_/1.png b/public/assets/img/vuelos/vuelo-compartido_/1.png new file mode 100644 index 0000000..7c6451f Binary files /dev/null and b/public/assets/img/vuelos/vuelo-compartido_/1.png differ diff --git a/public/assets/img/vuelos/vuelo-compartido_/2.jpg b/public/assets/img/vuelos/vuelo-compartido_/2.jpg new file mode 100644 index 0000000..177985e Binary files /dev/null and b/public/assets/img/vuelos/vuelo-compartido_/2.jpg differ diff --git a/public/assets/img/vuelos/vuelo-compartido_/2.png b/public/assets/img/vuelos/vuelo-compartido_/2.png new file mode 100644 index 0000000..4a68185 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-compartido_/2.png differ diff --git a/public/assets/img/vuelos/vuelo-compartido_/3.jpg b/public/assets/img/vuelos/vuelo-compartido_/3.jpg new file mode 100644 index 0000000..c4ee653 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-compartido_/3.jpg differ diff --git a/public/assets/img/vuelos/vuelo-compartido_/3.png b/public/assets/img/vuelos/vuelo-compartido_/3.png new file mode 100644 index 0000000..ffa582c Binary files /dev/null and b/public/assets/img/vuelos/vuelo-compartido_/3.png differ diff --git a/public/assets/img/vuelos/vuelo-compartido_/4.jpg b/public/assets/img/vuelos/vuelo-compartido_/4.jpg new file mode 100644 index 0000000..ab8b10f Binary files /dev/null and b/public/assets/img/vuelos/vuelo-compartido_/4.jpg differ diff --git a/public/assets/img/vuelos/vuelo-compartido_/4.png b/public/assets/img/vuelos/vuelo-compartido_/4.png new file mode 100644 index 0000000..b202d59 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-compartido_/4.png differ diff --git a/public/assets/img/vuelos/vuelo-compartido_/vuelo-compartido-2.jpg b/public/assets/img/vuelos/vuelo-compartido_/vuelo-compartido-2.jpg new file mode 100644 index 0000000..4fe4840 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-compartido_/vuelo-compartido-2.jpg differ diff --git a/public/assets/img/vuelos/vuelo-compartido_/vuelo-compartido-2.png b/public/assets/img/vuelos/vuelo-compartido_/vuelo-compartido-2.png new file mode 100644 index 0000000..429ed77 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-compartido_/vuelo-compartido-2.png differ diff --git a/public/assets/img/vuelos/vuelo-compartido_/vuelo-compartido.jpg b/public/assets/img/vuelos/vuelo-compartido_/vuelo-compartido.jpg new file mode 100644 index 0000000..081aa21 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-compartido_/vuelo-compartido.jpg differ diff --git a/public/assets/img/vuelos/vuelo-compartido_/vuelo-compartido.png b/public/assets/img/vuelos/vuelo-compartido_/vuelo-compartido.png new file mode 100644 index 0000000..2df35d7 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-compartido_/vuelo-compartido.png differ diff --git a/public/assets/img/vuelos/vuelo-compartido_/vuelo-compartido.pngold b/public/assets/img/vuelos/vuelo-compartido_/vuelo-compartido.pngold new file mode 100644 index 0000000..ab0057a Binary files /dev/null and b/public/assets/img/vuelos/vuelo-compartido_/vuelo-compartido.pngold differ diff --git a/public/assets/img/vuelos/vuelo-compartido_/vuelo-compartido2.jpg b/public/assets/img/vuelos/vuelo-compartido_/vuelo-compartido2.jpg new file mode 100644 index 0000000..3fe0129 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-compartido_/vuelo-compartido2.jpg differ diff --git a/public/assets/img/vuelos/vuelo-compartido_/vuelo-compartido_2023.jpg b/public/assets/img/vuelos/vuelo-compartido_/vuelo-compartido_2023.jpg new file mode 100644 index 0000000..e693b31 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-compartido_/vuelo-compartido_2023.jpg differ diff --git a/public/assets/img/vuelos/vuelo-compartido_/vuelo-compartido_old.jpg b/public/assets/img/vuelos/vuelo-compartido_/vuelo-compartido_old.jpg new file mode 100644 index 0000000..b631297 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-compartido_/vuelo-compartido_old.jpg differ diff --git a/public/assets/img/vuelos/vuelo-compartido_/vuelo-compartido_old.png b/public/assets/img/vuelos/vuelo-compartido_/vuelo-compartido_old.png new file mode 100644 index 0000000..61d002f Binary files /dev/null and b/public/assets/img/vuelos/vuelo-compartido_/vuelo-compartido_old.png differ diff --git a/public/assets/img/vuelos/vuelo-compartido_Until_07FEB2023/0.jpg b/public/assets/img/vuelos/vuelo-compartido_Until_07FEB2023/0.jpg new file mode 100644 index 0000000..27eec32 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-compartido_Until_07FEB2023/0.jpg differ diff --git a/public/assets/img/vuelos/vuelo-compartido_Until_07FEB2023/0.png b/public/assets/img/vuelos/vuelo-compartido_Until_07FEB2023/0.png new file mode 100644 index 0000000..c69c287 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-compartido_Until_07FEB2023/0.png differ diff --git a/public/assets/img/vuelos/vuelo-compartido_Until_07FEB2023/1.jpg b/public/assets/img/vuelos/vuelo-compartido_Until_07FEB2023/1.jpg new file mode 100644 index 0000000..6d531db Binary files /dev/null and b/public/assets/img/vuelos/vuelo-compartido_Until_07FEB2023/1.jpg differ diff --git a/public/assets/img/vuelos/vuelo-compartido_Until_07FEB2023/1.png b/public/assets/img/vuelos/vuelo-compartido_Until_07FEB2023/1.png new file mode 100644 index 0000000..7c6451f Binary files /dev/null and b/public/assets/img/vuelos/vuelo-compartido_Until_07FEB2023/1.png differ diff --git a/public/assets/img/vuelos/vuelo-compartido_Until_07FEB2023/2.jpg b/public/assets/img/vuelos/vuelo-compartido_Until_07FEB2023/2.jpg new file mode 100644 index 0000000..55bc2d0 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-compartido_Until_07FEB2023/2.jpg differ diff --git a/public/assets/img/vuelos/vuelo-compartido_Until_07FEB2023/2.png b/public/assets/img/vuelos/vuelo-compartido_Until_07FEB2023/2.png new file mode 100644 index 0000000..4a68185 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-compartido_Until_07FEB2023/2.png differ diff --git a/public/assets/img/vuelos/vuelo-compartido_Until_07FEB2023/3.jpg b/public/assets/img/vuelos/vuelo-compartido_Until_07FEB2023/3.jpg new file mode 100644 index 0000000..fcfba42 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-compartido_Until_07FEB2023/3.jpg differ diff --git a/public/assets/img/vuelos/vuelo-compartido_Until_07FEB2023/3.png b/public/assets/img/vuelos/vuelo-compartido_Until_07FEB2023/3.png new file mode 100644 index 0000000..ffa582c Binary files /dev/null and b/public/assets/img/vuelos/vuelo-compartido_Until_07FEB2023/3.png differ diff --git a/public/assets/img/vuelos/vuelo-compartido_Until_07FEB2023/4.jpg b/public/assets/img/vuelos/vuelo-compartido_Until_07FEB2023/4.jpg new file mode 100644 index 0000000..4c65180 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-compartido_Until_07FEB2023/4.jpg differ diff --git a/public/assets/img/vuelos/vuelo-compartido_Until_07FEB2023/4.png b/public/assets/img/vuelos/vuelo-compartido_Until_07FEB2023/4.png new file mode 100644 index 0000000..b202d59 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-compartido_Until_07FEB2023/4.png differ diff --git a/public/assets/img/vuelos/vuelo-compartido_Until_07FEB2023/vuelo-compartido-2.png b/public/assets/img/vuelos/vuelo-compartido_Until_07FEB2023/vuelo-compartido-2.png new file mode 100644 index 0000000..429ed77 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-compartido_Until_07FEB2023/vuelo-compartido-2.png differ diff --git a/public/assets/img/vuelos/vuelo-compartido_Until_07FEB2023/vuelo-compartido.jpg b/public/assets/img/vuelos/vuelo-compartido_Until_07FEB2023/vuelo-compartido.jpg new file mode 100644 index 0000000..2603e39 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-compartido_Until_07FEB2023/vuelo-compartido.jpg differ diff --git a/public/assets/img/vuelos/vuelo-compartido_Until_07FEB2023/vuelo-compartido.png b/public/assets/img/vuelos/vuelo-compartido_Until_07FEB2023/vuelo-compartido.png new file mode 100644 index 0000000..960ed59 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-compartido_Until_07FEB2023/vuelo-compartido.png differ diff --git a/public/assets/img/vuelos/vuelo-compartido_Until_07FEB2023/vuelo-compartido.pngold b/public/assets/img/vuelos/vuelo-compartido_Until_07FEB2023/vuelo-compartido.pngold new file mode 100644 index 0000000..ab0057a Binary files /dev/null and b/public/assets/img/vuelos/vuelo-compartido_Until_07FEB2023/vuelo-compartido.pngold differ diff --git a/public/assets/img/vuelos/vuelo-compartido_Until_07FEB2023/vuelo-compartido_old.jpg b/public/assets/img/vuelos/vuelo-compartido_Until_07FEB2023/vuelo-compartido_old.jpg new file mode 100644 index 0000000..11d7c55 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-compartido_Until_07FEB2023/vuelo-compartido_old.jpg differ diff --git a/public/assets/img/vuelos/vuelo-compartido_Until_07FEB2023/vuelo-compartido_old2007.jpg b/public/assets/img/vuelos/vuelo-compartido_Until_07FEB2023/vuelo-compartido_old2007.jpg new file mode 100644 index 0000000..5a33441 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-compartido_Until_07FEB2023/vuelo-compartido_old2007.jpg differ diff --git a/public/assets/img/vuelos/vuelo-cumpleaos/0.jpg b/public/assets/img/vuelos/vuelo-cumpleaos/0.jpg new file mode 100644 index 0000000..8b07118 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-cumpleaos/0.jpg differ diff --git a/public/assets/img/vuelos/vuelo-cumpleaos/1.jpg b/public/assets/img/vuelos/vuelo-cumpleaos/1.jpg new file mode 100644 index 0000000..2b27897 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-cumpleaos/1.jpg differ diff --git a/public/assets/img/vuelos/vuelo-cumpleaos/2.jpg b/public/assets/img/vuelos/vuelo-cumpleaos/2.jpg new file mode 100644 index 0000000..de34a21 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-cumpleaos/2.jpg differ diff --git a/public/assets/img/vuelos/vuelo-cumpleaos/3.jpg b/public/assets/img/vuelos/vuelo-cumpleaos/3.jpg new file mode 100644 index 0000000..d898d25 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-cumpleaos/3.jpg differ diff --git a/public/assets/img/vuelos/vuelo-cumpleaos/4.jpg b/public/assets/img/vuelos/vuelo-cumpleaos/4.jpg new file mode 100644 index 0000000..4fae462 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-cumpleaos/4.jpg differ diff --git a/public/assets/img/vuelos/vuelo-cumpleaos/5.jpg b/public/assets/img/vuelos/vuelo-cumpleaos/5.jpg new file mode 100644 index 0000000..b82321f Binary files /dev/null and b/public/assets/img/vuelos/vuelo-cumpleaos/5.jpg differ diff --git a/public/assets/img/vuelos/vuelo-cumpleaos/6.jpg b/public/assets/img/vuelos/vuelo-cumpleaos/6.jpg new file mode 100644 index 0000000..3e1fb6d Binary files /dev/null and b/public/assets/img/vuelos/vuelo-cumpleaos/6.jpg differ diff --git a/public/assets/img/vuelos/vuelo-cumpleaos/7.jpg b/public/assets/img/vuelos/vuelo-cumpleaos/7.jpg new file mode 100644 index 0000000..5b52ede Binary files /dev/null and b/public/assets/img/vuelos/vuelo-cumpleaos/7.jpg differ diff --git a/public/assets/img/vuelos/vuelo-cumpleaos/8.jpg b/public/assets/img/vuelos/vuelo-cumpleaos/8.jpg new file mode 100644 index 0000000..a7460a7 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-cumpleaos/8.jpg differ diff --git a/public/assets/img/vuelos/vuelo-cumpleaos/9.jpg b/public/assets/img/vuelos/vuelo-cumpleaos/9.jpg new file mode 100644 index 0000000..1dc5890 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-cumpleaos/9.jpg differ diff --git a/public/assets/img/vuelos/vuelo-cumpleaos/vuelo-cumpleaos-2.jpg b/public/assets/img/vuelos/vuelo-cumpleaos/vuelo-cumpleaos-2.jpg new file mode 100644 index 0000000..106a439 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-cumpleaos/vuelo-cumpleaos-2.jpg differ diff --git a/public/assets/img/vuelos/vuelo-cumpleaos/vuelo-cumpleaos.jpg b/public/assets/img/vuelos/vuelo-cumpleaos/vuelo-cumpleaos.jpg new file mode 100644 index 0000000..4d38945 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-cumpleaos/vuelo-cumpleaos.jpg differ diff --git a/public/assets/img/vuelos/vuelo-cumpleaos_/0.jpg b/public/assets/img/vuelos/vuelo-cumpleaos_/0.jpg new file mode 100644 index 0000000..8120c29 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-cumpleaos_/0.jpg differ diff --git a/public/assets/img/vuelos/vuelo-cumpleaos_/0.png b/public/assets/img/vuelos/vuelo-cumpleaos_/0.png new file mode 100644 index 0000000..8c8dbe6 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-cumpleaos_/0.png differ diff --git a/public/assets/img/vuelos/vuelo-cumpleaos_/1.jpg b/public/assets/img/vuelos/vuelo-cumpleaos_/1.jpg new file mode 100644 index 0000000..9fbf0d9 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-cumpleaos_/1.jpg differ diff --git a/public/assets/img/vuelos/vuelo-cumpleaos_/1.png b/public/assets/img/vuelos/vuelo-cumpleaos_/1.png new file mode 100644 index 0000000..3ee5172 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-cumpleaos_/1.png differ diff --git a/public/assets/img/vuelos/vuelo-cumpleaos_/2.jpg b/public/assets/img/vuelos/vuelo-cumpleaos_/2.jpg new file mode 100644 index 0000000..d83309f Binary files /dev/null and b/public/assets/img/vuelos/vuelo-cumpleaos_/2.jpg differ diff --git a/public/assets/img/vuelos/vuelo-cumpleaos_/2.png b/public/assets/img/vuelos/vuelo-cumpleaos_/2.png new file mode 100644 index 0000000..d0963a0 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-cumpleaos_/2.png differ diff --git a/public/assets/img/vuelos/vuelo-cumpleaos_/3.jpg b/public/assets/img/vuelos/vuelo-cumpleaos_/3.jpg new file mode 100644 index 0000000..cc2d156 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-cumpleaos_/3.jpg differ diff --git a/public/assets/img/vuelos/vuelo-cumpleaos_/3.png b/public/assets/img/vuelos/vuelo-cumpleaos_/3.png new file mode 100644 index 0000000..f183b6e Binary files /dev/null and b/public/assets/img/vuelos/vuelo-cumpleaos_/3.png differ diff --git a/public/assets/img/vuelos/vuelo-cumpleaos_/4.jpg b/public/assets/img/vuelos/vuelo-cumpleaos_/4.jpg new file mode 100644 index 0000000..ea7902c Binary files /dev/null and b/public/assets/img/vuelos/vuelo-cumpleaos_/4.jpg differ diff --git a/public/assets/img/vuelos/vuelo-cumpleaos_/4.png b/public/assets/img/vuelos/vuelo-cumpleaos_/4.png new file mode 100644 index 0000000..f0c2074 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-cumpleaos_/4.png differ diff --git a/public/assets/img/vuelos/vuelo-cumpleaos_/Cumpleaños.jpg b/public/assets/img/vuelos/vuelo-cumpleaos_/Cumpleaños.jpg new file mode 100644 index 0000000..a8fb0f2 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-cumpleaos_/Cumpleaños.jpg differ diff --git a/public/assets/img/vuelos/vuelo-cumpleaos_/Pastel.jpg b/public/assets/img/vuelos/vuelo-cumpleaos_/Pastel.jpg new file mode 100644 index 0000000..d83d5a2 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-cumpleaos_/Pastel.jpg differ diff --git a/public/assets/img/vuelos/vuelo-cumpleaos_/vuelo-cumpleaos-2-invertido.png b/public/assets/img/vuelos/vuelo-cumpleaos_/vuelo-cumpleaos-2-invertido.png new file mode 100644 index 0000000..5c0aa8c Binary files /dev/null and b/public/assets/img/vuelos/vuelo-cumpleaos_/vuelo-cumpleaos-2-invertido.png differ diff --git a/public/assets/img/vuelos/vuelo-cumpleaos_/vuelo-cumpleaos-2.png b/public/assets/img/vuelos/vuelo-cumpleaos_/vuelo-cumpleaos-2.png new file mode 100644 index 0000000..c9365b7 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-cumpleaos_/vuelo-cumpleaos-2.png differ diff --git a/public/assets/img/vuelos/vuelo-cumpleaos_/vuelo-cumpleaos-2.webp b/public/assets/img/vuelos/vuelo-cumpleaos_/vuelo-cumpleaos-2.webp new file mode 100644 index 0000000..2932395 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-cumpleaos_/vuelo-cumpleaos-2.webp differ diff --git a/public/assets/img/vuelos/vuelo-cumpleaos_/vuelo-cumpleaos.jpg b/public/assets/img/vuelos/vuelo-cumpleaos_/vuelo-cumpleaos.jpg new file mode 100644 index 0000000..649b4bb Binary files /dev/null and b/public/assets/img/vuelos/vuelo-cumpleaos_/vuelo-cumpleaos.jpg differ diff --git a/public/assets/img/vuelos/vuelo-cumpleaos_/vuelo-cumpleaos.png b/public/assets/img/vuelos/vuelo-cumpleaos_/vuelo-cumpleaos.png new file mode 100644 index 0000000..80c8bd7 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-cumpleaos_/vuelo-cumpleaos.png differ diff --git a/public/assets/img/vuelos/vuelo-cumpleaos_/vuelo-cumpleaos.webp b/public/assets/img/vuelos/vuelo-cumpleaos_/vuelo-cumpleaos.webp new file mode 100644 index 0000000..ed9a3c5 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-cumpleaos_/vuelo-cumpleaos.webp differ diff --git a/public/assets/img/vuelos/vuelo-cumpleaos_/vuelo-cumpleaos_temp.png b/public/assets/img/vuelos/vuelo-cumpleaos_/vuelo-cumpleaos_temp.png new file mode 100644 index 0000000..ab90c67 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-cumpleaos_/vuelo-cumpleaos_temp.png differ diff --git a/public/assets/img/vuelos/vuelo-cumpleaos_/vuelo-cumpleaños-2.webp b/public/assets/img/vuelos/vuelo-cumpleaos_/vuelo-cumpleaños-2.webp new file mode 100644 index 0000000..7178e93 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-cumpleaos_/vuelo-cumpleaños-2.webp differ diff --git a/public/assets/img/vuelos/vuelo-cumpleaos_/vuelo-cumpleaños-3.webp b/public/assets/img/vuelos/vuelo-cumpleaos_/vuelo-cumpleaños-3.webp new file mode 100644 index 0000000..d01f614 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-cumpleaos_/vuelo-cumpleaños-3.webp differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-familiar/0.png b/public/assets/img/vuelos/vuelo-en-globo-familiar/0.png new file mode 100644 index 0000000..8ca9d01 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-familiar/0.png differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-familiar/1.png b/public/assets/img/vuelos/vuelo-en-globo-familiar/1.png new file mode 100644 index 0000000..25f7ad9 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-familiar/1.png differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-familiar/2.png b/public/assets/img/vuelos/vuelo-en-globo-familiar/2.png new file mode 100644 index 0000000..5c81073 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-familiar/2.png differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-familiar/3.png b/public/assets/img/vuelos/vuelo-en-globo-familiar/3.png new file mode 100644 index 0000000..5241c69 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-familiar/3.png differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-familiar/4.png b/public/assets/img/vuelos/vuelo-en-globo-familiar/4.png new file mode 100644 index 0000000..9715ea6 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-familiar/4.png differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-familiar/vuelo-en-globo-familiar-2.png b/public/assets/img/vuelos/vuelo-en-globo-familiar/vuelo-en-globo-familiar-2.png new file mode 100644 index 0000000..4d4f800 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-familiar/vuelo-en-globo-familiar-2.png differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-familiar/vuelo-en-globo-familiar.png b/public/assets/img/vuelos/vuelo-en-globo-familiar/vuelo-en-globo-familiar.png new file mode 100644 index 0000000..4d4f800 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-familiar/vuelo-en-globo-familiar.png differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-hospedaje/0.jpg b/public/assets/img/vuelos/vuelo-en-globo-hospedaje/0.jpg new file mode 100644 index 0000000..e575b1a Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-hospedaje/0.jpg differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-hospedaje/1.jpg b/public/assets/img/vuelos/vuelo-en-globo-hospedaje/1.jpg new file mode 100644 index 0000000..9286694 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-hospedaje/1.jpg differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-hospedaje/2.jpg b/public/assets/img/vuelos/vuelo-en-globo-hospedaje/2.jpg new file mode 100644 index 0000000..0528ea4 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-hospedaje/2.jpg differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-hospedaje/3.jpg b/public/assets/img/vuelos/vuelo-en-globo-hospedaje/3.jpg new file mode 100644 index 0000000..cbdfccf Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-hospedaje/3.jpg differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-hospedaje/4.jpg b/public/assets/img/vuelos/vuelo-en-globo-hospedaje/4.jpg new file mode 100644 index 0000000..19424a5 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-hospedaje/4.jpg differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-hospedaje/5.jpg b/public/assets/img/vuelos/vuelo-en-globo-hospedaje/5.jpg new file mode 100644 index 0000000..444d14f Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-hospedaje/5.jpg differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-hospedaje/6.jpg b/public/assets/img/vuelos/vuelo-en-globo-hospedaje/6.jpg new file mode 100644 index 0000000..df71a38 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-hospedaje/6.jpg differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-hospedaje/7.jpg b/public/assets/img/vuelos/vuelo-en-globo-hospedaje/7.jpg new file mode 100644 index 0000000..4c17184 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-hospedaje/7.jpg differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-hospedaje/8.jpg b/public/assets/img/vuelos/vuelo-en-globo-hospedaje/8.jpg new file mode 100644 index 0000000..79fa49a Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-hospedaje/8.jpg differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-hospedaje/9.jpg b/public/assets/img/vuelos/vuelo-en-globo-hospedaje/9.jpg new file mode 100644 index 0000000..65b8ba6 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-hospedaje/9.jpg differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-hospedaje/vuelo-en-globo-hospedaje-2.jpg b/public/assets/img/vuelos/vuelo-en-globo-hospedaje/vuelo-en-globo-hospedaje-2.jpg new file mode 100644 index 0000000..8794f06 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-hospedaje/vuelo-en-globo-hospedaje-2.jpg differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-hospedaje/vuelo-en-globo-hospedaje.jpg b/public/assets/img/vuelos/vuelo-en-globo-hospedaje/vuelo-en-globo-hospedaje.jpg new file mode 100644 index 0000000..68beee3 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-hospedaje/vuelo-en-globo-hospedaje.jpg differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-hospedaje_/0.png b/public/assets/img/vuelos/vuelo-en-globo-hospedaje_/0.png new file mode 100644 index 0000000..250381e Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-hospedaje_/0.png differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-hospedaje_/1.png b/public/assets/img/vuelos/vuelo-en-globo-hospedaje_/1.png new file mode 100644 index 0000000..cd245ef Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-hospedaje_/1.png differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-hospedaje_/2.png b/public/assets/img/vuelos/vuelo-en-globo-hospedaje_/2.png new file mode 100644 index 0000000..4f41b3a Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-hospedaje_/2.png differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-hospedaje_/3.png b/public/assets/img/vuelos/vuelo-en-globo-hospedaje_/3.png new file mode 100644 index 0000000..5841325 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-hospedaje_/3.png differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-hospedaje_/4.png b/public/assets/img/vuelos/vuelo-en-globo-hospedaje_/4.png new file mode 100644 index 0000000..20312f6 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-hospedaje_/4.png differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-hospedaje_/5.png b/public/assets/img/vuelos/vuelo-en-globo-hospedaje_/5.png new file mode 100644 index 0000000..7437645 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-hospedaje_/5.png differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-hospedaje_/6.png b/public/assets/img/vuelos/vuelo-en-globo-hospedaje_/6.png new file mode 100644 index 0000000..bfbd614 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-hospedaje_/6.png differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-hospedaje_/vuelo-en-globo-hospedaje-2.png b/public/assets/img/vuelos/vuelo-en-globo-hospedaje_/vuelo-en-globo-hospedaje-2.png new file mode 100644 index 0000000..13b8e3c Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-hospedaje_/vuelo-en-globo-hospedaje-2.png differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-hospedaje_/vuelo-en-globo-hospedaje.png b/public/assets/img/vuelos/vuelo-en-globo-hospedaje_/vuelo-en-globo-hospedaje.png new file mode 100644 index 0000000..b93237b Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-hospedaje_/vuelo-en-globo-hospedaje.png differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-oferta-nodianino/0.png b/public/assets/img/vuelos/vuelo-en-globo-oferta-nodianino/0.png new file mode 100644 index 0000000..869552e Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-oferta-nodianino/0.png differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-oferta-nodianino/1.png b/public/assets/img/vuelos/vuelo-en-globo-oferta-nodianino/1.png new file mode 100644 index 0000000..4860bb5 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-oferta-nodianino/1.png differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-oferta-nodianino/2.png b/public/assets/img/vuelos/vuelo-en-globo-oferta-nodianino/2.png new file mode 100644 index 0000000..c74eed4 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-oferta-nodianino/2.png differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-oferta-nodianino/3.png b/public/assets/img/vuelos/vuelo-en-globo-oferta-nodianino/3.png new file mode 100644 index 0000000..6e5019f Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-oferta-nodianino/3.png differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-oferta-nodianino/4.png b/public/assets/img/vuelos/vuelo-en-globo-oferta-nodianino/4.png new file mode 100644 index 0000000..dd4f042 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-oferta-nodianino/4.png differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-oferta-nodianino/5.png b/public/assets/img/vuelos/vuelo-en-globo-oferta-nodianino/5.png new file mode 100644 index 0000000..77c6072 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-oferta-nodianino/5.png differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-oferta-nodianino/6.png b/public/assets/img/vuelos/vuelo-en-globo-oferta-nodianino/6.png new file mode 100644 index 0000000..012de52 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-oferta-nodianino/6.png differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-oferta-nodianino/vuelo-en-globo-oferta-2.jpg b/public/assets/img/vuelos/vuelo-en-globo-oferta-nodianino/vuelo-en-globo-oferta-2.jpg new file mode 100644 index 0000000..cbfd87b Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-oferta-nodianino/vuelo-en-globo-oferta-2.jpg differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-oferta-nodianino/vuelo-en-globo-oferta.jpg b/public/assets/img/vuelos/vuelo-en-globo-oferta-nodianino/vuelo-en-globo-oferta.jpg new file mode 100644 index 0000000..70b9a40 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-oferta-nodianino/vuelo-en-globo-oferta.jpg differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-oferta-nodianino/vuelo-en-globo-oferta_2023.jpg b/public/assets/img/vuelos/vuelo-en-globo-oferta-nodianino/vuelo-en-globo-oferta_2023.jpg new file mode 100644 index 0000000..c8aadc7 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-oferta-nodianino/vuelo-en-globo-oferta_2023.jpg differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-oferta-nodianino/vuelo-en-globo-oferta_2023.png b/public/assets/img/vuelos/vuelo-en-globo-oferta-nodianino/vuelo-en-globo-oferta_2023.png new file mode 100644 index 0000000..61d002f Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-oferta-nodianino/vuelo-en-globo-oferta_2023.png differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-oferta-nodianino/vuelo-en-globo-oferta_travelsale.jpg b/public/assets/img/vuelos/vuelo-en-globo-oferta-nodianino/vuelo-en-globo-oferta_travelsale.jpg new file mode 100644 index 0000000..54b5ead Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-oferta-nodianino/vuelo-en-globo-oferta_travelsale.jpg differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-oferta/0.jpg b/public/assets/img/vuelos/vuelo-en-globo-oferta/0.jpg new file mode 100644 index 0000000..796b233 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-oferta/0.jpg differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-oferta/1.jpg b/public/assets/img/vuelos/vuelo-en-globo-oferta/1.jpg new file mode 100644 index 0000000..00234af Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-oferta/1.jpg differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-oferta/2.jpg b/public/assets/img/vuelos/vuelo-en-globo-oferta/2.jpg new file mode 100644 index 0000000..7c0e9e4 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-oferta/2.jpg differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-oferta/3.jpg b/public/assets/img/vuelos/vuelo-en-globo-oferta/3.jpg new file mode 100644 index 0000000..c51410a Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-oferta/3.jpg differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-oferta/4.jpg b/public/assets/img/vuelos/vuelo-en-globo-oferta/4.jpg new file mode 100644 index 0000000..5194f68 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-oferta/4.jpg differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-oferta/5.jpg b/public/assets/img/vuelos/vuelo-en-globo-oferta/5.jpg new file mode 100644 index 0000000..4a9b429 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-oferta/5.jpg differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-oferta/6.jpg b/public/assets/img/vuelos/vuelo-en-globo-oferta/6.jpg new file mode 100644 index 0000000..bd41480 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-oferta/6.jpg differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-oferta/vuelo-en-globo-oferta-2_.jpg b/public/assets/img/vuelos/vuelo-en-globo-oferta/vuelo-en-globo-oferta-2_.jpg new file mode 100644 index 0000000..1486e13 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-oferta/vuelo-en-globo-oferta-2_.jpg differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-oferta/vuelo-en-globo-oferta.png b/public/assets/img/vuelos/vuelo-en-globo-oferta/vuelo-en-globo-oferta.png new file mode 100644 index 0000000..1486e13 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-oferta/vuelo-en-globo-oferta.png differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-oferta/vuelo-en-globo-oferta_.jpg b/public/assets/img/vuelos/vuelo-en-globo-oferta/vuelo-en-globo-oferta_.jpg new file mode 100644 index 0000000..25891a3 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-oferta/vuelo-en-globo-oferta_.jpg differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-oferta/vuelo-en.globo-oferta-2.png b/public/assets/img/vuelos/vuelo-en-globo-oferta/vuelo-en.globo-oferta-2.png new file mode 100644 index 0000000..1486e13 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-oferta/vuelo-en.globo-oferta-2.png differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-oferta_/0.png b/public/assets/img/vuelos/vuelo-en-globo-oferta_/0.png new file mode 100644 index 0000000..1b89bc5 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-oferta_/0.png differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-oferta_/1.png b/public/assets/img/vuelos/vuelo-en-globo-oferta_/1.png new file mode 100644 index 0000000..ef3dd52 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-oferta_/1.png differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-oferta_/2.png b/public/assets/img/vuelos/vuelo-en-globo-oferta_/2.png new file mode 100644 index 0000000..551e836 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-oferta_/2.png differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-oferta_/3.png b/public/assets/img/vuelos/vuelo-en-globo-oferta_/3.png new file mode 100644 index 0000000..738a1f5 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-oferta_/3.png differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-oferta_/4.png b/public/assets/img/vuelos/vuelo-en-globo-oferta_/4.png new file mode 100644 index 0000000..f8388fa Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-oferta_/4.png differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-oferta_/5.png b/public/assets/img/vuelos/vuelo-en-globo-oferta_/5.png new file mode 100644 index 0000000..0f2fb13 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-oferta_/5.png differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-oferta_/6.png b/public/assets/img/vuelos/vuelo-en-globo-oferta_/6.png new file mode 100644 index 0000000..60e4903 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-oferta_/6.png differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-oferta_/vuelo-en-globo-oferta-2.jpg b/public/assets/img/vuelos/vuelo-en-globo-oferta_/vuelo-en-globo-oferta-2.jpg new file mode 100644 index 0000000..bdf8d38 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-oferta_/vuelo-en-globo-oferta-2.jpg differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-oferta_/vuelo-en-globo-oferta.jpg b/public/assets/img/vuelos/vuelo-en-globo-oferta_/vuelo-en-globo-oferta.jpg new file mode 100644 index 0000000..bdf8d38 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-oferta_/vuelo-en-globo-oferta.jpg differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-patrio/0.png b/public/assets/img/vuelos/vuelo-en-globo-patrio/0.png new file mode 100644 index 0000000..23b3a54 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-patrio/0.png differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-patrio/1.png b/public/assets/img/vuelos/vuelo-en-globo-patrio/1.png new file mode 100644 index 0000000..aa0fcd4 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-patrio/1.png differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-patrio/2.png b/public/assets/img/vuelos/vuelo-en-globo-patrio/2.png new file mode 100644 index 0000000..2b803b4 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-patrio/2.png differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-patrio/3.png b/public/assets/img/vuelos/vuelo-en-globo-patrio/3.png new file mode 100644 index 0000000..3b8c6e6 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-patrio/3.png differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-patrio/4.png b/public/assets/img/vuelos/vuelo-en-globo-patrio/4.png new file mode 100644 index 0000000..32522e3 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-patrio/4.png differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-patrio/5.png b/public/assets/img/vuelos/vuelo-en-globo-patrio/5.png new file mode 100644 index 0000000..160b169 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-patrio/5.png differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-patrio/6.png b/public/assets/img/vuelos/vuelo-en-globo-patrio/6.png new file mode 100644 index 0000000..63d16a9 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-patrio/6.png differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-patrio/vuelo-en-globo-patrio.png b/public/assets/img/vuelos/vuelo-en-globo-patrio/vuelo-en-globo-patrio.png new file mode 100644 index 0000000..0d26a03 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-patrio/vuelo-en-globo-patrio.png differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-promocion/0.jpg b/public/assets/img/vuelos/vuelo-en-globo-promocion/0.jpg new file mode 100644 index 0000000..09f03dc Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-promocion/0.jpg differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-promocion/1.jpg b/public/assets/img/vuelos/vuelo-en-globo-promocion/1.jpg new file mode 100644 index 0000000..96a194c Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-promocion/1.jpg differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-promocion/2.jpg b/public/assets/img/vuelos/vuelo-en-globo-promocion/2.jpg new file mode 100644 index 0000000..04da722 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-promocion/2.jpg differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-promocion/3.jpg b/public/assets/img/vuelos/vuelo-en-globo-promocion/3.jpg new file mode 100644 index 0000000..767242b Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-promocion/3.jpg differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-promocion/4.jpg b/public/assets/img/vuelos/vuelo-en-globo-promocion/4.jpg new file mode 100644 index 0000000..51a9f98 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-promocion/4.jpg differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-promocion/5.jpg b/public/assets/img/vuelos/vuelo-en-globo-promocion/5.jpg new file mode 100644 index 0000000..05ae3e0 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-promocion/5.jpg differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-promocion/6.jpg b/public/assets/img/vuelos/vuelo-en-globo-promocion/6.jpg new file mode 100644 index 0000000..a89ac31 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-promocion/6.jpg differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-promocion/7.jpg b/public/assets/img/vuelos/vuelo-en-globo-promocion/7.jpg new file mode 100644 index 0000000..a689412 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-promocion/7.jpg differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-promocion/vuelo-en-globo-promocion-2.jpg b/public/assets/img/vuelos/vuelo-en-globo-promocion/vuelo-en-globo-promocion-2.jpg new file mode 100644 index 0000000..fbdc356 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-promocion/vuelo-en-globo-promocion-2.jpg differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-promocion/vuelo-en-globo-promocion.jpg b/public/assets/img/vuelos/vuelo-en-globo-promocion/vuelo-en-globo-promocion.jpg new file mode 100644 index 0000000..e56cbe3 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-promocion/vuelo-en-globo-promocion.jpg differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-promocion_/0.png b/public/assets/img/vuelos/vuelo-en-globo-promocion_/0.png new file mode 100644 index 0000000..869552e Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-promocion_/0.png differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-promocion_/1.png b/public/assets/img/vuelos/vuelo-en-globo-promocion_/1.png new file mode 100644 index 0000000..4860bb5 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-promocion_/1.png differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-promocion_/2.png b/public/assets/img/vuelos/vuelo-en-globo-promocion_/2.png new file mode 100644 index 0000000..c74eed4 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-promocion_/2.png differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-promocion_/3.png b/public/assets/img/vuelos/vuelo-en-globo-promocion_/3.png new file mode 100644 index 0000000..6e5019f Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-promocion_/3.png differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-promocion_/4.png b/public/assets/img/vuelos/vuelo-en-globo-promocion_/4.png new file mode 100644 index 0000000..dd4f042 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-promocion_/4.png differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-promocion_/5.png b/public/assets/img/vuelos/vuelo-en-globo-promocion_/5.png new file mode 100644 index 0000000..77c6072 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-promocion_/5.png differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-promocion_/6.png b/public/assets/img/vuelos/vuelo-en-globo-promocion_/6.png new file mode 100644 index 0000000..012de52 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-promocion_/6.png differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-promocion_/vuelo-en-globo-promocion-2.jpg b/public/assets/img/vuelos/vuelo-en-globo-promocion_/vuelo-en-globo-promocion-2.jpg new file mode 100644 index 0000000..4317753 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-promocion_/vuelo-en-globo-promocion-2.jpg differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-promocion_/vuelo-en-globo-promocion.jpg b/public/assets/img/vuelos/vuelo-en-globo-promocion_/vuelo-en-globo-promocion.jpg new file mode 100644 index 0000000..87c8487 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-promocion_/vuelo-en-globo-promocion.jpg differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-promocion_/vuelo-en-globo-promocion.png b/public/assets/img/vuelos/vuelo-en-globo-promocion_/vuelo-en-globo-promocion.png new file mode 100644 index 0000000..ec2fd46 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-promocion_/vuelo-en-globo-promocion.png differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-promocion_/vuelo-en-globo-promocion_bf.png b/public/assets/img/vuelos/vuelo-en-globo-promocion_/vuelo-en-globo-promocion_bf.png new file mode 100644 index 0000000..1d85b65 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-promocion_/vuelo-en-globo-promocion_bf.png differ diff --git a/public/assets/img/vuelos/vuelo-en-globo-promocion_/vuelo-en-globo-promocion_martes.png b/public/assets/img/vuelos/vuelo-en-globo-promocion_/vuelo-en-globo-promocion_martes.png new file mode 100644 index 0000000..7bbb130 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo-promocion_/vuelo-en-globo-promocion_martes.png differ diff --git a/public/assets/img/vuelos/vuelo-en-globo/0.jpg b/public/assets/img/vuelos/vuelo-en-globo/0.jpg new file mode 100644 index 0000000..0542c66 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo/0.jpg differ diff --git a/public/assets/img/vuelos/vuelo-en-globo/0.png b/public/assets/img/vuelos/vuelo-en-globo/0.png new file mode 100644 index 0000000..4c836b4 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo/0.png differ diff --git a/public/assets/img/vuelos/vuelo-en-globo/1.jpg b/public/assets/img/vuelos/vuelo-en-globo/1.jpg new file mode 100644 index 0000000..1ccf245 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo/1.jpg differ diff --git a/public/assets/img/vuelos/vuelo-en-globo/1.png b/public/assets/img/vuelos/vuelo-en-globo/1.png new file mode 100644 index 0000000..fd94ffe Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo/1.png differ diff --git a/public/assets/img/vuelos/vuelo-en-globo/2.jpg b/public/assets/img/vuelos/vuelo-en-globo/2.jpg new file mode 100644 index 0000000..6d56f09 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo/2.jpg differ diff --git a/public/assets/img/vuelos/vuelo-en-globo/2.png b/public/assets/img/vuelos/vuelo-en-globo/2.png new file mode 100644 index 0000000..6733bf5 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo/2.png differ diff --git a/public/assets/img/vuelos/vuelo-en-globo/3.jpg b/public/assets/img/vuelos/vuelo-en-globo/3.jpg new file mode 100644 index 0000000..0eb40c8 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo/3.jpg differ diff --git a/public/assets/img/vuelos/vuelo-en-globo/3.png b/public/assets/img/vuelos/vuelo-en-globo/3.png new file mode 100644 index 0000000..3e24975 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo/3.png differ diff --git a/public/assets/img/vuelos/vuelo-en-globo/4.jpg b/public/assets/img/vuelos/vuelo-en-globo/4.jpg new file mode 100644 index 0000000..afcd3fc Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo/4.jpg differ diff --git a/public/assets/img/vuelos/vuelo-en-globo/4.png b/public/assets/img/vuelos/vuelo-en-globo/4.png new file mode 100644 index 0000000..ae942e5 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo/4.png differ diff --git a/public/assets/img/vuelos/vuelo-en-globo/Vuelo en globo.jpg b/public/assets/img/vuelos/vuelo-en-globo/Vuelo en globo.jpg new file mode 100644 index 0000000..1a72aa6 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo/Vuelo en globo.jpg differ diff --git a/public/assets/img/vuelos/vuelo-en-globo/vuelo-en-globo-2-invertido.png b/public/assets/img/vuelos/vuelo-en-globo/vuelo-en-globo-2-invertido.png new file mode 100644 index 0000000..d43452c Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo/vuelo-en-globo-2-invertido.png differ diff --git a/public/assets/img/vuelos/vuelo-en-globo/vuelo-en-globo-2.png b/public/assets/img/vuelos/vuelo-en-globo/vuelo-en-globo-2.png new file mode 100644 index 0000000..30f1485 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo/vuelo-en-globo-2.png differ diff --git a/public/assets/img/vuelos/vuelo-en-globo/vuelo-en-globo-2.webp b/public/assets/img/vuelos/vuelo-en-globo/vuelo-en-globo-2.webp new file mode 100644 index 0000000..3aaa967 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo/vuelo-en-globo-2.webp differ diff --git a/public/assets/img/vuelos/vuelo-en-globo/vuelo-en-globo-3.webp b/public/assets/img/vuelos/vuelo-en-globo/vuelo-en-globo-3.webp new file mode 100644 index 0000000..de14081 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo/vuelo-en-globo-3.webp differ diff --git a/public/assets/img/vuelos/vuelo-en-globo/vuelo-en-globo-mini.png b/public/assets/img/vuelos/vuelo-en-globo/vuelo-en-globo-mini.png new file mode 100644 index 0000000..9046ef0 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo/vuelo-en-globo-mini.png differ diff --git a/public/assets/img/vuelos/vuelo-en-globo/vuelo-en-globo.jpg b/public/assets/img/vuelos/vuelo-en-globo/vuelo-en-globo.jpg new file mode 100644 index 0000000..11d7c55 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo/vuelo-en-globo.jpg differ diff --git a/public/assets/img/vuelos/vuelo-en-globo/vuelo-en-globo.png b/public/assets/img/vuelos/vuelo-en-globo/vuelo-en-globo.png new file mode 100644 index 0000000..909f865 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo/vuelo-en-globo.png differ diff --git a/public/assets/img/vuelos/vuelo-en-globo/vuelo-en-globo.webp b/public/assets/img/vuelos/vuelo-en-globo/vuelo-en-globo.webp new file mode 100644 index 0000000..3d20ea7 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo/vuelo-en-globo.webp differ diff --git a/public/assets/img/vuelos/vuelo-en-globo/vuelo-en-globo_ns.png b/public/assets/img/vuelos/vuelo-en-globo/vuelo-en-globo_ns.png new file mode 100644 index 0000000..2df35d7 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo/vuelo-en-globo_ns.png differ diff --git a/public/assets/img/vuelos/vuelo-en-globo/vuelo-en-globo_temp.png b/public/assets/img/vuelos/vuelo-en-globo/vuelo-en-globo_temp.png new file mode 100644 index 0000000..909f865 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-en-globo/vuelo-en-globo_temp.png differ diff --git a/public/assets/img/vuelos/vuelo-grupal/0.jpg b/public/assets/img/vuelos/vuelo-grupal/0.jpg new file mode 100644 index 0000000..0e489d5 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-grupal/0.jpg differ diff --git a/public/assets/img/vuelos/vuelo-grupal/1.jpg b/public/assets/img/vuelos/vuelo-grupal/1.jpg new file mode 100644 index 0000000..0a721a1 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-grupal/1.jpg differ diff --git a/public/assets/img/vuelos/vuelo-grupal/2.jpg b/public/assets/img/vuelos/vuelo-grupal/2.jpg new file mode 100644 index 0000000..95743a7 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-grupal/2.jpg differ diff --git a/public/assets/img/vuelos/vuelo-grupal/3.jpg b/public/assets/img/vuelos/vuelo-grupal/3.jpg new file mode 100644 index 0000000..7eab955 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-grupal/3.jpg differ diff --git a/public/assets/img/vuelos/vuelo-grupal/4.jpg b/public/assets/img/vuelos/vuelo-grupal/4.jpg new file mode 100644 index 0000000..30f5206 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-grupal/4.jpg differ diff --git a/public/assets/img/vuelos/vuelo-grupal/5.jpg b/public/assets/img/vuelos/vuelo-grupal/5.jpg new file mode 100644 index 0000000..94e2e2c Binary files /dev/null and b/public/assets/img/vuelos/vuelo-grupal/5.jpg differ diff --git a/public/assets/img/vuelos/vuelo-grupal/6.jpg b/public/assets/img/vuelos/vuelo-grupal/6.jpg new file mode 100644 index 0000000..c83fa9b Binary files /dev/null and b/public/assets/img/vuelos/vuelo-grupal/6.jpg differ diff --git a/public/assets/img/vuelos/vuelo-grupal/7.jpg b/public/assets/img/vuelos/vuelo-grupal/7.jpg new file mode 100644 index 0000000..9d0ef3d Binary files /dev/null and b/public/assets/img/vuelos/vuelo-grupal/7.jpg differ diff --git a/public/assets/img/vuelos/vuelo-grupal/vuelo-grupal-2.jpg b/public/assets/img/vuelos/vuelo-grupal/vuelo-grupal-2.jpg new file mode 100644 index 0000000..cc2f8bf Binary files /dev/null and b/public/assets/img/vuelos/vuelo-grupal/vuelo-grupal-2.jpg differ diff --git a/public/assets/img/vuelos/vuelo-grupal/vuelo-grupal.jpg b/public/assets/img/vuelos/vuelo-grupal/vuelo-grupal.jpg new file mode 100644 index 0000000..5729b93 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-grupal/vuelo-grupal.jpg differ diff --git a/public/assets/img/vuelos/vuelo-grupal_/0.jpg b/public/assets/img/vuelos/vuelo-grupal_/0.jpg new file mode 100644 index 0000000..d56f0bf Binary files /dev/null and b/public/assets/img/vuelos/vuelo-grupal_/0.jpg differ diff --git a/public/assets/img/vuelos/vuelo-grupal_/0.png b/public/assets/img/vuelos/vuelo-grupal_/0.png new file mode 100644 index 0000000..7de4809 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-grupal_/0.png differ diff --git a/public/assets/img/vuelos/vuelo-grupal_/1.jpg b/public/assets/img/vuelos/vuelo-grupal_/1.jpg new file mode 100644 index 0000000..a7ca8bc Binary files /dev/null and b/public/assets/img/vuelos/vuelo-grupal_/1.jpg differ diff --git a/public/assets/img/vuelos/vuelo-grupal_/1.png b/public/assets/img/vuelos/vuelo-grupal_/1.png new file mode 100644 index 0000000..b5149ad Binary files /dev/null and b/public/assets/img/vuelos/vuelo-grupal_/1.png differ diff --git a/public/assets/img/vuelos/vuelo-grupal_/2.jpg b/public/assets/img/vuelos/vuelo-grupal_/2.jpg new file mode 100644 index 0000000..66b37c1 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-grupal_/2.jpg differ diff --git a/public/assets/img/vuelos/vuelo-grupal_/2.png b/public/assets/img/vuelos/vuelo-grupal_/2.png new file mode 100644 index 0000000..6c8a4fc Binary files /dev/null and b/public/assets/img/vuelos/vuelo-grupal_/2.png differ diff --git a/public/assets/img/vuelos/vuelo-grupal_/3.jpg b/public/assets/img/vuelos/vuelo-grupal_/3.jpg new file mode 100644 index 0000000..e10010b Binary files /dev/null and b/public/assets/img/vuelos/vuelo-grupal_/3.jpg differ diff --git a/public/assets/img/vuelos/vuelo-grupal_/3.png b/public/assets/img/vuelos/vuelo-grupal_/3.png new file mode 100644 index 0000000..2d85ad4 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-grupal_/3.png differ diff --git a/public/assets/img/vuelos/vuelo-grupal_/4.jpg b/public/assets/img/vuelos/vuelo-grupal_/4.jpg new file mode 100644 index 0000000..a47b64c Binary files /dev/null and b/public/assets/img/vuelos/vuelo-grupal_/4.jpg differ diff --git a/public/assets/img/vuelos/vuelo-grupal_/4.png b/public/assets/img/vuelos/vuelo-grupal_/4.png new file mode 100644 index 0000000..788305f Binary files /dev/null and b/public/assets/img/vuelos/vuelo-grupal_/4.png differ diff --git a/public/assets/img/vuelos/vuelo-grupal_/Vuelo grupal.jpg b/public/assets/img/vuelos/vuelo-grupal_/Vuelo grupal.jpg new file mode 100644 index 0000000..963e8dd Binary files /dev/null and b/public/assets/img/vuelos/vuelo-grupal_/Vuelo grupal.jpg differ diff --git a/public/assets/img/vuelos/vuelo-grupal_/vuelo-en-globo-grupal-2.webp b/public/assets/img/vuelos/vuelo-grupal_/vuelo-en-globo-grupal-2.webp new file mode 100644 index 0000000..e66a9f6 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-grupal_/vuelo-en-globo-grupal-2.webp differ diff --git a/public/assets/img/vuelos/vuelo-grupal_/vuelo-en-globo-grupal-3.webp b/public/assets/img/vuelos/vuelo-grupal_/vuelo-en-globo-grupal-3.webp new file mode 100644 index 0000000..774136e Binary files /dev/null and b/public/assets/img/vuelos/vuelo-grupal_/vuelo-en-globo-grupal-3.webp differ diff --git a/public/assets/img/vuelos/vuelo-grupal_/vuelo-grupal-2-invertido.png b/public/assets/img/vuelos/vuelo-grupal_/vuelo-grupal-2-invertido.png new file mode 100644 index 0000000..7eb2772 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-grupal_/vuelo-grupal-2-invertido.png differ diff --git a/public/assets/img/vuelos/vuelo-grupal_/vuelo-grupal-2.jpg b/public/assets/img/vuelos/vuelo-grupal_/vuelo-grupal-2.jpg new file mode 100644 index 0000000..57f93c9 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-grupal_/vuelo-grupal-2.jpg differ diff --git a/public/assets/img/vuelos/vuelo-grupal_/vuelo-grupal-2.png b/public/assets/img/vuelos/vuelo-grupal_/vuelo-grupal-2.png new file mode 100644 index 0000000..2c9c275 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-grupal_/vuelo-grupal-2.png differ diff --git a/public/assets/img/vuelos/vuelo-grupal_/vuelo-grupal-2.webp b/public/assets/img/vuelos/vuelo-grupal_/vuelo-grupal-2.webp new file mode 100644 index 0000000..338d008 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-grupal_/vuelo-grupal-2.webp differ diff --git a/public/assets/img/vuelos/vuelo-grupal_/vuelo-grupal-mini.png b/public/assets/img/vuelos/vuelo-grupal_/vuelo-grupal-mini.png new file mode 100644 index 0000000..7c054eb Binary files /dev/null and b/public/assets/img/vuelos/vuelo-grupal_/vuelo-grupal-mini.png differ diff --git a/public/assets/img/vuelos/vuelo-grupal_/vuelo-grupal.jpg b/public/assets/img/vuelos/vuelo-grupal_/vuelo-grupal.jpg new file mode 100644 index 0000000..abd16b8 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-grupal_/vuelo-grupal.jpg differ diff --git a/public/assets/img/vuelos/vuelo-grupal_/vuelo-grupal.png b/public/assets/img/vuelos/vuelo-grupal_/vuelo-grupal.png new file mode 100644 index 0000000..36aef37 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-grupal_/vuelo-grupal.png differ diff --git a/public/assets/img/vuelos/vuelo-grupal_/vuelo-grupal.webp b/public/assets/img/vuelos/vuelo-grupal_/vuelo-grupal.webp new file mode 100644 index 0000000..339bc4f Binary files /dev/null and b/public/assets/img/vuelos/vuelo-grupal_/vuelo-grupal.webp differ diff --git a/public/assets/img/vuelos/vuelo-grupal_/vuelo-grupal_temp.png b/public/assets/img/vuelos/vuelo-grupal_/vuelo-grupal_temp.png new file mode 100644 index 0000000..3331416 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-grupal_/vuelo-grupal_temp.png differ diff --git a/public/assets/img/vuelos/vuelo-privado-teotihuacan/0.jpg b/public/assets/img/vuelos/vuelo-privado-teotihuacan/0.jpg new file mode 100644 index 0000000..b49e81b Binary files /dev/null and b/public/assets/img/vuelos/vuelo-privado-teotihuacan/0.jpg differ diff --git a/public/assets/img/vuelos/vuelo-privado-teotihuacan/1.jpg b/public/assets/img/vuelos/vuelo-privado-teotihuacan/1.jpg new file mode 100644 index 0000000..61ed2e1 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-privado-teotihuacan/1.jpg differ diff --git a/public/assets/img/vuelos/vuelo-privado-teotihuacan/2.jpg b/public/assets/img/vuelos/vuelo-privado-teotihuacan/2.jpg new file mode 100644 index 0000000..6f15771 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-privado-teotihuacan/2.jpg differ diff --git a/public/assets/img/vuelos/vuelo-privado-teotihuacan/3.jpg b/public/assets/img/vuelos/vuelo-privado-teotihuacan/3.jpg new file mode 100644 index 0000000..5fdbcf2 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-privado-teotihuacan/3.jpg differ diff --git a/public/assets/img/vuelos/vuelo-privado-teotihuacan/4.jpg b/public/assets/img/vuelos/vuelo-privado-teotihuacan/4.jpg new file mode 100644 index 0000000..561411d Binary files /dev/null and b/public/assets/img/vuelos/vuelo-privado-teotihuacan/4.jpg differ diff --git a/public/assets/img/vuelos/vuelo-privado-teotihuacan/5.jpg b/public/assets/img/vuelos/vuelo-privado-teotihuacan/5.jpg new file mode 100644 index 0000000..a992fc7 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-privado-teotihuacan/5.jpg differ diff --git a/public/assets/img/vuelos/vuelo-privado-teotihuacan/6.jpg b/public/assets/img/vuelos/vuelo-privado-teotihuacan/6.jpg new file mode 100644 index 0000000..61edf00 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-privado-teotihuacan/6.jpg differ diff --git a/public/assets/img/vuelos/vuelo-privado-teotihuacan/7.jpg b/public/assets/img/vuelos/vuelo-privado-teotihuacan/7.jpg new file mode 100644 index 0000000..6d2517c Binary files /dev/null and b/public/assets/img/vuelos/vuelo-privado-teotihuacan/7.jpg differ diff --git a/public/assets/img/vuelos/vuelo-privado-teotihuacan/8.jpg b/public/assets/img/vuelos/vuelo-privado-teotihuacan/8.jpg new file mode 100644 index 0000000..12d494c Binary files /dev/null and b/public/assets/img/vuelos/vuelo-privado-teotihuacan/8.jpg differ diff --git a/public/assets/img/vuelos/vuelo-privado-teotihuacan/9.jpg b/public/assets/img/vuelos/vuelo-privado-teotihuacan/9.jpg new file mode 100644 index 0000000..f613bd6 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-privado-teotihuacan/9.jpg differ diff --git a/public/assets/img/vuelos/vuelo-privado-teotihuacan/vuelo-privado-teotihuacan-2.jpg b/public/assets/img/vuelos/vuelo-privado-teotihuacan/vuelo-privado-teotihuacan-2.jpg new file mode 100644 index 0000000..fa840ef Binary files /dev/null and b/public/assets/img/vuelos/vuelo-privado-teotihuacan/vuelo-privado-teotihuacan-2.jpg differ diff --git a/public/assets/img/vuelos/vuelo-privado-teotihuacan/vuelo-privado-teotihuacan.jpg b/public/assets/img/vuelos/vuelo-privado-teotihuacan/vuelo-privado-teotihuacan.jpg new file mode 100644 index 0000000..e81a098 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-privado-teotihuacan/vuelo-privado-teotihuacan.jpg differ diff --git a/public/assets/img/vuelos/vuelo-privado/0.jpg b/public/assets/img/vuelos/vuelo-privado/0.jpg new file mode 100644 index 0000000..1b91423 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-privado/0.jpg differ diff --git a/public/assets/img/vuelos/vuelo-privado/0.png b/public/assets/img/vuelos/vuelo-privado/0.png new file mode 100644 index 0000000..83947e0 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-privado/0.png differ diff --git a/public/assets/img/vuelos/vuelo-privado/1.jpg b/public/assets/img/vuelos/vuelo-privado/1.jpg new file mode 100644 index 0000000..91a9261 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-privado/1.jpg differ diff --git a/public/assets/img/vuelos/vuelo-privado/1.png b/public/assets/img/vuelos/vuelo-privado/1.png new file mode 100644 index 0000000..f13da3b Binary files /dev/null and b/public/assets/img/vuelos/vuelo-privado/1.png differ diff --git a/public/assets/img/vuelos/vuelo-privado/2.jpg b/public/assets/img/vuelos/vuelo-privado/2.jpg new file mode 100644 index 0000000..d4a044c Binary files /dev/null and b/public/assets/img/vuelos/vuelo-privado/2.jpg differ diff --git a/public/assets/img/vuelos/vuelo-privado/2.png b/public/assets/img/vuelos/vuelo-privado/2.png new file mode 100644 index 0000000..fb3bf5c Binary files /dev/null and b/public/assets/img/vuelos/vuelo-privado/2.png differ diff --git a/public/assets/img/vuelos/vuelo-privado/3.jpg b/public/assets/img/vuelos/vuelo-privado/3.jpg new file mode 100644 index 0000000..efd72ca Binary files /dev/null and b/public/assets/img/vuelos/vuelo-privado/3.jpg differ diff --git a/public/assets/img/vuelos/vuelo-privado/3.png b/public/assets/img/vuelos/vuelo-privado/3.png new file mode 100644 index 0000000..271826c Binary files /dev/null and b/public/assets/img/vuelos/vuelo-privado/3.png differ diff --git a/public/assets/img/vuelos/vuelo-privado/4.jpg b/public/assets/img/vuelos/vuelo-privado/4.jpg new file mode 100644 index 0000000..5f8f025 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-privado/4.jpg differ diff --git a/public/assets/img/vuelos/vuelo-privado/4.png b/public/assets/img/vuelos/vuelo-privado/4.png new file mode 100644 index 0000000..9e87b53 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-privado/4.png differ diff --git a/public/assets/img/vuelos/vuelo-privado/Vuelo privado.jpg b/public/assets/img/vuelos/vuelo-privado/Vuelo privado.jpg new file mode 100644 index 0000000..f23135b Binary files /dev/null and b/public/assets/img/vuelos/vuelo-privado/Vuelo privado.jpg differ diff --git a/public/assets/img/vuelos/vuelo-privado/vuelo-en-globo-privado-2.webp b/public/assets/img/vuelos/vuelo-privado/vuelo-en-globo-privado-2.webp new file mode 100644 index 0000000..340dd2c Binary files /dev/null and b/public/assets/img/vuelos/vuelo-privado/vuelo-en-globo-privado-2.webp differ diff --git a/public/assets/img/vuelos/vuelo-privado/vuelo-en-globo-privado-3.webp b/public/assets/img/vuelos/vuelo-privado/vuelo-en-globo-privado-3.webp new file mode 100644 index 0000000..eeb3cd0 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-privado/vuelo-en-globo-privado-3.webp differ diff --git a/public/assets/img/vuelos/vuelo-privado/vuelo-grupal.png b/public/assets/img/vuelos/vuelo-privado/vuelo-grupal.png new file mode 100644 index 0000000..3331416 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-privado/vuelo-grupal.png differ diff --git a/public/assets/img/vuelos/vuelo-privado/vuelo-privado-2-invertido.png b/public/assets/img/vuelos/vuelo-privado/vuelo-privado-2-invertido.png new file mode 100644 index 0000000..43bc738 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-privado/vuelo-privado-2-invertido.png differ diff --git a/public/assets/img/vuelos/vuelo-privado/vuelo-privado-2.png b/public/assets/img/vuelos/vuelo-privado/vuelo-privado-2.png new file mode 100644 index 0000000..91cf9fa Binary files /dev/null and b/public/assets/img/vuelos/vuelo-privado/vuelo-privado-2.png differ diff --git a/public/assets/img/vuelos/vuelo-privado/vuelo-privado-2.webp b/public/assets/img/vuelos/vuelo-privado/vuelo-privado-2.webp new file mode 100644 index 0000000..da6c957 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-privado/vuelo-privado-2.webp differ diff --git a/public/assets/img/vuelos/vuelo-privado/vuelo-privado-mini.png b/public/assets/img/vuelos/vuelo-privado/vuelo-privado-mini.png new file mode 100644 index 0000000..03d2c9f Binary files /dev/null and b/public/assets/img/vuelos/vuelo-privado/vuelo-privado-mini.png differ diff --git a/public/assets/img/vuelos/vuelo-privado/vuelo-privado.jpg b/public/assets/img/vuelos/vuelo-privado/vuelo-privado.jpg new file mode 100644 index 0000000..dbb22d4 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-privado/vuelo-privado.jpg differ diff --git a/public/assets/img/vuelos/vuelo-privado/vuelo-privado.png b/public/assets/img/vuelos/vuelo-privado/vuelo-privado.png new file mode 100644 index 0000000..8a6e818 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-privado/vuelo-privado.png differ diff --git a/public/assets/img/vuelos/vuelo-privado/vuelo-privado.webp b/public/assets/img/vuelos/vuelo-privado/vuelo-privado.webp new file mode 100644 index 0000000..35b2559 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-privado/vuelo-privado.webp differ diff --git a/public/assets/img/vuelos/vuelo-privado/vuelo-privado_temp.png b/public/assets/img/vuelos/vuelo-privado/vuelo-privado_temp.png new file mode 100644 index 0000000..39a8fb6 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-privado/vuelo-privado_temp.png differ diff --git a/public/assets/img/vuelos/vuelo-todo-incluido-oferta/0.jpg b/public/assets/img/vuelos/vuelo-todo-incluido-oferta/0.jpg new file mode 100644 index 0000000..3df7830 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-todo-incluido-oferta/0.jpg differ diff --git a/public/assets/img/vuelos/vuelo-todo-incluido-oferta/1.jpg b/public/assets/img/vuelos/vuelo-todo-incluido-oferta/1.jpg new file mode 100644 index 0000000..6441364 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-todo-incluido-oferta/1.jpg differ diff --git a/public/assets/img/vuelos/vuelo-todo-incluido-oferta/2.jpg b/public/assets/img/vuelos/vuelo-todo-incluido-oferta/2.jpg new file mode 100644 index 0000000..4c5bf43 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-todo-incluido-oferta/2.jpg differ diff --git a/public/assets/img/vuelos/vuelo-todo-incluido-oferta/3.jpg b/public/assets/img/vuelos/vuelo-todo-incluido-oferta/3.jpg new file mode 100644 index 0000000..99f043a Binary files /dev/null and b/public/assets/img/vuelos/vuelo-todo-incluido-oferta/3.jpg differ diff --git a/public/assets/img/vuelos/vuelo-todo-incluido-oferta/4.jpg b/public/assets/img/vuelos/vuelo-todo-incluido-oferta/4.jpg new file mode 100644 index 0000000..58d08c1 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-todo-incluido-oferta/4.jpg differ diff --git a/public/assets/img/vuelos/vuelo-todo-incluido-oferta/5.jpg b/public/assets/img/vuelos/vuelo-todo-incluido-oferta/5.jpg new file mode 100644 index 0000000..69c9275 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-todo-incluido-oferta/5.jpg differ diff --git a/public/assets/img/vuelos/vuelo-todo-incluido-oferta/6.jpg b/public/assets/img/vuelos/vuelo-todo-incluido-oferta/6.jpg new file mode 100644 index 0000000..a7fdb0e Binary files /dev/null and b/public/assets/img/vuelos/vuelo-todo-incluido-oferta/6.jpg differ diff --git a/public/assets/img/vuelos/vuelo-todo-incluido-oferta/7.jpg b/public/assets/img/vuelos/vuelo-todo-incluido-oferta/7.jpg new file mode 100644 index 0000000..31380b9 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-todo-incluido-oferta/7.jpg differ diff --git a/public/assets/img/vuelos/vuelo-todo-incluido-oferta/8.jpg b/public/assets/img/vuelos/vuelo-todo-incluido-oferta/8.jpg new file mode 100644 index 0000000..07560cb Binary files /dev/null and b/public/assets/img/vuelos/vuelo-todo-incluido-oferta/8.jpg differ diff --git a/public/assets/img/vuelos/vuelo-todo-incluido-oferta/9.jpg b/public/assets/img/vuelos/vuelo-todo-incluido-oferta/9.jpg new file mode 100644 index 0000000..bcdce31 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-todo-incluido-oferta/9.jpg differ diff --git a/public/assets/img/vuelos/vuelo-todo-incluido-oferta/vuelo-todo-incluido-oferta-2.jpg b/public/assets/img/vuelos/vuelo-todo-incluido-oferta/vuelo-todo-incluido-oferta-2.jpg new file mode 100644 index 0000000..e3940df Binary files /dev/null and b/public/assets/img/vuelos/vuelo-todo-incluido-oferta/vuelo-todo-incluido-oferta-2.jpg differ diff --git a/public/assets/img/vuelos/vuelo-todo-incluido-oferta/vuelo-todo-incluido-oferta.jpg b/public/assets/img/vuelos/vuelo-todo-incluido-oferta/vuelo-todo-incluido-oferta.jpg new file mode 100644 index 0000000..96772f4 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-todo-incluido-oferta/vuelo-todo-incluido-oferta.jpg differ diff --git a/public/assets/img/vuelos/vuelo-todo-incluido-oferta_/0.jpg b/public/assets/img/vuelos/vuelo-todo-incluido-oferta_/0.jpg new file mode 100644 index 0000000..8b55583 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-todo-incluido-oferta_/0.jpg differ diff --git a/public/assets/img/vuelos/vuelo-todo-incluido-oferta_/0.png b/public/assets/img/vuelos/vuelo-todo-incluido-oferta_/0.png new file mode 100644 index 0000000..b0edc8e Binary files /dev/null and b/public/assets/img/vuelos/vuelo-todo-incluido-oferta_/0.png differ diff --git a/public/assets/img/vuelos/vuelo-todo-incluido-oferta_/1.jpg b/public/assets/img/vuelos/vuelo-todo-incluido-oferta_/1.jpg new file mode 100644 index 0000000..422eacf Binary files /dev/null and b/public/assets/img/vuelos/vuelo-todo-incluido-oferta_/1.jpg differ diff --git a/public/assets/img/vuelos/vuelo-todo-incluido-oferta_/1.png b/public/assets/img/vuelos/vuelo-todo-incluido-oferta_/1.png new file mode 100644 index 0000000..de9d0de Binary files /dev/null and b/public/assets/img/vuelos/vuelo-todo-incluido-oferta_/1.png differ diff --git a/public/assets/img/vuelos/vuelo-todo-incluido-oferta_/2.jpg b/public/assets/img/vuelos/vuelo-todo-incluido-oferta_/2.jpg new file mode 100644 index 0000000..9fe8b9e Binary files /dev/null and b/public/assets/img/vuelos/vuelo-todo-incluido-oferta_/2.jpg differ diff --git a/public/assets/img/vuelos/vuelo-todo-incluido-oferta_/2.png b/public/assets/img/vuelos/vuelo-todo-incluido-oferta_/2.png new file mode 100644 index 0000000..9974235 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-todo-incluido-oferta_/2.png differ diff --git a/public/assets/img/vuelos/vuelo-todo-incluido-oferta_/3.jpg b/public/assets/img/vuelos/vuelo-todo-incluido-oferta_/3.jpg new file mode 100644 index 0000000..0cfba25 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-todo-incluido-oferta_/3.jpg differ diff --git a/public/assets/img/vuelos/vuelo-todo-incluido-oferta_/3.png b/public/assets/img/vuelos/vuelo-todo-incluido-oferta_/3.png new file mode 100644 index 0000000..de51aa3 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-todo-incluido-oferta_/3.png differ diff --git a/public/assets/img/vuelos/vuelo-todo-incluido-oferta_/4.jpg b/public/assets/img/vuelos/vuelo-todo-incluido-oferta_/4.jpg new file mode 100644 index 0000000..26ff8d1 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-todo-incluido-oferta_/4.jpg differ diff --git a/public/assets/img/vuelos/vuelo-todo-incluido-oferta_/4.png b/public/assets/img/vuelos/vuelo-todo-incluido-oferta_/4.png new file mode 100644 index 0000000..ab48a35 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-todo-incluido-oferta_/4.png differ diff --git a/public/assets/img/vuelos/vuelo-todo-incluido-oferta_/vuelo-todo-incluido-oferta-2.jpg b/public/assets/img/vuelos/vuelo-todo-incluido-oferta_/vuelo-todo-incluido-oferta-2.jpg new file mode 100644 index 0000000..5311d63 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-todo-incluido-oferta_/vuelo-todo-incluido-oferta-2.jpg differ diff --git a/public/assets/img/vuelos/vuelo-todo-incluido-oferta_/vuelo-todo-incluido-oferta.jpg b/public/assets/img/vuelos/vuelo-todo-incluido-oferta_/vuelo-todo-incluido-oferta.jpg new file mode 100644 index 0000000..11d8651 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-todo-incluido-oferta_/vuelo-todo-incluido-oferta.jpg differ diff --git a/public/assets/img/vuelos/vuelo-todo-incluido-oferta_/vuelo-todo-incluido-oferta_2023.jpg b/public/assets/img/vuelos/vuelo-todo-incluido-oferta_/vuelo-todo-incluido-oferta_2023.jpg new file mode 100644 index 0000000..f3ebf4e Binary files /dev/null and b/public/assets/img/vuelos/vuelo-todo-incluido-oferta_/vuelo-todo-incluido-oferta_2023.jpg differ diff --git a/public/assets/img/vuelos/vuelo-todo-incluido-oferta_/vuelo-todo-incluido-oferta_old.jpg b/public/assets/img/vuelos/vuelo-todo-incluido-oferta_/vuelo-todo-incluido-oferta_old.jpg new file mode 100644 index 0000000..085ed83 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-todo-incluido-oferta_/vuelo-todo-incluido-oferta_old.jpg differ diff --git a/public/assets/img/vuelos/vuelo-todo-incluido-oferta_/vuelo-todo-incluido-oferta_old2.png b/public/assets/img/vuelos/vuelo-todo-incluido-oferta_/vuelo-todo-incluido-oferta_old2.png new file mode 100644 index 0000000..dead4a5 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-todo-incluido-oferta_/vuelo-todo-incluido-oferta_old2.png differ diff --git a/public/assets/img/vuelos/vuelo-todo-incluido-oferta_/vuelo-todo-incluido_old.png b/public/assets/img/vuelos/vuelo-todo-incluido-oferta_/vuelo-todo-incluido_old.png new file mode 100644 index 0000000..77852cb Binary files /dev/null and b/public/assets/img/vuelos/vuelo-todo-incluido-oferta_/vuelo-todo-incluido_old.png differ diff --git a/public/assets/img/vuelos/vuelo-todo-incluido-oferta_/vuelo-todo-incluido_old2.jpg b/public/assets/img/vuelos/vuelo-todo-incluido-oferta_/vuelo-todo-incluido_old2.jpg new file mode 100644 index 0000000..5fb44d8 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-todo-incluido-oferta_/vuelo-todo-incluido_old2.jpg differ diff --git a/public/assets/img/vuelos/vuelo-todo-incluido/0.jpg b/public/assets/img/vuelos/vuelo-todo-incluido/0.jpg new file mode 100644 index 0000000..8b55583 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-todo-incluido/0.jpg differ diff --git a/public/assets/img/vuelos/vuelo-todo-incluido/0.png b/public/assets/img/vuelos/vuelo-todo-incluido/0.png new file mode 100644 index 0000000..b0edc8e Binary files /dev/null and b/public/assets/img/vuelos/vuelo-todo-incluido/0.png differ diff --git a/public/assets/img/vuelos/vuelo-todo-incluido/1.jpg b/public/assets/img/vuelos/vuelo-todo-incluido/1.jpg new file mode 100644 index 0000000..422eacf Binary files /dev/null and b/public/assets/img/vuelos/vuelo-todo-incluido/1.jpg differ diff --git a/public/assets/img/vuelos/vuelo-todo-incluido/1.png b/public/assets/img/vuelos/vuelo-todo-incluido/1.png new file mode 100644 index 0000000..de9d0de Binary files /dev/null and b/public/assets/img/vuelos/vuelo-todo-incluido/1.png differ diff --git a/public/assets/img/vuelos/vuelo-todo-incluido/2.jpg b/public/assets/img/vuelos/vuelo-todo-incluido/2.jpg new file mode 100644 index 0000000..9fe8b9e Binary files /dev/null and b/public/assets/img/vuelos/vuelo-todo-incluido/2.jpg differ diff --git a/public/assets/img/vuelos/vuelo-todo-incluido/2.png b/public/assets/img/vuelos/vuelo-todo-incluido/2.png new file mode 100644 index 0000000..9974235 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-todo-incluido/2.png differ diff --git a/public/assets/img/vuelos/vuelo-todo-incluido/3.jpg b/public/assets/img/vuelos/vuelo-todo-incluido/3.jpg new file mode 100644 index 0000000..0cfba25 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-todo-incluido/3.jpg differ diff --git a/public/assets/img/vuelos/vuelo-todo-incluido/3.png b/public/assets/img/vuelos/vuelo-todo-incluido/3.png new file mode 100644 index 0000000..de51aa3 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-todo-incluido/3.png differ diff --git a/public/assets/img/vuelos/vuelo-todo-incluido/4.jpg b/public/assets/img/vuelos/vuelo-todo-incluido/4.jpg new file mode 100644 index 0000000..26ff8d1 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-todo-incluido/4.jpg differ diff --git a/public/assets/img/vuelos/vuelo-todo-incluido/4.png b/public/assets/img/vuelos/vuelo-todo-incluido/4.png new file mode 100644 index 0000000..ab48a35 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-todo-incluido/4.png differ diff --git a/public/assets/img/vuelos/vuelo-todo-incluido/Vuelo todo incluido.jpg b/public/assets/img/vuelos/vuelo-todo-incluido/Vuelo todo incluido.jpg new file mode 100644 index 0000000..b5218be Binary files /dev/null and b/public/assets/img/vuelos/vuelo-todo-incluido/Vuelo todo incluido.jpg differ diff --git a/public/assets/img/vuelos/vuelo-todo-incluido/vuelo-en-globo-todo-incluido-3.webp b/public/assets/img/vuelos/vuelo-todo-incluido/vuelo-en-globo-todo-incluido-3.webp new file mode 100644 index 0000000..fba8791 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-todo-incluido/vuelo-en-globo-todo-incluido-3.webp differ diff --git a/public/assets/img/vuelos/vuelo-todo-incluido/vuelo-en-globo-todo-incluido.webp b/public/assets/img/vuelos/vuelo-todo-incluido/vuelo-en-globo-todo-incluido.webp new file mode 100644 index 0000000..3e056ee Binary files /dev/null and b/public/assets/img/vuelos/vuelo-todo-incluido/vuelo-en-globo-todo-incluido.webp differ diff --git a/public/assets/img/vuelos/vuelo-todo-incluido/vuelo-todo-incluido-2-invertido.png b/public/assets/img/vuelos/vuelo-todo-incluido/vuelo-todo-incluido-2-invertido.png new file mode 100644 index 0000000..b39b350 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-todo-incluido/vuelo-todo-incluido-2-invertido.png differ diff --git a/public/assets/img/vuelos/vuelo-todo-incluido/vuelo-todo-incluido-2.png b/public/assets/img/vuelos/vuelo-todo-incluido/vuelo-todo-incluido-2.png new file mode 100644 index 0000000..b6c501c Binary files /dev/null and b/public/assets/img/vuelos/vuelo-todo-incluido/vuelo-todo-incluido-2.png differ diff --git a/public/assets/img/vuelos/vuelo-todo-incluido/vuelo-todo-incluido-2.webp b/public/assets/img/vuelos/vuelo-todo-incluido/vuelo-todo-incluido-2.webp new file mode 100644 index 0000000..bc8d5c2 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-todo-incluido/vuelo-todo-incluido-2.webp differ diff --git a/public/assets/img/vuelos/vuelo-todo-incluido/vuelo-todo-incluido-mini.png b/public/assets/img/vuelos/vuelo-todo-incluido/vuelo-todo-incluido-mini.png new file mode 100644 index 0000000..d2ca748 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-todo-incluido/vuelo-todo-incluido-mini.png differ diff --git a/public/assets/img/vuelos/vuelo-todo-incluido/vuelo-todo-incluido.jpg b/public/assets/img/vuelos/vuelo-todo-incluido/vuelo-todo-incluido.jpg new file mode 100644 index 0000000..5fb44d8 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-todo-incluido/vuelo-todo-incluido.jpg differ diff --git a/public/assets/img/vuelos/vuelo-todo-incluido/vuelo-todo-incluido.png b/public/assets/img/vuelos/vuelo-todo-incluido/vuelo-todo-incluido.png new file mode 100644 index 0000000..0e7892a Binary files /dev/null and b/public/assets/img/vuelos/vuelo-todo-incluido/vuelo-todo-incluido.png differ diff --git a/public/assets/img/vuelos/vuelo-todo-incluido/vuelo-todo-incluido.pngold b/public/assets/img/vuelos/vuelo-todo-incluido/vuelo-todo-incluido.pngold new file mode 100644 index 0000000..5846d9d Binary files /dev/null and b/public/assets/img/vuelos/vuelo-todo-incluido/vuelo-todo-incluido.pngold differ diff --git a/public/assets/img/vuelos/vuelo-todo-incluido/vuelo-todo-incluido.webp b/public/assets/img/vuelos/vuelo-todo-incluido/vuelo-todo-incluido.webp new file mode 100644 index 0000000..fd88fac Binary files /dev/null and b/public/assets/img/vuelos/vuelo-todo-incluido/vuelo-todo-incluido.webp differ diff --git a/public/assets/img/vuelos/vuelo-todo-incluido/vuelo-todo-incluido_old.png b/public/assets/img/vuelos/vuelo-todo-incluido/vuelo-todo-incluido_old.png new file mode 100644 index 0000000..77852cb Binary files /dev/null and b/public/assets/img/vuelos/vuelo-todo-incluido/vuelo-todo-incluido_old.png differ diff --git a/public/assets/img/vuelos/vuelo-todo-incluido/vuelo-todo-incluido_temp.png b/public/assets/img/vuelos/vuelo-todo-incluido/vuelo-todo-incluido_temp.png new file mode 100644 index 0000000..5846d9d Binary files /dev/null and b/public/assets/img/vuelos/vuelo-todo-incluido/vuelo-todo-incluido_temp.png differ diff --git a/public/assets/img/vuelos/vuelo-transporte/0.jpg b/public/assets/img/vuelos/vuelo-transporte/0.jpg new file mode 100644 index 0000000..0d4194f Binary files /dev/null and b/public/assets/img/vuelos/vuelo-transporte/0.jpg differ diff --git a/public/assets/img/vuelos/vuelo-transporte/1.jpg b/public/assets/img/vuelos/vuelo-transporte/1.jpg new file mode 100644 index 0000000..521383e Binary files /dev/null and b/public/assets/img/vuelos/vuelo-transporte/1.jpg differ diff --git a/public/assets/img/vuelos/vuelo-transporte/2.jpg b/public/assets/img/vuelos/vuelo-transporte/2.jpg new file mode 100644 index 0000000..b260b56 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-transporte/2.jpg differ diff --git a/public/assets/img/vuelos/vuelo-transporte/3.jpg b/public/assets/img/vuelos/vuelo-transporte/3.jpg new file mode 100644 index 0000000..84d62de Binary files /dev/null and b/public/assets/img/vuelos/vuelo-transporte/3.jpg differ diff --git a/public/assets/img/vuelos/vuelo-transporte/4.jpg b/public/assets/img/vuelos/vuelo-transporte/4.jpg new file mode 100644 index 0000000..5428e65 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-transporte/4.jpg differ diff --git a/public/assets/img/vuelos/vuelo-transporte/5.jpg b/public/assets/img/vuelos/vuelo-transporte/5.jpg new file mode 100644 index 0000000..497d516 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-transporte/5.jpg differ diff --git a/public/assets/img/vuelos/vuelo-transporte/6.jpg b/public/assets/img/vuelos/vuelo-transporte/6.jpg new file mode 100644 index 0000000..d3c4147 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-transporte/6.jpg differ diff --git a/public/assets/img/vuelos/vuelo-transporte/vuelo-transporte-2.jpg b/public/assets/img/vuelos/vuelo-transporte/vuelo-transporte-2.jpg new file mode 100644 index 0000000..b143ee0 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-transporte/vuelo-transporte-2.jpg differ diff --git a/public/assets/img/vuelos/vuelo-transporte/vuelo-transporte.jpg b/public/assets/img/vuelos/vuelo-transporte/vuelo-transporte.jpg new file mode 100644 index 0000000..a92639f Binary files /dev/null and b/public/assets/img/vuelos/vuelo-transporte/vuelo-transporte.jpg differ diff --git a/public/assets/img/vuelos/vuelo-transporte_/0.jpg b/public/assets/img/vuelos/vuelo-transporte_/0.jpg new file mode 100644 index 0000000..0d4194f Binary files /dev/null and b/public/assets/img/vuelos/vuelo-transporte_/0.jpg differ diff --git a/public/assets/img/vuelos/vuelo-transporte_/1.jpg b/public/assets/img/vuelos/vuelo-transporte_/1.jpg new file mode 100644 index 0000000..8e9b84f Binary files /dev/null and b/public/assets/img/vuelos/vuelo-transporte_/1.jpg differ diff --git a/public/assets/img/vuelos/vuelo-transporte_/2.jpg b/public/assets/img/vuelos/vuelo-transporte_/2.jpg new file mode 100644 index 0000000..b12dea6 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-transporte_/2.jpg differ diff --git a/public/assets/img/vuelos/vuelo-transporte_/3.jpg b/public/assets/img/vuelos/vuelo-transporte_/3.jpg new file mode 100644 index 0000000..c8046bd Binary files /dev/null and b/public/assets/img/vuelos/vuelo-transporte_/3.jpg differ diff --git a/public/assets/img/vuelos/vuelo-transporte_/4.jpg b/public/assets/img/vuelos/vuelo-transporte_/4.jpg new file mode 100644 index 0000000..5049637 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-transporte_/4.jpg differ diff --git a/public/assets/img/vuelos/vuelo-transporte_/5.jpg b/public/assets/img/vuelos/vuelo-transporte_/5.jpg new file mode 100644 index 0000000..052eef6 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-transporte_/5.jpg differ diff --git a/public/assets/img/vuelos/vuelo-transporte_/6.jpg b/public/assets/img/vuelos/vuelo-transporte_/6.jpg new file mode 100644 index 0000000..4177572 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-transporte_/6.jpg differ diff --git a/public/assets/img/vuelos/vuelo-transporte_/vuelo-transporte-2.jpg b/public/assets/img/vuelos/vuelo-transporte_/vuelo-transporte-2.jpg new file mode 100644 index 0000000..b143ee0 Binary files /dev/null and b/public/assets/img/vuelos/vuelo-transporte_/vuelo-transporte-2.jpg differ diff --git a/public/assets/img/vuelos/vuelo-transporte_/vuelo-transporte.jpg b/public/assets/img/vuelos/vuelo-transporte_/vuelo-transporte.jpg new file mode 100644 index 0000000..a92639f Binary files /dev/null and b/public/assets/img/vuelos/vuelo-transporte_/vuelo-transporte.jpg differ diff --git a/public/assets/js/aviso-min.js b/public/assets/js/aviso-min.js new file mode 100644 index 0000000..9002279 --- /dev/null +++ b/public/assets/js/aviso-min.js @@ -0,0 +1 @@ +document.querySelectorAll(".privacy-toc-link").forEach((t=>{t.addEventListener("click",(function(t){t.preventDefault();const e=this.getAttribute("href"),o=document.querySelector(e);if(o){const t=100,e=o.getBoundingClientRect().top+window.pageYOffset-t;window.scrollTo({top:e,behavior:"smooth"})}}))})); \ No newline at end of file diff --git a/public/assets/js/aviso.js b/public/assets/js/aviso.js new file mode 100644 index 0000000..9341712 --- /dev/null +++ b/public/assets/js/aviso.js @@ -0,0 +1,19 @@ +// Smooth scroll para la tabla de contenidos +document.querySelectorAll(".privacy-toc-link").forEach((anchor) => { + anchor.addEventListener("click", function (e) { + e.preventDefault(); + const targetId = this.getAttribute("href"); + const targetElement = document.querySelector(targetId); + if (targetElement) { + const offset = 100; + const elementPosition = targetElement.getBoundingClientRect().top; + const offsetPosition = + elementPosition + window.pageYOffset - offset; + + window.scrollTo({ + top: offsetPosition, + behavior: "smooth", + }); + } + }); +}); diff --git a/public/assets/js/checkkout.js b/public/assets/js/checkkout.js new file mode 100644 index 0000000..267f992 --- /dev/null +++ b/public/assets/js/checkkout.js @@ -0,0 +1,75 @@ +document.addEventListener("DOMContentLoaded", () => { + // Navegación móvil + const hamburger = document.querySelector(".hamburger-menu"); + const mobileMenu = document.querySelector(".mobile-menu"); + const menuOverlay = document.querySelector(".menu-overlay"); + + if (hamburger && mobileMenu && menuOverlay) { + hamburger.addEventListener("click", () => { + hamburger.classList.toggle("active"); + mobileMenu.classList.toggle("active"); + menuOverlay.classList.toggle("active"); + document.body.style.overflow = mobileMenu.classList.contains( + "active", + ) + ? "hidden" + : ""; + }); + + menuOverlay.addEventListener("click", () => { + hamburger.classList.remove("active"); + mobileMenu.classList.remove("active"); + menuOverlay.classList.remove("active"); + document.body.style.overflow = ""; + }); + } + + // Tema claro/oscuro + const themeToggle = document.querySelector(".theme-toggle"); + if (themeToggle) { + themeToggle.addEventListener("click", () => { + document.body.classList.toggle("light-mode"); + localStorage.setItem( + "theme", + document.body.classList.contains("light-mode") + ? "light" + : "dark", + ); + }); + + const savedTheme = localStorage.getItem("theme"); + if (savedTheme === "light") { + document.body.classList.add("light-mode"); + } + } + + // Chatbot + const chatbotToggle = document.querySelector(".chatbot-toggle"); + const chatbotContainer = document.querySelector(".chatbot-container"); + const botClose = document.querySelector(".chatbot-close"); + + if (chatbotToggle && chatbotContainer) { + chatbotToggle.addEventListener("click", () => { + chatbotContainer.classList.toggle("active"); + }); + + if (botClose) { + botClose.addEventListener("click", () => { + chatbotContainer.classList.remove("active"); + }); + } + } + + // Preloader + window.addEventListener("load", () => { + const preloader = document.getElementById("preloader"); + if (preloader) { + setTimeout(() => { + preloader.classList.add("loaded"); + setTimeout(() => { + preloader.style.display = "none"; + }, 600); + }, 1500); + } + }); +}); diff --git a/public/assets/js/contacto.js b/public/assets/js/contacto.js new file mode 100644 index 0000000..e69de29 diff --git a/public/assets/js/instalaciones.js b/public/assets/js/instalaciones.js new file mode 100644 index 0000000..f069eb4 --- /dev/null +++ b/public/assets/js/instalaciones.js @@ -0,0 +1,120 @@ +(function () { + // Configuración de idioma para LightGallery + const lang = document.documentElement.lang || "es"; + const translations = { + es: { + zoom: "Zoom", + fullscreen: "Pantalla completa", + close: "Cerrar", + previous: "Anterior", + next: "Siguiente", + }, + en: { + zoom: "Zoom", + fullscreen: "Fullscreen", + close: "Close", + previous: "Previous", + next: "Next", + }, + }; + + const currentLang = translations[lang] || translations.es; + + // Esperar a que cargue todo + function initGallery() { + var galleryItems = document.querySelectorAll(".gallery-item"); + + if (!galleryItems || galleryItems.length === 0) { + console.log("No hay elementos de galería"); + return; + } + + // Verificar que lightGallery existe + if (typeof lightGallery === "undefined") { + console.error("LightGallery no cargado, reintentando..."); + setTimeout(initGallery, 500); + return; + } + + // Configurar cada item + galleryItems.forEach(function (item, index) { + item.addEventListener("click", function (e) { + e.preventDefault(); + + // Construir array de imágenes + var items = []; + galleryItems.forEach(function (el) { + var src = el.getAttribute("data-src"); + var title = ""; + var titleEl = el.querySelector(".gallery-title"); + if (titleEl) { + title = titleEl.textContent.trim(); + } + + items.push({ + src: src, + thumb: src, + subHtml: + '
' + + '

' + + title + + "

" + + "
", + }); + }); + + // Inicializar galería + var gallery = lightGallery(items, { + speed: 500, + showMaximizeIcon: true, + showZoomIcon: true, + zoom: true, + fullscreen: true, + thumbnail: true, + counter: true, + loop: true, + escKey: true, + lang: currentLang, + zoomPluginStrings: { + zoomIn: currentLang.zoom, + zoomOut: currentLang.zoom, + }, + fullscreenPluginStrings: { + fullscreen: currentLang.fullscreen, + }, + closable: true, + download: false, + }); + + gallery.openGallery(index); + }); + }); + + // Scroll Reveal + var observer = new IntersectionObserver( + function (entries) { + entries.forEach(function (entry) { + if (entry.isIntersecting) { + entry.target.classList.add("revealed"); + observer.unobserve(entry.target); + } + }); + }, + { + threshold: 0.1, + rootMargin: "50px", + }, + ); + + galleryItems.forEach(function (item) { + observer.observe(item); + }); + } + + // Iniciar cuando el DOM esté listo + if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", initGallery); + } else { + initGallery(); + } +})(); diff --git a/public/assets/js/main-min.js b/public/assets/js/main-min.js new file mode 100644 index 0000000..231b1a5 --- /dev/null +++ b/public/assets/js/main-min.js @@ -0,0 +1,1099 @@ +// ==================== CHATBOT VUELOS EN GLOBO - INTEGRACIÓN n8n ==================== +(function() { + "use strict"; + + // ---------- CONFIGURACIÓN CENTRAL ---------- + const CONFIG = { + N8N_WEBHOOK_ID: 'd9de4096-c21a-41e8-aa2c-30dd1de5c3ba/chat', + N8N_BASE_URL: 'https://n8npix.pixelteo.com/', + get CHAT_API_URL() { + return `${this.N8N_BASE_URL}/webhook/${this.N8N_WEBHOOK_ID}`; + }, + TIMEOUT_MS: 45000, + MAX_RETRIES: 2, + SESSION_STORAGE_KEY: 'chatbotSessionGlobo', + CHAT_HISTORY_KEY: 'chatbotHistoryGlobo', + MAX_HISTORY_MESSAGES: 50, + SESSION_MAX_IDLE_MINUTES: 30 // Tiempo en minutos para resetear conversación + }; + + // ---------- 0. INYECTAR CSS DINÁMICAMENTE ---------- + const injectStyles = () => { + if (document.getElementById('chatbot-dynamic-styles')) return; + const style = document.createElement('style'); + style.id = 'chatbot-dynamic-styles'; + style.textContent = ` + .chatbot-container { + position: fixed; + bottom: 90px; + right: 20px; + width: 380px; + max-width: calc(100vw - 40px); + height: 550px; + background: white; + border-radius: 24px; + box-shadow: 0 20px 40px rgba(0, 0, 0, 0.2), 0 0 0 1px rgba(0, 0, 0, 0.05); + display: flex; + flex-direction: column; + overflow: hidden; + z-index: 9999; + transform: scale(0.9); + opacity: 0; + visibility: hidden; + transition: all 0.25s ease; + font-family: system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, sans-serif; + } + .chatbot-container.active { + transform: scale(1); + opacity: 1; + visibility: visible; + } + .chatbot-header { + background: linear-gradient(135deg, #04cdff, #0099cc); + color: white; + padding: 16px 20px; + display: flex; + justify-content: space-between; + align-items: center; + font-weight: bold; + font-size: 1.1rem; + } + .chatbot-header h3 { + margin: 0; + font-size: 1.2rem; + display: flex; + align-items: center; + gap: 8px; + } + .chatbot-header button { + background: none; + border: none; + color: white; + font-size: 1.4rem; + cursor: pointer; + padding: 0; + line-height: 1; + opacity: 0.8; + transition: opacity 0.2s; + } + .chatbot-header button:hover { opacity: 1; } + .chatbot-messages { + flex: 1; + overflow-y: auto; + padding: 16px; + background: #f9fafb; + display: flex; + flex-direction: column; + gap: 12px; + } + .chatbot-message { + max-width: 85%; + padding: 10px 14px; + border-radius: 18px; + font-size: 0.9rem; + line-height: 1.4; + word-wrap: break-word; + animation: fadeInUp 0.2s ease-out; + } + .chatbot-message.user { + background: #04cdff; + color: white; + align-self: flex-end; + border-bottom-right-radius: 4px; + } + .chatbot-message.bot { + background: white; + color: #1f2937; + border: 1px solid #e5e7eb; + align-self: flex-start; + border-bottom-left-radius: 4px; + box-shadow: 0 1px 2px rgba(0,0,0,0.05); + } + .chatbot-message.error { + background: #fee2e2; + color: #b91c1c; + border: 1px solid #fecaca; + align-self: center; + text-align: center; + font-size: 0.85rem; + } + .chatbot-message img { + max-width: 100%; + border-radius: 16px; + margin: 10px 0; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + display: block; + } + .chatbot-message video { + max-width: 100%; + border-radius: 16px; + margin: 10px 0; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + display: block; + } + .chatbot-message h2, .chatbot-message h3 { + margin: 0.75rem 0 0.5rem; + font-weight: 600; + color: #1e293b; + } + .chatbot-message.bot h2 { + font-size: 1.3rem; + border-left: 4px solid #04cdff; + padding-left: 12px; + } + .chatbot-message p { margin: 0.5rem 0; } + .chatbot-message ul, .chatbot-message ol { + margin: 0.5rem 0; + padding-left: 1.5rem; + } + .chatbot-message li { margin: 0.25rem 0; } + .chatbot-message a { + color: #04cdff; + text-decoration: underline; + font-weight: 500; + cursor: pointer; + } + .chatbot-message a:hover { color: #0099cc; } + .chatbot-price { + color: #10b981; + font-weight: bold; + background: #e0f2fe; + padding: 2px 6px; + border-radius: 20px; + display: inline-block; + } + .chatbot-message .btn, .chatbot-message button, + .chatbot-message a[style*="background:#ff9800"] { + background: linear-gradient(135deg, #ff9800, #f57c00); + color: white; + border: none; + padding: 12px 22px; + border-radius: 10px; + font-weight: bold; + cursor: pointer; + transition: transform 0.1s, background 0.2s; + display: inline-block; + margin: 8px 4px 0 0; + text-decoration: none; + font-size: 1rem; + } + .chatbot-message .btn:hover, .chatbot-message button:hover, + .chatbot-message a[style*="background:#ff9800"]:hover { + background: linear-gradient(135deg, #f57c00, #e65100); + transform: translateY(-2px); + } + .chatbot-message code { + background: #f1f5f9; + padding: 2px 6px; + border-radius: 8px; + font-family: monospace; + font-size: 0.85em; + } + .chatbot-message hr { + margin: 12px 0; + border: none; + border-top: 1px solid #e2e8f0; + } + .chatbot-footer { + display: flex; + gap: 8px; + padding: 12px; + background: white; + border-top: 1px solid #e5e7eb; + } + .chatbot-footer input { + flex: 1; + padding: 10px 14px; + border: 1px solid #d1d5db; + border-radius: 40px; + font-size: 0.9rem; + outline: none; + transition: border 0.2s; + } + .chatbot-footer input:focus { + border-color: #04cdff; + box-shadow: 0 0 0 2px rgba(4,205,255,0.2); + } + .chatbot-footer button { + background: #04cdff; + border: none; + color: white; + width: 42px; + border-radius: 40px; + cursor: pointer; + transition: background 0.2s; + display: flex; + align-items: center; + justify-content: center; + } + .chatbot-footer button:hover:not(:disabled) { background: #0099cc; } + .chatbot-footer button:disabled { opacity: 0.5; cursor: not-allowed; } + .chatbot-status { + font-size: 0.7rem; + padding: 4px 12px; + background: #f1f5f9; + text-align: center; + color: #475569; + border-top: 1px solid #e2e8f0; + } + .chatbot-loading { + display: flex; + gap: 4px; + align-items: center; + justify-content: center; + padding: 8px; + background: white; + border-radius: 20px; + width: 50px; + margin: 8px 0; + align-self: flex-start; + } + .chatbot-loading span { + width: 8px; + height: 8px; + background: #94a3b8; + border-radius: 50%; + animation: bounce 1.4s infinite ease-in-out both; + } + .chatbot-loading span:nth-child(1) { animation-delay: -0.32s; } + .chatbot-loading span:nth-child(2) { animation-delay: -0.16s; } + @keyframes bounce { + 0%, 80%, 100% { transform: scale(0); } + 40% { transform: scale(1); } + } + @keyframes fadeInUp { + from { opacity: 0; transform: translateY(10px); } + to { opacity: 1; transform: translateY(0); } + } + body.light-mode .chatbot-container { background: #ffffff; } + body.light-mode .chatbot-messages { background: #f9fafb; } + body.light-mode .chatbot-message.bot { + background: #ffffff; + border-color: #e2e8f0; + } + /* NUEVO: Botón flotante de cierre para móviles */ + .chatbot-close-float { + display: none; /* oculto por defecto */ + position: fixed; + top: 16px; + right: 16px; + width: 40px; + height: 40px; + border-radius: 50%; + background: rgba(0,0,0,0.5); + color: white; + border: none; + font-size: 1.5rem; + cursor: pointer; + z-index: 10000; + align-items: center; + justify-content: center; + backdrop-filter: blur(4px); + transition: transform 0.2s; + } + .chatbot-close-float:hover { transform: scale(1.1); } + /* Ajuste para pantalla completa en móviles */ + @media (max-width: 480px) { + .chatbot-container { + width: 100%; + max-width: 100%; + bottom: 0; + right: 0; + left: 0; + height: 100vh; /* Altura completa de la ventana */ + height: 100dvh; /* Soporte para navegadores modernos */ + border-radius: 0; /* Sin bordes redondeados */ + transform: none; + transition: transform 0.25s ease, opacity 0.25s ease; + } + .chatbot-container.active { + transform: none; + opacity: 1; + visibility: visible; + } + .chatbot-message { max-width: 90%; } + .chatbot-close-float { + display: flex; /* visible solo en móviles cuando chat activo */ + } + } + .chatbot-float-btn { + position: fixed; + bottom: 20px; + right: 20px; + width: 60px; + height: 60px; + border-radius: 50%; + background: linear-gradient(135deg, #04cdff, #0099cc); + border: none; + box-shadow: 0 4px 12px rgba(0,0,0,0.2); + color: white; + font-size: 28px; + cursor: pointer; + z-index: 9998; + transition: transform 0.2s; + } + .chatbot-float-btn:hover { transform: scale(1.05); } + `; + document.head.appendChild(style); + }; + + // ==================== 1. UTILIDADES GENERALES ==================== + const utils = { + throttle(fn, delay) { + let lastCall = 0; + return function (...args) { + const now = Date.now(); + if (now - lastCall >= delay) { + lastCall = now; + fn.apply(this, args); + } + }; + }, + debounce(fn, delay) { + let timer; + return function (...args) { + clearTimeout(timer); + timer = setTimeout(() => fn.apply(this, args), delay); + }; + }, + escapeHtml(str) { + if (!str) return ''; + return str.replace(/[&<>"']/g, function(m) { + if (m === '&') return '&'; + if (m === '<') return '<'; + if (m === '>') return '>'; + if (m === '"') return '"'; + if (m === "'") return '''; + return m; + }); + }, + generateUUID() { + if (crypto && crypto.randomUUID) return crypto.randomUUID(); + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { + const r = Math.random() * 16 | 0; + const v = c === 'x' ? r : (r & 0x3 | 0x8); + return v.toString(16); + }); + }, + formatMessage(text) { + if (!text) return ""; + let formatted = text; + + // Convertir Markdown imágenes a HTML + formatted = formatted.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, (match, alt, src) => { + const isVideo = src.toLowerCase().endsWith('.mp4') || src.toLowerCase().endsWith('.webm'); + if (isVideo) { + return ``; + } + return `${this.escapeHtml(alt)}`; + }); + + // Convertir Markdown enlaces a HTML + formatted = formatted.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1'); + + // Convertir negrita + formatted = formatted.replace(/\*\*(.+?)\*\*/g, '$1'); + + // Convertir itálica + formatted = formatted.replace(/\*(.+?)\*/g, '$1'); + + // Convertir saltos de línea + formatted = formatted.replace(/\n/g, '
'); + + // Resaltar precios + formatted = formatted.replace(/\$(\d+(?:,\d+)*(?:\.\d+)?)/g, '$$$1'); + + return formatted; + }, + // Buscar vuelos directamente desde n8n + async searchVuelosEnN8n(query = '') { + try { + const sessionId = localStorage.getItem(CONFIG.SESSION_STORAGE_KEY) || ''; + const response = await fetch(CONFIG.CHAT_API_URL, { + method: 'POST', + headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' }, + body: JSON.stringify({ + action: 'searchVuelos', + query: query, + sessionId: sessionId, + timestamp: new Date().toISOString() + }) + }); + if (!response.ok) throw new Error(`HTTP ${response.status}`); + const data = await response.json(); + return data.vuelos || (Array.isArray(data) ? data : []); + } catch (error) { + console.error('Error buscando vuelos en n8n:', error); + return []; + } + } + }; + + // ==================== 2. GESTOR DE HISTORIAL DE CHAT ==================== + class ChatHistory { + constructor(storageKey, maxMessages, maxIdleMinutes) { + this.storageKey = storageKey; + this.maxMessages = maxMessages; + this.maxIdleMinutes = maxIdleMinutes; + this.lastActivityKey = storageKey + '_lastActivity'; + } + + getMessages() { + try { + const stored = localStorage.getItem(this.storageKey); + return stored ? JSON.parse(stored) : []; + } catch { + return []; + } + } + + addMessage(message) { + const messages = this.getMessages(); + messages.push(message); + if (messages.length > this.maxMessages) { + messages.splice(0, messages.length - this.maxMessages); + } + try { + localStorage.setItem(this.storageKey, JSON.stringify(messages)); + localStorage.setItem(this.lastActivityKey, Date.now().toString()); + } catch {} + } + + clear() { + localStorage.removeItem(this.storageKey); + localStorage.removeItem(this.lastActivityKey); + } + + getLastActivityTime() { + const timestamp = localStorage.getItem(this.lastActivityKey); + return timestamp ? parseInt(timestamp, 10) : 0; + } + + isExpired() { + const now = Date.now(); + const last = this.getLastActivityTime(); + if (!last) return false; // sin actividad previa no expira + const diffMinutes = (now - last) / (1000 * 60); + return diffMinutes > this.maxIdleMinutes; + } + + getContextForAPI() { + return this.getMessages().slice(-10); + } + } + + // ==================== 3. CHATBOT ==================== + class ChatbotManager { + constructor(options) { + this.isOpen = false; + this.isLoading = false; + this.sessionId = options.sessionId; + this.apiUrl = options.apiUrl; + this.dom = options.domElements; + this.utils = options.utils; + this.history = options.history; + this.beepAudio = null; + this.retryCount = 0; + // NUEVO: Referencia al botón flotante de cierre + this.closeFloatBtn = null; + this.init(); + } + + init() { + if (!this.dom.toggle) return; + + this.dom.toggle.addEventListener('click', (e) => { + e.stopPropagation(); + this.playBeep(); + this.toggle(); + }); + + this.dom.close?.addEventListener('click', () => this.close()); + this.dom.sendBtn?.addEventListener('click', () => this.sendMessage()); + this.dom.input?.addEventListener('keypress', (e) => { + if (e.key === 'Enter') { + e.preventDefault(); + this.sendMessage(); + } + }); + + // Cerrar al hacer clic fuera + document.addEventListener('click', (e) => { + if (this.isOpen && this.dom.container && this.dom.toggle && + !this.dom.container.contains(e.target) && !this.dom.toggle.contains(e.target)) { + this.close(); + } + }); + + // Manejar clics en enlaces dentro de mensajes + this.dom.messages?.addEventListener('click', (e) => { + const target = e.target.closest('a'); + if (target && target.href && !target.href.startsWith('javascript')) { + e.preventDefault(); + window.open(target.href, '_blank', 'noopener,noreferrer'); + } + }); + + // NUEVO: Crear botón flotante de cierre para móviles + this.createCloseFloatButton(); + // NUEVO: Gesto swipe para cerrar en móviles + this.enableSwipeToClose(); + + // Restaurar historial visual (con verificación de expiración) + this.restoreHistory(); + } + + // NUEVO: Método para crear el botón flotante de cierre + createCloseFloatButton() { + const btn = document.createElement('button'); + btn.className = 'chatbot-close-float'; + btn.innerHTML = '✕'; + btn.setAttribute('aria-label', 'Cerrar chat'); + btn.addEventListener('click', () => this.close()); + document.body.appendChild(btn); + this.closeFloatBtn = btn; + } + + // NUEVO: Habilitar swipe hacia abajo para cerrar en móviles + enableSwipeToClose() { + let touchStartY = 0; + const container = this.dom.container; + if (!container) return; + + container.addEventListener('touchstart', (e) => { + touchStartY = e.touches[0].clientY; + }, { passive: true }); + + container.addEventListener('touchmove', (e) => { + const touchY = e.touches[0].clientY; + const deltaY = touchY - touchStartY; + // Solo permitir swipe hacia abajo en la parte superior del chat + if (deltaY > 80 && container.scrollTop <= 5 && this.isOpen) { + this.close(); + } + }, { passive: true }); + } + + restoreHistory() { + // Verificar si la sesión ha expirado por inactividad + if (this.history.isExpired()) { + console.log('Sesión expirada, limpiando historial y renovando sesión'); + this.history.clear(); + // Regenerar sessionId para que el backend reconozca nueva conversación + this.sessionId = this.utils.generateUUID(); + localStorage.setItem(CONFIG.SESSION_STORAGE_KEY, this.sessionId); + return; // No restaurar nada, empezar limpio + } + + const messages = this.history.getMessages(); + messages.forEach(msg => { + this.addMessageToDOM(msg.text, msg.type, false); + }); + } + + playBeep() { + if (!this.beepAudio) { + this.beepAudio = document.getElementById('chatbotBeep') || new Audio(); + if (!this.beepAudio.src || this.beepAudio.src === window.location.href) { + this.beepAudio.src = "data:audio/wav;base64,UklGRiQAAABXQVZFZm10IBAAAAABAAEARKwAAIhYAQACABAAZGF0YQAAAAA="; + } + } + this.beepAudio.play().catch(() => {}); + } + + toggle() { + this.isOpen ? this.close() : this.open(); + } + + open() { + if (!this.dom.container) return; + // Al abrir, verificar también expiración (por si acaso) + if (this.history.isExpired()) { + this.history.clear(); + this.sessionId = this.utils.generateUUID(); + localStorage.setItem(CONFIG.SESSION_STORAGE_KEY, this.sessionId); + // Limpiar mensajes visuales si los hubiera + if (this.dom.messages) this.dom.messages.innerHTML = ''; + } + this.dom.container.classList.add('active'); + this.isOpen = true; + // Mostrar botón flotante de cierre si es móvil + if (window.innerWidth <= 480 && this.closeFloatBtn) { + this.closeFloatBtn.style.display = 'flex'; + } + this.dom.messages.scrollTop = this.dom.messages.scrollHeight; + setTimeout(() => this.dom.input?.focus(), 300); + } + + close() { + if (!this.dom.container) return; + this.dom.container.classList.remove('active'); + this.isOpen = false; + // Ocultar botón flotante de cierre + if (this.closeFloatBtn) { + this.closeFloatBtn.style.display = 'none'; + } + } + + addMessageToDOM(text, type, saveToHistory = true) { + const msgDiv = document.createElement('div'); + msgDiv.className = `chatbot-message ${type}`; + msgDiv.innerHTML = this.utils.formatMessage(text); + this.dom.messages?.appendChild(msgDiv); + this.dom.messages.scrollTop = this.dom.messages.scrollHeight; + + if (saveToHistory) { + this.history.addMessage({ text, type, timestamp: new Date().toISOString() }); + } + } + + addMessage(text, type) { + let finalText = text; + try { + const parsed = JSON.parse(text); + if (parsed.message) finalText = parsed.message; + } catch {} + this.addMessageToDOM(finalText, type); + } + + showLoading() { + if (this.isLoading) return; + this.isLoading = true; + const loadingDiv = document.createElement('div'); + loadingDiv.className = 'chatbot-loading'; + loadingDiv.id = 'chatbotLoading'; + loadingDiv.innerHTML = ''; + this.dom.messages?.appendChild(loadingDiv); + this.dom.messages.scrollTop = this.dom.messages.scrollHeight; + if (this.dom.status) this.dom.status.textContent = '✍️ Escribiendo...'; + if (this.dom.sendBtn) this.dom.sendBtn.disabled = true; + if (this.dom.input) this.dom.input.disabled = true; + } + + hideLoading() { + const loading = document.getElementById('chatbotLoading'); + if (loading) loading.remove(); + this.isLoading = false; + if (this.dom.status) this.dom.status.textContent = '🟢 Conectado'; + if (this.dom.sendBtn) this.dom.sendBtn.disabled = false; + if (this.dom.input) this.dom.input.disabled = false; + if (this.isOpen) { + setTimeout(() => this.dom.input?.focus(), 100); + } + } + + async sendMessage() { + const input = this.dom.input; + if (!input) return; + const userText = input.value.trim(); + if (!userText || this.isLoading) return; + + this.addMessage(userText, 'user'); + input.value = ''; + this.showLoading(); + this.retryCount = 0; + + await this.makeRequest(userText); + } + + async makeRequest(userText) { + try { + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), CONFIG.TIMEOUT_MS); + + // Construir payload compatible con n8n Chat Trigger + const payload = { + chatInput: userText, + sessionId: this.sessionId, + timestamp: new Date().toISOString(), + // Incluir contexto de historial para el AI Agent + conversationHistory: this.history.getContextForAPI(), + userData: { + userAgent: navigator.userAgent, + language: navigator.language, + platform: navigator.platform + } + }; + + console.log('Enviando a n8n:', CONFIG.CHAT_API_URL, payload); + + const response = await fetch(CONFIG.CHAT_API_URL, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json' + }, + body: JSON.stringify(payload), + signal: controller.signal + }); + + clearTimeout(timeoutId); + + if (!response.ok) { + throw new Error(`HTTP ${response.status}: ${response.statusText}`); + } + + const data = await response.json(); + console.log('Respuesta de n8n:', data); + + this.hideLoading(); + this.retryCount = 0; + + // Extraer la respuesta del bot del formato de n8n + let botReply = this.extractBotReply(data); + this.addMessage(botReply, 'bot'); + + } catch (error) { + console.error('Chatbot error:', error); + this.handleError(error, userText); + } + } + + extractBotReply(data) { + // El Chat Trigger de n8n devuelve la respuesta en diferentes formatos + if (data.output) return data.output; + if (data.message) return data.message; + if (data.response) return data.response; + if (data.text) return data.text; + if (typeof data === 'string') return data; + + // Buscar en propiedades anidadas + const findMessage = (obj) => { + if (!obj || typeof obj !== 'object') return null; + if (obj.output) return obj.output; + if (obj.message) return obj.message; + if (obj.text) return obj.text; + for (const key of Object.keys(obj)) { + if (typeof obj[key] === 'object') { + const found = findMessage(obj[key]); + if (found) return found; + } + } + return null; + }; + + const found = findMessage(data); + if (found) return found; + + // Si no se encuentra, devolver todo como string + return JSON.stringify(data); + } + + handleError(error, userText) { + this.hideLoading(); + + if (this.retryCount < CONFIG.MAX_RETRIES) { + this.retryCount++; + console.log(`Reintentando (${this.retryCount}/${CONFIG.MAX_RETRIES})...`); + this.showLoading(); + setTimeout(() => this.makeRequest(userText), 1000 * this.retryCount); + return; + } + + this.addMessage( + '⚠️ Estoy experimentando una pequeña intermitencia momentánea. ' + + 'El equipo de soporte ya está al tanto y me encargaré personalmente de dar seguimiento a tu solicitud. ' + + '¿Podrías intentarlo de nuevo en unos momentos? 🙏', + 'error' + ); + } + + sendPredefined(message) { + if (!this.dom.input) return; + if (!this.isOpen) this.open(); + this.dom.input.value = message; + // Pequeño delay para asegurar que el chat esté abierto + setTimeout(() => this.sendMessage(), 400); + } + } + + // ==================== 4. DOM Y UI ==================== + const dom = { + elements: {}, + cacheSelectors() { + this.elements = { + preloader: document.getElementById('preloader'), + progressBar: document.getElementById('preloaderProgressBar'), + themeToggles: document.querySelectorAll('.theme-toggle'), + hamburger: document.getElementById('hamburgerMenu'), + mobileMenu: document.getElementById('mobileMenu'), + closeMenu: document.getElementById('closeMenu'), + menuOverlay: document.getElementById('menuOverlay'), + mobileNavLinks: document.querySelectorAll('.mobile-nav-link'), + searchBtn: document.getElementById('searchBtn'), + searchModal: document.getElementById('searchModal'), + closeSearch: document.getElementById('closeSearch'), + searchInput: document.getElementById('searchInput'), + searchResults: document.getElementById('searchResults'), + searchSuggestions: document.querySelectorAll('.search-suggestion'), + notificationBtn: document.getElementById('notificationBtn'), + reservarBtn: document.getElementById('reservarBtn'), + heroVideo: document.getElementById('heroVideo'), + chatbotToggle: document.getElementById('chatbotToggle'), + chatbotContainer: document.getElementById('chatbotContainer'), + chatbotClose: document.getElementById('chatbotClose'), + chatbotMessages: document.getElementById('chatbotMessages'), + chatbotInput: document.getElementById('chatbotInput'), + chatbotSendBtn: document.getElementById('chatbotSendBtn'), + chatbotStatus: document.getElementById('chatbotStatus') + }; + } + }; + + const ui = { + init(chatbotInstance) { + this.chatbot = chatbotInstance; + this.setupPreloader(); + this.setupTheme(); + this.setupMobileMenu(); + this.setupSearchModal(); + this.setupAnimations(); + this.setupVideo(); + this.setupBookingButtons(); + this.setupResponsive(); + }, + setupPreloader() { + const preloader = dom.elements.preloader; + const progressBar = dom.elements.progressBar; + if (!preloader) return; + let progress = 0; + const interval = setInterval(() => { + progress += Math.random() * 15; + if (progress >= 100) { + progress = 100; + clearInterval(interval); + this.finishPreloader(preloader); + } + if (progressBar) progressBar.style.width = `${Math.min(progress, 100)}%`; + }, 100); + window.addEventListener('load', () => { + clearInterval(interval); + this.finishPreloader(preloader); + }); + }, + finishPreloader(preloader) { + if (!preloader) return; + preloader.classList.add('loaded'); + setTimeout(() => { if (preloader) preloader.style.display = 'none'; }, 600); + }, + setupTheme() { + const isLightMode = localStorage.getItem('theme') === 'light'; + if (isLightMode) document.body.classList.add('light-mode'); + dom.elements.themeToggles?.forEach(toggle => { + toggle.addEventListener('click', () => { + document.body.classList.toggle('light-mode'); + const nowLight = document.body.classList.contains('light-mode'); + localStorage.setItem('theme', nowLight ? 'light' : 'dark'); + }); + }); + }, + setupMobileMenu() { + const { hamburger, mobileMenu, closeMenu, menuOverlay, mobileNavLinks } = dom.elements; + let menuOpen = false; + const toggle = () => { + menuOpen = !menuOpen; + hamburger?.classList.toggle('active'); + mobileMenu?.classList.toggle('active'); + menuOverlay?.classList.toggle('active'); + document.body.style.overflow = menuOpen ? 'hidden' : 'auto'; + }; + hamburger?.addEventListener('click', toggle); + closeMenu?.addEventListener('click', toggle); + menuOverlay?.addEventListener('click', toggle); + mobileNavLinks?.forEach(link => link.addEventListener('click', toggle)); + }, + setupSearchModal() { + const { searchBtn, searchModal, closeSearch, searchInput, searchResults, searchSuggestions } = dom.elements; + if (!searchBtn || !searchModal) return; + let searchOpen = false; + const open = () => { + searchOpen = true; + searchModal.classList.add('active'); + document.body.style.overflow = 'hidden'; + setTimeout(() => searchInput?.focus(), 300); + }; + const close = () => { + searchOpen = false; + searchModal.classList.remove('active'); + document.body.style.overflow = 'auto'; + if (searchInput) searchInput.value = ''; + if (searchResults) { + searchResults.innerHTML = ''; + searchResults.classList.remove('active'); + } + }; + searchBtn.addEventListener('click', open); + closeSearch?.addEventListener('click', close); + document.addEventListener('keydown', (e) => e.key === 'Escape' && searchOpen && close()); + + const performSearch = utils.debounce(async () => { + const query = searchInput?.value.toLowerCase().trim() || ''; + if (query.length === 0) { + if (searchResults) { + searchResults.innerHTML = ''; + searchResults.classList.remove('active'); + } + return; + } + if (searchResults) { + searchResults.innerHTML = '
Buscando vuelos...
'; + searchResults.classList.add('active'); + } + const vuelos = await utils.searchVuelosEnN8n(query); + this.displaySearchResults(vuelos, query); + }, 400); + + searchInput?.addEventListener('input', performSearch); + searchSuggestions?.forEach(sugg => { + sugg.addEventListener('click', () => { + const text = sugg.getAttribute('data-search'); + if (searchInput && text) { + searchInput.value = text; + performSearch(); + searchInput.focus(); + } + }); + }); + }, + displaySearchResults(results, query) { + const container = dom.elements.searchResults; + if (!container) return; + if (!results || results.length === 0) { + container.innerHTML = `
+ +

No se encontraron vuelos para "${utils.escapeHtml(query)}"

+

Prueba con: amanecer, familiar, premium, teotihuacán

+
`; + } else { + container.innerHTML = results.map(vuelo => { + let imgUrl = vuelo.imagen || vuelo.image || vuelo.foto || ''; + if (imgUrl && !imgUrl.startsWith('http') && !imgUrl.startsWith('//') && !imgUrl.startsWith('data:')) { + imgUrl = window.location.origin + '/' + imgUrl.replace(/^\.?\//, ''); + } + return `
+
+ ${utils.escapeHtml(vuelo.nombre || 'Vuelo')} +
+
+

${utils.escapeHtml(vuelo.nombre || 'Vuelo en Globo')}

+
+ ${utils.escapeHtml(vuelo.duracion || 'Consulta disponibilidad')} + ${utils.escapeHtml(vuelo.ubicacion || 'Teotihuacán')} +
+
+ ${vuelo.precioOriginal ? `$${vuelo.precioOriginal.toLocaleString()}` : ''} + $${(vuelo.precio || 0).toLocaleString()} + ${vuelo.unidad ? `/ ${utils.escapeHtml(vuelo.unidad)}` : ''} +
+
+
`; + }).join(''); + } + container.classList.add('active'); + }, + setupAnimations() { + const observer = new IntersectionObserver((entries) => { + entries.forEach(entry => { + if (entry.isIntersecting) { + entry.target.style.animation = 'fadeInUp 0.8s ease-out forwards'; + observer.unobserve(entry.target); + } + }); + }, { threshold: 0.1, rootMargin: '0px 0px -100px 0px' }); + document.querySelectorAll('.feature-card, .tour-one__single').forEach(el => observer.observe(el)); + }, + setupVideo() { + const video = dom.elements.heroVideo; + if (video) { + video.play().catch(() => { + video.style.display = 'none'; + const container = document.querySelector('.video-container'); + if (container) { + container.style.backgroundImage = 'url("assets/img/banner/globopuertoteotihuacan-video.jpg")'; + container.style.backgroundSize = 'cover'; + container.style.backgroundPosition = 'center'; + } + }); + } + }, + setupResponsive() { + const handler = utils.throttle(() => { + const w = window.innerWidth; + document.body.classList.remove('mobile-small', 'mobile-large', 'tablet', 'desktop'); + if (w <= 480) document.body.classList.add('mobile-small'); + else if (w <= 768) document.body.classList.add('mobile-large'); + else if (w <= 1024) document.body.classList.add('tablet'); + else document.body.classList.add('desktop'); + }, 200); + window.addEventListener('resize', handler); + handler(); + }, + setupBookingButtons() { + const { notificationBtn, reservarBtn } = dom.elements; + notificationBtn?.addEventListener('click', () => { + alert("Notificaciones:\n\n• Tu reserva para el 15 de mayo está confirmada\n• Nuevo destino disponible: Valle de la Luna\n• Oferta especial: 20% de descuento en vuelos nocturnos"); + }); + reservarBtn?.addEventListener('click', (e) => { + e.preventDefault(); + this.chatbot.sendPredefined("Quiero reservar un vuelo en globo"); + }); + } + }; + + // Función global para manejar clics en resultados de búsqueda + window.handleSearchResultClick = function(id, enlace) { + const modal = dom.elements.searchModal; + if (modal) modal.classList.remove('active'); + document.body.style.overflow = 'auto'; + if (enlace) { + window.open(enlace, '_blank', 'noopener,noreferrer'); + } + }; + + // ==================== 5. INICIALIZACIÓN ==================== + document.addEventListener('DOMContentLoaded', () => { + console.log('🚀 Inicializando Chatbot Vuelos en Globo...'); + injectStyles(); + dom.cacheSelectors(); + + // Obtener o crear sessionId persistente + let sessionId = localStorage.getItem(CONFIG.SESSION_STORAGE_KEY); + if (!sessionId) { + sessionId = utils.generateUUID(); + localStorage.setItem(CONFIG.SESSION_STORAGE_KEY, sessionId); + } + console.log('Session ID:', sessionId); + + // Inicializar historial con tiempo de expiración + const history = new ChatHistory( + CONFIG.CHAT_HISTORY_KEY, + CONFIG.MAX_HISTORY_MESSAGES, + CONFIG.SESSION_MAX_IDLE_MINUTES + ); + + // Inicializar chatbot + const chatbot = new ChatbotManager({ + sessionId: sessionId, + apiUrl: CONFIG.CHAT_API_URL, + utils: utils, + history: history, + domElements: { + toggle: dom.elements.chatbotToggle, + container: dom.elements.chatbotContainer, + close: dom.elements.chatbotClose, + messages: dom.elements.chatbotMessages, + input: dom.elements.chatbotInput, + sendBtn: dom.elements.chatbotSendBtn, + status: dom.elements.chatbotStatus + } + }); + + // Inicializar UI + ui.init(chatbot); + + console.log('✅ Chatbot Vuelos en Globo inicializado correctamente'); + }); +})(); \ No newline at end of file diff --git a/public/assets/js/main.js b/public/assets/js/main.js new file mode 100644 index 0000000..4b0ffe5 --- /dev/null +++ b/public/assets/js/main.js @@ -0,0 +1,575 @@ +// ==================== MÓDULO PRINCIPAL (IIFE) ==================== +(function() { + 'use strict'; + + // -------------------- ESTADO PRIVADO -------------------- + const state = { + menuOpen: false, + searchOpen: false, + isLightMode: localStorage.getItem('theme') === 'light', + isPreloaderActive: true, + chatbot: { + isOpen: false, + isLoading: false, + sessionId: localStorage.getItem('chatbotSession') || crypto.randomUUID(), + apiUrl: 'https://n8npix.pixelteo.com/webhook/d9de4096-c21a-41e8-aa2c-30dd1de5c3ba/chat' + } + }; + + // Guardar session ID + localStorage.setItem('chatbotSession', state.chatbot.sessionId); + + // -------------------- UTILIDADES -------------------- + const utils = { + // Throttle simple para eventos de alta frecuencia + throttle(fn, delay) { + let lastCall = 0; + return function(...args) { + const now = Date.now(); + if (now - lastCall >= delay) { + lastCall = now; + fn.apply(this, args); + } + }; + }, + + // Debounce para búsqueda + debounce(fn, delay) { + let timer; + return function(...args) { + clearTimeout(timer); + timer = setTimeout(() => fn.apply(this, args), delay); + }; + }, + + // Formateo seguro de mensajes (evita XSS) + formatMessage(text) { + if (!text) return ''; + let formatted = text + .replace(/[&<>]/g, function(m) { + if (m === '&') return '&'; + if (m === '<') return '<'; + if (m === '>') return '>'; + return m; + }) + .replace(/\*\*(.*?)\*\*/g, '$1') + .replace(/\*(.*?)\*/g, '$1') + .replace(/`(.*?)`/g, '$1') + .replace(/\n/g, '
') + .replace(/\$(\d+(?:,\d+)*(?:\.\d+)?)/g, '$$$1'); + return formatted; + }, + + // Obtener vuelos desde el DOM (una sola vez con caché) + getVuelosFromDOM() { + if (this._vuelosCache) return this._vuelosCache; + const vuelos = []; + document.querySelectorAll('.tour-one__single').forEach((card, index) => { + const img = card.querySelector('.tour-one__image img'); + const imgSrc = img?.getAttribute('src') || img?.getAttribute('data-src') || ''; + const link = card.querySelector('.tour-one__content h3 a'); + const precioEspecial = parseFloat(card.getAttribute('data-precio-especial') || 0); + const precioPublico = parseFloat(card.getAttribute('data-precio-publico') || 0); + const precio = precioEspecial > 0 ? precioEspecial : precioPublico; + const meta = card.querySelectorAll('.tour-one__meta li a'); + vuelos.push({ + id: card.getAttribute('data-vuelo-id') || index, + nombre: link?.textContent.trim() || '', + enlace: link?.getAttribute('href') || '', + precio, + precioOriginal: precioEspecial > 0 ? precioPublico : null, + unidad: card.getAttribute('data-unidad') || '', + duracion: meta[0]?.textContent.trim() || '', + ubicacion: meta[1]?.textContent.trim() || '', + imagen: imgSrc, + imagenAlt: img?.getAttribute('alt') || 'Vuelo en globo' + }); + }); + this._vuelosCache = vuelos; + return vuelos; + }, + _vuelosCache: null + }; + + // -------------------- MANIPULACIÓN DEL DOM -------------------- + const dom = { + elements: {}, + + cacheSelectors() { + this.elements = { + preloader: document.getElementById('preloader'), + progressBar: document.getElementById('preloaderProgressBar'), + themeToggles: document.querySelectorAll('.theme-toggle'), + hamburger: document.getElementById('hamburgerMenu'), + mobileMenu: document.getElementById('mobileMenu'), + closeMenu: document.getElementById('closeMenu'), + menuOverlay: document.getElementById('menuOverlay'), + mobileNavLinks: document.querySelectorAll('.mobile-nav-link'), + searchBtn: document.getElementById('searchBtn'), + searchModal: document.getElementById('searchModal'), + closeSearch: document.getElementById('closeSearch'), + searchInput: document.getElementById('searchInput'), + searchResults: document.getElementById('searchResults'), + searchSuggestions: document.querySelectorAll('.search-suggestion'), + chatbotToggle: document.getElementById('chatbotToggle'), + chatbotContainer: document.getElementById('chatbotContainer'), + chatbotClose: document.getElementById('chatbotClose'), + chatbotMessages: document.getElementById('chatbotMessages'), + chatbotInput: document.getElementById('chatbotInput'), + chatbotSendBtn: document.getElementById('chatbotSendBtn'), + chatbotStatus: document.getElementById('chatbotStatus'), + notificationBtn: document.getElementById('notificationBtn'), + reservarBtn: document.getElementById('reservarBtn'), + heroVideo: document.getElementById('heroVideo') + }; + }, + + show(element) { + if (element) element.classList.add('active'); + }, + hide(element) { + if (element) element.classList.remove('active'); + }, + setHtml(element, html) { + if (element) element.innerHTML = html; + }, + setText(element, text) { + if (element) element.textContent = text; + } + }; + + // -------------------- CHATBOT (CLASE) -------------------- + class ChatbotManager { + constructor() { + this.isOpen = false; + this.isLoading = false; + this.sessionId = state.chatbot.sessionId; + this.apiUrl = state.chatbot.apiUrl; + this.beepAudio = null; + this.init(); + } + + init() { + if (!dom.elements.chatbotToggle) return; + dom.elements.chatbotToggle.addEventListener('click', (e) => { + e.stopPropagation(); + this.playBeep(); + this.toggle(); + }); + dom.elements.chatbotClose?.addEventListener('click', () => this.close()); + dom.elements.chatbotSendBtn?.addEventListener('click', () => this.sendMessage()); + dom.elements.chatbotInput?.addEventListener('keypress', (e) => { + if (e.key === 'Enter') { + e.preventDefault(); + this.sendMessage(); + } + }); + // Cerrar al hacer clic fuera + document.addEventListener('click', (e) => { + if (this.isOpen && + dom.elements.chatbotContainer && + dom.elements.chatbotToggle && + !dom.elements.chatbotContainer.contains(e.target) && + !dom.elements.chatbotToggle.contains(e.target)) { + this.close(); + } + }); + } + + playBeep() { + if (!this.beepAudio) { + this.beepAudio = new Audio(); + this.beepAudio.src = 'data:audio/wav;base64,U3RlYWx0aCBibG9i...'; // Opcional: puedes mantener el elemento