diff --git a/docs/_images/add-on-html-string.png b/docs/_images/add-on-html-string.png deleted file mode 100644 index da3ae9777..000000000 Binary files a/docs/_images/add-on-html-string.png and /dev/null differ diff --git a/docs/_images/add-on-main-body.png b/docs/_images/add-on-main-body.png deleted file mode 100644 index 891c1225d..000000000 Binary files a/docs/_images/add-on-main-body.png and /dev/null differ diff --git a/docs/_images/add-on-mcp.png b/docs/_images/add-on-mcp.png deleted file mode 100644 index 8e3e3c816..000000000 Binary files a/docs/_images/add-on-mcp.png and /dev/null differ diff --git a/docs/_images/add-on-simple-view.png b/docs/_images/add-on-simple-view.png deleted file mode 100644 index b88668335..000000000 Binary files a/docs/_images/add-on-simple-view.png and /dev/null differ diff --git a/docs/_images/add-on-view.png b/docs/_images/add-on-view.png deleted file mode 100644 index 94eced60b..000000000 Binary files a/docs/_images/add-on-view.png and /dev/null differ diff --git a/docs/_images/addon_breadcrumbs.png b/docs/_images/addon_breadcrumbs.png deleted file mode 100644 index 9645d6ce8..000000000 Binary files a/docs/_images/addon_breadcrumbs.png and /dev/null differ diff --git a/docs/_images/addon_page.png b/docs/_images/addon_page.png deleted file mode 100644 index 26591962e..000000000 Binary files a/docs/_images/addon_page.png and /dev/null differ diff --git a/docs/_images/addon_sidebar_start.png b/docs/_images/addon_sidebar_start.png deleted file mode 100644 index 484cde78a..000000000 Binary files a/docs/_images/addon_sidebar_start.png and /dev/null differ diff --git a/docs/_images/addon_toolbar.png b/docs/_images/addon_toolbar.png deleted file mode 100644 index 57df8c454..000000000 Binary files a/docs/_images/addon_toolbar.png and /dev/null differ diff --git a/docs/_images/addons_text_formatting.png b/docs/_images/addons_text_formatting.png deleted file mode 100644 index 740a1722b..000000000 Binary files a/docs/_images/addons_text_formatting.png and /dev/null differ diff --git a/docs/_images/dashboard_widget_example.png b/docs/_images/dashboard_widget_example.png deleted file mode 100644 index 84d269fff..000000000 Binary files a/docs/_images/dashboard_widget_example.png and /dev/null differ diff --git a/docs/_images/gear_icon.png b/docs/_images/gear_icon.png deleted file mode 100644 index 9145169d0..000000000 Binary files a/docs/_images/gear_icon.png and /dev/null differ diff --git a/docs/_images/prolet_example.png b/docs/_images/prolet_example.png deleted file mode 100644 index 99961fe98..000000000 Binary files a/docs/_images/prolet_example.png and /dev/null differ diff --git a/docs/_images/structure_tab.png b/docs/_images/structure_tab.png deleted file mode 100644 index c236d3d02..000000000 Binary files a/docs/_images/structure_tab.png and /dev/null differ diff --git a/docs/add-ons/overview.md b/docs/add-ons/overview.md index 2f5c2a36d..da7261314 100755 --- a/docs/add-ons/overview.md +++ b/docs/add-ons/overview.md @@ -9,52 +9,14 @@ # Add-Ons -[TOC] - -## Overview Add-ons can extend the functionality of ExpressionEngine, adding more features, fields, template tags, and more! -![add-on manager](_images/addon_page.png) - -There are three main sources of add-ons for ExpressionEngine: - -- Add-ons that are shipped with ExpressionEngine (found in the `Add-ons` section of the Control Panel) -- Add-ons that are downloaded from an outside resource such as the [ExpressionEngine Add-on Store](https://expressionengine.com/add-ons) or other developers. -- Add-ons that you create yourself (see [Add-on Development](/development/addon-development-structure.md) for more information on creating your own add-ons). - -Add-ons that are not shipped with ExpressionEngine or created by the ExpressionEngine team are typically referred to as "third-party add-ons." - -TIP: Visit the [Add-on Store](https://expressionengine.com/add-ons) to browse available add-ons. - - +Visit the [Add-on Store](https://expressionengine.com/add-ons) to browse available add-ons. ## Installing Add-ons Installing an add-on is easy! 1. Download the add-on and unzip its contents. -2. Upload the add-on's folder to your `/system/user/addons/` folder. If needed, also copy any themes to you `/themes/user/` folder. +2. Upload the add-on's folder to your `system/user/addons/` folder. 3. Once the add-on is uploaded, you should see it listed in the [Add-on Manager](control-panel/addons-manager.md) in your ExpressionEngine Control Panel. You can then click install! - -NOTE: Some add-ons include a `themes` folder. This folder includes supporting styles and scripts to help the add-on function properly. Add-on themes should be copied to the `themes/user/` folder. - -## Updating Add-ons - -Updating an installed add-on is just as easy as installing. - -1. Download the add-on and unzip its contents. -2. Overwrite any older versions of the add-on that may be in place by upload the add-on's folder to your `/system/user/addons/` folder. If needed, also copy and overwrite any themes to you `/themes/user/` folder. -3. Once the add-on is uploaded, you will see it listed in the "Updates" tab of the [Add-on Manager](control-panel/addons-manager.md) in your ExpressionEngine Control Panel. You can then click the Update button to update your add-on to its newest version! - -TIP: Some add-ons may notify you in the Add-on Manager that there is an upload available for download. Others you will have to check with the add-on vendor to determine if a newer version is available. - -## Uninstalling Add-ons - -Follow these steps to ensure an installed add-on is properly removed from your site. - -1. In the Add-on Manager, select the gear icon ( gear icon ) next to your add-on. -2. Select Uninstall from the flyout menu options. -3. Follow the prompts to uninstall your add-on. - -After you have uninstalled your add-on, you may then delete it's folder from your filesystem if you no longer need it. - diff --git a/docs/cli/built-in-commands/make-action.md b/docs/cli/built-in-commands/make-action.md index 8314a5a99..610c92a43 100644 --- a/docs/cli/built-in-commands/make-action.md +++ b/docs/cli/built-in-commands/make-action.md @@ -15,5 +15,3 @@ Action Generator -- Creates a new action for an add-on ### Generating an action: `php eecli.php make:action MyNewAction --addon=my_existing_addon` - -TIP: For more information on using the `make:action` command to create new action, reference [Adding Actions](development/actions.md). diff --git a/docs/cli/built-in-commands/make-addon.md b/docs/cli/built-in-commands/make-addon.md index 09a717a3f..9a57fa9a7 100644 --- a/docs/cli/built-in-commands/make-addon.md +++ b/docs/cli/built-in-commands/make-addon.md @@ -10,6 +10,34 @@ Check out our video tutorial generating an add-on! ## Options list: ``` + --extension + --ext + Create an extension + + --plugin + --pi + Create a plugin + + --fieldtype + --ft + Create a fieldtype + + --module + --mod + Create a module + + --typography + -t + Should use plugin typography + + --has-settings= + -e + Add-on has settings (yes/no) + + --compatibility-mode + -p + Generate add-on that is compatible with ExpressionEngine versions lower than 7.2.0 and lower than 6.4.0 + --version= -v Version of the add-on @@ -26,14 +54,43 @@ Check out our video tutorial generating an add-on! -u Author url of the add-on + --services= [--services= [...]] + -s [-s [...]] + Services to create. Multi-pass option. + + --models= [--models= [...]] + -m [-m [...]] + Models to create. Multi-pass option. + --commands= [--commands= [...]] + -c [-c [...]] + Commands to create. Multi-pass option. + + --consents= [--consents= [...]] + -n [-n [...]] + Consents. Multi-pass option. + + --cookies= [--cookies= [...]] + -k [-k [...]] + Cookies to create, with a colon separating name and value (i.e. name:value). Multi-pass option. + + --hooks= [--hooks= [...]] + -o [-o [...]] + Hooks in use. Multi-pass option. ``` ## Examples: -### Generating an add-on: +### Generating an extension: + +`php eecli.php make:addon example_extension --ext --description "Description of addon" --version="1.0.0" --author="Joe Shmoe" --author-url='www.example.com' --has-settings='yes' --hooks=cp_custom_menu` + +### Generating a module: + +`php eecli.php make:addon my_awesome_mod --mod --description "Description of addon" --version="1.0.0" --author="Joe Shmoe" --author-url='www.example.com' --has-settings='yes'` + +### Generating a module in compatibility mode: -`php eecli.php make:addon amazing_add_on --description "Description of addon" --version="1.0.0" --author="Joe Shmoe" --author-url='www.example.com'` +`php eecli.php make:addon my_awesome_mod --mod --description "Description of addon" --version="1.0.0" --author="Joe Shmoe" --author-url='www.example.com' --has-settings='yes' --compatibility-mode` -TIP: For more information on using the `make:addon` command to create new add-on, reference [Getting Started](development/addon-development-overview.md#getting-started) in the Building An Add-On section of the docs. \ No newline at end of file diff --git a/docs/cli/built-in-commands/make-command.md b/docs/cli/built-in-commands/make-command.md index 23f494842..07667823c 100644 --- a/docs/cli/built-in-commands/make-command.md +++ b/docs/cli/built-in-commands/make-command.md @@ -24,5 +24,3 @@ Command Generator -- Creates a new CLI command for an add-on ### Generating a command: `php eecli.php make:command "Awesome CLI Command" --addon=my_example_addon --description='This command is awesome' --signature='my_addon:awesome-example'` - -TIP: For more information on using the `make:command` command to create new CLI command with your add-on, reference [Adding CLI Commands](cli/creating-a-command.md). diff --git a/docs/cli/built-in-commands/make-extension-hook.md b/docs/cli/built-in-commands/make-extension-hook.md index 467f11fb2..9b73c9b25 100644 --- a/docs/cli/built-in-commands/make-extension-hook.md +++ b/docs/cli/built-in-commands/make-extension-hook.md @@ -15,5 +15,3 @@ Extension Hook Generator -- Implements an EE extension hook in an add-on ### Implementing the sessions_start extension hook: `php eecli.php make:extension-hook sessions_start --addon=my_existing_addon` - -TIP: For more information on using the `make:extension-hook` command to create new action, reference [Extending The Core](development/extensions.md). diff --git a/docs/cli/built-in-commands/make-prolet.md b/docs/cli/built-in-commands/make-prolet.md index 254d18759..713583238 100644 --- a/docs/cli/built-in-commands/make-prolet.md +++ b/docs/cli/built-in-commands/make-prolet.md @@ -28,5 +28,3 @@ Prolet Generator -- Creates a new prolet for an add-on ### Generating a prolet: `php eecli.php make:prolet MyNewProlet --addon=my_addon --description="This is my prolet description"` - -TIP: For more information on using the `make:prolet` command to create new Prolet with your add-on, reference [Adding Prolets](development/prolets.md). diff --git a/docs/cli/built-in-commands/make-tag.md b/docs/cli/built-in-commands/make-tag.md index 05faa9b26..e420e245f 100644 --- a/docs/cli/built-in-commands/make-tag.md +++ b/docs/cli/built-in-commands/make-tag.md @@ -1,4 +1,4 @@ -# make:template-tag +# make:tag Tag Generator -- Creates a new tag for an add-on @@ -14,6 +14,4 @@ Tag Generator -- Creates a new tag for an add-on ### Generating a new tag: -`php eecli.php make:template-tag MyNewTag --addon=my_existing_addon` - -TIP: For more information on using the `make:template-tag` command to create new template tag with your add-on, reference [Adding Template Tags](development/custom-template-tags.md). +`php eecli.php make:tag MyNewTag --addon=my_existing_addon` diff --git a/docs/cli/built-in-commands/make-widget.md b/docs/cli/built-in-commands/make-widget.md index 7a7ff6106..48b43aaed 100644 --- a/docs/cli/built-in-commands/make-widget.md +++ b/docs/cli/built-in-commands/make-widget.md @@ -16,5 +16,3 @@ Widget Generator -- Creates a new Dashboard Widget for an add-on ### Generating a widget: `php eecli.php make:widget MyNewWidget --addon=my_existing_addon` - -TIP: For more information on using the `make:widget` command to create new Dashboard Widget with your add-on, reference [Adding Dashboard Widgets](development/widgets.md). \ No newline at end of file diff --git a/docs/cli/creating-a-command.md b/docs/cli/creating-a-command.md index 45be44794..5f312dabe 100644 --- a/docs/cli/creating-a-command.md +++ b/docs/cli/creating-a-command.md @@ -1,118 +1,32 @@ -# Creating a CLI Command +# Creating a Command -[TOC] +Commands are created to live within add-ons, and are registered as part of the add-on process. -## Overview -The ExpressionEngine [Command Line Inferface (CLI)](/cli/intro.md) makes it simple and more efficent to do many things inside of ExpressionEngine. In fact, if you're following the docs to build your own custom add-on then you're probably using the CLI to build out the architecture of your add-on. +## addon.setup.php -You can also build your own commands that will enable users to interact with your add-on or do other things inside of ExpressionEngine. Commands are created to live within add-ons, and are registered as part of the add-on process. - -NOTE:Before adding a custom CLI Command to your add-on, you need to already have an add-on in place. See [Building An Add-On: Getting Started](development/addon-development-overview.md#getting-started) for how to generate the starter files for your add-on. - -## Creating An Amazing Command -We add custom commands to the CLI when our add-on is installed by using the CLI. - -``` -php system/ee/eecli.php make:command -``` - -Follow the prompts to add a CLI command to your add-on. - -This will create a `Commands` folder in your add-on along with a class and file based on your command name. +In order to add commands to your addon, you should add the `commands` parameter as an associative array to your `addon.setup.php` file with the handle as the key, and the class of your command as the value. ``` -amazing_add_on - ┣ Commands - ┃ ┣ Command[CommandName].php +return array( + 'author' => 'Awesome Developer', + 'author_url' => 'https://example.com/', + 'name' => 'My Amazing Module', + 'description' => 'Does amazing things', + 'version' => '1.0', + 'namespace' => 'Awesome\AmazingModule', + 'settings_exist' => true, + 'commands' => [ + 'amazing:run' => Awesome\AmazingModule\Commands\DoThings::class, + 'amazing:more-things' => 'Awesome\AmazingModule\Commands\DoMoreThings', + ] +); ``` -CLI Commands are installed through an add-on's `addon.setup.php` file. The CLI takes care of this for us. We can see here that CLI creates the `$commands` array inside the array that gets returned from our `addon.setup.php` file. - -``` - 'Expressionengine Developer', - 'author_url' => 'https://www.expressionengine.com', - 'name' => 'Amazing Add-on', - 'description' => 'asdfjasdf', - 'version' => '1.0.0', - 'namespace' => 'ExpressionengineDeveloper\AmazingAddon', - 'settings_exist' => true, - 'commands' => [ - 'amazing_add_on:make:amazing-things' => ExpressionengineDeveloper\AmazingAddon\Commands\CommandAnAmazingCommand::class, - ], - - ], - -]; - -``` - -Now when our add-on is installed, users will have access to our new command. - -## Anatomy of a Command - `/Commands/Command[CommandName]` -Inside of our add-on we now have a file named with our command's name (in PascalCase). - -In this example we have created a new command named "An Amazing Command" to our Amazing Add-on: - -``` -info('Hello World!'); - } -} -``` +Creating commands is simple. Each commands is built in a similar way as part of a custom add-on: ### Class Structure diff --git a/docs/cli/intro.md b/docs/cli/intro.md index 1d94d4e9c..bc25ff11b 100644 --- a/docs/cli/intro.md +++ b/docs/cli/intro.md @@ -1,6 +1,6 @@ -# Command Line Interface (CLI) +# Command Line Inferface (CLI) -The Command Line Interface (CLI) allows a user to run system and user-generated commands in the terminal. The CLI has access to all of the ExpressionEngine resources, and can be used to update the system, clear caches, and much more. +The Command Line Inferface (CLI) allows a user to run system and user-generated commands in the terminal. The CLI has access to all of the ExpressionEngine resources, and can be used to update the system, clear caches, and much more. By default the CLI is located `system/ee/eecli.php` . @@ -18,7 +18,7 @@ By default the CLI is located `system/ee/eecli.php` . - [make:migration - Creates a new migration](cli/built-in-commands/make-migration.md) - [make:model - Creates a new model for an add-on](cli/built-in-commands/make-model.md) - [make:prolet - Creates a new prolet for an add-on](cli/built-in-commands/make-prolet.md) - - [make:template-tag - Creates a new tag for an add-on](cli/built-in-commands/make-tag.md) + - [make:tag - Creates a new tag for an add-on](cli/built-in-commands/make-tag.md) - [make:widget - Generates widgets for existing add-ons](cli/built-in-commands/make-widget.md) - Migrate - [migrate - Runs specified migrations (all, core, or add-ons)](cli/built-in-commands/migrate.md) diff --git a/docs/control-panel/addons-manager.md b/docs/control-panel/addons-manager.md index ebd75141f..33d71ac5a 100755 --- a/docs/control-panel/addons-manager.md +++ b/docs/control-panel/addons-manager.md @@ -9,10 +9,8 @@ # Add-On Manager -**Control Panel Location: `Add-Ons`** +**Control Panel Location: `Developer > Add-Ons`** This section of the Control Panel is where add-on functionality is installed, updated, configured, and removed. They can be filtered by status and developer, and sorted by the add-on name and version. This section is divided into to sections, one for add-on functionality included with ExpressionEngine, and one for third party add-ons. -![add-on manager](_images/addon_page.png) - See [Installing Add-ons](add-ons/overview.md#installing-add-ons) to learn how to install an add-on. diff --git a/docs/development/actions.md b/docs/development/actions.md deleted file mode 100644 index 9a59befcb..000000000 --- a/docs/development/actions.md +++ /dev/null @@ -1,331 +0,0 @@ - - -# Actions - -[TOC] - -## Overview -Actions in ExpressionEngine are URL endpoints that are reached with the `ACT` query parameter. An example of this might be `http://myamazingsite.com/?ACT=43` where 43 is the ID given to an action registered in the `exp_actions` database table. These actions are tied to methods in an add-on which can be used to accept input from forms or run some sort of other functionality defined in the add-on. - -NOTE:Before adding an action to your add-on, you need to already have an add-on in place. See [Building An Add-On: Getting Started](development/addon-development-overview.md#getting-started) for how to generate the starter files for your add-on. - -## Creating An Amazing Action -To generate an action we use the CLI to add the action to our existing add-on named "Amazing Add-on". - -``` -php system/ee/eecli.php make:action -``` - -Follow the prompts to add an action file to your add-on. - - -This will create an `Actions` folder inside our add-on's folder where will build out the code we want to run when a user hits our `ACT` URL. Inside our `Actions` folder the CLI will create a file with the same name as the method we defined when creating our action. - -``` -amazing_add_on - ┣ Actions - ┃ ┗ [MethodName].php - ┗... -``` - -We also notice that a migration is created in our `database/migrations` folder. This migration is ran when your add-on is installed or uninstalled to tell ExpressionEngine what actions we needed added or removed with our add-on. - -``` -//database/migrations/[timestamp]_[mirgrationname].php -make('Action', [ - 'class' => 'Amazing_add_on', - 'method' => 'AmazingAztion', - 'csrf_exempt' => false, - ])->save(); - } - - /** - * Rollback the migration - * @return void - */ - public function down() - { - ee('Model')->get('Action') - ->filter('class', 'Amazing_add_on') - ->filter('method', 'AmazingAztion') - ->delete(); - } -} -``` - -Actions are not available for use until they have been added to the `exp_actions` database table. Actions have the following schema in the database: - -| action_id | class | method |csrf_exempt | -|-----------|----------------|----------------|------------| -| 41 | Amazing_add_on | ExampleAction | 0 | - -If you want your action to be accessible immediately, you can either run the migration that was created by the CLI or set a flag in the CLI when creating the add-on. - -### Run Migration To Activate -After creating your action using the CLI run `$ php system/ee/eecli.php migrate`. When asked for the location, type in the name of your add-on. This will run all migrations that have not been ran for your add-on including entering the action into `exp_actions`. - - -### Setting Flag To Activate On Creation -On creation of an action, you can also specify to add it to the database after the CLI creates it. You can do this with the `--install` or `-i` flag by running your `make:action` command like so: `$ php system/ee/eecli.php make:action --install`. - - - -## Anatomy of An Action - -Once we've added an action to our add-on, an `Actions` folder is created for us. The CLI will generate a class and respective file for us based on the action name we passed to the CLI when creating our action. In this case we added an action named "ExampleAction" to Amazing Add-on. - -``` -php system/ee/eecli.php make:action -What is the action name? ExampleAction -What add-on is the action being added to? amazing_add_on -Action created successfully! -``` - -``` -amazing_add_on - ┣ Actions - ┃ ┗ ExampleAction.php - ┗... -``` - - -### class [ActionName] - -Inside `/Actions/ExampleAction.php` we see the following code generated for us: - -``` -make('Action', [ - 'class' => 'Amazing_add_on', - 'method' => 'AmazingAztion', - 'csrf_exempt' => true, - ])->save(); -} -... -``` -- To set an action as CSRF exempt on creation, use the `--csrf_exempt` or `-c` flag in the CLI: - -``` -$ php system/ee/eecli.php make:action --csrf_exempt -``` - - - -## Do Something - Build An Action - -Let's do something with our action to demonstrate how this would work. - -### Form Data -In this example we want to insert a row into our database when a user submits a form. - -For this example we'll use a really basic form that would be found in our template which uses our action's endpoint as the action for the form. We know our action's ID from the `exp_actions` table and we're just going to collect the user's first name and last name. We'll then take that information and store it in our database. For the purpose of this example, we'll insert this into a custom table we've added to ExpressionEngine which just has columns `ID`, `first_name`, `last_name`. - - -Create our action: -``` -$ php system/ee/eecli.php make:action --install -What is the action name? ExampleAction -What add-on is the action being added to? [amazing_add_on,...]: amazing_add_on -``` - -This creates our required files. - -Now we had some functionality to our action which will add the first and last name submitted from a form to our custom database table. - -Our action's code (`Actions/ExampleAction.php`): - -``` - ee()->input->post('fname'), - 'last_name' => ee()->input->post('lname'), - ); - - ee()->db->insert('our_amazing_table', $data); - - return true; - - } -} -``` - -Our template code: - -``` -
- -
-
-
-

- - -
- -``` - - -**A note about action IDs.** For the example above, we looked up the action ID in the database. However, the action ID of your method may be different in your database than someone else as the IDs are auto-incremented by the database on insertion. Therefore, when dynamically creating the form with a custom template tag, it's always best practice to fetch your action ID for use in a template by using the [`fetch_action_id()`](/development/legacy/libraries/cp.md#fetch_action_idclass-method) method from the `CP Class` library. - -We would do this in our add-on's template tag with something like this: - -``` -$aid = ee()->cp->fetch_action_id(`Amazing_add_on`, `ExampleAction`); -``` - - -### Return Data - -In this next example we are just creating an endpoint which will be reachable from servers outside of our domain. We are going to expect the application to use cURL or similar library to post an ID to our endpoint. Upon receiving the request our action will return the name of the entry matching the ID if it exists. - -Create our action: -``` -$ php system/ee/eecli.php make:action --install -What is the action name? ExampleAction -What add-on is the action being added to? [amazing_add_on,...]: amazing_add_on -``` - -Our action code: - -``` -input->post('id'); - - // here we're using the Channel Entry Model - // to request the entry's title - $entry = ee('Model') - ->get('ChannelEntry') - ->filter('entry_id', $entry_id) - ->first(); - - - if ($entry){ - $response = $entry->title; - }else{ - $response = "No Matching Entry" - } - - echo $response; - } -} -``` - -However, when I attempt to reach this endpoint from another application I get an error returned. - -The request: -``` -curl --location --request POST 'https://anamzingwebsite.test/?ACT=41' --form 'id="1"' -``` - -The response: - -``` -... -
-
-

The following errors were encountered

-
-
-
  • This form has expired. Please refresh and try again.
-
-
-... -``` -This is because of the [`csrf_exempt` value](#cross-site-request-forgerycsrf-exemption) mentioned above. To fix this we can go into the `exp_actions` table of our database and update the `csrf_exempt` column to `1`. - -Now when I send that same request, I simply get the entry title I requested: - -The response after disabling CSRF protection: - -``` -... -The Entry You Requested -... -``` - diff --git a/docs/development/add-on-language-files.md b/docs/development/add-on-language-files.md deleted file mode 100644 index 4ce7977e2..000000000 --- a/docs/development/add-on-language-files.md +++ /dev/null @@ -1,51 +0,0 @@ ---- -lang: php ---- - - - -# The Language File (addon_name_lang.php) - -The Language file contains an array named `$lang`, which is used along with the Language class to display text on a page in whatever language is selected in the user's account settings. - -## Required Fields -There are two required lines in the language file for each add-on, which allows the name and description of the add-on to be viewable on the MODULES page: - - $lang = array( - - // Required for MODULES page - - 'my_module_module_name' => 'Module Name', - 'my_module_module_description' => 'Brief description of the module- displayed on the Modules page', - - //---------------------------------------- - - // Additional Key => Value pairs go here - - // END - ''=>'' - ); - -If the ExpressionEngine core language files contains string with the same key, it will be used in favor of add-on specified string. If an add-on needs to override that string, that can be done by adding it to `$ee_lang` array in the add-on's language file. - -## Publish Form Tab Label - -In addition to the two required fields you can have a custom tab label for your publish fields. Just assign the desired label to a key which shares the name of your module name: - - // Additional Key => Value pairs go here - - /** - * Tab Label for publish fields - * - * Assign the label you wish to use to the module_name array key - * Remember only alphanumeric characters, underscores, dashes and spaces are allowed. - */ - - 'module_name' => 'Tab label' \ No newline at end of file diff --git a/docs/development/add-on-update-file.md b/docs/development/add-on-update-file.md deleted file mode 100644 index 1fa8058db..000000000 --- a/docs/development/add-on-update-file.md +++ /dev/null @@ -1,180 +0,0 @@ - - -# Add-on Update File - -## Overview - -The `upd.[addon_name].php` file (commonly just called the `upd` file) is critical to ExpressionEngine knowing what to do with your add-on. Here we tell ExpressionEngine what actions to register, core hooks we want to use, database tables to update, and much more. We need to tell ExpressionEngine what to do when we install an add-on, update an add-on, and uninstall and add-on. Thankfully the CLI takes care of most of this for us. - -TIP:When using the CLI, your add-on update file will automatically be created for you. See [Building An Add-On: Getting Started](development/addon-development-overview.md#getting-started) for how to generate the starter files for your add-on. - -## Initial Setup - -When you first create your add-on using the [`make:addon`](/cli/built-in-commands/make-addon.md) command from the CLI, a `upd` file is created for you in the root of your add-on. - -Here I have created an add-on called Amazing Add-on using the CLI. - -``` -load->library('layout'); - ee()->layout->add_layout_tabs($this->tabs(), 'module_name'); - - -## Add Publish Tabs With Your Add-on (`tabs()`) - -| Parameter | Type | Description | -| --------- | ------- | ------------------------------------------------ | -| Returns | `Array` | Associative array of the tab name and tab fields | - -An optional function included only if your add-on adds a tab to the publish page. This function should return a multidimensional associative array: the top array key consisting of the tab name, followed by any field names, with each field having a variety of default settings. Note that when the fields are added to the publish page, they are namespaced to prevent variable collisions: - - function tabs() - { - $tabs['tab_name'] = array( - 'field_name_one'=> array( - 'visible' => 'true', - 'collapse' => 'false', - 'htmlbuttons' => 'true', - 'width' => '100%' - ), - 'field_name_two'=> array( - 'visible' => 'true', - 'collapse' => 'false', - 'htmlbuttons' => 'true', - 'width' => '100%' - ), - ); - - return $tabs; - } - -Be sure that you also update your [`install()` function](#adding-publish-tabs) to add your specified tabs. - - - - - -## Update Your Add-on (`update()`) -The `update` method will run code when a user installs an update to our add-on. - -| Parameter | Type | Description | -| --------- | --------- | ------------------------------------------------------------------ | -| \$current | `string` | The last recorded version of the module in the `exp_modules` table | -| Returns | `Boolean` | `FALSE` if no update is needed, `TRUE` otherwise - - public function update($current = '') - { - // Runs migrations - parent::update($current); - - // only run the update if the user is currently running a version less than 2.0 - if (version_compare($current, '2.0', '<')) - { - // Do your update code here - // update database - // notify mission control of the update - } - - - return true; - } - -## Uninstall Your Add-On (`uninstall()`) -The CLI automatically generates our uninstall method. This method will ensure that all extensions and actions declared above will be properly uninstalled. Similarly to installation, if you just need to ensure your actions and extensions are uninstalled, you can leave this method as is. However, you added custom functionality in the `install()` method, then you need to also ensure you clean up after yourself here. - -| Parameter | Type | Description | -| --------- | --------- | ------------------------------------------------------------ | -| Returns | `Boolean` | `TRUE` if everything uninstalled properly, `FALSE` otherwise | - - public function uninstall() - { - parent::uninstall(); - - // remove my database tables - // remove any publish tabs - // turn off the lights - - return true; - } - -### Removing Tabs -Optionally delete any publish page tab fields saved in publish layouts. This is ONLY used if the module adds a tab to the publish page and it requires the `tabs()` function: - - ee()->load->library('layout'); - ee()->layout->delete_layout_tabs($this->tabs(), 'module_name'); \ No newline at end of file diff --git a/docs/development/addon-development-overview.md b/docs/development/addon-development-overview.md deleted file mode 100644 index 3938b193a..000000000 --- a/docs/development/addon-development-overview.md +++ /dev/null @@ -1,222 +0,0 @@ - - -# Add-on Development Overview - -With custom add-ons you can add new fieldtypes, features, template tags, and much more to ExpressionEngine. Here we are going to look at different parts of an add-on, and how to define just what our add-on is going to do. - -TIP: In this section, we're explaining the parts of an add-on. No need to memorize everything though, the [CLI](/cli/intro.html) will generate all the pieces we need based on what functions we want our add-on to have. - -[TOC] - -## Why Create A Custom Add-On -While ExpressionEngine offers a lot of functionality right out of the box, sometimes you want more power or to do things that ExpressionEngine doesn't natively do. - -Here are some ideas of what you can accomplish with a custom add-on: - -- Add custom [template tags](development/custom-template-tags.md) like `{exp:amazing_add_on:member_info}`. -- Run functions or return data when someone reaches a specific URL using [URL endpoints (called Actions)](development/actions.md). -- Add custom [fieldtypes](development/fieldtypes/fieldtypes.md) for content editors when creating channel entries. -- Add custom [CLI commands](cli/creating-a-command.md) like `$ eecli.php amazing_add_on:do_something_amazing` -- Add custom [Publish Form tabs](development/tab-files.md) to help organize entry fields for content editors. -- Hook into ExpressionEngine and run [custom functions (called Extensions)](development/extensions.md) when ExpressionEngine does certain actions, like emailing your team whenever a post is created or manipulating text when a template is rendered. -- Display information from your add-on to content editors on the front-end by adding a [Prolet](/development/prolets.md) to the [ExpressionEngine Dock](/advanced-usage/front-end/dock.md). -- Display information in the [Control Panel Dashboard](/control-panel/dashboard_management.md) using a [custom Dashboard Widget](/development/widgets.md). - -These are just a few ideas of what you can do with custom add-ons. The possibilities are almost endless. - -## Getting Started -Getting started making an add-on is incredibly easy with the CLI. To begin making an add-on simply, use the `make:addon` command from the [CLI](/cli/intro.html). - -``` -$ php system/ee/eecli.php make:addon -Let's build your add-on! -What is the name of your add-on? Amazing Add-On -Add-on description? [Amazing Add-on description] This add-on does amazing things! -Add-on version? [1.0.0]1.0.0 -Add-on author? ExpressionEngine Developer -Add-on author URL? www.expressionengine.com -Let's build! -Your add-on has been created successfully! - -``` - -This will create an add-on named Amazing Add-On in your `system/user/addons` folder with a skeleton file structure like below: - -``` -amazing_add_on/ -┣ language/ -┃ ┣ english/ -┃ ┃ ┣ amazing_add_on_lang.php -┣ addon.setup.php -┣ mod.amazing_add_on.php -┗ upd.amazing_add_on.php -``` - -At this point, your add-on can't really do anything other than be installed. However, from here, you can add more functionality to your add-on via the CLI depending on your needs. - -Here's a list of functionality that can be added to your add-on and the corresponding CLI command if applicable: - -- [Extension hooks (`make:extension-hook`)](development/extensions.md) -- [Control Panel Pages (`make:cp-route`)](development/modules.md) -- [Actions (`make:action`)](development/actions.md) -- [Fieldtypes (`make:fieldtype`)](development/fieldtypes/fieldtypes.md) -- [CLI Commands (`make:command`)](cli/creating-a-command.md) -- [Template Tags (`make:template-tag`)](development/custom-template-tags.md) -- [Language Files](development/add-on-language-files.md) -- [Publish Form Tabs](development/tab-files.md) -- [Prolets (`make:prolet`)](development/prolets.md) -- [Dashboard Widgets (`make:widget`)](development/widgets.md) - -Continue reading below to understand all the files and folders found in the structure of an add-on. - -## Add-On Structure - -Below is the complete structure of an add-on that we'll call "Amazing Add-on". There's a lot in this structure because this add-on can do many things (it's amazing 😀)! Don't worry though; your add-on can be as simple or complex as you want to make it. This example just shows all the possibilities. Continue reading as we break down the parts of this add-on. - -``` -amazing_add_on - ┣ Commands - ┃ ┗ CommandAnAmazingCommand.php - ┣ database - ┃ ┣ migrations - ┃ ┃ ┗ 2022_11_14_170449_amazing_migration.php - ┣ Extensions - ┃ ┣ TemplatePostParse.php - ┃ ┗ TypographyParseTypeEnd.php - ┣ ControlPanel - ┃ ┣ Routes - ┃ ┃ ┣ Index.php - ┃ ┃ ┗ Page2.php - ┃ ┣ Sidebar.php - ┣ Actions - ┃ ┗ ExampleAction.php - ┣ Tags - ┃ ┗ ExampleTag.php - ┣ Model - ┃ ┗ AmazingModel.php - ┣ language - ┃ ┣ english - ┃ ┃ ┣ amazing_add_on_lang.php - ┃ ┃ ┗ index.html - ┃ ┗ index.html - ┣ widgets - ┃ ┗ AnAmazingWidget.php - ┣ views - ┃ ┣ Index.php - ┃ ┗ Page2.php - ┣ addon.setup.php - ┣ ext.amazing_add_on.php - ┣ ft.amazing_add_on.php - ┣ icon.svg - ┣ mcp.amazing_add_on.php - ┣ tab.amazing_add_on.php - ┣ mod.amazing_add_on.php - ┣ pro.amazing_add_on.php - ┗ upd.amazing_add_on.php - ``` - -NOTE: **Note:** Pay attention to how these filenames are structured. For filenames, hyphens and special characters are removed, and spaces are replaced with underscores. - - -### The Add-on Setup File (`addon.setup.php`) -Starting with version 3.0 each add-on in ExpressionEngine must have an `addon.setup.php` file in its package directory. This file provides descriptive data about a specific add-on, such as author, name, and version. -Reference [The Add-on Setup File](development/addon-setup-php-file.md) for more information on the contents of this file. - -### The Update File (`upd.[addon_name].php`) -**class `Add_on_name_upd extends Installer`** -The Update file for an add-on includes a class with a name that is a combination of the add-on's name with a `_upd` suffix. Here you define functionality that should be executed on installation, update, and uninstallation of your add-on. -Reference [The Add-on Update File](development/add-on-update-file.md) for more information on this file. - - -### The Extension File (`ext.[addon_name].php`) -**class `Add_on_name_upd extends Extension`** -The extension file is used to route ExpressionEngine to our `Extensions` Folder. -Reference [Extending The Core](development/extensions.md) for more information on how to use core hooks to extend what ExpressionEngine can do. - -### The Fieldtype File (`ft.[addon_name].php`) -**class `Add_on_name_ft extends EE_Fieldtype`** -The fieldtype file is used to create new [fieldtypes](/fieldtypes/overview.md) in ExpressionEngine when your add-on is installed. -Reference [Adding Fieldtypes](development/fieldtypes/fieldtypes.md) for more information on adding fieldtypes with your add-on. - -### The Mcp File (`mcp.[addon_name].php`) -**class `Add_on_name_upd extends Mcp`** -The Mcp file is used to route ExpressionEngine to our `ControlPanel` Folder, which contains logic for your Control Panel pages (settings or other pages you might want to add to the Control Panel for your users to interact with). -Reference [Adding Control Panel Pages](development/modules.md) for more information on adding Control Panel pages with your add-on. - -### The Module File `mod.[addon_name].php` -**class `Add_on_name_upd extends Module`** -The module file is used to route ExpressionEngine to our `Modules` Folder, which contains any actions or template tags you are adding with your add-on. -Reference [Adding Template Tags](development/custom-template-tags.md) for more information on adding template tags with your add-on or [Adding Actions](development/actions.md) for more information on creating URL endpoints (actions) with your add-on. - -### The Tab File (`tab.[addon_name].php`) -**class `Module_name_tab`** -The tab file is used to create tabs that are visible in [Publish Layouts](control-panel/channels.md#publish-layouts). Respectively, these tabs would also be visible on the Entry Publish/Edit page if selected in the publish layout. -Reference [Adding Publish Form Tabs](development/tab-files.md) for more information on adding Publish Form Tabs with your add-on. - -### The Prolet File `pro.[addon_name].php` -**class `Add_on_name_upd extends AbstractProlet implements ProletInterface`** -The Prolet file is used to create new [Prolets](/development/prolets.md) with our add-on. -Reference [Adding Prolets](development/prolets.md) for more information on adding prolets to your add-on. - -### The Add-on Icon File `icon.svg` -The add-on icon folder is used both in the Add-on Manager and in the Dock on the front-end to distinguish your add-on from others. - -### Extensions - `/Extensions` -When we tell the CLI that we want to create an extension, classes are automatically created in the `Extensions` folder along with the above mentioned `ext.[addon_name].php` file. Interacting with hooks allows us to extend ExpressionEngine's functionality, thus we refer to these as "extensions". - -TIP: Reference the [Extensions](development/extensions.md) section of the docs for more information on using extensions in your add-on. - -### Actions - `/Actions` -The `/Actions` folder stores all the business logic for any actions that we are adding to ExpressionEngine with our add-on. Each action will have a separate file and corresponding class created based on information provided in the `$actions` array in the `upd` file. -Reference [Adding Actions](development/actions.md) for more information on creating URL endpoints (actions) with your add-on. - -### Control Panel Routes - `/ControlPanel` -The `ControlPanel` folder contains all the Control Panel routes and we create for our add-on as well as our sidebar. -Reference [Adding Control Panel Pages](development/modules.md) for more information on adding Control Panel routes and pages with your add-on. - -### `/Tags` -The `/Tags` folder stores all the business logic for any template tags we create with our add-on. -Reference [Adding Template Tags](development/custom-template-tags.md) for more information on adding template tags with your add-on. - -### `/views` -The `views` folder contains all of our Control Panel views which will be used to render our add-on's control panel pages. - -### `/language` -The `language` folder contains all of our language files that will be used to display text on a page in whatever language is selected in the user’s account settings. -Reference [Using Language Files](development/add-on-language-files.md) for more information on using language files with your add-on. - -### `/database/migrations` -The `/database/migrations` folder holds all migrations that will be ran on installation or updating of our add-on. Using the CLI, migrations can also be ran independently. - -### `Model` -The `Model` folder holds all models that we are creating with our add-on. -Reference [Building Your Own Models](/development/services/model/building-your-own.md) for more information on creating your own models with your add-on. - -### `widgets` -The `widgets` folder holds all dashboard widgets we create with our add-on. -Reference [Developing Dashboard Widgets](development/widgets.md) for more information on creating widgets with your add-on. - -## Setting Default CLI Config Values - -When creating an add-on via the CLI you will be asked for author and the author's URL. If you'd like to skip these questions when creating an add-on, you can set default values in your [config](/general/system-configuration-overrides.md#main-configuration-file) file like so: - -``` -... -$config['cli_default_addon_author'] = 'ExpressionEngine Developer'; -$config['cli_default_addon_author_url'] = 'https://expressionengine.com'; -... -``` - -## A Word About Legacy Add-On Development -In the past, add-ons were often categorized based on their functionality. We identified our add-on to ExpressionEngine as a fieldtype, extension, module, or plug-in. Thus there was never a straightforward process on structuring one add-on that contained all these categories in one. - -With the release of 6.4.x and 7.2.x this paradigm has been updated to reflect the idea that we are just creating add-ons, and those add-ons can have multiple types of functionality. The CLI has also been updated to make creating add-ons and adding functionality incredibly easy. We have also updated the docs to reflect the ideal workflow of creating an add-on. - -While the latest changes shift our view of add-ons and how developers will create add-ons, you may still come across add-ons using the old methodology. We have left much of the old methods and structure in place in the core so that older add-ons will continue to work. However, we are choosing to not actively update the documentation for the old methods because we feel it's no longer in the best interest of the community to develop add-ons in this way. If you need to access how the docs once were regarding add-ons, you can reference the [legacy docs in GitHub](https://github.com/ExpressionEngine/ExpressionEngine-User-Guide/releases/tag/legacy-add-on-structure) (note that v7 and v6 were the same in these regards). \ No newline at end of file diff --git a/docs/development/addon-installer.md b/docs/development/addon-installer.md new file mode 100644 index 000000000..824dc9002 --- /dev/null +++ b/docs/development/addon-installer.md @@ -0,0 +1,187 @@ +--- +lang: ee +--- + + + +# Add-on Installer + +ExpressionEngine 6 has simplified the add-on installation procedure by allowing a module or extension extend the `Installer` service. + +NOTE: **Note:** This feature is only available in ExpressionEngine 6 (and greater). For previous CMS versions, please use appropriate function for [Modules](development/modules.md#update-file-function-reference) and [Extensions](development/extensions.md#activating-and-updating). + +## Module installer + +To use the new installer service, the module's `upd.[module_name].php` file needs to include this code at a minimum. + + use ExpressionEngine\Service\Addon\Installer; + + class Query_upd extends Installer + { + + public function __construct() + { + parent::__construct(); + } + + } + +Additional functionality can be installed using the following guidelines: + + 'My_addon', + 'method' => 'action_function', // required + 'csrf_exempt' => true + ] + ]; + + // defines an extension's methods and hooks that should be installed + public $methods = [ + [ + 'method' => 'run', // will default to same as hook if not defined + 'hook' => 'template_fetch_template', // required + 'priority' => "", + 'enabled' => "" // y/n + ], + [ + 'method' => 'cleanup', // will default to same as hook if not defined + 'hook' => 'template_post_parse' // required + ] + ]; + + + /** + * Constructor alone will install module and actions. + */ + public function __construct() + { + parent::__construct(); + } + + /** + * install() and uninstall() are optional functions. + * Only use if additional install or uninstall functionality is needed. + * If needed, must include parent::__construct(); + */ + + public function install() + { + return parent::install(); + } + + public function uninstall() + { + return parent::uninstall(); + } + + } + + +## Extension installer + + +Extension files can now be as simplified as well. However they must include the `$methods` declaration. + + use ExpressionEngine\Service\Addon\Installer; + + class Query_ext extends Installer + { + // defines an extension's methods and hooks that should be installed + public $methods = [ + [ + 'method' => 'run', // will default to same as hook if not defined + 'hook' => 'template_fetch_template', // required + 'priority' => "", + 'enabled' => "" // y/n + ], + [ + 'method' => 'cleanup', // will default to same as hook if not defined + 'hook' => 'template_post_parse' // required + ] + ]; + + /** + * Notice that for extensions you must include $settings + * as a parameter in the constructor + */ + public function __construct($settings = []) + { + parent::__construct($settings); + } + + } + + + +Additionally you may use `activate_extension()` and `disable_extension()` if needed for additional functionality as shown below. + + 'run', // will default to same as hook if not defined + 'hook' => 'template_fetch_template', // required + 'priority' => "", + 'enabled' => "" // y/n + ], + [ + 'method' => 'cleanup', // will default to same as hook if not defined + 'hook' => 'template_post_parse' // required + ] + ]; + + /** + * Notice that for extensions you must include $settings + * as a parameter in the constructor + */ + public function __construct($settings = []) + { + parent::__construct($settings); + } + + /** + * activate_extension() and disable_extension() are optional functions. + * Only use if additional functionality is needed. + * If needed, must include parent::__construct(); + */ + + public function activate_extension() + { + parent::activate_extension(); + } + + public function disable_extension () + { + parent::disable_extension(); + } + + + } + diff --git a/docs/development/addon-setup-php-file.md b/docs/development/addon-setup-php-file.md index ec1f220cf..364ef1297 100755 --- a/docs/development/addon-setup-php-file.md +++ b/docs/development/addon-setup-php-file.md @@ -11,18 +11,13 @@ lang: php @license https://expressionengine.com/license Licensed under Apache License, Version 2.0 --> -# The Add-On Setup File +# The addon.setup.php File [TOC] -## Overview +Starting with version 3.0 each add-on in ExpressionEngine must have an `addon.setup.php` file in its package directory. This file provides descriptive data about a specific add-on such as author, name, and version. See the [Addon Service](development/services/addon.md) for API access. -Starting with version 3.0 each add-on in ExpressionEngine must have an `addon.setup.php` file in its package directory. This file provides descriptive data about a specific add-on, such as author, name, and version. Below we walk through the format and available keys. However, most of the time, the CLI will take care of generating and updating this file as needed for you. - - -TIP:When using the CLI, your add-on setup file will automatically be created for you. See [Building An Add-On: Getting Started](development/addon-development-overview.md#getting-started) for how to generate the starter files for your add-on. - -## Anatomy Of The Add-On Setup File +## Format The file must return an associative array. For example: @@ -51,7 +46,7 @@ This is the name of the company or individual responsible for the add-on. This v 'author_url' => 'https://example.com' -This is the URL associated with the add-on. This value is used in manual display for add-ons, as such this is a **required** key for all add-ons. +This is the URL associated with the add-on. This value is used in manual display for plugins, as such this is a **required** key for all plugins. ### `name` @@ -63,7 +58,7 @@ This is the name of the add-on. This value is used in the Add-On Manager as the 'description' => 'Displays a friendly "Hello world!" message.' -This is a brief description of the add-on. This value is used in the manual display for add-ons, as such this is a **required** key for all add-ons. +This is a brief description of the add-on. This value is used in the manual display for plugins, as such this is a **required** key for all plugins. ### `version` @@ -106,7 +101,7 @@ This indicates whether or not the add-on provides a plugin that should be made a ) ) -This is an associative array of the fieldtypes the add-on contains where the key corresponds to the fieldtype, `ft.hello_world.php` in the above example. Each fieldtype defines its name, which is used when creating or editing Channel Fields. +This is an associative array of the fieldtypes the add-on contains where the key corresponds to the fieldtype, `ft.hello_world.php` in the above example. Each fieldtype defines its name which is used when creating or editing Channel Fields. As of 3.1.0 fieldtypes can specify their compatibility. When editing a Channel Field the fieldtype options will be restricted to those fieldtypes that have the same compatibility. ExpressionEngine's native fieldtypes have the following compatibilities: @@ -129,7 +124,7 @@ As of 3.1.0 fieldtypes can specify their compatibility. When editing a Channel F } ) -This is an associative array of services to register on the [Dependency Injection Container](development/architecture.md#dependencies). This is typically used to help you place class construction code in a single place that can be easily called throughout your app. If your service code is written to be unit-testable, you may have several classes you need to insert through dependency injection. Instead of having to copy and paste boilerplate code to set up your service throughout your add-on, you can just register it in the Dependency Injection Container and call it from your add-on like this: +This is an associative array of services to register on the [Dependency Injection Container](development/architecture.md#dependencies). This is typically used to help you place class construction code in a single place that can be easily called throughout your app. If your service code is written to be unit-testable, you may have several classes you need to insert through dependency injection. Instead of having to copy and paste boiler plate code to set up your service throughout your add-on, you can just register it in the Dependency Injection Container and call it from your add-on like this: ee('example:MyService'); @@ -235,6 +230,3 @@ It is also possible to set up class aliases to an arbitrary FQCN. The example be 'aliases' => [ 'MyVendor\Services\ClassName' => 'AnotherVendor\Services\ClassName', ], - -## Accessing Add-On Information From Another Add-on -See the [Addon Service](development/services/addon.md) for API access. \ No newline at end of file diff --git a/docs/development/best-practices/about.md b/docs/development/best-practices/about.md new file mode 100644 index 000000000..8589c7b8d --- /dev/null +++ b/docs/development/best-practices/about.md @@ -0,0 +1,219 @@ +# New Best Practices + +With the release of ExpressionEngine 7.2 we are modernizing how add-ons are constructed. New Add-on classes allow add-on developers to create their add-on methods using an abstracted object layer while keeping backwards compatibility intact. It covers Extensions, Modules (Action and Tags), and the Control Panel layers. It's very simple to implement (just inherit an object), while maintaining the legacy implementation in play. + +To implement, you simply set your add-on `mod.`, `ext.`, and/or `mcp.` files to inherit from a base object (depending on the implementation). Then create your actions, tags, extension hooks, and mcp routes in folders consistent with the supported naming convention. Tags go in the `Module/Tags` folder, Actions go in the `Module/Actions` folder, Extension hooks go in the `Extensions` folder, and Mcp routes go in the `Mcp` folder. + +## Add-on File Examples + +Here's a breakdown for how the add-on files would look for an Add-on named `Custom_addon`. + +> Note that this works by using set or determined namespaces for a given Add-on. By default, this Controller implementation will use the add-on name to get the configured namespace within the add-on. For this example of `Custom_addon`, we are setting the namespace in the `addon.setup.php` file to be `YourAddon`; all route namespaces are determined by that. + +### Module + +The `mod` file would consist exclusively of the below. + +```php + 'Custom_addon', + 'method' => 'my-test-action' + ] +]; +``` + +### Extension + +The `ext` is structured just like the others: + +```php + 'Custom_addon_ext', + 'method' => 'test-extension', + 'hook' => 'sessions_start' + ] +]; +``` + + +#### Route Examples + +A basic 'index' route, normally done through a method attached to the `mcp` object, would look like the example below: + +```php +addBreadcrumb('test-link', 'My Link') + ->addBreadcrumb('test-link2', 'My Second Link'); + + $variables = []; + $this->setBody('my-view', $variables); + return $this; + } +} +``` diff --git a/docs/development/best-practices/cp.md b/docs/development/best-practices/cp.md new file mode 100644 index 000000000..d2324a2ac --- /dev/null +++ b/docs/development/best-practices/cp.md @@ -0,0 +1,164 @@ +# Control Panel + +Just like with Extension and Module routing, you have to extend your ExpressionEngine Addon file to use the corresponding Add-on Controller Base route. In this case, that'll be `ExpressionEngine\Service\Addon\Mcp`. + +You'll also have to add a new property to your Control Panel object called `$route_namespace` which is the namespace for route objects. + +> Note the below example uses the `setRouteNamespace` method to set the `$route_namespace` property. + +### Update Control Panel File + +```php +use ExpressionEngine\Service\Addon\Mcp; + +class My_addon_mcp extends Cp +{ + protected $module_name = 'my_addon'; + + public function __construct() + { + $this->setRouteNamespace('MyAddon\Addon\Controllers'); + } + + public function index() + { + return $this->route('index', func_get_args()); + } +} + +``` + +#### About `$route_namespace` + +Note that `$route_namespace` isn't directly one to one. The Add-on Controller does some magic to keep things compartmentalized. Using the below example, the namespace used to locate your Route objects would be `Namespace\For\Your\Controllers\Cp\Routes`. + +#### About `$module_name` + +This value should contain the internal value ExpressionEngine uses to classify your parent Addon. + +> An extra special note about the use of `func_get_args()`: you should 100% keep doing that if you want Routing to be consistent. Basically, every declaration of a `route()` call should look like: `return $this->route('ROUTE_NAME', func_get_args());` + +### Create Control Panel Routes + +Unlike any other Controller Route, Control Panel routes require an addition to your ExpressionEngine Control Panel object. The idea is you tell ExpressionEngine to your object then the Router takes over. For example, in the above example, we define an `index` method and then route it internally. + +The route object itself would look like the below: + +```php +namespace YourAddon`\Addon\Controllers\Cp\Routes; + +use ExpressionEngine\Service\Addon\Mcp\AbstractRoute; + +class Index extends AbstractRoute +{ + protected $route_path = 'index'; + + public function process($id = false): AbstractRoute + { + return $this; + } +} +``` + +You may have noticed that unlike traditional ExpressionEngine Control Panel methods, you don't return an array; you return an instance of your route instead. To generate output, the Router will take everything you setup within your object and pass that to ExpressionEngine. + +#### About `$route_path` + +The value MUST contain the `noun|/verb` your Route is accessed through. See the for more details. + +#### Full Example + +```php +namespace YourAddon\Controllers\Mcp\Routes\ControllersExamples; + +use ExpressionEngine\Service\Addon\Mcp\AbstractRoute; + +class Cp extends AbstractRoute +{ + protected $route_path = 'controllers-examples/cp'; + + public function process($id = false): AbstractRoute + { + $this->setBody('test-route/my-action', []); + $this->setHeading('cp'); + + $this->addBreadcrumb($this->url('controllers-examples'), 'eo.cp.nav.controller.examples'); + return $this; + } +} +``` + + +## Helper Functionality + +### Sidebar Generator + +To automatically generate your sidebar, you'll need to add a property to your Route object called `$sidebar_data` that contains an array that outlines your sidebar. + +```php +protected $sidebar_data = [ + 'eo.cp.nav.controller.examples' => [ + 'path' => 'controllers-examples', + 'list' => [ + 'cp' => 'controllers-examples/cp', + 'module' => 'controllers-examples/mod', + ] + ], + 'eo.cp.nav.forms' => [ + 'path' => '', + 'list' => [ + 'eo.cp.nav.example' => 'forms/example' + ] + ], + 'eo.cp.nav.members' => [ + 'path' => '', + 'list' => [ + 'eo.cp.nav.example' => 'members' + ] + ], + 'eo.cp.nav.entries' => [ + 'path' => '', + 'list' => [ + 'eo.cp.nav.example' => 'entries' + ] + ] +]; +``` + +Note the `$active_sidebar` property can be used to specify a specific sidebar node as having an `active` state. Otherwise, that'll be determined through the `$route_path` property. + +### URL Helper + +To remove a considerable amount of keystrokes, you can use the `url($path, $with_base, $query)` method. What this does is allow you to create Control Panel URLs using the short syntax of a Route as well as any URL. + +```php +protected function url(string $path, bool $with_base = true, array $query = []): string +``` +#### `$path` + +Either the Router shortname for the route you want, or a full URL to the destination. + +#### `$with_base` + +Boolean to compile the URL along with the `$base_url` property. If your link is to anything BUT a Route, you'll want this to be false. + +#### `$query` + +An array of key=>values you want to use for the query string on the URL. + +### Breadcrumbs + +You apply breadcrumbs using either the `addBreadcrumb($url, $text)` or the `setBreadcrumbs(array $breadcrumbs = [])` method(s). Note that `setBreadcrumbs` will completely reset any previously compiled `breadcrumb` items. + +```php +protected function setBreadcrumbs(array $breadcrumbs = []): AbstractRoute +protected function addBreadcrumb(string $url, string $text): AbstractRoute +``` + +### Body Content + +To output content within the ExpressionEngine Control Panel, you have to dictate both a view script to use and the variables to pass to it. + +```php +public function setBody(string $view, array $variables = []): AbstractRoute +``` \ No newline at end of file diff --git a/docs/development/best-practices/extensions.md b/docs/development/best-practices/extensions.md new file mode 100644 index 000000000..29c645c56 --- /dev/null +++ b/docs/development/best-practices/extensions.md @@ -0,0 +1,38 @@ +# Extensions + +Just like with Module and Control Panel routing, you have to extend your ExpressionEngine Addon file to use the cooresponding Add-on Controller Base route. In this case, that'll be `ExpressionEngine\Service\Addon\Extension`. + +You'll also have to add a new property to your Extension called `$route_namespace` which is the namespace for route objects. + +### Update Extension File + +```php +use ExpressionEngine\Service\Addon\Extension; + +class Your_addon_ext extends Extension +{ + protected $route_namespace = 'Namespace\For\Your\Controllers'; +} +``` + +#### About `$route_namespace` + +Note that `$route_namespace` isn't directly one to one. The Add-on Controller does some magic to keep things compartmentalized. Using the below example, the namespace used to locate your Route objects would be `Namespace\For\Your\Controllers\Extensions\Routes`. + +### Create Route + +Note that your `process` method's signature should match up with the ExpressionEngine hooks passed parameters. + +```php +namespace YourAddon\Addon\Controllers\Extension\Routes; + +use ExpressionEngine\Service\Addon\Extension\AbstractRoute; + +class TemplatePostParse extends AbstractRoute +{ + public function process(string $final_template, bool $is_partial, int $site_id, array $currentTemplateInfo): string + { + return $final_template; + } +} +``` diff --git a/docs/development/best-practices/modules.md b/docs/development/best-practices/modules.md new file mode 100644 index 000000000..07f929578 --- /dev/null +++ b/docs/development/best-practices/modules.md @@ -0,0 +1,54 @@ +# Modules + +Just like with Extension and Control Panel routing, you have to extend your Add-on file to use the corresponding Add-on Controller Base route. In this case, that'll be `ExpressionEngine\Service\Addon\Module`. + +You'll also have to add a new property to your Module called `$route_namespace` which is the namespace for route objects. + +> Note that since a Module file is responsible for both Actions and Template Tags within ExpressionEngine, the handling of Module routes is split into both `Tag` and `Action` route objects. + +### Update Module File + +```php +use ExpressionEngine\Service\Addon\Module; + +class Your_addon extends Module +{ + protected $route_namespace = 'Namespace\For\Your\Controllers'; +} +``` + +#### About `$route_namespace` + +Note that `$route_namespace` isn't directly one to one. The Add-on Controller does some magic to keep things compartmentalized. Using the below example, the namespace used to locate your Route objects would be `Namespace\For\Your\Controllers\Action|Tag\Routes`. + +### Create Tag Routes + +```php +namespace YourAddon\Controllers\Module\Tag\Routes; + +use ExpressionEngine\Service\Addon\Controllers\Tag\AbstractRoute; + +class TemplatePostParse extends AbstractRoute +{ + public function process(): string + { + //magic + } +} +``` + +### Create Action Routes + +```php +namespace YourAddon\Controllers\Module\Action\Routes; + +use ExpressionEngine\Service\Addon\Controllers\Action\AbstractRoute; + +class AnotherAction extends AbstractRoute +{ + public function process(): string + { + //magic + } +} +``` \ No newline at end of file diff --git a/docs/development/best-practices/routing.md b/docs/development/best-practices/routing.md new file mode 100644 index 000000000..729cef9a5 --- /dev/null +++ b/docs/development/best-practices/routing.md @@ -0,0 +1,38 @@ +# Routing + +> NOTE this document goes into detail on how Routing works as a concept. To say this is for Advanced developers would be an understatement. + +## Router Basics + +Routing is handled through a simple `__call` translation to find the appropriate object. Standard and as traditional as can be. For example, consider an ExpressionEngine module with a template tag called `my_template_tag`. Using a Module object like the below: + +```php +class Your_addon extends Module +{ + protected $route_namespace = 'Namespace\For\Your\Controllers'; +} +``` + +would be Routed to `Namespace\For\Your\Controllers\Tag\Routes\MyTemplateTag`. And this is the case for 90% of use-cases. Everything gets magically translated to the proper object. Again. Standard. Traditional. Nothing really fancy here. Basically: + +```php +if(!method_exists($method)) { + return Namespace\For\Your\Controllers\Tag\Routes\MyTemplateTag->process(); +} +``` + +But the Control Panel Router is different. + +### Control Panel Routing + +Unlike the above, where everything's really one to one, an ExpressionEngine Control Panel is based around navigation. There can be multiple views. And multiple URL parameters. Which makes this much closer to an actual Controller paradigm than the other methods. + +> Quick word on setting up Routes for the Control Panel: you HAVE to declare a dedicated `route()` method available to process the request. ExpressionEngine has to delegate the request + +#### Noun/Verb Routing + +That's made that up BTW, but it's fairly accurate way to think of the routing. This allows for quite a few situations though: + +1. You have a "thing" ("/thing" routes to "Cp\Routes\Thing") +2. You have a thing you wanna do something with ("thing/create" routes to "Cp\Routes\Thing\Create") +3. You have a "thing", you declare what you wanna do with it, then you say which thing you want to use ("/thing/edit/12" routes to "Cp\Routes\Thing\Edit", passing "12" as a parameter) \ No newline at end of file diff --git a/docs/development/custom-template-tags.md b/docs/development/custom-template-tags.md deleted file mode 100644 index f3c0976af..000000000 --- a/docs/development/custom-template-tags.md +++ /dev/null @@ -1,392 +0,0 @@ ---- -lang: php ---- - - - -# Adding Template Tags - -[TOC] - -## Overview - -TIP:If you are unfamiliar with Template Tags be sure to read the docs on [ExpressionEngine's template language](/templates/language.md) first. - -Creating your own custom template tags allows you to display dynamic data from your add-on anywhere you want, in any template. - -NOTE:Before adding a template tag to your add-on, you need to already have an add-on in place. See [Building An Add-On: Getting Started](development/addon-development-overview.md#getting-started) for how to generate the starter files for your add-on. - -## Creating An Amazing Template Tag -Tags are created via the CLI by using the `make:template-tag` command. - -``` -php system/ee/eecli.php make:template-tag -Let's build a new tag! -What is the tag name? Amazing -What add-on is the tag being added to? [amazing_add_on]: amazing_add_on -Building Tag. -Tag created successfully! -``` - -Follow the prompts to add a tag file to your add-on. - -This will create a `Tags` folder in your add-on. - -``` -amazing_add_on - ┣ Tags - ┃ ┗ AmazingTag.php - ┗ ... - ``` - -## Anatomy of A Template Tag - -**class** `class [TagName]` - -Inside `Tags/ExampleTag.php` we see the following code generated for us: - -``` -TMPL->tagdata;` to capture the template data that is between our opening and closing tag. - -Let's update our example to capture the text we want to make bold in our template and render it back to the browser. - -Our tag's class: - -``` -class Bold extends AbstractRoute -{ - // Example tag: {exp:amazing_add_on:bold} - public function process() - { - return ''.ee()->TMPL->tagdata.''; - } -} -``` - -In our template: - -``` -We want to bold {exp:amazing_add_on:bold}this text{/exp:amazing_add_on:bold}. -``` - -Page source code after our template is rendered in the browser: - -``` -We want to bold this text. -``` - -As you can see, any template data between our opening and closing tags is captured using `ee()->TMPL->tagdata`. - -## Do Something - Create A Template Tag With Parameters -Both single tags and tag pairs can accept parameters. The template engine makes it easy to fetch them using the following variable: - - ee()->TMPL->fetch_param('param_name'); - -To see how this is used, let's create a plugin using the CLI that lets you format text based on the parameter. Our new plugin will have this syntax: - - {exp:amazing_add_on:format type="uppercase"} - Some text to process. - {/exp:amazing_add_on:format} - -We will allow the following parameter choices: - -- `type="uppercase"` -- `type="lowercase"` -- `type="bold"` -- `type="italic"` - -``` -php system/ee/eecli.php make:template-tag -What is the tag name? format -What add-on is the tag being added to? [amazing_add_on,...]: amazing_add_on -Tag created successfully! -``` - -Now we have our template tag's class located at `Tags/Format.php`: - -``` -TMPL->fetch_param('type'); - - switch ($parameter) - { - case "uppercase": - return strtoupper(ee()->TMPL->tagdata); - break; - case "lowercase": - return strtolower(ee()->TMPL->tagdata); - break; - case "bold" : - return "".ee()->TMPL->tagdata.""; - break; - case "italic": - return "".ee()->TMPL->tagdata.""; - break; - } - - } -} - -``` - -## Using Variables In Your Template Tag -With variables, we can take our tag a step further. Imagine that you have tag pair that you want to use to return data based on some parameters. We can do that using variables inside of our tag. - -We all know and love [`{exp:channel:entries}`](channels/entries.md). We use the Channel Entries tag by opening and closing the tag, passing some parameters, and then placing some template tags in our template that we know the tag will replace when rendered. - -This typically looks something like this: - -``` -{exp:channel:entries channel="news" limit="10"} -

{title}

- {body} -{/exp:channel:entries} -``` - -In the snippet above, we're passing in the `channel` and `limit` as parameters. We're then expecting the Channel Entries tag to replace the `{title}` and `{body}` **variables** when the tag is parsed. Now, let's do something similar to our add-on. - - -## Do Something - Create A Tag With Variables -Let's add a tag to our add-on that will render the current date and time. The user can pass in their timezone and the tag will return the current Date and time. - -First, generate the tag (we're calling our tag "date and time" and adding it to our Amazing Add-On): - -``` -php system/ee/eecli.php make:template-tag -What is the tag name? date and time -What add-on is the tag being added to? [amazing_add_on,...]: amazing_add_on -Tag created successfully! -``` - -Now, let's update the `DateAndTime` class (`Tags/DateAndTime.php`) to read the timezone that is passed in: - -``` -namespace ExpressionengineDeveloper\AmazingAddOn\Tags; - -use ExpressionEngine\Service\Addon\Controllers\Tag\AbstractRoute; - -class DateAndTime extends AbstractRoute -{ - // Example tag: {exp:amazing_add_on:date_and_time} - public function process() - { - $userTimeZone = ee()->TMPL->fetch_param('timezone'); - - // return something; - } -} -``` - -Now we drop in some magic using the `TMPL::parse_variables` method that's provided by the [`Template Class`](development/legacy/libraries/template.md). Here we'll get the current date and time, then create an array of variables the user can use in their template to show the current date and time. - -``` -namespace ExpressionengineDeveloper\AmazingAddOn\Tags; - -use ExpressionEngine\Service\Addon\Controllers\Tag\AbstractRoute; - -use DateTime; -use DateTimeZone; - -class DateAndTime extends AbstractRoute -{ - // Example tag: {exp:amazing_add_on:date_and_time} - public function process() - { - //check if the user set a timezone. If not just - //use the `America/New_York` timezone - $userTimeZone = empty(ee()->TMPL->fetch_param('timezone')) ? 'America/New_York' : ee()->TMPL->fetch_param('timezone'); - - //get the DateTime object - $currentDateTime = new DateTime("now", new DateTimeZone('America/New_York') ); - - //assign value to our array that will match - //the variables in our template. - $variables[] = array( - 'timezone' => $userTimeZone, - 'date' => $currentDateTime->format("F d, Y"), - 'time' => $currentDateTime->format("h:i A") - ); - - //use parse_variables method to parse our array as variables - return ee()->TMPL->parse_variables(ee()->TMPL->tagdata, $variables); - } -} -``` - -Putting this into practice, let's create a new template and add the following to our template: - -``` -{exp:amazing_add_on:date_and_time timezone="Europe/London"} - In the {timezone} timezone:
- The current date is: {date}
- The current time is: {time} - -{/exp:amazing_add_on:date_and_time} -``` - -This will render the following in our browser: - -``` -In the Europe/London timezone: -The current date is: November 15, 2022 -The current time is: 02:40 PM -``` - -TIP: Of course, this is only the beginning of what you can do with variables in your tag. We created single variables here, but you can create pair variables and much more. For more information about this, and manipulating the tagdata in your plugin, check out the [Template Class](development/legacy/libraries/template.md). \ No newline at end of file diff --git a/docs/development/database-access.md b/docs/development/database-access.md deleted file mode 100644 index c9b3fcb5a..000000000 --- a/docs/development/database-access.md +++ /dev/null @@ -1,91 +0,0 @@ - - -# Accessing the Database -You may often want to query or update your database from within your add-on. This can be done using the [Model Service](development/services/model.md) or you can also execute SQL statements by using the legacy [Database Driver](development/legacy/database/index.md). - -TIP: The Model Service is much cleaner than the legacy Database Driver. However, it also has limitations on what it can do compared to the Database Driver. - - -Let's use a real example to show how you might access data using both methods: - -We will use the Member model to show a list of members. For this we will add a template tag called `memberlist` to our add-on named "Amazing Add-On". The tag syntax will be this: - - {exp:amazing_add_on:memberlist} - - -Here is the class syntax using the Model Service: - -``` -namespace ExpressionengineDeveloper\AmazingAddOn\Module\Tags; - -use ExpressionEngine\Service\Addon\Controllers\Tag\AbstractRoute; - -class Memberlist extends AbstractRoute -{ - public $return_data = ''; - - // Example tag: {exp:amazing_add_on:memberlist} - public function process() - { - $members = ee('Model')->get('Member')->all(); - - foreach($members as $member) - { - $this->return_data .= $member->screen_name."
"; - } - - return $this->return_data; - } -} -``` - -Here is the class syntax using the legacy Database Drive: - -``` -namespace ExpressionengineDeveloper\AmazingAddOn\Module\Tags; - -use ExpressionEngine\Service\Addon\Controllers\Tag\AbstractRoute; - -class Memberlist extends AbstractRoute -{ - public $return_data = ''; - - // Example tag: {exp:amazing_add_on:memberlist} - public function process() - { - - ee()->db->select('screen_name'); - $query = ee()->db->get('members'); - - if ($query->num_rows() > 0) - { - foreach ($query->result() as $row) - { - $this->return_data .= $row->screen_name."
"; - } - } - - return $this->return_data; - } -} -``` - -Both of these examples would produce a list similiar to below: - -``` -admin -Moss -Jen -Roy -Douglas -Richmond -``` - -This is only the begining of how you can interact with the database through your add-on. Explore the [Model Service](development/services/model.md) and the legacy [Database Driver](development/legacy/database/index.md) to learn how to add more power to your add-on. \ No newline at end of file diff --git a/docs/development/extension-hooks/extension-hooks-overview.md b/docs/development/extension-hooks/extension-hooks-overview.md deleted file mode 100644 index 7bac8c885..000000000 --- a/docs/development/extension-hooks/extension-hooks-overview.md +++ /dev/null @@ -1,85 +0,0 @@ ---- -lang: php ---- - - - -# Available Core Hooks - -Throughout ExpressionEngine are what is known as "hooks"; little snippets of code in over 100 strategic places that allow the calling of third-party scripts that can rewrite and modify the inner workings of the program. By hooking into the core, you can do things like modify an entire Control Panel page, add/remove functionality, and modify the appearance of certain page elements. Hooks enable third party developers to modify aspects of ExpressionEngine without hacking the core. - -TIP: Reference [Extending The Core](development/extensions.md) for more information on developing extensions that hook into the core. - -Core hooks are categorized into 5 categories: -- Global - - [Core Library](development/extension-hooks/global/core.md) - - [Email Library](development/extension-hooks/global/email.md) - - [Filemanager Library](development/extension-hooks/global/filemanager.md) - - [Functions Library](development/extension-hooks/global/functions.md) - - [Grid Fieldtype](development/extension-hooks/global/grid.md) - - [RTE Fieldtype](development/extension-hooks/global/rte.md) - - [Input Library](development/extension-hooks/global/input.md) - - [Output Library](development/extension-hooks/global/output.md) - - [Pagination Library](development/extension-hooks/global/pagination.md) - - [Relationships Fieldtype](development/extension-hooks/global/relationships.md) - - [Session Library](development/extension-hooks/global/session.md) - - [Template Library](development/extension-hooks/global/template.md) - - [Text Helper](development/extension-hooks/global/text-helper.md) - - [Typography Library](development/extension-hooks/global/typography.md) -- API Libraries - - [Channel Fields API](development/extension-hooks/api/channel-fields.md) - - [Template Structure API](development/extension-hooks/api/template-structure.md) -- Control Panel - - [Admin Content Controller](development/extension-hooks/cp/admin-content.md) - - [CSS Controller](development/extension-hooks/cp/css.md) - - [Design Controller](development/extension-hooks/cp/design.md) - - [Javascript Controller](development/extension-hooks/cp/javascript.md) - - [Login Controller](development/extension-hooks/cp/login.md) - - [Members Controller](development/extension-hooks/cp/members.md) - - [Control Panel Menu](development/extension-hooks/cp/menu.md) - - [My Account Controller](development/extension-hooks/cp/myaccount.md) - - [Publish Controller](development/extension-hooks/cp/publish.md) -- Models - - [CategoryField Model](development/extension-hooks/model/category-field.md) - - [CategoryGroup Model](development/extension-hooks/model/category-group.md) - - [Category Model](development/extension-hooks/model/category.md) - - [Channel Model](development/extension-hooks/model/channel.md) - - [Channel Entry Model](development/extension-hooks/model/channel-entry.md) - - [ChannelFieldGroup Model](development/extension-hooks/model/channel-field-group.md) - - [ChannelField Model](development/extension-hooks/model/channel-field.md) - - [ChannelFormSettings Model](development/extension-hooks/model/channel-form-settings.md) - - [ChannelLayout Model](development/extension-hooks/model/channel-layout.md) - - [Comment Model](development/extension-hooks/model/comment.md) - - [File Model](development/extension-hooks/model/file.md) - - [Fluid Field Library](development/extension-hooks/model/fluid-field.md) - - [GlobalVariable Model](development/extension-hooks/model/template-global-variable.md) - - [MemberField Model](development/extension-hooks/model/member-field.md) - - [Member Model](development/extension-hooks/model/member.md) - - [Role Model](development/extension-hooks/model/role.md) - - [Site Model](development/extension-hooks/model/site.md) - - [Snippet Model](development/extension-hooks/model/template-snippet.md) - - [SpecialityTemplate Model](development/extension-hooks/model/template-specialty-template.md) - - [Status Model](development/extension-hooks/model/status.md) - - [TemplateGroup Model](development/extension-hooks/model/template-group.md) - - [TemplateRoute Model](development/extension-hooks/model/template-route.md) - - [Template Model](development/extension-hooks/model/template.md) -- Modules - - [Channel Module](development/extension-hooks/module/channel.md) - - [Channel Form](development/extension-hooks/module/channel-form.md) - - [Comment Module](development/extension-hooks/module/comment.md) - - [Email Module](development/extension-hooks/module/email.md) - - [Forum Module](development/extension-hooks/module/forum.md) - - [Member Module](development/extension-hooks/module/member.md) - - [Member Module Authorization](development/extension-hooks/module/member-auth.md) - - [Member Module Registration](development/extension-hooks/module/member-register.md) - - [Member Module Settings](development/extension-hooks/module/member-settings.md) - - [Search Module](development/extension-hooks/module/search.md) - - [Simple Commerce Module](development/extension-hooks/module/simple-commerce.md) - - [Wiki Module](development/extension-hooks/module/wiki.md) \ No newline at end of file diff --git a/docs/development/extensions.md b/docs/development/extensions.md index fc22ce741..5d78f1ea2 100755 --- a/docs/development/extensions.md +++ b/docs/development/extensions.md @@ -11,178 +11,435 @@ lang: php @license https://expressionengine.com/license Licensed under Apache License, Version 2.0 --> -# Extensions and Hooks +# Extensions Development [TOC] ## Overview -Within ExpressionEngine are what is known as "hooks"; little snippets of code in over 100 strategic places that allow the calling of third-party scripts that can rewrite and modify the inner workings of the program. By hooking into the core, you can do things like modify an entire Control Panel page, add/remove functionality, and modify the appearance of certain page elements. Hooks enable third party developers to modify aspects of ExpressionEngine without hacking the core. +Within ExpressionEngine are what is known as 'hooks'; little snippets of code in over 100 strategic places that allow the calling of third-party scripts that can rewrite and modify the inner workings of the program. Extensions can do things like modify an entire Control Panel page, add/remove functionality, and modify the appearance of certain page elements. Extensions enable third party developers to modify aspects of ExpressionEngine without hacking the backend scripts. You can think of an Extension as a plugin. But unlike a plugin, Extensions are not used within your templates, they instead allow you to modify the core system itself. -NOTE:Before adding an extension hook to your add-on, you need to already have an add-on in place. See [Building An Add-On: Getting Started](development/addon-development-overview.md#getting-started) for how to generate the starter files for your add-on. +An Extension is an add-on script that is placed in the `/system/user/addons//` directory and then enabled via the [Add-on Manager](control-panel/addons-manager.md) in the Control Panel. Extensions can have their own settings and their own database tables, if necessary, but neither is required. If settings are available for an Extension, a language file is required, but unlike a module there is no control panel for Extensions. -## Creating An Amazing Extension +NOTE: Extensions can also be **generated quickly by the Command Line Interface (CLI)**. Refer to the [make:addon command](cli/built-in-commands/make-addon.md) for more information. -We can give our add-on the ability to hook into the core of ExpressionEngine by using the CLI: -``` -$ php system/ee/eecli.php make:extension-hook -Let's implement an extension hook! -What hooks would you like to use? (Read more: https://docs.expressionengine.com/latest/development/extensions.html) typography_parse_type_end -What add-on is the extension hook being added to? [amazing_add_on]: amazing_add_on -Building Extension hook. -Extension hook created successfully! +## Naming Convention -``` +Extensions have a similar naming convention to ExpressionEngine plugins so current developers should get the hang of them quickly. There is only a single file required for an extension and inside this file should be a PHP class. The name of the class is used in the file name of the extension with the addition of the suffix `_ext` so that the name of the file is the _lower-cased_ class name with the prefix ext. and the standard PHP suffix of `.php`. So, if we have an extension named '`Link_truncator`', then the file name for this extension would be '`ext.link_truncator.php`' and the class would be called '`Link_truncator_ext`'. +## Add-on Setup file -TIP: Files that interact with ExpressionEngine core hooks are referred to as "extensions" because they extend the functionality of ExpressionEngine. +All add-ons must have an `addon.setup.php` file. This one will be quite simple: -This will create an `ext.[addon_name].php` file in our add-on along with an `Extensions` folder where we will build out the code we want to run when we interact with a core hook. + 'Me', + 'author_url' => 'https://example.com/', + 'name' => 'Link Truncator', + 'description' => '', + 'version' => '1.0.0', + 'namespace' => 'MyAddon\LinkTruncator', + 'settings_exist' => TRUE + ); -``` -amazing_add_on - ┣ Extensions - ┃ ┣ [HookName].php - ┃ ... - ┗ ext.amazing_add_on.php - ``` +## The Base Extension Class -TIP: A single add-on can interact with as many hooks as you want. +Inside an extension file should be a class, which will be called by ExpressionEngine whenever this particular extension is required. The constructor for the class will require one parameter that will receive settings for the extension and set a class variable. -TIP: Extensions need to be enabled to work. When you create an extension, a migration is added which will enable the extension on install. However if you need it immediately available, you can use the `--install` or `-i` flag when creating your extension. This would look like `make:extension-hook --install`. + class Link_truncator_ext { -## Anatomy Of An Extension -Once we've added the ability to hook into the core with our add-on, an `Extensions` folder is created. The CLI will generate a class and a respective file for each core hook we wish to use. + var $settings = array(); -Here we have added the ability to interact with the [`typography_parse_type_end()`](/development/extension-hooks/global/typography.html#typography_parse_type_endstr-this-prefs) hook. + /** + * Constructor + * + * @param mixed Settings array or empty string if none exist. + */ + function __construct($settings='') + { + $this->settings = $settings; + } + // END + } + // END CLASS -So our add-on structure now looks like this: +Besides the \$settings class variable, there are five other required class variables that your extension should have. These variables output meta information about your extension to the Extensions Manager so that it can describe your extension, provide documentation, and update settings (if any). -``` -amazing_add_on - ┣ Extensions - ┃ ┣ TypographyParseTypeEnd.php - ┣ addon.setup.php - ┣ ext.amazing_add_on.php - ┗ upd.amazing_add_on.php - ``` + class Link_truncator_ext { + var $name = 'Link Truncator'; + var $version = '1.0'; + var $description = 'Truncates long links'; + var $settings_exist = 'y'; + var $docs_url = ''; // 'https://expressionengine.com/expressionengine/user-guide/'; -### `class [HookName]` + var $settings = array(); -Inside `Extensions\TypographyParseTypeEnd.php` we see the following code generated for us: + /** + * Constructor + * + * @param mixed Settings array or empty string if none exist. + */ + function __construct($settings = '') + { + $this->settings = $settings; + } + } + // END CLASS -``` -settings = array( + 'max_link_length' => 18, + 'truncate_cp_links' => 'no', + 'use_in_forum' => 'no' + ); + + + $data = array( + 'class' => __CLASS__, + 'method' => 'truncate_this', + 'hook' => 'typography_parse_type_end', + 'settings' => serialize($this->settings), + 'priority' => 10, + 'version' => $this->version, + 'enabled' => 'y' + ); + + ee()->db->insert('extensions', $data); } -} -``` +Here is a quick run down of what each of these fields in the database table mean: + +- `extension_id` - primary id for row in table +- `class` - name of your extension's class +- `method` - method being called for this extension hook +- `hook` - name of the extension hook in the program +- `settings` - serialized array of settings, usually empty by default +- `priority` - an extension hook could have many extensions being called, so there needs to be priority. 1 => First, 10 => Last. +- `version` - version of extension when activated, used for updating +- `enabled` - is this extension activated + +Updating an extension is extremely easy in ExpressionEngine. The user will simply upload the new version of the extension and ExpressionEngine will automatically update the extension the next time it is called. All that is required is an intelligent function called `update_extension()`. The program will automatically compare the version of the extension information in the database against the version of the extension file, and if the extension file is a newer version it calls this function. + + /** + * Update Extension + * + * This function performs any necessary db updates when the extension + * page is visited + * + * @return mixed void on update / false if none + */ + function update_extension($current = '') + { + if ($current == '' OR $current == $this->version) + { + return FALSE; + } + + if ($current < '1.0') + { + // Update to version 1.0 + } -As we can see, the CLI has correctly created a new class using our core hook in PascalCase as the name. + ee()->db->where('class', __CLASS__); + ee()->db->update( + 'extensions', + array('version' => $this->version) + ); + } -Inside our class is the `process()` function. Again the CLI has already added all parameters that will be passed in from the core hook. +## Disabling -TIP:Reference the [Available Core Hooks](development/extension-hooks/extension-hooks-overview.md) section of the docs to read on what parameters your hook uses. +When an extension is enabled for the very _first_ time, the `activate_extension()` function is called and all of the extension calls are inserted into the database. When an extension is disabled though, these extension calls are not removed from the database. Instead they are merely disabled, which allows settings to be preserved and not removed so that they are still there if the extension is enabled again in the future. -From the [`typography_parse_type_end()`](/development/extension-hooks/global/typography.html#typography_parse_type_endstr-this-prefs) docs we can see that this hook modifies a string after all other typography is processed. Thus we should be able to take a string, manipulate it, then pass it back to ExpressionEngine to be rendered in the template. +This causes a problem for developers who, while developing an extension, will often enable an extension to test their code but before they have added all of their extension calls to the `activate_extension()` function. What we have done is allowed the creation of a `disable_extension()` function in an extension's class. If this function exists in the class, it will be called whenever your extension is disabled. This will allow you to clear out your extension's data and basically start fresh every single time. -We know that we should expect the following parameters for this hook: + /** + * Disable Extension + * + * This method removes information from the exp_extensions table + * + * @return void + */ + function disable_extension() + { + ee()->db->where('class', __CLASS__); + ee()->db->delete('extensions'); + } +## Settings -| Parameter | Type | Description | -| --------- | -------- | -------------------------------------------------------- | -| \$str | `String` | The string currently being parsed | -| \$this | `Object` | The Typography library object | -| \$prefs | `Array` | Array of preferences sent to `EE_Typography::parse_type` | +[TOC=3] -We also know that we should be returning a string from our `process()` function. +### Abstracted Settings Form and Processing +If you want to give your extension the ability to have settings, then we have written an abstracted layer to make it extremely easy. First, you have to make sure that you have your `$settings_exist` class variable set to '`y`'. Second, you need a language file for your extension with the file name of the language file being the extension's lower-cased class name with a suffix of '`_lang.php`'. Make sure the language file is put in the `/system/user/addons/package name/language/` directory too. And finally, you need to have a method in your extension's class called `settings()`. This function will return an array in a certain form that will help the Extensions Manager automatically create a form for your settings. -## Do Something - Create An Extension Hook + // -------------------------------- + // Settings + // -------------------------------- -Let's do something with our hook to demonstrate how this would work. We're going to continue working with the `typography_parse_type_end()` hook by replacing "e" with "EE" everywhere in our templates (because EE is amazing!) + function settings() + { + $settings = array(); -Using the CLI to generate the extension hook (notice the `-i` flag to immediately enable the extension hook): + // Creates a text input with a default value of "EllisLab Brand Butter" + $settings['brand'] = array('i', '', "EllisLab Brand Butter"); -``` -$ php system/ee/eecli.php make:extension-hook -i -Let's implement an extension hook! -What is the extension hook name? Amazing Hook -What hooks would you like to use? (Read more: https://docs.expressionengine.com/latest/development/extensions.html) typography_parse_type_end -What add-on is the extension hook being added to? [amazing_add_on]: amazing_add_on -Building Extension hook. -Extension hook created successfully! -Installing extension hook... -Extension hook installed! -``` + // Creates a textarea with 20 rows and an empty default value + $settings['description'] = array('t', array('rows' => '20'), ''); -This creates our `Extensions/TypographyParseTypeEnd.php` file for us. This file will initially look like this: + // Creates a set of radio buttons, one for "Yes" (y), one for "No" (n) and a default of "Yes" + $settings['tasty'] = array('r', array('y' => "Yes", 'n' => "No"), 'y'); -``` + // Creates a set of checkboxes, one for "Lowfat" (l) and one for "Salty" (s), and a + // default of both items being checked + $settings['details'] = array('c', array('l' => "Lowfat", 's' => "Salty"), array('l', 's')); - 'France', 'de' => 'Germany', 'us' => 'United States'), 'us'); -namespace ExpressionengineDeveloper\AmazingAddOn\Extensions; + // Creates a multi-select box with the options "Derek" (dj), "Leslie" (lc), and "Rick" (re) with + // Derek and Rick selected by default + $settings['enjoyed_by'] = array('ms', array('dj' => 'Derek', 'lc' => 'Leslie', 're' => 'Rick'), array('dj', 're')); -use ExpressionEngine\Service\Addon\Controllers\Extension\AbstractRoute; -class TypographyParseTypeEnd extends AbstractRoute -{ - public function process() + // General pattern: + // + // $settings[variable_name] => array(type, options, default); + // + // variable_name: short name for the setting and the key for the language file variable + // type: i - text input, t - textarea, r - radio buttons, c - checkboxes, s - select, ms - multiselect + // options: can be string (i, t) or array (r, c, s, ms) + // default: array member, array of members, string, nothing + + return $settings; + } + // END + +A note about the values array for the second field: The keys will be used as the value for that item while the value will be the language text for that item. If you want, the value can be the name of a language variable from your extension's language file and the Extensions Manager will automatically retrieve it for you. + +### Built In Settings Form and Processing + +Alternatively, if your settings require a special form that cannot be created by the abstracted layer above, then ExpressionEngine permits you to create your own settings form and processing functions within your Extension. First, you will need to have a method in your extension's class called `settings_form()`. + + /** + * Settings Form + * + * @param Array Settings + * @return void + */ + function settings_form($current) { + $name = 'link_truncator'; + + if ($current == '') + { + $current = array(); + } + + $defaults = array( + 'max_link_length' => 20, + 'truncate_cp_links' => 'n' + ); + + $values = array_replace($defaults, $current); + + $vars = array( + 'base_url' => ee('CP/URL')->make('addons/settings/' . $name . '/save'), + 'cp_page_title' => 'Link Truncator Settings', + 'save_btn_text' => 'btn_save_settings', + 'save_btn_text_working' => 'btn_saving', + 'alerts_name' => 'link-truncator-save', + 'sections' => array(array()) + ); + + $vars['sections'] = array( + array( + array( + 'title' => 'max_link_length', + 'fields' => array( + 'max_link_length' => array( + 'type' => 'text', + 'value' => $values['max_link_length'], + 'required' => TRUE + ) + ) + ), + // Site short name field + array( + 'title' => 'truncate_cp_links', + 'desc' => 'truncate_cp_links_desc', + 'fields' => array( + 'truncate_cp_links' => array( + 'type' => 'yes_no', + 'value' => $values['truncate_cp_links'], + 'required' => TRUE + ) + ) + ) + ) + ); + + return ee('View')->make('link_truncator:index')->render($vars); } -} -``` -All the functionality we want to include when our hook is executed needs to go inside our `process()` method. Here we are going to take the string passed in by the `TypographyParseTypeEnd()` core hook, replace all instances of `e` with `EE`, and then return the updated string. +### View File -``` - + embed('ee:_shared/form')?> + -use ExpressionEngine\Service\Addon\Controllers\Extension\AbstractRoute; +### Save Settings -class TypographyParseTypeEnd extends AbstractRoute -{ - public function process($str, $obj, $prefs) +Lastly, you will need to have a method in your extension's class called `save_settings()`. This function will be called when your `settings_form()` method's form is submitted. Use it to process the data sent and put it into the exp_extensions database table. Remember that the data put into the database is a serialized array, so handle it appropriately. + + /** + * Save Settings + * + * This function provides a little extra processing and validation + * than the generic settings form. + * + * @return void + */ + function save_settings() { - //check if $str has content, if so replace - //all "e" with "EE" - if(!is_null($str) ){ - $str = str_replace("e","EE",$str); + if (empty($_POST)) + { + show_error(lang('unauthorized_access')); } - return $str; + + ee()->lang->loadfile('link_truncator'); + + $len = ee()->input->post('max_link_length'); + + if ( ! is_numeric($len) OR $len <= 0) + { + ee('CP/Alert')->makeInline('link-truncator-save') + ->asIssue() + ->withTitle(lang('message_failure')) + ->addToBody(sprintf(lang('max_link_length_range'), $len)) + ->defer(); + } + else + { + ee('CP/Alert')->makeInline('link-truncator-save') + ->asSuccess() + ->withTitle(lang('message_success')) + ->addToBody(lang('preferences_updated')) + ->defer(); + } + + ee()->functions->redirect(ee('CP/URL')->make('addons/settings/link_truncator')); + } + +## Calling of the Extension + +The following is an example of an ExpressionEngine Extension Hook that is available for use: + + // ------------------------------------------- + // 'typography_parse_type_end' hook. + // - Modify string after all other typography processing + // + if (ee()->extensions->active_hook('typography_parse_type_end') === TRUE) + { + $str = ee()->extensions->call('typography_parse_type_end', $str, $this, $prefs); + } + // + // ------------------------------------------- + +The first parameter of `$this->extensions->call_extension` is the name of the hook, which lets the Extension class know what extensions to call. The other three parameters are variables taken from the function that the hook is embedded within. They provide information and data for the extensions being called for this hook, which allows those extensions to have information about the script that allow them to perform certain actions or manipulate data. When an extension is called, ExpressionEngine loads the extension file, instantiates the extension's class, and then calls the method specified for this extension hook as specified by the extension when it was activated (see above concerning activation). + +When that method is called in the extension's class those other three parameters will be sent to the method automatically. Here is what the method might look like: + + /** + * Shorten Link Text + * + * This function is a callback method for preg_replace_callback in the method below. + * + * @param array array from the preg_match + * @return string Newly truncated Link. + */ + function _shorten_link_text($matches) + { + $link_text = $matches[3]; + $link_text = substr($link_text, strpos($link_text, '://') + 3); + + if (strlen($link_text) >= (int) $this->settings['max_link_length'] ) + { + $l = (int) $this->settings['max_link_length'] / 2; + + $b_part = substr($link_text, 0, $l); + $e_part = substr($link_text, -$l); + + $link_text = $b_part . '…' . $e_part; + } + + return $matches[1].$link_text.''; } -} -``` + /** + * Truncate This + * + * This function is the meat & potatoes of the extension, where all + * the work is done. + * + * @see https://expressionengine.com/user-guide/development/extension_hooks/global/typography/index.html#typography-parse-type-end + * + * @param string string to look + * @param object typography object + * @param array array of preferences + * @return string + */ + function truncate_this($str, $obj, $prefs) + { + if ($this->settings['truncate_cp_links'] == 'no' && REQ == 'CP') + { + return $str; + } -Now when we render our site using fields like textarea or other fields that use the Typography Library, our users will see how amazing EE is. + if (isset($obj->EE->FRM_CORE) && $this->settings['use_in_forum'] == 'no') + { + return $str; + } + + $pattern = "/(]*\s+href\s*=\s*(\042|047)([^\\2]*?)\\2[^>]*>)\\3<\/a>/i"; + + $str = preg_replace_callback($pattern, array(get_class($this), '_shorten_link_text'), $str); + + return $str; + } -NOTE:**NOTE:** Although ExpressionEngine is amazing, we do not suggest using the above example in production. +The three parameters from the extension hook are mapped straight to the three parameters of the method being called, and so your extension can easily use those parameters and do what it needs to do. The Extension Hook library will have a record of all extension hooks and the parameters available to you, along with a suggestion or two about what can be done with the extension hook. ## Multiple Extensions, Same Hook -When an extension hook is called, ExpressionEngine checks the database to see if there are any extensions available for the hook. If there are extensions, then it processes them in order based on their priority level with the lower the priority number the sooner the extension is called. Because of priority, add-ons that use extensions might interfere with each other, so we have provided two variables for helping with that. +When an extension hook is called, ExpressionEngine checks the database to see if there are any extensions available for the hook. If there are extensions, then it processes them in order based on their priority level with the lower the priority number the sooner the extension is called. Because of priority, extensions might interfere with each other, so we have provided two variables for helping with that. -### `ee()->extensions->last_call` +### `$this->extensions->last_call` -There will be rather popular hooks being used by multiple extensions and some hooks will expect you to return data to the extension hook. Because of that, there is a variable available from the Extensions class (`ee()->extensions`) that will contain the returned data of any prior extensions for that hook. Say, there is a hook for formatting text and an extension before yours is called. That extension will be returning the text formatted in its own way, but then your extension is called with the original text details being sent. In such an instance of data being returned and possible prior extensions, there is a variable available to retrieve that already formatted text: `ee()->extensions->last_call`. This variable will return whatever the last extension returned to this hook. If there was no prior extension, then the value of this variable is `FALSE`. +There will be rather popular hooks being used by multiple extensions and some hooks will expect you to return data to the extension hook. Because of that, there is a variable available from the Extensions class (`$this->extensions`) that will contain the returned data of any prior extensions for that hook. Say, there is a hook for formatting text and an extension before yours is called. That extension will be returning the text formatted in its own way, but then your extension is called with the original text details being sent. In such an instance of data being returned and possible prior extensions, there is a variable available to retrieve that already formatted text: `$this->extensions->last_call`. This variable will return whatever the last extension returned to this hook. If there was no prior extension, then the value of this variable is `FALSE`. -### `ee()->extensions->end_script` +### `$this->extensions->end_script` -Many extension hooks exist for the express purpose of totally controlling a page or script in the Control Panel. They are meant for redesigning the appearance of a form or perhaps usurping a script for processing form data. In those instances you want your extension to be the last thing called for that extension hook so that nothing else is processed after that point. The `ee()->extensions->end_script` exists solely for that purpose. If you set this value to TRUE, then once your extension is done being processed the execution of the hook is finished, as is the script that the extension hook is contained within. \ No newline at end of file +Many extension hooks exist for the express purpose of totally controlling a page or script in the Control Panel. They are meant for redesigning the appearance of a form or perhaps usurping a script for processing form data. In those instances you want your extension to be the last thing called for that extension hook so that nothing else is processed after that point. The `$this->extensions->end_script` exists solely for that purpose. If you set this value to TRUE, then once your extension is done being processed the execution of the hook is finished, as is the script that the extension hook is contained within. diff --git a/docs/development/fieldtypes/example.md b/docs/development/fieldtypes/example.md index d2dea2c68..90fc2fca0 100644 --- a/docs/development/fieldtypes/example.md +++ b/docs/development/fieldtypes/example.md @@ -17,34 +17,9 @@ The snippets below were truncated for clarity. The full example fieldtype can be [TOC] -## Generate Fieldtype File -Start by generating a custom fieldtype for your add-on using the `make:fieldtype` command. - -``` -$ php system/ee/eecli.php make:fieldtype -Let's implement a fieldtype! -What is the fieldtype name? Amazing Fieldtype -What add-on is the fieldtype being added to? [amazing_add_on]: amazing_add_on -Building fieldype. -Fieldtype created successfully! -``` - -This will create a `ft.amazing__fieldtype.php` in your add-on's folder. - -We'll now update the methods found within our fieldtype's class to provide our functionality: -- `function install()` -- `function display_global_settings()` -- `function save_global_settings()` -- `function display_settings()` -- `function save_settings()` -- `function display_field()` -- `function replace_tag()` -- `function replace_latitude()` -- `function replace_tag_catchall()` - -## Installation - `install()` - -The google maps fieldtype is going to have 3 global settings. Latitude, longitude, and zoom. These will determine what the default map looks like. By returning an array from within our `install()` method we can provide a default set of global settings. +## Installation + +The google maps fieldtype is going to have 3 global settings. Latitude, longitude, and zoom. These will determine what the default map looks like. By returning an array from within install we can provide a default set of global settings. function install() { @@ -60,7 +35,7 @@ The google maps fieldtype is going to have 3 global settings. Latitude, longitud The installation method for this fieldtype does not create any additional tables, so no cleanup work needs to be done. The default `uninstall()` method provided by the EE_Fieldtype parent class will suffice. Most fieldtype methods have sensible defaults to help reduce duplicate code. -## Global - `display_global_settings()` +## Global Settings The installer sets the default global settings, but currently there is no way to change these from the control panel. We can use the `display_global_settings()` method to return the contents of the settings form. Having this method also enables the global settings link on the overview page. @@ -75,9 +50,9 @@ The installer sets the default global settings, but currently there is no way to return $form; } -Manually entering longitudes and latitudes is inconvenient so the final method in the example download also adds some JavaScript to let the user choose from a map. +Manually entering longitudes and latitudes is inconvenient so the final method in the example download also adds some javascript to let the user choose from a map. -## Saving Global Settings - `save_global_settings()` +## Saving Global Settings In most instances saving the global settings is as easy as storing the `$_POST` array. Remember to include existing global settings if not everything can be changed. @@ -86,7 +61,7 @@ In most instances saving the global settings is as easy as storing the `$_POST` return array_merge($this->settings, $_POST); } -## Individual Settings - `display_settings()` +## Individual Settings The default map may not always be the desired choice for each map field, so on the regular settings page it will display a similar configuration screen. We will use the familiar [Shared Form View](development/shared-form-view.md) format to display our settings. @@ -152,7 +127,7 @@ The default map may not always be the desired choice for each map field, so on t )); } -## Saving Individual Settings - `save_settings()` +## Saving Individual Settings Saving individual field settings works largely the same as saving global settings. Keep be aware that they are later merged with global settings, so they can override a global setting. @@ -167,7 +142,7 @@ If your fieldtype needs a wide style on the publish form, like Grid or a Textare ); } -## Displaying the Field On the Publish Page - `display_field()` +## Displaying the Field (Publish Page) With all the settings set up, it can now be displayed on the publish screen. A key factor when you get to this stage is to decide in what format the data should be stored. Since all three available values in this case are numbers, this field will store them separated by pipes (`lang|lat|zoom`). @@ -198,9 +173,9 @@ With all the settings set up, it can now be displayed on the publish screen. A k return $hidden_input.'
'; } -## Rendering the Tag - `replace_tag()` +## Rendering the Tag -Finally, the field needs a frontend display. For google maps this will almost exclusively be JavaScript. +Finally, the field needs a frontend display. For google maps this will almost exclusively be javascript. function replace_tag($data, $params = array(), $tagdata = FALSE) { @@ -218,8 +193,6 @@ Finally, the field needs a frontend display. For google maps this will almost ex Along with parameters a field can also provide tag modifiers to change its output. In the template these are called by adding a colon to the fieldname, followed by the modifier name. For example: `{myfield:latitude}`. The advantage that field modifiers have over parameters is that they can be used in conditionals. -These methods are not created by the CLI and need to be added as needed to your fieldtype's class. - Parsing the modifiers is identical to using the regular `replace_tag()` function. The method name must start with `replace_` followed by the modifier name. function replace_latitude($data, $params = array(), $tagdata = FALSE) diff --git a/docs/development/fieldtypes/fieldtypes.md b/docs/development/fieldtypes/fieldtypes.md index 0de4e271b..dcd10e96b 100644 --- a/docs/development/fieldtypes/fieldtypes.md +++ b/docs/development/fieldtypes/fieldtypes.md @@ -15,40 +15,12 @@ lang: php [TOC] -## Overview -ExpressionEngine ships with a range of fieldtypes already in place. However, perhaps you want to had your fieldtype that executes functionality differently than the fields that ship with ExpressionEngine. If so, then read below for how to create your own custom fieldtype. -TIP: For an overview of what a Fieldtype is, read the [Fieldtype Overview docs](/fieldtypes/overview.md). +NOTE: Fieldtypes can also be **generated quickly by the Command Line Interface (CLI)**. Refer to the [make:addon command](cli/built-in-commands/make-addon.md) for more information. -NOTE:Before adding a fieldtype to your add-on, you need to already have an add-on in place. See [Building An Add-On: Getting Started](development/addon-development-overview.md#getting-started) for how to generate the starter files for your add-on. +## Basic File Structure -## Creating An Amazing Fieldtype - -Adding a custom fieldtype to your add-on is easy with the `make:fieldtype` command. - -``` -$ php system/ee/eecli.php make:fieldtype -Let's implement a fieldtype! -What is the fieldtype name? Amazing Fieldtype -What add-on is the fieldtype being added to? [amazing_add_on]: amazing_add_on -Building fieldype. -Fieldtype created successfully! -``` - -This will create a `ft.[fieldtype_name].php` in your add-on's folder. In the example above, this creates a file named `ft.amazing__fieldtype.php`. - -``` -amazing_add_on - ... -┣ ft.[fieldtype_name].php -┗ ... - ``` - -## Anatomy of A Fieldtype - -[TOC=3] - -All fieldtypes must inherit from the `EE_Fieldtype` base class and they must provide an `$info` array with a name and version number. +All fieldtypes should be placed into the `system/user/addons` folder in a package and be named after that package name. So in a packaged named google_maps the fieldtype file will be `ft.google_maps.php`. All fieldtypes must inherit from the `EE_Fieldtype` base class and they must provide an \$info array with a name and version number. -# Add Control Panel Pages To Your Add-On +# Modules -[TOC=2-3] +[TOC] -## Overview -If you have ever used some of the add-ons that ship with ExpressionEngine such as [Block and Allow](/add-ons/blocklist.md) or [Pro Search](/add-ons/pro-search/overview.md), you will notice those add-ons have settings and configuration pages associated with them in the Control Panel. You add this functionality to your add-on using Control Panel Routes.. Whenever you add a Control Panel Route to your add-on using the CLI, a `ControlPanel/Routes` and `views` folder is automatically created for you, opening the door to creating your own Control Panel settings and pages. +Modules are the most complex form of add-on. They can have their own database tables, backend control panels, tabs and fields that are included on the publish page, as well as their own tags for use in templates. -NOTE:**NOTE:** Control Pages are what is rendered in the browser when visiting your add-on in the Add-on Manager. Control Panel Routes are what we had to our add-on that tells ExpressionEngine to render the pages. Think of it as we add a route to create a page. +NOTE: Modules can also be **generated quickly by the Command Line Interface (CLI)**. Refer to the [make:addon command](cli/built-in-commands/make-addon.md) for more information. -NOTE:Before adding a Control Panel route to your add-on, you need to already have an add-on in place. See [Building An Add-On: Getting Started](development/addon-development-overview.md#getting-started) for how to generate the starter files for your add-on. -## Creating An Amazing Control Panel Route +## Basic File Structure -If your add-on doesn't already have the required `ControlPanel/Routes` and `views` files, you can add a route to your add-on using the `make:cp-route` command in the CLI. +Modules should be placed into the add-ons folder in a package and be named after that package name. At a minimum, there are 4 required files for any module: -``` -$ php system/ee/eecli.php make:cp-route -Let's create a control panel route! -What is the route name? index -What add-on is the route being added to? [amazing_add_on]: amazing_add_on -Building control panel route. -Control panel route created successfully! -``` +- `addons/module_name/upd.module_name.php` - installs, uninstalls and updates the module +- `addons/module_name/mcp.module_name.php` - the backend control panel +- `addons/module_name/mod.module_name.php` - the core module file, which process module tags used in templates +- `addons/module_name/language/english/module_name_lang.php` -holds all language variables, allowing multiple language versions of the module -Follow the prompts to add the route to your add-on. +In addition to these required files, there are a number of optional files that may be useful for modules: -This will create an `mcp.[addon_name].php` file in your add-on along with a `ControlPanel/Routes` and `views` folder. +- `addons/module_name/tab.module_name.php` - required to add a tab/fields to the publish page +- `addons/module_name/views/anyname.html` - multiple view files inside the view folder are the primary method of presenting the backend control panel pages +- `addons/module_name/libraries/anyname.php` - modules may make use of their own libraries, either extending existing libraries or adding new ones for use within the module -Inside of the `ControlPanel/Routes` folder, you will see that the CLI has created your first control route with `ControlPanel/Routes/Index.php` and a corresponding view in you `views` folder. This page is accessible via The Add-On Manager -> [Add-On Name] or via the `/admin.php?/cp/addons/settings/[add-on-name]` URL. +With the possible exception of library files, file names and folders should be lower-case and contain no spaces. +## The Update file (upd.module_name.php) -## Anatomy Of A Control Panel Route -When you first add a route to your add-on a `ControlPanel/Routes` folder along with an `ControlPanel/Routes/[RouteName].php` starter file is created. The starter file will look something like this: +**class `Module_name_upd`** -``` -addBreadcrumb('index', 'Home'); - - $variables = [ - 'name' => 'Matt', - 'color' => 'Green' - ]; - - $this->setBody('Index', $variables); +**class `Module_name_upd`** - return $this; - } -} -``` - -Let's dissect the starter file: +[TOC=3] -### `$cp_page_title = 'home';` -`$cp_page_title` defines what is rendered in the `` tag in the browser. +### `install()` -### `function process($id = false)` -The `process()` function, similar to other functionality in your add-on, is the meat of your route file. This is where you will render views, add sidebars, and add any necessary logic. +Installs the module, adding a record to the `exp_modules` table, creates and populates and necessary database tables, adds any necessary records to the `exp_actions` table, and if custom tabs are to be used, adds those fields to any saved publish layouts. -## Adding Content To Your Page +| Parameter | Type | Description | +| --------- | --------- | ------------------------------------------------------- | +| Returns | `Boolean` | `TRUE` if everything installed properly, `FALSE` if not | -Now that you have a page in the Control Panel that users can access, let's look at how to output what you want users to see. There are four main areas to a Control Panel page where you will be outputting information to: +- Add the module to the `exp_modules` table---this step is required. Note `has_cp_backend` should be `'y'` if the module has a control panel, `'n'` otherwise; `has_publish_fields`' should be `'y'` if the module adds tabs/fields to the publish page, `'n'` otherwise: -- Breadcrumb -- Sidebar -- Toolbar -- Main Body + $data = array( + 'module_name' => 'Module_name' , + 'module_version' => $this->version, + 'has_cp_backend' => 'y', + 'has_publish_fields' => 'y' + ); -![Add-on Breadcrumbs](_images/add-on-mcp.png) + ee()->db->insert('modules', $data); -### Breadcrumbs -Located at the top of the Control Panel screen, breadcrumbs help users easily know where they are in the Control Panel and navigate your add-on's settings quickly. +- Optionally add records to the `exp_actions` table---used if your module needs to invoke actions based on frontend behavior such as form submission: -![Add-on Breadcrumbs](_images/addon_breadcrumbs.png) + $data = array( + 'class' => 'Module_name' , + 'method' => 'method_to_call' + ); -To add a breadcrumb for your current Control Panel page, simply use `$this->addBreadcrumb()`. + ee()->db->insert('actions', $data); -In the starter route file created for you by the CLI you will already see `$this->addBreadcrumb('index', 'Home');`. This will add a breadcrumb similar to the one in the screen shot above (`Add-Ons -> [Add-On Name] -> Home`). +- Optionally add the publish page tab fields to any saved publish layouts. This is ONLY used if the module adds a tab to the publish page and it requires the `tabs()` function: -If you needed to add more levels to your breadcrumbs you can chain them together as such: + ee()->load->library('layout'); + ee()->layout->add_layout_tabs($this->tabs(), 'module_name'); -``` - $this->addBreadcrumb('amazing_add_on', 'Settings') - ->addBreadcrumb('amazing_add_on/configuration', 'Configuration'); -``` +### `update($current = '')` -This would add a breadcrumb that would look like `Add-Ons -> [Add-On Name] -> Settings -> Configuration` +| Parameter | Type | Description | +| --------- | --------- | ------------------------------------------------------------------ | +| \$current | `string` | The last recorded version of the module in the `exp_modules` table | +| Returns | `Boolean` | `FALSE` if no update is needed, `TRUE` otherwise | -### Sidebar -If your add-on has multiple Control Panel pages, a sidebar can be incredibly helpful for your users to easily navigate between pages. Sidebars can either be generated automatically or manually. +This function is checked on any visit to the module's control panel, and compares the current version number in the file to the recorded version in the database. This allows you to easily make database or other changes as new versions of the module come out: -Both of these options start with generating your sidebar class using the CLI. + function update($current = '') + { + if (version_compare($current, '2.0', '=')) + { + return FALSE; + } -We use the `make:sidebar` CLI command to generate a sidebar file for us. + if (version_compare($current, '2.0', '<')) + { + // Do your update code here + } -``` -$ php system/ee/eecli.php make:sidebar -What add-on is the sidebar being added to? [amazing_add_on]: amazing_add_on -Building sidebar. -Sidebar created successfully! -``` + return TRUE; + } -This will create the file `ControlPanel/Sidebar.php` in your add-on's folder. +### `uninstall()` -``` -amazing_add_on -┣ ControlPanel -┃ ┣ Sidebar.php -┗ ... -``` +| Parameter | Type | Description | +| --------- | --------- | ------------------------------------------------------------ | +| Returns | `Boolean` | `TRUE` if everything uninstalled properly, `FALSE` otherwise | -**Note:** An add-on can only have one sidebar file. +Deletes the module record from exp_modules, any associated actions from exp_actions, and uninstalls any tables created by the module. Returns TRUE -Inside of our `Sidebpar.php` file we'll have the following: +- Optionally delete any publish page tab fields saved in publish layouts. This is ONLY used if the module adds a tab to the publish page and it requires the `tabs()` function: -``` -<?php + ee()->load->library('layout'); + ee()->layout->delete_layout_tabs($this->tabs(), 'module_name'); -namespace ExpressionEngineDeveloper\ControlPanel\Routes; +### `tabs()` -use ExpressionEngine\Service\Addon\Controllers\Mcp\AbstractSidebar; +| Parameter | Type | Description | +| --------- | ------- | ------------------------------------------------ | +| Returns | `Array` | Associative array of the tab name and tab fields | -class Sidebar extends AbstractSidebar -{ - public $automatic = true; +An optional function, included only if the module adds a tab to the publish page. This function should return an multidimensional associative array, the top array key consisting of the tab name, followed by any field names, with each field having a variety of default settings. Note that when the fields are added to the publish page, they are namespaced to prevent variable collisions: - /** - * @param false $id - * @return AbstractRoute - */ - public function process() + function tabs() { + $tabs['tab_name'] = array( + 'field_name_one'=> array( + 'visible' => 'true', + 'collapse' => 'false', + 'htmlbuttons' => 'true', + 'width' => '100%' + ), + 'field_name_two'=> array( + 'visible' => 'true', + 'collapse' => 'false', + 'htmlbuttons' => 'true', + 'width' => '100%' + ), + ); + + return $tabs; } -} -``` -#### Automatically Generate Your Sidebar -If you'd like to have your sidebar automatically generated then that's it! The `Sidebar` class will scan our `ControlPanel/Routes` folder for all available Control Panel routes and automatically create respective entries in our sidebar. There's no need to add the sidebar to your Control Panel page as it will automatically be added when the page is rendered. +## The Language File (module_name_lang.php) -Example: +The Language file contains an array named `$lang`, which is used along with the Language class to display text on a page in whatever language is selected in the user's account settings. There are two required lines in the language file for each module, which allows the name and description of the module to be viewable on the MODULES page: -With an add-on folder like this: + $lang = array( -``` -amazing_add_on -┣ ControlPanel -┃ ┣ Routes -┃ ┃ ┣ Configurations.php -┃ ┃ ┣ Settings.php -┃ ┃ ┣ Licenses.php -┃ ┃ ┣ Index.php -┃ ┗ Sidebar.php -┗ ... -``` + // Required for MODULES page -Would produce a sidebar in the Control Panel like this: + 'my_module_module_name' => 'Module Name', + 'my_module_module_description' => 'Brief description of the module- displayed on the Modules page', -![control panel sidebar](_images/addon_sidebar_start.png) + //---------------------------------------- + // Additional Key => Value pairs go here -You can take this a step further by adjusting the following properties in your Control Panel routes (`ControlPanel/Routes/[route_name].php`) to adjust how sidebar items are displayed: -##### `protected $sidebar_title (string)` -By default the sidebar link text is based on your route's name. This property will overwrite the text displayed for this route. + // END + ''=>'' + ); -##### `protected $sidebar_icon (string)` -The Font Awesome icon you wish to display next to the sidebar link for this route. +If the ExpressionEngine core language files contains string with the same key, it will be used in favor of add-on specified string. If an add-on needs to override that string, that can be done by adding it to `$ee_lang` array in the add-on's language file. -##### `protected $sidebar_priority (int)` -Give this route a higher priority in the sidebar. Higher priority items will appear first in the sidebar. +### module tab label -##### `protected $exclude_from_sidebar (bool)` -Exclude this route from the sidebar. +In addition to the two required fields you can have a custom tab label for your publish fields. Just assign the desired label to a key which shares the name of your module name: -##### `protected $sidebar_divider_before (bool)` -Inserts a divider in the sidebar before the link to this route. + // Additional Key => Value pairs go here -##### `protected $sidebar_divider_after (bool)` -Inserts a divider in the sidebar after the link to this route. + /** + * Tab Label for publish fields + * + * Assign the label you wish to use to the module_name array key + * Remember only alphanumeric characters, underscores, dashes and spaces are allowed. + */ + 'module_name' => 'Tab label' -You can even modify the generated sidebar in the `process()` method of your `ControlPanel/Routes/Sidebar.php` file by utilizing the [`CP/Sidebar Service`](development/services/sidebar.md). +## The Tab File (tab.module_name.php) -#### Manually Generate Your Sidebar +**class `Module_name_tab`** -We will set the `$automatic` property in our `ControlPanel/Routes/Sidebar.php` file to `false`. +This is an optional file, required only if your module needs to include a tab on the publish page. It must have a class with a name that is a combination of the package's name with a `_tab` suffix. There are no required class variables. Because multiple modules may be adding fields to the publish page, all third party tab fields are namespaced using the package name when displayed on the publish page. This namespacing will be stripped prior to any data being returned to the tab functions. -``` -public $automatic = false; -``` +NOTE: **Note:** if your module includes a tab, do not forget to indicate this in the update file when installing the module. Further, be sure to include the `tabs()` function in the update file, and use it when updating custom layouts on installation and uninstallation. -This tells our Sidebar class not to automatically create any sidebars. +## Tab File Function Reference -From here you can utilize utilize the [`CP/Sidebar Service`](development/services/sidebar.md) in the `process()` method of our Sidebar class to manually create your sidebar and all related items. +**class `Module_name_tab`** +[TOC=3] -### Toolbar +### `display($channel_id, $entry_id = '')` -The toolbar is a series of icons located in the top right of your add-on settings page. Many add-ons use this area for quick links to settings or other areas of their add-on settings. +| Parameter | Type | Description | +| ------------ | ------- | ----------------------------------------------------- | +| \$channel_id | `int` | Channel ID where the entry is being created or edited | +| \$entry_id | `int` | Entry ID if this is an edit, empty otherwise | +| Returns | `Array` | Settings (see below) | -![Add-on Toolbar](_images/addon_toolbar.png) +This function creates the fields that will be displayed on the publish page. It must return `$settings`, an associative array specifying the display settings and values associated with each of your fields. -We can add items to the Toolbar by passing an array into `ee()->view->header`. +The settings array elements: -``` -$header['toolbar_items'] = array( - 'settings' => array( - 'href' => ee('CP/URL','addons/settings/amazing_add_on'), - 'title' => 'settings' - ), - 'user ' => array( - 'href' => ee('CP/URL','addons/settings/amazing_add_on/user_settings'), - 'title' => 'Users' - ), - 'export ' => array( - 'href' => ee('CP/URL','addons/settings/amazing_add_on/export'), - 'title' => 'Export Data' + Array( + '...' => Array( // name of the field (same as 'field_id' below) + 'field_id' => '...', // name of the field + 'field_label' => '...', // field label, typically a language variable is used here + 'field_required' => '...', // whether to include the 'required' class next to the field label: y/n + 'field_data' => '...', // current data, if applicable + 'field_list_items' => '...', // array of options, otherwise empty string + 'options' => '...', // array of options, otherwise empty string + 'selected' => '...', // selected value if applicable to the field_type + 'field_fmt' => '...', // allowed field format options, if applicable + 'field_instructions' => '...', // instructions to be displayed for this field on the publish page + 'field_show_fmt' => '...', // whether the field format dropdown shows: y/n. Note: if 'y', you must specify the options available in field_fmt + 'field_pre_populate' => '...', // can pre-populate a field when it's a new entry + 'field_text_direction' => '...', // direction of the text: ltr/rtl + 'field_type' => '...' // may be any existing field type + ) ) -); -ee()->view->header = $header; -``` +### `validate($entry, $values)` -The icon used in the toolbar for each link corresponds to each items `title` element in the array. Available icons are `add`,`author`,`cart`,`category`,`caution`,`changes`,`channel`,`close`,`columns`,`dashboard`,`date`,`delete`,`export`,`files`,`filters`,`folder`,`gift`,`home`,`invisible`,`issue`,`locked`,`logout`,`members`,`missing`,`nested`,`offline`,`primary`,`remove`,`reorder`,`settings`,`status`,`success`,`sync`,`tabbed`,`tip`,`tools`,`user`,`view`,`visible`,`export`,`settings`. +| Parameter | Type | Description | +| --------- | ---------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| \$entry | <small>`ExpressionEngine\Module\Channel\Model\ChannelEntry`</small> | The channel entry entity | +| \$values | `array` | an associative array with field names as keys and form submission data as the value (i.e. `array('fortune' => 'All your hard work will soon pay off.'))`. The keys are derrived from the data returned by `display()`. | +| Returns | <small>`ExpressionEngine\Service\Validation\Result`</small> | A result object | -### Main Body -Probably the most important part of your add-on's Control Panel page is the main body. +Allows you to validate the data after the publish form has been submitted but before any additions to the database: -![Add-on Main body](_images/add-on-main-body.png) - -There are two ways to output to the main body of your add-on's page. -- HTML String -- Using Views - -The `setBody()` method is smart enough to know what you're trying to do. If you pass a single parameter to the method such as `$this->setBody($myString)`, the method knows you are only passing a single string to be rendered on your page. However, if you pass multiple parameters such as `$this->setBody('Index', $variables);`, the method knows that you are passing a View and an array of variables that should be passed to the View. - -Continue reading to see examples of how to use these two options. - - -#### HTML String -If you would just like to output a string of HTML for you control panel page, then you would just do something like the following: - -``` - public function process($id = false) + function validate($entry, $values) { - $this->addBreadcrumb('index', 'Home'); - - $html = "<h2>Welcome to my add-on</h2><p>This is an amazing add-on that does amazing things!" - - $this->setBody($html); + $validator = ee('Validation')->make(array( + 'foo_field_one' => 'required', + 'foo_field_two' => 'required|enum[y,n]', + )); - return $this; + return $validator->validate($values); } -``` -This would produce a page looking like this: - -![simple MCP page](_images/add-on-html-string.png) - -If you would like to make this page more dynamic or include additional functionality, then you need to explore using [Views](#views). - -#### Views - -When your add-on was created via the CLI a `views` folder was also created in your add-on's folder. Inside of this folder a file was also created, `views/Index.php`. By default this Control Panel page is already using the [View service](/development/services/view.md) by calling `$this->setBody()`. - -``` -$variables = [ - 'name' => 'Matt', - 'color' => 'Green' -]; - -$this->setBody('Index', $variables); -``` - -As we can see here, the route file is passing two arguments to `$this->setBody()`: -- `Index` is the name of the corresponding view to use (references `views/Index.php`) -- `$variables` is an array of variables that will be available to the view. - -Now let's look at the view itself, found at `views/Index.php`. +### `cloneData($entry, $values)` -``` -<?php +| Parameter | Type | Description | +| --------- | ------------------------------------------------------------------- | ------ ----------------------------- | +| \$entry | <small>`ExpressionEngine\Module\Channel\Model\ChannelEntry`</small> | The channel entry entity | +| \$values | `array` | an associative array with field names as keys and form submission data as the value (i.e. `array('fortune' => 'All your hard work will soon pay off.'))`. The keys are derrived from the data returned by `display()`. | +| Returns | `array` | $values modified array of values | -echo "<h2>Time to make magic</h2>"; +Code that needs to be executed when an entry is being [cloned](/channels/entry_cloning.md). This function is called before `validate`, so if you need to modify the data that will be passed to validation service (as well as `$_POST` array), this is the place to do it. -if (isset($name)) { - echo "<p>Name: " . $name . "</p>"; -} - -if (isset($color)) { - echo "<p>Color: " . $color . "</p>"; -} -``` - -Notice the variables `$name` and `$color` correspond to the `$variables` array passed to the view in the route file. - - -This renders as such: - -![add-on rendered with view](_images/add-on-simple-view.png) - - -Taking this a bit further, we can use the [`CP\Form` service](development/services/cp-form.md) to display panels and forms similar to what is found on many pages in the Control Panel. - -TIP:Using the `CP\Form` is highly recommend to speed up your development and to maintain the integrity of the Control Panel design. + public function cloneData(ChannelEntry $entry, $values) + { + if ($values['pages_uri'] == '') { + return $values; + } + //check if submitted URI exists + $static_pages = ee()->config->item('site_pages'); + $uris = $static_pages[ee()->config->item('site_id')]['uris']; + + //exclude current page from check + if (isset($uris[$entry->entry_id])) { + unset($uris[$entry->entry_id]); + } + //ensure leading slash is present + $value = '/' . trim($values['pages_uri'], '/'); + + while (in_array($value, $uris)) { + $value .= '_1'; + } + $_POST['pages__pages_uri'] = $values['pages_uri'] = $value; + + return $values; + } -We'll start by using the `CP\Form` service in a method which will return our `$form` array. The `$form` array will then be passed to our View through our `$variables` array. +### `save($entry, $values)` -``` -private function getForm() -{ - $form = ee('CP/Form'); - $form->asTab(); - $form->asFileUpload(); - $field_group = $form->getGroup('header 1'); - $field_set = $field_group->getFieldSet('first_name'); - $field_set->getField('first_name', 'text') - ->setDisabled(true) - ->setValue('Eric'); - - return $form->toArray(); -} -``` +| Parameter | Type | Description | +| --------- | ---------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| \$entry | <small>`ExpressionEngine\Module\Channel\Model\ChannelEntry`</small> | The channel entry entity | +| \$values | `array` | an associative array with field names as keys and form submission data as the value (i.e. `array('fortune' => 'Do not make extra work for yourself.'))`. The keys are derrived from the data returned by `display()`. | +| Returns | `Void` | | -Now, let's add that to our route file and be sure to pass the array returned from `getForm()` to our view. +Called during a `ChannelEntry` entity's `afterSave` event, this allows you to insert data/update data: -``` -public function process($id = false) -{ + function save($entry, $values) + { + if (! isset($values['field_name_one']) OR $values['field_name_one'] == '') + { + return; + } - // set the breadcrumb - $this->addBreadcrumb('index', 'Home'); + $data = array( + 'entry_id' => $entry->entry_id, + 'file_id' => $values['field_name_one'] + ); - // call our getForm() method to get - // our array - $form = $this->getForm(); + ee()->db->insert('table_name', $data); + } - // store our form in our $variables array - // to be passed into our view - $variables = [ - 'form' => $form - ]; +### `delete($entry_ids)` - $this->setBody('Index', $variables); +| Parameter | Type | Description | +| ----------- | ------- | ----------------------------------------------------- | +| \$entry_ids | `array` | Channel ID where the entry is being created or edited | +| Returns | `Void` | | - return $this; -} +Called during a `ChannelEntry` entity's `beforeDelete` event, this allows you to sync your records if any are tied to channel entry_ids. -private function getForm() -{ - $form = ee('CP/Form'); - $form->asTab(); - $form->asFileUpload(); - $field_group = $form->getGroup('header 1'); - $field_set = $field_group->getFieldSet('first_name'); - $field_set->getField('first_name', 'text') - ->setDisabled(true) - ->setValue('Eric'); - - return $form->toArray(); -} -``` +## The Control Panel File (mcp.module_name.php) -Since we passed our form to our view in the `$variables` array, inside of `views/Index.php` we can now use the View service to render our `$form` array as the main body of our Control Panel page. +Used to create the backend control panel, it includes a class with a name that is a combination of the package's name with a `_mcp` suffix. The first letter and only the first letter of the class name should be capitalized. There are no required class variables. The control panel file for a module without a backend control panel would look like: -``` -if (isset($form)) { - echo ee('View')->make('ee:_shared/form')->render($form); -} -``` + <?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); -Your add-on will now have a Control Panel page with a form as seen in this screenshot: + class Module_name_mcp { -![add-on with form](_images/add-on-view.png) + } + // END CLASS -Finally, make the form submission do something. The form is being submitted to same CP controller, so we can check whether it has been submitted by adding `ee('Request')->isPost()` check. For the sake of this demo, we'll skip the database saving part and just display success message on submission. + /* End of file mcp.module_name.php */ + /* Location: ./system/user/addons/modules/module_name/mcp.module_name.php */ -``` -public function process($id = false) -{ - if (ee('Request')->isPost()) { - ee('CP/Alert')->makeBanner('amazing_add_on') - ->asSuccess() - ->withTitle(lang('success')) - ->addToBody('Form has been submitted!') - ->now(); - } +### Control Panel URLS - // set the breadcrumb - $this->addBreadcrumb('index', 'Home'); +The Control Panel URLs for your module follow the pattern `addons/settings/package_name/method_name/arguments`. For example, if we had a fortune cookie module with a view for list our cookies its URL would be `addons/settings/fortune_cookie/cookies`. Like 2.x the routing is automatic; all public methods in your `mcp.package_name.php` are automatically routed. We will also pass any arguments to your method found in the url. If the URL is `addons/settings/fortune_cookie/edit_cookie/3` we would need to have the following method signature: - // call our getForm() method to get - // our array - $form = $this->getForm(); + public function edit_cookie($id) {...} - // store our form in our $variables array - // to be passed into our view - $variables = [ - 'form' => $form - ]; +We have a [CP/URL Service](development/services/url.md) to help you construct your URLs. - $this->setBody('Index', $variables); +### Output, Breadcrumbs, and Headings - return $this; -} -``` +There are two ways to output your control panels. You may either return an HTML string, or you may return an associative array. +If you return a string that data will be used in the "body" section of the Control Panel layout inside our Add-On Manager. The breadcrumb will default to `Add-On Manager / Your Add-On Name` and the heading will default to `Your Add-On Name Configuration`. In our fortune cookie module example we would have `Add-On Manager / Fortune Cookies` as the breadcrumb and `Fortune Cookie Configuration` as the heading. -TIP: This is only the beginning of what you can do with forms in the Control Panel. Read more in the docs on the [`CP\Form` service](development/services/cp-form.md) to understand what else is possible. +If you return an associative array it must contain the key `body` and may contain the keys `breadcrumb`, and `heading`: -TIP: **{ee:u}** Learn more about the [CP\Form service on ExpressionEngine University](https://u.expressionengine.com/article/ultra-double-secret-manual-shared-form-part-four). + return array( + 'body' => $html, + 'breadcrumb' => array( + ee('CP/URL')->make('addons/settings/module_name')->compile() => lang('module_name') + ), + 'heading' => lang('module_name_settings') + ); +- `body` (string): HTML string which will be used in the "body" section of the Control Panel layout inside the Add-On manager +- `breadcrumb` (array): Associative array containing key/value pairs where the key is the [CP/URL](development/services/url.md) and the value is the string to display as the breadcrumb +- `heading` (string): The string to display as the page `<title>` and the Section Header. -## Adding More Pages +If your add-on needs a sidebar use the [Sidebar Service](development/services/sidebar.md). -Often your add-on will need more than one page in the Control Panel. The CLI makes this simple. +### `ee()->cp->header` -Start with: +This variable allows you to further customize your Section Header by specifying icons to go in front of the title. -``` -php system/ee/eecli.php make:cp-route -``` +Within your control panel method, or potentially the constructor, just set `ee()->cp->header`: -This adds a new file to your `ControlPanel/Routes` folder which will act similar to the Index page we discussed above, along with a matching View in our `views` folder. + ee()->cp->header = array( + 'toolbar_items' => array( + 'settings' => array( + 'href' => ee('CP/URL')->make('settings/template'), + 'title' => lang('settings') + ), + ) + ); -Now inside of your `ControlPanel/Routes` folder you have a new file and matching class based on the name you chose in the CLI. This page will be accessible based on the class name you provided. For example: +- `toolbar_items` (array): An associative array of buttons to go in front of the title. The key will define the class and provide an icon (e.g. `settings` and `download`), and the value is another associative array containing the `href` and the `title` of the link. -``` -php system/ee/eecli.php make:cp-route -What is the route name? configurations -Which add-on would you like to add this to? amazing_add_on -Control panel route created successfully! -``` +### Javascript -This will create a file in my `ControlPanel/Routes` folder called `Configurations.php` which will look like this: +ExpressionEngine includes both its own JavaScript library as well as the [The jQuery](https://jquery.com/) JavaScript library, enabling developers to easily include JavaScript enhancements. It is worth noting some 'best practices' when using JavaScript in your control panel: -``` -<?php +- Loading jQuery plugins: -namespace ExpressionengineDeveloper\ControlPanel\Routes; + ee()->cp->add_js_script(array('plugin' => 'dataTables')); -use ExpressionEngine\Service\Addon\Controllers\Mcp\AbstractRoute; +- Outputting JavaScript to the browser: -class Configurations extends AbstractRoute -{ + ee()->javascript->output(); - /** - * @var string - */ - protected $cp_page_title = 'configurations'; +- After defining any JavaScript output, you must compile in order to display it: - /** - * @param false $id - * @return AbstractRoute - */ - public function process($id = false) - { + ee()->javascript->compile(); - $variables = [ - 'name' => 'Matt', - 'color' => 'Green' - ]; +### Working with Forms - $this->setBody('Index', $variables); +While creating forms for the backend is fairly routine, there are several differences/additions worth noting: - return $this; - } -} -``` +- The [Form Validation library](development/legacy/libraries/form-validation.md) is available, but the best means of checking submitted form data and returning in-line errors is to either use [Model Validation](development/services/model.md#validation) or the [Validation Service](development/services/validation.md). +- After form submission, you will generally want to output a success (or failure) message using the [CP/Alert Service](development/services/alert.md). -This page will now be accessible via `/admin.php?/cp/addons/settings/amazing_add_on/configurations`. +### Outputting Pages -At this point you probably want to make sure you update your breadcrumb on your second page and create a sidebar item to ensure users can easily access this page. +There are two ways to output content to the screen. For very simple pages, you may want to simply return the desired output in a string. Any string that the method returns is placed inside the cp page's content container. With all but the simplest of output, the use of View files will be the preferred method for handling your markup and presentation. -TIP:If you just want to create a new View without a Control Panel Route, simply duplicate a current view and rename the file and class accordingly. +## View Files -## JavaScript +While you aren't required to use views to create your backend pages, they are the most modular and easy to read, modify, and edit approach to building control panel pages. A view is simply an html page, or snippet of a page, with some minimal php used to output variables. The variables are passed to the view in an array when you make it: -ExpressionEngine includes both its own JavaScript library as well as the [The jQuery](https://jquery.com/) JavaScript library, enabling developers to easily include JavaScript enhancements. It is worth noting some 'best practices' when using JavaScript in your control panel: + return ee('View')->make('module_name:index')->render($vars); -- Loading jQuery plugins: +This would return the index.php view page, located in a `views` folder. The view file is passed an array with all of the variables used by the view, and those variables are simple 'plugged into' the html. See the [View Service](development/services/view.md) for more details. - ee()->cp->add_js_script(array('plugin' => 'dataTables')); +It is recommended that in view pages only, you use the [PHP's alternate syntax](development/guidelines/view-php-syntax.md) in your views, as it makes them easier to read and limits the amount of php. If this is not supported by your server, ExpressionEngine will automatically rewrite the tags. -- Outputting JavaScript to the browser: +### The Core Module File (mod.module_name.php) - ee()->javascript->output(); +The Core Module file is used for outputting content via Templates and doing any processing that is required by both the Control Panel and any module tags contained in a template. It includes a class with a name that matches the package (the first letter of the class name must be capitalized). There is one required class variable, \$return_data, which will contain the module's outputted content and is retrieved by the Template parser after the module is done processing. -- After defining any JavaScript output, you must compile in order to display it: +The tag structure of a module follows the same rules as the [Plugins API](development/plugins.md): - ee()->javascript->compile(); \ No newline at end of file + {exp:module_name:method} diff --git a/docs/development/plugins.md b/docs/development/plugins.md new file mode 100755 index 000000000..e410d636c --- /dev/null +++ b/docs/development/plugins.md @@ -0,0 +1,362 @@ +--- +lang: php +--- + +<!-- + This source file is part of the open source project + ExpressionEngine User Guide (https://github.com/ExpressionEngine/ExpressionEngine-User-Guide) + + @link https://expressionengine.com/ + @copyright Copyright (c) 2003-2020, Packet Tide, LLC (https://packettide.com) + @license https://expressionengine.com/license Licensed under Apache License, Version 2.0 +--> + +# Plugins + +[TOC] + +## Overview + +Plugins are used within Expression Engine Templates. Once a plugin is made, its tags can be used anywhere in templates to help edit content. Below, tags and plugins are described as well as a walkthrough of how to build your own plugin. + +NOTE: Plugins can also be **generated quickly by the Command Line Interface (CLI)**. Refer to the [make:addon command](cli/built-in-commands/make-addon.md) for more information. + +## Tag Construction + +A typical ExpressionEngine tag looks like this: + + {exp:channel:entries} + +The first segment is the tag identifier: {exp:. It tells the template engine that it has just encountered a tag. + +The second segment is the "family" name: `{exp:channel`. There are different families of tags: channel, comments, members, email, stats, etc. In programming terms, the second segment is the name of the 'class' that is instantiated by the tag. + +The above example would tell the template engine to dynamically instantiate the "channel" class. + +The third segment indicates the 'function' from within a particular family of tags: `{exp:channel:entries}`. This example would tell ExpressionEngine you want to use the "entries" method in the "channel" class. + +A tag, therefore, mirrors an object oriented approach: `Class->method`: + + {exp:class_name:method_name} + +NOTE: **Note:** Tags are not always required to have three segments. If your plugin is very simple you might opt to only use the class constructor. In this case you can get by only using two segments: + + {exp:class_name} + +### Two Kinds of Tags + +There are two kinds of tags: Single tags and tag pairs. A single tag does not have a closing tag: + + {exp:randomizer:set_one} + +Single tags are designed to return a single value. Tag pairs look like this: + + {exp:xml_encode} + Stuff between the tags + {/exp:xml_encode} + +Tag pairs allow you to process the information contained between the tags. In the above example, the text between the pairs would be encoded with XML entities. + +## Anatomy of a Plugin + +A plugin consists of a class and at least one function: + + <?php + + class Plugin_name { + + public function __construct() + { + + } + } + // END Class + + // EOF + +Your plugin's name and other details are provided by your [addon.setup.php file](development/addon-setup-php-file.md). + +## Creating a Plugin + +The best way to learn how a plugin is created is to walk you through the process of creating one. For this example, we will create a plugin that prints "Hello World". Our example plugin will have this syntax: + + {exp:hello_world} + +You will be able to use this plugin anywhere in a Template. + +## Creating the Plugin File + +Once you've decided on a name for your plugin you will create a text file for it. The file name must be the same as the class name and it must have `pi.` as the prefix. We will name our file: `pi.hello_world.php`. + +Plugin file names are always lower case and they must be identical to the name of the second segment of the tag: + + {exp:hello_world} + +NOTE: **Note:** The file should be saved in the folder that the `addon.setup.php` will later reference in the namespace key. The root of this folder is system/user/addons. + +So for this example, the pi.hello_world.php should be located at system\user\addons\HelloWorld\pi.hello_world.php + +## Creating the Class + +In the new file you've created add this class and constructor: + + class Hello_world + { + public function __construct() + { + + } + } + +NOTE: **Note:** Class name must always be capitalized. This is the one exception to the rule. Tag names and file names are always lowercase, while the class name is capitalized. + +And we'll create our `addon.setup.php` file to tell ExpressionEngine a bit about our plugin, which will also allow it to be installed in the Add-on Manager: + +NOTE: **Note:** Once your plugin is complete the Add-on Manager will need to be used to install it before the plugin's tags can be used + + <?php + return [ + 'author' => 'Developer James', + 'author_url' => 'https://example.com/', + 'name' => 'Hello World', + 'description' => 'Outputs a simple "Hello World" message to test plugins!', + 'version' => '1.0.0', + 'namespace' => 'HelloWorld', + 'settings_exist' => FALSE, + ]; + +Similar to above, the `addon.setup.php` file should be located at system\user\addons\HelloWorld\addon.setup.php + +### Returning a Value + +Your new class is useless unless it can return a value. There are two ways to return a value depending on whether your tag has three segments or two. + +### Two Segments + +The above tag only provides the plugin class, and no method, so it will use a constructor. Since constructors in PHP do not have return values, we will assign it to a public class property called: `$return_data`: + + class Hello_world + { + public $return_data = ''; + + public function __construct() + { + $this->return_data = 'Hello World'; + } + } + +### Three Segments + +With tags that use three segments you can return directly since a class method is being called. Consider a tag with this syntax: + + {exp:hello_world:bold} + +The third segment represents a method called `bold()`, which can return a value directly: + + class Hello_world + { + public function bold() + { + return '<b>Hello World</b>'; + } + } + +You could create a class with several methods this way: + + class Hello_world + { + public function normal() + { + return 'Hello World'; + } + + public function bold() + { + return '<b>Hello World</b>'; + } + + public function italic() + { + return '<i>Hello World</i>'; + } + } + +Each function would be accessible using these tags: + + {exp:hello_world:normal} + {exp:hello_world:bold} + {exp:hello_world:italic} + + + +### Processing Content Within Tag Pairs + +Often you will want to process content contained between a pair of tags. Let's create a simple tag that makes text bold to illustrate how this is done. Our example plugin will have this syntax: + + {exp:bold} + Some text we want to process. + {/exp:bold} + +You will be able to use this plugin anywhere in a Template. You can even put this tag within another tag in order to affect a variable: + + {exp:channel:entries} + {exp:bold}{title}{/exp:bold} + {/exp:channel:entries} + +In following our naming rules, we will create a plugin file named: `pi.bold.php`. And we will create a class with this syntax: + + class Bold + { + public $return_data = ''; + + public function __construct() + { + + } + } + +So how do we fetch the content contained within the tag pairs? Using the following variable: + + ee()->TMPL->tagdata; + +Here is how the variable is used: + + class Bold + { + public $return_data = ''; + + public function __construct() + { + $this->return_data = ee()->TMPL->tagdata; + } + } + +Of course you'll want to do something with the data before you return it, so let's make it bold: + + class Bold + { + public $return_data = ''; + + public function __construct() + { + $this->return_data = '<b>'.ee()->TMPL->tagdata.'</b>'; + } + } + +### Parameters + +Since tags will often have parameters, the template engine makes it easy to fetch them using the following variable: + + ee()->TMPL->fetch_param('param_name'); + +To see how this is used, let's create a plugin that lets you format text based on the parameter. Our new plugin will have this syntax: + + {exp:format type="uppercase"} + Some text to process. + {/exp:format} + +We will allow the following parameter choices: + +- `type="uppercase"` +- `type="lowercase"` +- `type="bold"` +- `type="italic"` + +Create a plugin file named pi.format.php and in it put this: + + class Format + { + public $return_data = ''; + + public function __construct() + { + $parameter = ee()->TMPL->fetch_param('type'); + $this->return_data = ee()->TMPL->tagdata; + + switch ($parameter) + { + case "uppercase": + $this->return_data = strtoupper(ee()->TMPL->tagdata); + break; + case "lowercase": + $this->return_data = strtolower(ee()->TMPL->tagdata); + break; + case "bold" : + $this->return_data = "<b>".ee()->TMPL->tagdata."</b>"; + break; + case "italic": + $this->return_data = "<i>".ee()->TMPL->tagdata."</i>"; + break; + } + } + } + +### Passing Data Directly + +ExpressionEngine allows any plugin to be assigned as a text formatting choice in the Publish page of the Control Panel. In order to allow a plugin to be used this way it needs to be able to accept data directly. This is how it's done. + +Add a parameter to the function. It's best to make the parameter conditional so it will know whether it's being used in a template or directly as a formatting choice: + + class Bold + { + public $return_data = ''; + + function __construct($str = NULL) + { + if (empty($str)) + { + $str = ee()->TMPL->tagdata; + } + + $this->return_data = "<b>".$str."</b>"; + } + } + +The above tag can then be assigned in the Publish page, allowing you to run your channel entries through it. + +## Database Access + +ExpressionEngine makes it easy to access your database using the [Model Service](development/services/model.md). You can also execute SQL statements by using the legacy [Database Driver](development/legacy/database/index.md): + + $member = ee('Model')->get('Member')->first(); + +Let's use a real example to show how you might use this. + +We will use the Member model to show a list of members. For this we will create a plugin called `pi.memberlist.php`. The tag syntax will be this: + + {exp:memberlist} + +Here is the class syntax: + + class Memberlist + { + public $return_data = ''; + + public function __construct() + { + $members = ee('Model')->get('Member')->all(); + + foreach($members as $member) + { + $this->return_data .= $member->screen_name."<br>"; + } + } + } + +## Where do you go from here? + +Now that you understand the anatomy of a plugin, you can do whatever task you need. Your plugin can even have its own variables. For more information about this, and manipulating the tagdata in your plugin, check out the [Template Class](development/legacy/libraries/template.md). + +## Debugging + +Below are some possible errors you could be getting and how you can fix them. Before anything else be sure that your plugin is installed from the Add-On manager. In the control panel go to Developer --> Add-ons and check that your plugin is installed. + +#### Problem: The following tag has a syntax error: + + - Possible Solution: Check the spelling of your pi.nameofaddon.php and addon.settup.php file names. Both of these files need to be saved under system\user\addons\nameofplugin + + + +#### Problem: The following tag cannot be processed: + + - Possible Solution: Check the tag used in the template if it has three segments to it as in `exp:nameofplugin:function` make sure that the last segment is a function that exists in your pi. file. diff --git a/docs/development/prolets.md b/docs/development/prolets.md index 8bd46044f..04193ee4e 100644 --- a/docs/development/prolets.md +++ b/docs/development/prolets.md @@ -11,66 +11,35 @@ [TOC] -## Overview +Prolets are add-on components that reveal some of add-on's functionality for the Control Panel to the front-end, making possible to edit data directly on page where it belongs. -Prolets are add-on components that live in the [Dock](/advanced-usage/front-end/dock.md) and bring parts of an add-on's functionality to content editors on the front end. +## Prolet File Structure -Here is an example of the add-on SEEO's prolet, which easily allows editors to edit SEO data for the entry they are currently viewing: -![prolet example](_images/prolet_example.png) +A Prolet's file needs to be placed in main add-on's directory, together with other main components (modules, extensions, fieldtypes). The file name should start with `pro.` followed by prolet short name and ending with `.php` extension. So if you have `sample` add-on and you want to create `sample_prolet` prolet for it, the file will be `user/addons/sample/pro.sample_prolet.php`. -NOTE:Before adding a Prolet to your add-on, you need to already have an add-on in place. See [Building An Add-On: Getting Started](development/addon-development-overview.md#getting-started) for how to generate the starter files for your add-on. - -## Creating An Amazing Prolet - -Creating a Prolet is straightforward using the `make:prolet` command in the CLI. - -``` -$ php system/ee/eecli.php make:prolet -``` - -Follow the prompts to add a prolet to your add-on. - -This will create a file in your add-on's folder named `pro.[addon_name].php` - -``` -amazing_add_on -... -┣ pro.amazing_add_on.php -┗... -``` - - -## Anatomy Of A Prolet - -Inside a prolet file, you are required to define the prolet class. The class name should consist of prolet name with the first letter capitalized followed by `_pro` postfix. So for the example above, the class name will be `Sample_prolet_pro`. +Inside a prolet file, you are required to define the prolet class. The class name should consist of prolet name with first letter capitalized followed by `_pro` postfix. So for the example above, the class name will be `Sample_prolet_pro`. All prolets are required to implement `ExpressionEngine\Addons\Pro\Service\Prolet\ProletInterface`. The easiest way to achieve that is to make prolet extend abstract class `ExpressionEngine\Addons\Pro\Service\Prolet\AbstractProlet`. -TIP:When using the CLI, this file will be properly constructed for you. - -When generated with the CLI, your prolet will initially look like this: +The sample prolet would then look something like -``` -<?php - -use ExpressionEngine\Addons\Pro\Service\Prolet\AbstractProlet; -use ExpressionEngine\Addons\Pro\Service\Prolet\ProletInterface; - -class Amazing_add_on_pro extends AbstractProlet implements ProletInterface -{ - protected $name = 'Amazing Prolet'; + use ExpressionEngine\Addons\Pro\Service\Prolet; - public function index() + class Sample_prolet_pro extends Prolet\AbstractProlet { - return 'This is a new prolet generated from the CLI.'; + protected $name = 'Sample prolet'; + + public function index() + { + return 'Hello world!'; + } } -} -``` +## Properties and Methods ### Icon -On each page load, prolets appear on the front-end as Dock buttons. If not specified otherwise, `icon.svg` from the add-on package folder is used as the Dock button. If you want to specify a different icon to use from the add-on's folder, you can do that using `protected $icon` property or `public function getIcon()`. +On each page load, prolets appear on the front-end as Dock buttons. If not specified different, `icon.svg` from the add-on package folder is used as the Dock button. If you want to specify a different icon to use from the add-on's folder, you can do that using `protected $icon` property or `public function getIcon()`. protected $icon = 'sample_prolet.png'; @@ -82,7 +51,7 @@ or } ### Name -Each prolet is required to have a name, which is used as the title for the Dock button and also for the prolet's popup window. It can be defined using `protected $name` property or `public function getName()`. Using the function is recommended because you are able to use the `lang` key in it, making the name translatable. +Each prolet is required to have a name, which is used as title for the Dock button and also for prolet's popup window. It can be defined using `protected $name` property or `public function getName()`. Using function is recommended because you are able to use lang key in it, making the name translatable. protected $name = 'Sample Prolet'; @@ -95,7 +64,7 @@ or ### Popup Window Size -If the prolet is opening a popup window (which is what currently all prolets are doing), you are able to specify the window size. Available options are `footer`, `large` and `small` (default). You do this using the `protected $size` property or `public function getSize()`. +If the prolet is opening a popup window (which is what currently all prolets are doing) you are able to specify the window size. Available options are `footer`, `large` and `small` (default) and you can do that using `protected $size` property or `public function getSize()`. protected $size = 'footer'; @@ -108,7 +77,7 @@ or ### Popup Window Buttons -By default, each prolet popup window is generated with a "Save" button in the footer that sends the "save" JavaScript event to the prolet. You can change that to display different buttons, or no buttons at all, using the `protected $buttons` property or `public function getButtons()`. +By default each prolet popup window is generated with a "Save" button in the footer which sends "save" JavaScript event to the prolet. You can change that to display different buttons, or no buttons at all, using `protected $buttons` property or `public function getButtons()`. protected $buttons = []; // No buttons will be shown @@ -124,7 +93,7 @@ or ### Controller Action -Prolets are required to contain a method which will generate the data to be outputted. By default, this is assumed to be the `index()` method. However you can specify a different function name using `protected $action` property or `public function getAction()`. +Prolets are required to contain some method which will generate the data to be outputted. By default this is assumed to be `index()` method, however you can specify different function name using `protected $action` property or `public function getAction()`. protected $action = 'do_something_cool'; @@ -135,13 +104,13 @@ or return 'do_something_else'; } -The prolet's action method (`public function index()`; or function with name returned by `getAction()` as explained above) can return an array of data or a string. +Prolet action method (`public function index()`; or function with name returned by `getAction()` as explained above) can return array of data or string. If the data returned is of *Array* type, it is being passed to ExpressionEngine Pro's shared form view, which is similar to ExpressionEngine's [Shared Form View](development/shared-form-view.md), however you are only required to have `sections` key in the returned data array. The result will be a form with submission endpoint being set to same prolet controller action. public function index() { - if (ee('Request')->isPost()) { + if (ee('Request)->isPost()) { //handle form submission return ee()->output->send_ajax_response(['success']); } @@ -162,7 +131,7 @@ If the data returned is of *Array* type, it is being passed to ExpressionEngine return $vars; } -If the data returned is of *String* type then this string is being wrapped in some required HTML and returned into the prolet popup window. In you need a form to be created, you would need to handle that (additionally you can use one of your [actions](development/actions.md) as endpoint). +If the data returned is of *String* type then this string is being wrapped in some required HTML and returned into prolet popup window. In you need a form to be created, you would need to handle that (additionally you can use one of your MCP actions as endpoint) Prolets are expecting form submissions to return JSON upon successful response, which will close their windows. @@ -170,7 +139,7 @@ Prolets are expecting form submissions to return JSON upon successful response, Essentially there are two types of prolets. -Non-initializable prolets are displayed and allow data manipulation on every page of website where the Dock is enabled. +Non-initializable prolets are displayed and allow data manipulation on every page of website where Dock is enabled. Initializable prolets work the same way, but they are only displayed on pages where they have been initialized, which usually would happen by placing certain template tags in the template. @@ -189,7 +158,7 @@ Initializable prolets are required to implement `ExpressionEngine\Addons\Pro\Ser } } -In order for the prolet to be initialized, the template tag from your add-on needs to be called on page. In the programmatic code for that tag, you would need to place a call to `ee('pro:Prolet')->initialize()` +In order for the prolet to be initialized, the module tag from your add-on needs to be called on page. In the programmatic code for that tag, you would need to place a call to `ee('pro:Prolet')->initialize()` if (defined('IS_PRO') && IS_PRO) { ee('pro:Prolet')->initialize('sample_prolet', ['entry_id' => $entry_id]); diff --git a/docs/development/services/cp-form.md b/docs/development/services/cp-form.md index 9d60007fc..5b1c7d102 100644 --- a/docs/development/services/cp-form.md +++ b/docs/development/services/cp-form.md @@ -7,17 +7,17 @@ @license https://expressionengine.com/license Licensed under Apache License, Version 2.0 --> -# CP/Form Service +# Cp/Form Service [TOC] -The CP/Form Service allows for the creation of Shared Form View data using an object oriented interface. +The Cp/Form Service allows for the creation of Shared Form View data using an object oriented interface. TIP: Note this requires PHP >= 7.1 ## Usage -The below shows The CP/Form Service at its simplest; every form can contain multiple groups, which can contain multiple field sets, which can contain multiple fields. +The below shows The Cp/Form Service at its simplest; every form can contain multiple groups, which can contain multiple field sets, which can contain multiple fields. ### Basic ``` @@ -53,7 +53,7 @@ $form->toArray(); ``` ## API Reference -**class `ExpressionEngine\Library\CP\Form`** +**class `ExpressionEngine\Library\Cp\Form`** This object contains the complete outline for your Shared Form View array. For simple forms, all you'll have to do is call the Service using EE's loader, but for more complex Forms, you have quite a few options available. @@ -65,7 +65,7 @@ Will output the Form object as a Tabbed format array form. | Parameter | Type | Description | | --------- | ---- | ----------- | -| Returns | `CP\Form` | `$this`, the Form object to help in chaining | +| Returns | `Cp\Form` | `$this`, the Form object to help in chaining | ### `isTab()` @@ -81,7 +81,7 @@ Will output the Form object as a linear array form (the default) | Parameter | Type | Description | | --------- | ---- | ----------- | -| Returns | `CP\Form` | `$this`, the Form object to help in chaining | +| Returns | `Cp\Form` | `$this`, the Form object to help in chaining | ### `getGroup($group_name)` @@ -104,7 +104,7 @@ Removes the specified group from the Form object ### `toArray()` -Returns the entire CP\Form object into an array compatible with the Shared Form View layer. Note that all child elements are converted to an array as well. +Returns the entire Cp\Form object into an array compatible with the Shared Form View layer. Note that all child elements are converted to an array as well. | Parameter | Type | Description | | --------- | ---- | ----------- | @@ -112,7 +112,7 @@ Returns the entire CP\Form object into an array compatible with the Shared Form ### `render()` -Returns the entire CP\Form object as a string for use within the Control Panel. +Returns the entire Cp\Form object as a string for use within the Control Panel. | Parameter | Type | Description | | --------- | ---- | ----------- | @@ -124,16 +124,16 @@ Sets the Shared Form View layer to set the form input's `enctype` to multipart/f | Parameter | Type | Description | | --------- | ---- | ----------- | -| Returns | `CP\Form` | `$this`, the Form object to help in chaining | +| Returns | `Cp\Form` | `$this`, the Form object to help in chaining | ### `addAlert($alert_name)` -Sets the Form to render specific CP/Alert objects +Sets the Form to render specific Cp/Alert objects | Parameter | Type | Description | | --------- | ---- | ----------- | | \$alert_name | `string` | The name for the Alert | -| Returns | `CP\Form` | `$this`, the Form object to help in chaining | +| Returns | `Cp\Form` | `$this`, the Form object to help in chaining | ### `removeAlert($alert_name)` @@ -152,7 +152,7 @@ Will return the specified `Form\Button` object if it exists, or prepare and retu | Parameter | Type | Description | | --------- | ---- | ----------- | | \$name | `string` | The name for the Button | -| Returns | `CP\Form\Button` | The Button object ready for use | +| Returns | `Cp\Form\Button` | The Button object ready for use | ### `removeButton($name)` @@ -170,7 +170,7 @@ Will return the specified `Form\Fields\Hidden` object if it exists, or prepare a | Parameter | Type | Description | | --------- | ---- | ----------- | | \$name | `string` | The name for the Button | -| Returns | `CP\Form\Fields\Hidden` | The Hidden Field object | +| Returns | `Cp\Form\Fields\Hidden` | The Hidden Field object | ### `removeHiddenField($name)` @@ -190,7 +190,7 @@ Will include a custom HTML button with a link in lieu of the top right button of | \$text | `string` | The value to display on your custom button | | \$href | `string` | What URL to send the user to | | \$rel | `string` | For HTML directives | -| Returns | `CP\Form` | `$this`, the Form object to help in chaining | +| Returns | `Cp\Form` | `$this`, the Form object to help in chaining | ### `withOutActionButton()` @@ -198,7 +198,7 @@ Will remove the set action button | Parameter | Type | Description | | --------- | ---- | ----------- | -| Returns | `CP\Form` | `$this`, the Form object to help in chaining | +| Returns | `Cp\Form` | `$this`, the Form object to help in chaining | ### `setCpPageTitle($text)` @@ -207,7 +207,7 @@ Sets the string to use for the Page Title within the Control Panel | Parameter | Type | Description | | --------- | ---- | ----------- | | \$text | `string` | The name for the page | -| Returns | `CP\Form` | `$this`, the Form object to help in chaining | +| Returns | `Cp\Form` | `$this`, the Form object to help in chaining | ### `getCpPageTitle()` @@ -224,7 +224,7 @@ Sets the string to use for the Alternative Page Title within the Control Panel ( | Parameter | Type | Description | | --------- | ---- | ----------- | | \$text | `string` | The name for the page | -| Returns | `CP\Form` | `$this`, the Form object to help in chaining | +| Returns | `Cp\Form` | `$this`, the Form object to help in chaining | ### `getCpPageTitle()` @@ -241,7 +241,7 @@ Sets the internal Alerts ID to use specific to this Form (`alerts_name`) | Parameter | Type | Description | | --------- | ---- | ----------- | | \$text | `string` | The name for the page | -| Returns | `CP\Form` | `$this`, the Form object to help in chaining | +| Returns | `Cp\Form` | `$this`, the Form object to help in chaining | ### `getAlertsName()` @@ -258,7 +258,7 @@ The URL to process the Form (`base_url`) | Parameter | Type | Description | | --------- | ---- | ----------- | | \$url | `string` | The URL the form should be processed at | -| Returns | `CP\Form` | `$this`, the Form object to help in chaining | +| Returns | `Cp\Form` | `$this`, the Form object to help in chaining | ### `getBaseUrl()` diff --git a/docs/development/services/cp-form/buttons.md b/docs/development/services/cp-form/buttons.md index 42da0e08c..843f17ebd 100644 --- a/docs/development/services/cp-form/buttons.md +++ b/docs/development/services/cp-form/buttons.md @@ -26,7 +26,7 @@ $button->setType('submit')->setText('Submit Button') ## API Reference -**class `ExpressionEngine\Library\CP\Form\Button`** +**class `ExpressionEngine\Library\Cp\Form\Button`** [TOC=3] @@ -40,7 +40,7 @@ Returns the name used upon creation for the Button and the id. ### `toArray()` -Returns the entire `CP\Form\Button` object into an array. +Returns the entire `Cp\Form\Button` object into an array. | Parameter | Type | Description | | --------- | ---- | ----------- | diff --git a/docs/development/services/cp-form/field-sets.md b/docs/development/services/cp-form/field-sets.md index a5cbcb60a..cbc9bea92 100644 --- a/docs/development/services/cp-form/field-sets.md +++ b/docs/development/services/cp-form/field-sets.md @@ -33,13 +33,13 @@ It's important to note that while Field Sets are built to contain multiple Field ## API Reference -**class `ExpressionEngine\Library\CP\Form\Set`** +**class `ExpressionEngine\Library\Cp\Form\Set`** [TOC=3] ### `toArray()` -Returns the entire `CP\Form\Set` object into an array. Note that all child elements are converted to an array as well. +Returns the entire `Cp\Form\Set` object into an array. Note that all child elements are converted to an array as well. | Parameter | Type | Description | | --------- | ---- | ----------- | diff --git a/docs/development/services/cp-form/fields.md b/docs/development/services/cp-form/fields.md index 06a2e1986..67bcc46fc 100644 --- a/docs/development/services/cp-form/fields.md +++ b/docs/development/services/cp-form/fields.md @@ -29,7 +29,7 @@ $field = $field_set->getField('first_name', 'text') The above will attach a `text` Input field onto the Field Set "First Name". -Every existing Shared Form View input field is respected as are a couple additions exclusively through the [CP/Form](development/services/cp-form.md) layer: +Every existing Shared Form View input field is respected as are a couple additions exclusively through the Cp/Form layer: - A simplified and streamlined Grid and Table layer - Native ExpressionEngine Filepicker @@ -41,7 +41,7 @@ Every existing Shared Form View input field is respected as are a couple additio ### `action_button` -**class `ExpressionEngine\Library\CP\Form\Fields\ActionButton`** +**class `ExpressionEngine\Library\Cp\Form\Fields\ActionButton`** Adds a “pretty” button style link to your form. @@ -83,25 +83,25 @@ Returns URL you want to use (`text`) ### `checkbox` -**class `ExpressionEngine\Library\CP\Form\Fields\Checbox`** +**class `ExpressionEngine\Library\Cp\Form\Fields\Checbox`** Adds a specialized widget for checkbox ### `dropdown` -**class `ExpressionEngine\Library\CP\Form\Fields\Dropdown`** +**class `ExpressionEngine\Library\Cp\Form\Fields\Dropdown`** Adds a fancy select input field especially useful for large data sets to choose from. ### `file` -**class `ExpressionEngine\Library\CP\Form\Fields\File`** +**class `ExpressionEngine\Library\Cp\Form\Fields\File`** Adds a traditional file HTML field to your form. ### `file-picker` -**class `ExpressionEngine\Library\CP\Form\Fields\FilePicker`** +**class `ExpressionEngine\Library\Cp\Form\Fields\FilePicker`** Adds a File Picker widget to your shared form @@ -158,7 +158,7 @@ Determines whether the mime type is locked to images only ### `grid` -**class `ExpressionEngine\Library\CP\Form\Fields\Grid`** +**class `ExpressionEngine\Library\Cp\Form\Fields\Grid`** Allows for a more streamlined implementation of Grid within Shared Forms. Two important differences with this implementation is a simpler API and direct handling of POST values. @@ -219,13 +219,13 @@ $grid->setBaseUrl( ee('CP/URL')->make($this->base_url )); ### `hidden` -**class `ExpressionEngine\Library\CP\Form\Fields\Hidden`** +**class `ExpressionEngine\Library\Cp\Form\Fields\Hidden`** Adds a hidden field to your form ### `html` -**class `ExpressionEngine\Library\CP\Form\Fields\Html`** +**class `ExpressionEngine\Library\Cp\Form\Fields\Html`** Allows for putting raw (unformatted) HTML inside your form @@ -250,7 +250,7 @@ Returns the content (empty string by default) ### `multiselect` -**class `ExpressionEngine\Library\CP\Form\Fields\Multiselect`** +**class `ExpressionEngine\Library\Cp\Form\Fields\Multiselect`** Deploys a series of select input fields together @@ -279,25 +279,25 @@ Returns the content (empty string by default) ### `password` -**class `ExpressionEngine\Library\CP\Form\Fields\Password`** +**class `ExpressionEngine\Library\Cp\Form\Fields\Password`** Adds your run of the mill password input field wrapped in magic that puts an eye next to it. ### `radio` -**class `ExpressionEngine\Library\CP\Form\Fields\Radio`** +**class `ExpressionEngine\Library\Cp\Form\Fields\Radio`** This behaves just like checkbox with the exception of being scalar so uses a scalar for the value. See checkbox for details. ### `select` -**class `ExpressionEngine\Library\CP\Form\Fields\Select`** +**class `ExpressionEngine\Library\Cp\Form\Fields\Select`** Adds a traditional select input field to your form. ### `short-text` -**class `ExpressionEngine\Library\CP\Form\Fields\ShortText`** +**class `ExpressionEngine\Library\Cp\Form\Fields\ShortText`** Adds a small input HTML field wrapped in a div with flex-input as the class. Useful for stacking fields horizontally. @@ -322,7 +322,7 @@ Returns the label (empty string by default) ### `slider` -**class `ExpressionEngine\Library\CP\Form\Fields\Slider`** +**class `ExpressionEngine\Library\Cp\Form\Fields\Slider`** Puts a slider widget into your form @@ -398,7 +398,7 @@ Returns the unit value ### `table` -**class `ExpressionEngine\Library\CP\Form\Fields\Table`** +**class `ExpressionEngine\Library\Cp\Form\Fields\Table`** Allows for a more streamlined implementation of tables within Shared Forms @@ -533,13 +533,13 @@ $table->addRow([ ### `text` -**class `ExpressionEngine\Library\CP\Form\Fields\Text`** +**class `ExpressionEngine\Library\Cp\Form\Fields\Text`** Adds a traditional input HTML field ### `textarea` -**class `ExpressionEngine\Library\CP\Form\Fields\Textarea`** +**class `ExpressionEngine\Library\Cp\Form\Fields\Textarea`** Adds a full textarea input field @@ -599,13 +599,13 @@ Returns the number of rows ### `toggle` -**class `ExpressionEngine\Library\CP\Form\Fields\Toggle`** +**class `ExpressionEngine\Library\Cp\Form\Fields\Toggle`** Adds a Toggle binary toggle ### `yes_no` -**class `ExpressionEngine\Library\CP\Form\Fields\YesNo`** +**class `ExpressionEngine\Library\Cp\Form\Fields\YesNo`** Adds a Toggle control that returns either y or n respectively. @@ -636,7 +636,7 @@ $field_set->getField('my_number_field', 'number')->params(['min' => 100, 'max' = ## Field API Reference -**class `ExpressionEngine\Library\CP\Form\Field`** +**class `ExpressionEngine\Library\Cp\Form\Field`** All input fields are based on the `Field` object. Note that while every Field will have the below methods available, whether they're respected by the Shared Form View layer is a different matter. @@ -644,7 +644,7 @@ All input fields are based on the `Field` object. Note that while every Field wi ### `toArray()` -Returns the entire `CP\Form\Field` object into an array. +Returns the entire `Cp\Form\Field` object into an array. | Parameter | Type | Description | | --------- | ---- | ----------- | @@ -846,7 +846,7 @@ Whether this Field is required (`placeholder`) ## OptionsField API Reference -**class `ExpressionEngine\Library\CP\Form\OptionsField`** +**class `ExpressionEngine\Library\Cp\Form\OptionsField`** Input Fields that allow for multiple options or set input parameters are based on the `OptionsField` abstract. Note that `OptionsField` extends from `Field` so you'll also have access to those parent methods, as well. diff --git a/docs/development/services/cp-form/group.md b/docs/development/services/cp-form/group.md index 062af52bf..91bad60da 100644 --- a/docs/development/services/cp-form/group.md +++ b/docs/development/services/cp-form/group.md @@ -15,7 +15,7 @@ Every Form consists of Field Groups that contain Field Sets. The below covers ev ## Usage -When working with Field Groups, you'll always request it from the [`CP\Form`](development/services/cp-form.md) object to request it, initially. Once you have a Field Group, you'll decorate it like everything else. For example: +When working with Field Groups, you'll always request it from the [`Cp\form`](development/services/cp-form.md) object to request it, initially. Once you have a Field Group, you'll decorate it like everything else. For example: ``` $form = ee('CP/Form'); @@ -32,7 +32,7 @@ TIP: If your Form object is set to be a Tabbed form, it's the Groups that make u ## API Reference -**class `ExpressionEngine\Library\CP\Form\Group`** +**class `ExpressionEngine\Library\Cp\Form\Group`** The Group object consists of only a couple methods of use. @@ -48,7 +48,7 @@ Returns the name used upon creation for the Field Group. ### `toArray()` -Returns the entire `CP\Form\Group` object into an array. Note that all child elements are converted to an array as well. +Returns the entire `Cp\Form\Group` object into an array. Note that all child elements are converted to an array as well. | Parameter | Type | Description | | --------- | ---- | ----------- | diff --git a/docs/development/services/sidebar.md b/docs/development/services/sidebar.md index cac353b39..3a8c05906 100755 --- a/docs/development/services/sidebar.md +++ b/docs/development/services/sidebar.md @@ -15,12 +15,6 @@ lang: php [TOC] -## Automatically Created Sidebars -If you are automatically generating a sidebar in your add-on you can use either of these methods to customize the sidebar further: -- Modify the sidebar on every page load in the in the `process()` method of your `ControlPanel/Routes/Sidebar.php` file. The sidebar object is available using `$this->getSidebar()` You can also loop through each sidebar item with $this->getItems()` -- Modify the sidebar only when a specific route is loaded by accessing the sidebar instance in the `process()` function of the route file (`ControlPanel/Routes/[route_name].php`).You can modify the sidebar item connected to the current route by using `$this->getCurrentSidebarItem()` with the [BasicItem Methods](#basicitem-methods) listed below. You can also use `$this->getSidebar()` to get the entire sidebar instance. - - ## Simple Example ```php diff --git a/docs/development/tab-files.md b/docs/development/tab-files.md deleted file mode 100644 index 5ff23ad0e..000000000 --- a/docs/development/tab-files.md +++ /dev/null @@ -1,204 +0,0 @@ ---- -lang: php ---- - -<!-- - This source file is part of the open source project - ExpressionEngine User Guide (https://github.com/ExpressionEngine/ExpressionEngine-User-Guide) - - @link https://expressionengine.com/ - @copyright Copyright (c) 2003-2020, Packet Tide, LLC (https://packettide.com) - @license https://expressionengine.com/license Licensed under Apache License, Version 2.0 ---> - - -# Adding Publish Layout Tabs - -[TOC] - -## Overview -Add-ons can also add tabs which are visible on in [Publish Layouts](control-panel/channels.md#publish-layouts). Respectivley these tabs would also be visible on the Entry Publish/Edit page if selected in the publish layout. Two things are required for your add-on to have this functionality: -- [`tabs()` method](/development/add-on-update-file.md#add-publish-tabs-with-your-add-on-tabs) added to the Update File -- The Tab File (`tab.[addon_name].php`) - -Here is an example of the publish layout's tab for the Structure add-on: -![structure tab](_images/structure_tab.png) - -NOTE:Before adding a tab to your add-on, you need to already have an add-on in place. See [Building An Add-On: Getting Started](development/addon-development-overview.md#getting-started) for how to generate the starter files for your add-on. - -## Creating An Amazing Tab File -Tab files are not currently able to be generated through the CLI, thus you will need to manually create the file `tab.[addon_name].php` in the root of your add-on's folder. - -``` -amazing_add_on -... -┣ tab.amazing_add_on.php -┗... -``` - -## Anatomy Of A Tab File - -``` -<?php - -class Amazing_add_on_tab -{ - - public function display($channel_id, $entry_id = ''){ - - $settings = [ - //array of settings - ] - return $settings; - } - - public function validate($entry, $values){ - $validator = ee('Validation')->make(array( - 'amazing_field_one' => 'required', - 'amazing_field_two' => 'required|enum[y,n]', - )); - - return $validator->validate($values); - } - - public function cloneData($entry, $values){ - - return $values; - } - - public function save($entry, $values){ - - } - - public function delete($entry_ids){ - - } - - -} -``` - -**class `Add_on_name_tab`** - -There are no required class variables. Because multiple modules may be adding fields to the publish page, all third party tab fields are namespaced using the package name when displayed on the publish page. This namespacing will be stripped prior to any data being returned to the tab functions. - -NOTE: **Note:** if your module includes a tab, do not forget to indicate this in the update file when installing the module. Further, be sure to include the `tabs()` function in the update file, and use it when updating custom layouts on installation and uninstallation. - - -### `display($channel_id, $entry_id = '')` - -| Parameter | Type | Description | -| ------------ | ------- | ----------------------------------------------------- | -| \$channel_id | `int` | Channel ID where the entry is being created or edited | -| \$entry_id | `int` | Entry ID if this is an edit, empty otherwise | -| Returns | `Array` | Settings (see below) | - -This function creates the fields that will be displayed on the publish page. It must return `$settings`, an associative array specifying the display settings and values associated with each of your fields. - -The settings array elements: - - Array( - '...' => Array( // name of the field (same as 'field_id' below) - 'field_id' => '...', // name of the field - 'field_label' => '...', // field label, typically a language variable is used here - 'field_required' => '...', // whether to include the 'required' class next to the field label: y/n - 'field_data' => '...', // current data, if applicable - 'field_list_items' => '...', // array of options, otherwise empty string - 'options' => '...', // array of options, otherwise empty string - 'selected' => '...', // selected value if applicable to the field_type - 'field_fmt' => '...', // allowed field format options, if applicable - 'field_instructions' => '...', // instructions to be displayed for this field on the publish page - 'field_show_fmt' => '...', // whether the field format dropdown shows: y/n. Note: if 'y', you must specify the options available in field_fmt - 'field_pre_populate' => '...', // can pre-populate a field when it's a new entry - 'field_text_direction' => '...', // direction of the text: ltr/rtl - 'field_type' => '...' // may be any existing field type - ) - ) - -### `validate($entry, $values)` - -| Parameter | Type | Description | -| --------- | ---------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| \$entry | <small>`ExpressionEngine\Module\Channel\Model\ChannelEntry`</small> | The channel entry entity | -| \$values | `array` | an associative array with field names as keys and form submission data as the value (i.e. `array('fortune' => 'All your hard work will soon pay off.'))`. The keys are derrived from the data returned by `display()`. | -| Returns | <small>`ExpressionEngine\Service\Validation\Result`</small> | A result object | - -Allows you to validate the data after the publish form has been submitted but before any additions to the database: - - function validate($entry, $values) - { - $validator = ee('Validation')->make(array( - 'foo_field_one' => 'required', - 'foo_field_two' => 'required|enum[y,n]', - )); - - return $validator->validate($values); - } - -### `cloneData($entry, $values)` - -| Parameter | Type | Description | -| --------- | ------------------------------------------------------------------- | ------ ----------------------------- | -| \$entry | <small>`ExpressionEngine\Module\Channel\Model\ChannelEntry`</small> | The channel entry entity | -| \$values | `array` | an associative array with field names as keys and form submission data as the value (i.e. `array('fortune' => 'All your hard work will soon pay off.'))`. The keys are derrived from the data returned by `display()`. | -| Returns | `array` | $values modified array of values | - -Code that needs to be executed when an entry is being [cloned](/channels/entry_cloning.md). This function is called before `validate`, so if you need to modify the data that will be passed to validation service (as well as `$_POST` array), this is the place to do it. - - public function cloneData(ChannelEntry $entry, $values) - { - if ($values['pages_uri'] == '') { - return $values; - } - //check if submitted URI exists - $static_pages = ee()->config->item('site_pages'); - $uris = $static_pages[ee()->config->item('site_id')]['uris']; - - //exclude current page from check - if (isset($uris[$entry->entry_id])) { - unset($uris[$entry->entry_id]); - } - //ensure leading slash is present - $value = '/' . trim($values['pages_uri'], '/'); - - while (in_array($value, $uris)) { - $value .= '_1'; - } - $_POST['pages__pages_uri'] = $values['pages_uri'] = $value; - - return $values; - } - -### `save($entry, $values)` - -| Parameter | Type | Description | -| --------- | ---------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| \$entry | <small>`ExpressionEngine\Module\Channel\Model\ChannelEntry`</small> | The channel entry entity | -| \$values | `array` | an associative array with field names as keys and form submission data as the value (i.e. `array('fortune' => 'Do not make extra work for yourself.'))`. The keys are derrived from the data returned by `display()`. | -| Returns | `Void` | | - -Called during a `ChannelEntry` entity's `afterSave` event, this allows you to insert data/update data: - - function save($entry, $values) - { - if (! isset($values['field_name_one']) OR $values['field_name_one'] == '') - { - return; - } - - $data = array( - 'entry_id' => $entry->entry_id, - 'file_id' => $values['field_name_one'] - ); - - ee()->db->insert('table_name', $data); - } - -### `delete($entry_ids)` - -| Parameter | Type | Description | -| ----------- | ------- | ----------------------------------------------------- | -| \$entry_ids | `array` | Channel ID where the entry is being created or edited | -| Returns | `Void` | | - -Called during a `ChannelEntry` entity's `beforeDelete` event, this allows you to sync your records if any are tied to channel entry_ids. \ No newline at end of file diff --git a/docs/development/text-formatting.md b/docs/development/text-formatting.md deleted file mode 100644 index ba5920763..000000000 --- a/docs/development/text-formatting.md +++ /dev/null @@ -1,40 +0,0 @@ -<!-- - This source file is part of the open source project - ExpressionEngine User Guide (https://github.com/ExpressionEngine/ExpressionEngine-User-Guide) - - @link https://expressionengine.com/ - @copyright Copyright (c) 2003-2020, Packet Tide, LLC (https://packettide.com) - @license https://expressionengine.com/license Licensed under Apache License, Version 2.0 ---> - -# Adding Text Formatting Options - -ExpressionEngine allows any add-on to add a [text formatting](general/text-formatting.md) option in the Publish page of the Control Panel. In order to do this and add-on needs `pi` file also referred to as a plugin file. These files are not supported by the CLI, so we need to make our own. - -We'll start by creating a file named `pi.[addonname].php`. In our case, our add-on is named "Amazing Bold Add-on" so our file will be named `pi.amazing_bold_add_on.php`. - -Now we'll need to our code, being sure to add a string as a parameter. In this case we'll use `$str` - -``` -class Bold -{ - public $return_data = ''; - - function __construct($str = NULL) - { - if (empty($str)) - { - $str = ee()->TMPL->tagdata; - } - - $this->return_data = "<b>".$str."</b>"; - } -} -``` - -The above tag can then be assigned in the Publish page or fieldtype settings, allowing you to run your channel entries through it. - -![text formatting options](_images/addons_text_formatting.png) - -TIP: Plugins allow you to manipulate text much much like a [Template Tag](/development/custom-template-tags.md). - diff --git a/docs/development/widgets.md b/docs/development/widgets.md index 30155a29a..b2ea3af07 100644 --- a/docs/development/widgets.md +++ b/docs/development/widgets.md @@ -11,27 +11,14 @@ [TOC] -## Overview +Along with the basic widgets which will come native with ExpressionEngine Pro, each third-party add-on can provide multiple widgets to show pertinent information to users. -Along with the basic widgets which will come native with ExpressionEngine, each third-party add-on can provide multiple widgets to show pertinent information to users. +Widgets which are shipped with add-ons can have `.html` or `.php` extension and have to be placed into `widgets` sub-directory of the add-on. They are then installed automatically when the add-on is installed or updated. -Widgets which are shipped with add-ons can have `.html` or `.php` extension and have to be placed into `widgets` sub-directory of the add-on. They are then installed automatically when the add-on is installed or updated. When using the CLI to generate a widget, a PHP widget will be created. - -TIP:Users can also create widgets using ExpressionEngine template manager without needing to create an add-on. - -Here is an example of the Dashboard Widget shipped with the SEEO add-on: -![SEEO dashboard widget](_images/dashboard_widget_example.png) - -NOTE:Before adding a Dashboard Widget to your add-on, you need to already have an add-on in place. See [Building An Add-On: Getting Started](development/addon-development-overview.md#getting-started) for how to generate the starter files for your add-on. +Users can also create widgets using ExpressionEngine template manager. ## PHP Widgets -To generate widget for an add-on with the CLI, run this command to have the necessary files created automatically: - -``` -php system/ee/eecli.php make:widget -``` - In order for an add-on to provide dashboard widgets, it needs to contain `widgets` folder inside its main directory, which will contain the widget files. All widgets are required to implement `ExpressionEngine\Addons\Pro\Service\Dashboard\DashboardWidgetInterface`. diff --git a/docs/toc_sections/_advanced_usage_toc.yml b/docs/toc_sections/_advanced_usage_toc.yml index 3c78959af..4ceaf57d4 100644 --- a/docs/toc_sections/_advanced_usage_toc.yml +++ b/docs/toc_sections/_advanced_usage_toc.yml @@ -122,55 +122,18 @@ - name: Add-On Development items: - - name: Building An Add-On + - name: The addon.setup.php File + href: development/addon-setup-php-file.md + - name: Add-on Installer + href: development/addon-installer.md + - name: Plugins + href: development/plugins.md + - name: Modules + href: development/modules.md + - name: Extensions + href: development/extensions.md + - name: Extension Hooks items: - - name: Start Here - href: development/addon-development-overview - - name: Add-on Setup File - href: development/addon-setup-php-file.md - - name: Add-on Update File - href: development/add-on-update-file.md - - name: Extending the Core - href: development/extensions.md - - name: Adding Control Panel Pages - href: development/modules.md - - name: Adding Actions - href: development/actions.md - - name: Adding Fieldtypes - items: - - name: Fieldtype Development - href: development/fieldtypes/fieldtypes.md - - name: Fieldtype Example - href: development/fieldtypes/example.md - - name: Enhanced Fieldtype Features - href: development/fieldtypes/enhanced.md - - name: Adding CLI Commands - items: - - name: Creating A Command - href: cli/creating-a-command.md - - name: Defining Input - href: cli/defining-input.md - - name: Displaying Output - href: cli/displaying-output.md - - name: Adding Template Tags - href: development/custom-template-tags.md - - name: Using Language Files - href: development/add-on-language-files.md - - name: Adding Publish Form Tabs - href: development/tab-files.md - - name: Adding Prolets - href: development/prolets.md - - name: Adding Dashboard Widgets - href: development/widgets.md - #- name: REMOVE - Adding Text Formatting Options - # href: development/text-formatting.md - - name: Accessing the Database - href: development/database-access.md - - - name: Available Core Hooks - items: - - name: Overview - href: development/extension-hooks/extension-hooks-overview.md - name: Global items: - name: Core Library @@ -302,9 +265,17 @@ - name: Wiki Module href: development/extension-hooks/module/wiki.md + - name: Fieldtypes + items: + - name: Fieldtype Development + href: development/fieldtypes/fieldtypes.md + - name: Fieldtype Example + href: development/fieldtypes/example.md + - name: Enhanced Fieldtype Features + href: development/fieldtypes/enhanced.md - name: Jump Menu href: development/jump-menu.md - - name: Learn About the Core + - name: Architecture href: development/architecture.md - name: Services items: @@ -447,6 +418,8 @@ - name: Upload Destinations Model href: development/models/upload-destination.md + - name: Shared Form View + href: development/shared-form-view.md - name: Control Panel Javascript items: - name: Global Variables @@ -459,6 +432,10 @@ href: development/control-panel-js/rangesandselections.md - name: WysiHat API href: development/control-panel-js/wysihat-api.md + - name: Dashboard Widgets + href: development/widgets.md + - name: Prolets + href: development/prolets.md - name: Constants Reference href: development/constants.md - name: Cypress Tests @@ -651,7 +628,12 @@ href: cli/built-in-commands/make-prolet.md - name: Widget Generator href: cli/built-in-commands/make-widget.md - + - name: Creating A Command + href: cli/creating-a-command.md + - name: Defining Input + href: cli/defining-input.md + - name: Displaying Output + href: cli/displaying-output.md - name: Entry Cloning href: channels/entry_cloning.md diff --git a/scripts/embed-render.js b/scripts/embed-render.js index b7d1c88e4..9670599aa 100644 --- a/scripts/embed-render.js +++ b/scripts/embed-render.js @@ -90,22 +90,6 @@ renderer.heading = function (text, level, raw, slugger) { </h${level}>` } -// ------------------------------------------------------------------- -// Render lists with tailwind classes -renderer.list = function (body, ordered, start,) { - if(ordered){ - return ` - <ol class="list-decimal"> - ${body} - </ol>` - }else{ - return ` - <ol class="list-disc"> - ${body} - </ol>` - } -} - // ------------------------------------------------------------------- // Validate links and make doc links relative diff --git a/scripts/md-render.js b/scripts/md-render.js index 711e3aea0..32001a915 100644 --- a/scripts/md-render.js +++ b/scripts/md-render.js @@ -99,23 +99,6 @@ renderer.heading = function (text, level, raw, slugger) { </h${level}>` } -// ------------------------------------------------------------------- -// Render lists with tailwind classes -renderer.list = function (body, ordered, start,) { - if(ordered){ - return ` - <ol class="list-decimal"> - ${body} - </ol>` - }else{ - return ` - <ol class="list-disc"> - ${body} - </ol>` - } -} - - // ------------------------------------------------------------------- // Validate links and make doc links relative diff --git a/theme/_assets/default.min.css b/theme/_assets/default.min.css index bdbdba306..a3f0af6ce 100755 --- a/theme/_assets/default.min.css +++ b/theme/_assets/default.min.css @@ -20,11 +20,7 @@ */@font-face{font-family:'Font Awesome 5 Brands';font-style:normal;font-weight:400;font-display:auto;src:url(webfonts/fa-brands-400.eot);src:url(webfonts/fa-brands-400.eot?#iefix) format('embedded-opentype'),url(webfonts/fa-brands-400.woff2) format('woff2'),url(webfonts/fa-brands-400.woff) format('woff'),url(webfonts/fa-brands-400.ttf) format('truetype'),url(webfonts/fa-brands-400.svg#fontawesome) format('svg')}.fab{font-family:'Font Awesome 5 Brands'}/*! * Font Awesome Pro 5.8.2 by @fontawesome - https://fontawesome.com * License - https://fontawesome.com/license (Commercial License) -<<<<<<< HEAD */@font-face{font-family:'Font Awesome 5 Pro';font-style:normal;font-weight:900;font-display:auto;src:url(webfonts/fa-solid-900.eot);src:url(webfonts/fa-solid-900.eot?#iefix) format('embedded-opentype'),url(webfonts/fa-solid-900.woff2) format('woff2'),url(webfonts/fa-solid-900.woff) format('woff'),url(webfonts/fa-solid-900.ttf) format('truetype'),url(webfonts/fa-solid-900.svg#fontawesome) format('svg')}.fa,.fas{font-family:'Font Awesome 5 Pro';font-weight:900}@tailwind base;@tailwind utilities;@tailwind components;*,::after,::before{box-sizing:border-box;border-width:0;border-style:solid;border-color:currentColor}::after,::before{--tw-content:''}html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji"}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;font-weight:inherit;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}:root,[data-theme]{background-color:hsla(var(--b1) / var(--tw-bg-opacity,1));color:hsla(var(--bc) / var(--tw-text-opacity,1))}html{-webkit-tap-highlight-color:transparent}:root{color-scheme:light;--pf:259 94% 41%;--sf:314 100% 38%;--af:174 60% 41%;--nf:219 14% 22%;--in:198 93% 60%;--su:158 64% 52%;--wa:43 96% 56%;--er:0 91% 71%;--inc:198 100% 12%;--suc:158 100% 10%;--wac:43 100% 11%;--erc:0 100% 14%;--rounded-box:1rem;--rounded-btn:0.5rem;--rounded-badge:1.9rem;--animation-btn:0.25s;--animation-input:.2s;--btn-text-case:uppercase;--btn-focus-scale:0.95;--border-btn:1px;--tab-border:1px;--tab-radius:0.5rem;--p:259 94% 51%;--pc:0 0% 100%;--s:314 100% 47%;--sc:0 0% 100%;--a:174 60% 51%;--ac:175 44% 15%;--n:219 14% 28%;--nc:0 0% 100%;--b1:0 0% 100%;--b2:0 0% 95%;--b3:180 2% 90%;--bc:215 28% 17%}@media (prefers-color-scheme:dark){:root{color-scheme:dark;--pf:262 80% 40%;--sf:316 70% 40%;--af:175 70% 33%;--in:198 93% 60%;--su:158 64% 52%;--wa:43 96% 56%;--er:0 91% 71%;--inc:198 100% 12%;--suc:158 100% 10%;--wac:43 100% 11%;--erc:0 100% 14%;--rounded-box:1rem;--rounded-btn:0.5rem;--rounded-badge:1.9rem;--animation-btn:0.25s;--animation-input:.2s;--btn-text-case:uppercase;--btn-focus-scale:0.95;--border-btn:1px;--tab-border:1px;--tab-radius:0.5rem;--p:262 80% 50%;--pc:0 0% 100%;--s:316 70% 50%;--sc:0 0% 100%;--a:175 70% 41%;--ac:0 0% 100%;--n:218 18% 12%;--nf:223 17% 8%;--nc:220 13% 69%;--b1:220 18% 20%;--b2:220 17% 17%;--b3:219 18% 15%;--bc:220 13% 69%}}[data-theme=light]{color-scheme:light;--pf:259 94% 41%;--sf:314 100% 38%;--af:174 60% 41%;--nf:219 14% 22%;--in:198 93% 60%;--su:158 64% 52%;--wa:43 96% 56%;--er:0 91% 71%;--inc:198 100% 12%;--suc:158 100% 10%;--wac:43 100% 11%;--erc:0 100% 14%;--rounded-box:1rem;--rounded-btn:0.5rem;--rounded-badge:1.9rem;--animation-btn:0.25s;--animation-input:.2s;--btn-text-case:uppercase;--btn-focus-scale:0.95;--border-btn:1px;--tab-border:1px;--tab-radius:0.5rem;--p:259 94% 51%;--pc:0 0% 100%;--s:314 100% 47%;--sc:0 0% 100%;--a:174 60% 51%;--ac:175 44% 15%;--n:219 14% 28%;--nc:0 0% 100%;--b1:0 0% 100%;--b2:0 0% 95%;--b3:180 2% 90%;--bc:215 28% 17%}[data-theme=dark]{color-scheme:dark;--pf:262 80% 40%;--sf:316 70% 40%;--af:175 70% 33%;--in:198 93% 60%;--su:158 64% 52%;--wa:43 96% 56%;--er:0 91% 71%;--inc:198 100% 12%;--suc:158 100% 10%;--wac:43 100% 11%;--erc:0 100% 14%;--rounded-box:1rem;--rounded-btn:0.5rem;--rounded-badge:1.9rem;--animation-btn:0.25s;--animation-input:.2s;--btn-text-case:uppercase;--btn-focus-scale:0.95;--border-btn:1px;--tab-border:1px;--tab-radius:0.5rem;--p:262 80% 50%;--pc:0 0% 100%;--s:316 70% 50%;--sc:0 0% 100%;--a:175 70% 41%;--ac:0 0% 100%;--n:218 18% 12%;--nf:223 17% 8%;--nc:220 13% 69%;--b1:220 18% 20%;--b2:220 17% 17%;--b3:219 18% 15%;--bc:220 13% 69%}[data-theme=cupcake]{color-scheme:light;--pf:183 47% 47%;--sf:338 71% 62%;--af:39 84% 46%;--nf:280 46% 11%;--in:198 93% 60%;--su:158 64% 52%;--wa:43 96% 56%;--er:0 91% 71%;--pc:183 100% 12%;--sc:338 100% 16%;--ac:39 100% 12%;--nc:280 83% 83%;--inc:198 100% 12%;--suc:158 100% 10%;--wac:43 100% 11%;--erc:0 100% 14%;--rounded-box:1rem;--rounded-badge:1.9rem;--animation-btn:0.25s;--animation-input:.2s;--btn-text-case:uppercase;--btn-focus-scale:0.95;--border-btn:1px;--p:183 47% 59%;--s:338 71% 78%;--a:39 84% 58%;--n:280 46% 14%;--b1:24 33% 97%;--b2:27 22% 92%;--b3:22 14% 89%;--bc:280 46% 14%;--rounded-btn:1.9rem;--tab-border:2px;--tab-radius:.5rem}[data-theme=bumblebee]{color-scheme:light;--pf:41 74% 42%;--sf:50 94% 46%;--af:240 33% 11%;--nf:240 33% 11%;--b2:0 0% 90%;--b3:0 0% 81%;--in:198 93% 60%;--su:158 64% 52%;--wa:43 96% 56%;--er:0 91% 71%;--bc:0 0% 20%;--ac:240 60% 83%;--nc:240 60% 83%;--inc:198 100% 12%;--suc:158 100% 10%;--wac:43 100% 11%;--erc:0 100% 14%;--rounded-box:1rem;--rounded-btn:0.5rem;--rounded-badge:1.9rem;--animation-btn:0.25s;--animation-input:.2s;--btn-text-case:uppercase;--btn-focus-scale:0.95;--border-btn:1px;--tab-border:1px;--tab-radius:0.5rem;--p:41 74% 53%;--pc:240 33% 14%;--s:50 94% 58%;--sc:240 33% 14%;--a:240 33% 14%;--n:240 33% 14%;--b1:0 0% 100%}[data-theme=emerald]{color-scheme:light;--pf:141 50% 48%;--sf:219 96% 48%;--af:10 81% 45%;--nf:219 20% 20%;--b2:0 0% 90%;--b3:0 0% 81%;--in:198 93% 60%;--su:158 64% 52%;--wa:43 96% 56%;--er:0 91% 71%;--inc:198 100% 12%;--suc:158 100% 10%;--wac:43 100% 11%;--erc:0 100% 14%;--rounded-box:1rem;--rounded-btn:0.5rem;--rounded-badge:1.9rem;--btn-text-case:uppercase;--border-btn:1px;--tab-border:1px;--tab-radius:0.5rem;--p:141 50% 60%;--pc:151 28% 19%;--s:219 96% 60%;--sc:210 20% 98%;--a:10 81% 56%;--ac:210 20% 98%;--n:219 20% 25%;--nc:210 20% 98%;--b1:0 0% 100%;--bc:219 20% 25%;--animation-btn:0;--animation-input:0;--btn-focus-scale:1}[data-theme=corporate]{color-scheme:light;--pf:229 96% 51%;--sf:215 26% 47%;--af:154 49% 48%;--nf:233 27% 10%;--b2:0 0% 90%;--b3:0 0% 81%;--in:198 93% 60%;--su:158 64% 52%;--wa:43 96% 56%;--er:0 91% 71%;--pc:229 100% 93%;--sc:215 100% 12%;--ac:154 100% 12%;--inc:198 100% 12%;--suc:158 100% 10%;--wac:43 100% 11%;--erc:0 100% 14%;--btn-text-case:uppercase;--border-btn:1px;--tab-border:1px;--tab-radius:0.5rem;--p:229 96% 64%;--s:215 26% 59%;--a:154 49% 60%;--n:233 27% 13%;--nc:210 38% 95%;--b1:0 0% 100%;--bc:233 27% 13%;--rounded-box:0.25rem;--rounded-btn:.125rem;--rounded-badge:.125rem;--animation-btn:0;--animation-input:0;--btn-focus-scale:1}[data-theme=synthwave]{color-scheme:dark;--pf:321 70% 55%;--sf:197 87% 52%;--af:48 89% 46%;--nf:253 61% 15%;--b2:254 59% 23%;--b3:254 59% 21%;--pc:321 100% 14%;--sc:197 100% 13%;--ac:48 100% 11%;--rounded-box:1rem;--rounded-btn:0.5rem;--rounded-badge:1.9rem;--animation-btn:0.25s;--animation-input:.2s;--btn-text-case:uppercase;--btn-focus-scale:0.95;--border-btn:1px;--tab-border:1px;--tab-radius:0.5rem;--p:321 70% 69%;--s:197 87% 65%;--a:48 89% 57%;--n:253 61% 19%;--nc:260 60% 98%;--b1:254 59% 26%;--bc:260 60% 98%;--in:199 87% 64%;--inc:257 63% 17%;--su:168 74% 68%;--suc:257 63% 17%;--wa:48 89% 57%;--wac:257 63% 17%;--er:352 74% 57%;--erc:260 60% 98%}[data-theme=retro]{color-scheme:light;--pf:3 74% 61%;--sf:145 27% 58%;--af:49 67% 61%;--nf:42 17% 34%;--inc:221 100% 91%;--suc:142 100% 87%;--wac:32 100% 9%;--erc:0 100% 90%;--animation-btn:0.25s;--animation-input:.2s;--btn-text-case:uppercase;--btn-focus-scale:0.95;--border-btn:1px;--tab-border:1px;--tab-radius:0.5rem;--p:3 74% 76%;--pc:345 5% 15%;--s:145 27% 72%;--sc:345 5% 15%;--a:49 67% 76%;--ac:345 5% 15%;--n:42 17% 42%;--nc:45 47% 80%;--b1:45 47% 80%;--b2:45 37% 72%;--b3:42 36% 65%;--bc:345 5% 15%;--in:221 83% 53%;--su:142 76% 36%;--wa:32 95% 44%;--er:0 72% 51%;--rounded-box:0.4rem;--rounded-btn:0.4rem;--rounded-badge:0.4rem}[data-theme=cyberpunk]{color-scheme:light;--pf:345 100% 58%;--sf:195 80% 56%;--af:276 74% 57%;--nf:57 100% 10%;--b2:56 100% 45%;--b3:56 100% 41%;--in:198 93% 60%;--su:158 64% 52%;--wa:43 96% 56%;--er:0 91% 71%;--bc:56 100% 10%;--pc:345 100% 15%;--sc:195 100% 14%;--ac:276 100% 14%;--inc:198 100% 12%;--suc:158 100% 10%;--wac:43 100% 11%;--erc:0 100% 14%;--animation-btn:0.25s;--animation-input:.2s;--btn-text-case:uppercase;--btn-focus-scale:0.95;--border-btn:1px;--tab-border:1px;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;--p:345 100% 73%;--s:195 80% 70%;--a:276 74% 71%;--n:57 100% 13%;--nc:56 100% 50%;--b1:56 100% 50%;--rounded-box:0;--rounded-btn:0;--rounded-badge:0;--tab-radius:0}[data-theme=valentine]{color-scheme:light;--pf:353 74% 54%;--sf:254 86% 61%;--af:181 56% 56%;--nf:336 43% 38%;--b2:318 46% 80%;--b3:318 46% 72%;--pc:353 100% 13%;--sc:254 100% 15%;--ac:181 100% 14%;--inc:221 100% 91%;--suc:142 100% 87%;--wac:32 100% 9%;--erc:0 100% 90%;--rounded-box:1rem;--rounded-badge:1.9rem;--animation-btn:0.25s;--animation-input:.2s;--btn-text-case:uppercase;--btn-focus-scale:0.95;--border-btn:1px;--tab-border:1px;--tab-radius:0.5rem;--p:353 74% 67%;--s:254 86% 77%;--a:181 56% 70%;--n:336 43% 48%;--nc:318 46% 89%;--b1:318 46% 89%;--bc:344 38% 28%;--in:221 83% 53%;--su:142 76% 36%;--wa:32 95% 44%;--er:0 72% 51%;--rounded-btn:1.9rem}[data-theme=halloween]{color-scheme:dark;--pf:32 89% 42%;--sf:271 46% 34%;--af:91 100% 26%;--nf:180 4% 9%;--b2:0 0% 12%;--b3:0 0% 10%;--bc:0 0% 83%;--sc:271 100% 88%;--ac:91 100% 7%;--nc:180 5% 82%;--inc:221 100% 91%;--suc:142 100% 87%;--wac:32 100% 9%;--erc:0 100% 90%;--rounded-box:1rem;--rounded-btn:0.5rem;--rounded-badge:1.9rem;--animation-btn:0.25s;--animation-input:.2s;--btn-text-case:uppercase;--btn-focus-scale:0.95;--border-btn:1px;--tab-border:1px;--tab-radius:0.5rem;--p:32 89% 52%;--pc:180 7% 8%;--s:271 46% 42%;--a:91 100% 33%;--n:180 4% 11%;--b1:0 0% 13%;--in:221 83% 53%;--su:142 76% 36%;--wa:32 95% 44%;--er:0 72% 51%}[data-theme=garden]{color-scheme:light;--pf:139 16% 34%;--sf:97 37% 75%;--af:0 68% 75%;--nf:0 4% 28%;--b2:0 4% 82%;--b3:0 4% 74%;--in:198 93% 60%;--su:158 64% 52%;--wa:43 96% 56%;--er:0 91% 71%;--pc:139 100% 89%;--inc:198 100% 12%;--suc:158 100% 10%;--wac:43 100% 11%;--erc:0 100% 14%;--rounded-box:1rem;--rounded-btn:0.5rem;--rounded-badge:1.9rem;--animation-btn:0.25s;--animation-input:.2s;--btn-text-case:uppercase;--btn-focus-scale:0.95;--border-btn:1px;--tab-border:1px;--tab-radius:0.5rem;--p:139 16% 43%;--s:97 37% 93%;--sc:96 32% 15%;--a:0 68% 94%;--ac:0 22% 16%;--n:0 4% 35%;--nc:0 4% 91%;--b1:0 4% 91%;--bc:0 3% 6%}[data-theme=forest]{color-scheme:dark;--pf:141 72% 34%;--sf:141 75% 38%;--af:35 69% 42%;--nf:0 10% 5%;--b2:0 12% 7%;--b3:0 12% 7%;--in:198 93% 60%;--su:158 64% 52%;--wa:43 96% 56%;--er:0 91% 71%;--bc:0 12% 82%;--sc:141 100% 10%;--ac:35 100% 10%;--nc:0 7% 81%;--inc:198 100% 12%;--suc:158 100% 10%;--wac:43 100% 11%;--erc:0 100% 14%;--rounded-box:1rem;--rounded-badge:1.9rem;--animation-btn:0.25s;--animation-input:.2s;--btn-text-case:uppercase;--btn-focus-scale:0.95;--border-btn:1px;--tab-border:1px;--tab-radius:0.5rem;--p:141 72% 42%;--pc:141 100% 88%;--s:141 75% 48%;--a:35 69% 52%;--n:0 10% 6%;--b1:0 12% 8%;--rounded-btn:1.9rem}[data-theme=aqua]{color-scheme:dark;--pf:182 93% 40%;--sf:274 31% 45%;--af:47 100% 64%;--nf:205 54% 40%;--b2:219 53% 39%;--b3:219 53% 35%;--bc:219 100% 89%;--sc:274 100% 91%;--ac:47 100% 16%;--nc:205 100% 90%;--inc:221 100% 91%;--suc:142 100% 87%;--wac:32 100% 9%;--erc:0 100% 90%;--rounded-box:1rem;--rounded-btn:0.5rem;--rounded-badge:1.9rem;--animation-btn:0.25s;--animation-input:.2s;--btn-text-case:uppercase;--btn-focus-scale:0.95;--border-btn:1px;--tab-border:1px;--tab-radius:0.5rem;--p:182 93% 49%;--pc:181 100% 17%;--s:274 31% 57%;--a:47 100% 80%;--n:205 54% 50%;--b1:219 53% 43%;--in:221 83% 53%;--su:142 76% 36%;--wa:32 95% 44%;--er:0 72% 51%}[data-theme=lofi]{color-scheme:light;--pf:0 0% 4%;--sf:0 2% 8%;--af:0 0% 12%;--nf:0 0% 0%;--btn-text-case:uppercase;--border-btn:1px;--tab-border:1px;--p:0 0% 5%;--pc:0 0% 100%;--s:0 2% 10%;--sc:0 0% 100%;--a:0 0% 15%;--ac:0 0% 100%;--n:0 0% 0%;--nc:0 0% 100%;--b1:0 0% 100%;--b2:0 0% 95%;--b3:0 2% 90%;--bc:0 0% 0%;--in:212 100% 48%;--inc:0 0% 100%;--su:137 72% 46%;--suc:0 0% 100%;--wa:5 100% 66%;--wac:0 0% 100%;--er:325 78% 49%;--erc:0 0% 100%;--rounded-box:0.25rem;--rounded-btn:0.125rem;--rounded-badge:0.125rem;--animation-btn:0;--animation-input:0;--btn-focus-scale:1;--tab-radius:0}[data-theme=pastel]{color-scheme:light;--pf:284 22% 64%;--sf:352 70% 70%;--af:158 55% 65%;--nf:199 44% 49%;--in:198 93% 60%;--su:158 64% 52%;--wa:43 96% 56%;--er:0 91% 71%;--bc:0 0% 20%;--pc:284 59% 16%;--sc:352 100% 18%;--ac:158 100% 16%;--nc:199 100% 12%;--inc:198 100% 12%;--suc:158 100% 10%;--wac:43 100% 11%;--erc:0 100% 14%;--rounded-box:1rem;--rounded-badge:1.9rem;--animation-btn:0.25s;--animation-input:.2s;--btn-text-case:uppercase;--btn-focus-scale:0.95;--border-btn:1px;--tab-border:1px;--tab-radius:0.5rem;--p:284 22% 80%;--s:352 70% 88%;--a:158 55% 81%;--n:199 44% 61%;--b1:0 0% 100%;--b2:210 20% 98%;--b3:216 12% 84%;--rounded-btn:1.9rem}[data-theme=fantasy]{color-scheme:light;--pf:296 83% 20%;--sf:200 100% 30%;--af:31 94% 41%;--nf:215 28% 13%;--b2:0 0% 90%;--b3:0 0% 81%;--in:198 93% 60%;--su:158 64% 52%;--wa:43 96% 56%;--er:0 91% 71%;--pc:296 100% 85%;--sc:200 100% 87%;--ac:31 100% 10%;--nc:215 62% 83%;--inc:198 100% 12%;--suc:158 100% 10%;--wac:43 100% 11%;--erc:0 100% 14%;--rounded-box:1rem;--rounded-btn:0.5rem;--rounded-badge:1.9rem;--animation-btn:0.25s;--animation-input:.2s;--btn-text-case:uppercase;--btn-focus-scale:0.95;--border-btn:1px;--tab-border:1px;--tab-radius:0.5rem;--p:296 83% 25%;--s:200 100% 37%;--a:31 94% 51%;--n:215 28% 17%;--b1:0 0% 100%;--bc:215 28% 17%}[data-theme=wireframe]{color-scheme:light;--pf:0 0% 58%;--sf:0 0% 58%;--af:0 0% 58%;--nf:0 0% 74%;--bc:0 0% 20%;--pc:0 0% 14%;--sc:0 0% 14%;--ac:0 0% 14%;--nc:0 0% 18%;--inc:240 100% 90%;--suc:120 100% 85%;--wac:60 100% 10%;--erc:0 100% 90%;--animation-btn:0.25s;--animation-input:.2s;--btn-text-case:uppercase;--btn-focus-scale:0.95;--border-btn:1px;--tab-border:1px;font-family:Chalkboard,comic sans ms,sanssecondaryerif;--p:0 0% 72%;--s:0 0% 72%;--a:0 0% 72%;--n:0 0% 92%;--b1:0 0% 100%;--b2:0 0% 93%;--b3:0 0% 87%;--in:240 100% 50%;--su:120 100% 25%;--wa:60 30% 50%;--er:0 100% 50%;--rounded-box:0.2rem;--rounded-btn:0.2rem;--rounded-badge:0.2rem;--tab-radius:0.2rem}[data-theme=black]{color-scheme:dark;--pf:0 2% 16%;--sf:0 2% 16%;--af:0 2% 16%;--bc:0 0% 80%;--pc:0 5% 84%;--sc:0 5% 84%;--ac:0 5% 84%;--nc:0 3% 83%;--inc:240 100% 90%;--suc:120 100% 85%;--wac:60 100% 10%;--erc:0 100% 90%;--border-btn:1px;--tab-border:1px;--p:0 2% 20%;--s:0 2% 20%;--a:0 2% 20%;--b1:0 0% 0%;--b2:0 0% 5%;--b3:0 2% 10%;--n:0 1% 15%;--nf:0 2% 20%;--in:240 100% 50%;--su:120 100% 25%;--wa:60 100% 50%;--er:0 100% 50%;--rounded-box:0;--rounded-btn:0;--rounded-badge:0;--animation-btn:0;--animation-input:0;--btn-text-case:lowercase;--btn-focus-scale:1;--tab-radius:0}[data-theme=luxury]{color-scheme:dark;--pf:0 0% 80%;--sf:218 54% 14%;--af:319 22% 21%;--nf:270 4% 7%;--pc:0 0% 20%;--sc:218 100% 84%;--ac:319 85% 85%;--inc:202 100% 14%;--suc:89 100% 10%;--wac:54 100% 13%;--erc:0 100% 14%;--rounded-box:1rem;--rounded-btn:0.5rem;--rounded-badge:1.9rem;--animation-btn:0.25s;--animation-input:.2s;--btn-text-case:uppercase;--btn-focus-scale:0.95;--border-btn:1px;--tab-border:1px;--tab-radius:0.5rem;--p:0 0% 100%;--s:218 54% 18%;--a:319 22% 26%;--n:270 4% 9%;--nc:37 67% 58%;--b1:240 10% 4%;--b2:270 4% 9%;--b3:270 2% 18%;--bc:37 67% 58%;--in:202 100% 70%;--su:89 62% 52%;--wa:54 69% 64%;--er:0 100% 72%}[data-theme=dracula]{color-scheme:dark;--pf:326 100% 59%;--sf:265 89% 62%;--af:31 100% 57%;--nf:230 15% 24%;--b2:231 15% 17%;--b3:231 15% 15%;--pc:326 100% 15%;--sc:265 100% 16%;--ac:31 100% 14%;--nc:230 71% 86%;--inc:191 100% 15%;--suc:135 100% 13%;--wac:65 100% 15%;--erc:0 100% 93%;--rounded-box:1rem;--rounded-btn:0.5rem;--rounded-badge:1.9rem;--animation-btn:0.25s;--animation-input:.2s;--btn-text-case:uppercase;--btn-focus-scale:0.95;--border-btn:1px;--tab-border:1px;--tab-radius:0.5rem;--p:326 100% 74%;--s:265 89% 78%;--a:31 100% 71%;--n:230 15% 30%;--b1:231 15% 18%;--bc:60 30% 96%;--in:191 97% 77%;--su:135 94% 65%;--wa:65 92% 76%;--er:0 100% 67%}[data-theme=cmyk]{color-scheme:light;--pf:203 83% 48%;--sf:335 78% 48%;--af:56 100% 48%;--nf:0 0% 8%;--b2:0 0% 90%;--b3:0 0% 81%;--bc:0 0% 20%;--pc:203 100% 12%;--sc:335 100% 92%;--ac:56 100% 12%;--nc:0 0% 82%;--inc:192 100% 10%;--suc:291 100% 88%;--wac:25 100% 11%;--erc:4 100% 91%;--rounded-box:1rem;--rounded-btn:0.5rem;--rounded-badge:1.9rem;--animation-btn:0.25s;--animation-input:.2s;--btn-text-case:uppercase;--btn-focus-scale:0.95;--border-btn:1px;--tab-border:1px;--tab-radius:0.5rem;--p:203 83% 60%;--s:335 78% 60%;--a:56 100% 60%;--n:0 0% 10%;--b1:0 0% 100%;--in:192 48% 52%;--su:291 48% 38%;--wa:25 85% 57%;--er:4 81% 56%}[data-theme=autumn]{color-scheme:light;--pf:344 96% 22%;--sf:0 63% 47%;--af:27 56% 50%;--nf:22 17% 35%;--b2:0 0% 85%;--b3:0 0% 77%;--bc:0 0% 19%;--pc:344 100% 86%;--sc:0 100% 92%;--ac:27 100% 13%;--nc:22 100% 89%;--inc:187 100% 10%;--suc:165 100% 9%;--wac:30 100% 10%;--erc:354 100% 90%;--rounded-box:1rem;--rounded-btn:0.5rem;--rounded-badge:1.9rem;--animation-btn:0.25s;--animation-input:.2s;--btn-text-case:uppercase;--btn-focus-scale:0.95;--border-btn:1px;--tab-border:1px;--tab-radius:0.5rem;--p:344 96% 28%;--s:0 63% 58%;--a:27 56% 63%;--n:22 17% 44%;--b1:0 0% 95%;--in:187 48% 50%;--su:165 34% 43%;--wa:30 84% 50%;--er:354 79% 49%}[data-theme=business]{color-scheme:dark;--pf:210 64% 24%;--sf:200 13% 44%;--af:13 80% 48%;--nf:213 14% 13%;--b2:0 0% 11%;--b3:0 0% 10%;--bc:0 0% 83%;--pc:210 100% 86%;--sc:200 100% 11%;--ac:13 100% 12%;--nc:213 28% 83%;--inc:199 100% 88%;--suc:144 100% 11%;--wac:39 100% 12%;--erc:6 100% 89%;--animation-btn:0.25s;--animation-input:.2s;--btn-text-case:uppercase;--btn-focus-scale:0.95;--border-btn:1px;--tab-border:1px;--tab-radius:0.5rem;--p:210 64% 31%;--s:200 13% 55%;--a:13 80% 60%;--n:213 14% 16%;--b1:0 0% 13%;--in:199 100% 42%;--su:144 31% 56%;--wa:39 64% 60%;--er:6 56% 43%;--rounded-box:0.25rem;--rounded-btn:.125rem;--rounded-badge:.125rem}[data-theme=acid]{color-scheme:light;--pf:303 100% 40%;--sf:27 100% 40%;--af:72 98% 40%;--nf:238 43% 14%;--b2:0 0% 88%;--b3:0 0% 79%;--bc:0 0% 20%;--pc:303 100% 90%;--sc:27 100% 10%;--ac:72 100% 10%;--nc:238 99% 83%;--inc:210 100% 12%;--suc:149 100% 12%;--wac:53 100% 11%;--erc:1 100% 89%;--animation-btn:0.25s;--animation-input:.2s;--btn-text-case:uppercase;--btn-focus-scale:0.95;--border-btn:1px;--tab-border:1px;--tab-radius:0.5rem;--p:303 100% 50%;--s:27 100% 50%;--a:72 98% 50%;--n:238 43% 17%;--b1:0 0% 98%;--in:210 92% 58%;--su:149 50% 58%;--wa:53 93% 57%;--er:1 100% 45%;--rounded-box:1.25rem;--rounded-btn:1rem;--rounded-badge:1rem}[data-theme=lemonade]{color-scheme:light;--pf:89 96% 24%;--sf:60 81% 44%;--af:63 80% 71%;--nf:238 43% 14%;--b2:0 0% 90%;--b3:0 0% 81%;--bc:0 0% 20%;--pc:89 100% 86%;--sc:60 100% 11%;--ac:63 100% 18%;--nc:238 99% 83%;--inc:192 79% 17%;--suc:74 100% 16%;--wac:50 100% 15%;--erc:1 100% 17%;--rounded-box:1rem;--rounded-btn:0.5rem;--rounded-badge:1.9rem;--animation-btn:0.25s;--animation-input:.2s;--btn-text-case:uppercase;--btn-focus-scale:0.95;--border-btn:1px;--tab-border:1px;--tab-radius:0.5rem;--p:89 96% 31%;--s:60 81% 55%;--a:63 80% 88%;--n:238 43% 17%;--b1:0 0% 100%;--in:192 39% 85%;--su:74 76% 79%;--wa:50 87% 75%;--er:1 70% 83%}[data-theme=night]{color-scheme:dark;--pf:198 93% 48%;--sf:234 89% 59%;--af:329 86% 56%;--b2:222 47% 10%;--b3:222 47% 9%;--bc:222 66% 82%;--pc:198 100% 12%;--sc:234 100% 15%;--ac:329 100% 14%;--nc:217 76% 83%;--inc:198 100% 10%;--suc:172 100% 10%;--wac:41 100% 13%;--erc:351 100% 14%;--rounded-box:1rem;--rounded-btn:0.5rem;--rounded-badge:1.9rem;--animation-btn:0.25s;--animation-input:.2s;--btn-text-case:uppercase;--btn-focus-scale:0.95;--border-btn:1px;--tab-border:1px;--tab-radius:0.5rem;--p:198 93% 60%;--s:234 89% 74%;--a:329 86% 70%;--n:217 33% 17%;--nf:217 30% 22%;--b1:222 47% 11%;--in:198 90% 48%;--su:172 66% 50%;--wa:41 88% 64%;--er:351 95% 71%}[data-theme=coffee]{color-scheme:dark;--pf:30 67% 46%;--sf:182 25% 16%;--af:194 74% 20%;--nf:300 20% 5%;--b2:306 19% 10%;--b3:306 19% 9%;--pc:30 100% 12%;--sc:182 67% 84%;--ac:194 100% 85%;--nc:300 14% 81%;--inc:171 100% 13%;--suc:93 100% 12%;--wac:43 100% 14%;--erc:10 100% 15%;--rounded-box:1rem;--rounded-btn:0.5rem;--rounded-badge:1.9rem;--animation-btn:0.25s;--animation-input:.2s;--btn-text-case:uppercase;--btn-focus-scale:0.95;--border-btn:1px;--tab-border:1px;--tab-radius:0.5rem;--p:30 67% 58%;--s:182 25% 20%;--a:194 74% 25%;--n:300 20% 6%;--b1:306 19% 11%;--bc:37 8% 42%;--in:171 37% 67%;--su:93 25% 62%;--wa:43 100% 69%;--er:10 95% 75%}[data-theme=winter]{color-scheme:light;--pf:212 100% 41%;--sf:247 47% 35%;--af:310 49% 42%;--nf:217 92% 8%;--pc:212 100% 90%;--sc:247 100% 89%;--ac:310 100% 90%;--nc:217 100% 82%;--inc:192 100% 16%;--suc:182 100% 13%;--wac:32 100% 17%;--erc:0 100% 14%;--rounded-box:1rem;--rounded-btn:0.5rem;--rounded-badge:1.9rem;--animation-btn:0.25s;--animation-input:.2s;--btn-text-case:uppercase;--btn-focus-scale:0.95;--border-btn:1px;--tab-border:1px;--tab-radius:0.5rem;--p:212 100% 51%;--s:247 47% 43%;--a:310 49% 52%;--n:217 92% 10%;--b1:0 0% 100%;--b2:217 100% 97%;--b3:219 44% 92%;--bc:214 30% 32%;--in:192 93% 78%;--su:182 47% 66%;--wa:32 62% 84%;--er:0 63% 72%}*,::after,::before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-scroll-snap-strictness:proximity;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgb(59 130 246 / 0.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000}::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-scroll-snap-strictness:proximity;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgb(59 130 246 / 0.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000}.visible{visibility:visible!important}.invisible{visibility:hidden!important}.collapse{visibility:collapse!important}.static{position:static!important}.fixed{position:fixed!important}.absolute{position:absolute!important}.relative{position:relative!important}.sticky{position:sticky!important}.inset-0{top:0!important;right:0!important;bottom:0!important;left:0!important}.top-0\.5{top:.125rem!important}.left-0{left:0!important}.top-0{top:0!important}.m-0{margin:0!important}.mx-auto{margin-left:auto!important;margin-right:auto!important}.my-8{margin-top:2rem!important;margin-bottom:2rem!important}.mb-1{margin-bottom:.25rem!important}.mt-12{margin-top:3rem!important}.mr-10{margin-right:2.5rem!important}.mb-4{margin-bottom:1rem!important}.mb-0{margin-bottom:0!important}.mt-0{margin-top:0!important}.mr-2\.5{margin-right:.625rem!important}.mr-2{margin-right:.5rem!important}.ml-2{margin-left:.5rem!important}.mb-7{margin-bottom:1.75rem!important}.mt-3{margin-top:.75rem!important}.mb-2{margin-bottom:.5rem!important}.mb-12{margin-bottom:3rem!important}.mr-4{margin-right:1rem!important}.ml-auto{margin-left:auto!important}.block{display:block!important}.inline-block{display:inline-block!important}.inline{display:inline!important}.flex{display:flex!important}.inline-flex{display:inline-flex!important}.table{display:table!important}.grid{display:grid!important}.contents{display:contents!important}.hidden{display:none!important}.h-20{height:5rem!important}.h-64{height:16rem!important}.h-12{height:3rem!important}.h-56{height:14rem!important}.h-full{height:100%!important}.h-16{height:4rem!important}.h-screen{height:100vh!important}.w-20{width:5rem!important}.w-64{width:16rem!important}.w-16{width:4rem!important}.w-full{width:100%!important}.max-w-7xl{max-width:80rem!important}.max-w-full{max-width:100%!important}.max-w-md{max-width:28rem!important}.flex-shrink-0{flex-shrink:0!important}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.resize{resize:both!important}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))!important}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))!important}.flex-col{flex-direction:column!important}.flex-wrap{flex-wrap:wrap!important}.items-center{align-items:center!important}.justify-start{justify-content:flex-start!important}.justify-center{justify-content:center!important}.gap-x-4{-moz-column-gap:1rem!important;column-gap:1rem!important}.gap-y-8{row-gap:2rem!important}.gap-x-6{-moz-column-gap:1.5rem!important;column-gap:1.5rem!important}.gap-y-6{row-gap:1.5rem!important}.gap-x-7{-moz-column-gap:1.75rem!important;column-gap:1.75rem!important}.gap-y-7{row-gap:1.75rem!important}.gap-y-4{row-gap:1rem!important}.gap-y-1{row-gap:.25rem!important}.space-y-8>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0!important;margin-top:calc(2rem * calc(1 - var(--tw-space-y-reverse)))!important;margin-bottom:calc(2rem * var(--tw-space-y-reverse))!important}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0!important;margin-top:calc(1rem * calc(1 - var(--tw-space-y-reverse)))!important;margin-bottom:calc(1rem * var(--tw-space-y-reverse))!important}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0!important;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)))!important;margin-bottom:calc(.5rem * var(--tw-space-y-reverse))!important}.overflow-hidden{overflow:hidden!important}.truncate{overflow:hidden!important;text-overflow:ellipsis!important;white-space:nowrap!important}.rounded-full{border-radius:9999px!important}.rounded-lg{border-radius:.5rem!important}.rounded-5{border-radius:5px!important}.rounded{border-radius:.25rem!important}.border{border-width:1px!important}.border-t{border-top-width:1px!important}.border-solid{border-style:solid!important}.border-none{border-style:none!important}.border-red{--tw-border-opacity:1!important;border-color:rgb(246 41 88 / var(--tw-border-opacity))!important}.border-gray{--tw-border-opacity:1!important;border-color:rgb(229 230 240 / var(--tw-border-opacity))!important}.bg-white{--tw-bg-opacity:1!important;background-color:rgb(255 255 255 / var(--tw-bg-opacity))!important}.bg-red{--tw-bg-opacity:1!important;background-color:rgb(246 41 88 / var(--tw-bg-opacity))!important}.bg-purple-new{--tw-bg-opacity:1!important;background-color:rgb(65 49 133 / var(--tw-bg-opacity))!important}.bg-purple-lightest{--tw-bg-opacity:1!important;background-color:rgb(232 232 249 / var(--tw-bg-opacity))!important}.bg-purple-regular{--tw-bg-opacity:1!important;background-color:rgb(120 125 242 / var(--tw-bg-opacity))!important}.bg-purple-tint{--tw-bg-opacity:1!important;background-color:rgb(216 213 238 / var(--tw-bg-opacity))!important}.p-8{padding:2rem!important}.p-0{padding:0!important}.p-4{padding:1rem!important}.py-12{padding-top:3rem!important;padding-bottom:3rem!important}.px-4{padding-left:1rem!important;padding-right:1rem!important}.px-7{padding-left:1.75rem!important;padding-right:1.75rem!important}.py-9{padding-top:2.25rem!important;padding-bottom:2.25rem!important}.py-3\.5{padding-top:.875rem!important;padding-bottom:.875rem!important}.py-3{padding-top:.75rem!important;padding-bottom:.75rem!important}.px-6{padding-left:1.5rem!important;padding-right:1.5rem!important}.py-4{padding-top:1rem!important;padding-bottom:1rem!important}.px-3{padding-left:.75rem!important;padding-right:.75rem!important}.py-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.pb-7{padding-bottom:1.75rem!important}.pt-5{padding-top:1.25rem!important}.pl-5{padding-left:1.25rem!important}.pr-6{padding-right:1.5rem!important}.pt-0{padding-top:0!important}.pt-6{padding-top:1.5rem!important}.pl-7{padding-left:1.75rem!important}.pt-4{padding-top:1rem!important}.pb-2{padding-bottom:.5rem!important}.text-center{text-align:center!important}.text-xs{font-size:.75rem!important;line-height:1rem!important}.text-2{font-size:2rem!important}.text-base{font-size:1rem!important;line-height:1.5rem!important}.text-3xl{font-size:1.875rem!important;line-height:2.25rem!important}.text-4xl{font-size:2.25rem!important;line-height:2.5rem!important}.text-2xl{font-size:1.5rem!important;line-height:2rem!important}.text-sm{font-size:.875rem!important;line-height:1.25rem!important}.text-7xl{font-size:4.5rem!important;line-height:1!important}.text-1\.75{font-size:1.75rem!important}.text-lg{font-size:1.125rem!important;line-height:1.75rem!important}.text-xl{font-size:1.25rem!important;line-height:1.75rem!important}.font-medium{font-weight:500!important}.font-bold{font-weight:700!important}.font-normal{font-weight:400!important}.font-semibold{font-weight:600!important}.uppercase{text-transform:uppercase!important}.lowercase{text-transform:lowercase!important}.capitalize{text-transform:capitalize!important}.italic{font-style:italic!important}.ordinal{--tw-ordinal:ordinal!important;font-variant-numeric:var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure) var(--tw-numeric-spacing) var(--tw-numeric-fraction)!important}.leading-snug{line-height:1.375!important}.leading-none{line-height:1!important}.tracking-wider{letter-spacing:.05em!important}.text-indigo-600{--tw-text-opacity:1!important;color:rgb(79 70 229 / var(--tw-text-opacity))!important}.text-red{--tw-text-opacity:1!important;color:rgb(246 41 88 / var(--tw-text-opacity))!important}.text-purple-dark{--tw-text-opacity:1!important;color:rgb(53 38 112 / var(--tw-text-opacity))!important}.text-white{--tw-text-opacity:1!important;color:rgb(255 255 255 / var(--tw-text-opacity))!important}.text-brand{--tw-text-opacity:1!important;color:rgb(65 49 133 / var(--tw-text-opacity))!important}.text-black{--tw-text-opacity:1!important;color:rgb(0 0 0 / var(--tw-text-opacity))!important}.text-purple-light{--tw-text-opacity:1!important;color:rgb(83 121 218 / var(--tw-text-opacity))!important}.text-current{color:currentColor!important}.text-indigo-500{--tw-text-opacity:1!important;color:rgb(99 102 241 / var(--tw-text-opacity))!important}.text-opacity-80{--tw-text-opacity:0.8!important}.text-opacity-50{--tw-text-opacity:0.5!important}.text-opacity-70{--tw-text-opacity:0.7!important}.underline{text-decoration-line:underline!important}.shadow-custom-black{--tw-shadow:0 2px 25px rgba(0,0,0,0.1)!important;--tw-shadow-colored:0 2px 25px var(--tw-shadow-color)!important;box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)!important}.shadow-lg{--tw-shadow:0 10px 15px -3px rgb(0 0 0 / 0.1),0 4px 6px -4px rgb(0 0 0 / 0.1)!important;--tw-shadow-colored:0 10px 15px -3px var(--tw-shadow-color),0 4px 6px -4px var(--tw-shadow-color)!important;box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)!important}.shadow{--tw-shadow:0 1px 3px 0 rgb(0 0 0 / 0.1),0 1px 2px -1px rgb(0 0 0 / 0.1)!important;--tw-shadow-colored:0 1px 3px 0 var(--tw-shadow-color),0 1px 2px -1px var(--tw-shadow-color)!important;box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)!important}.outline{outline-style:solid!important}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)!important}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter!important;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter!important;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter!important;transition-timing-function:cubic-bezier(.4,0,.2,1)!important;transition-duration:150ms!important}.\[make\:migration\]{make:migration}.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.alert{display:flex;width:100%;flex-direction:column;align-items:center;justify-content:space-between;gap:1rem;--tw-bg-opacity:1;background-color:hsl(var(--b2,var(--b1)) / var(--tw-bg-opacity));padding:1rem;border-radius:var(--rounded-box,1rem)}.alert>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}@media (min-width:768px){.alert{flex-direction:row}.alert>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(0px * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(0px * var(--tw-space-y-reverse))}}.alert>:where(*){display:flex;align-items:center;gap:.5rem}.avatar{position:relative;display:inline-flex}.avatar>div{display:block;aspect-ratio:1/1;overflow:hidden}.avatar img{height:100%;width:100%;-o-object-fit:cover;object-fit:cover}.avatar.placeholder>div{display:flex;align-items:center;justify-content:center}.breadcrumbs{max-width:100%;overflow-x:auto;padding-top:.5rem;padding-bottom:.5rem}.breadcrumbs>ul{display:flex;align-items:center;white-space:nowrap;min-height:-moz-min-content;min-height:min-content}.breadcrumbs>ul>li{display:flex;align-items:center}.breadcrumbs>ul>li>a{display:flex;cursor:pointer;align-items:center}.breadcrumbs>ul>li>a:hover{text-decoration-line:underline}.btn{display:inline-flex;flex-shrink:0;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none;flex-wrap:wrap;align-items:center;justify-content:center;border-color:transparent;border-color:hsl(var(--n) / var(--tw-border-opacity));text-align:center;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-duration:.2s;transition-timing-function:cubic-bezier(.4,0,.2,1);border-radius:var(--rounded-btn,.5rem);height:3rem;padding-left:1rem;padding-right:1rem;font-size:.875rem;line-height:1.25rem;line-height:1em;min-height:3rem;font-weight:600;text-transform:uppercase;text-transform:var(--btn-text-case,uppercase);text-decoration-line:none;border-width:var(--border-btn,1px);animation:button-pop var(--animation-btn,.25s) ease-out;--tw-border-opacity:1;--tw-bg-opacity:1;background-color:hsl(var(--n) / var(--tw-bg-opacity));--tw-text-opacity:1;color:hsl(var(--nc) / var(--tw-text-opacity))}.btn-disabled,.btn[disabled]{pointer-events:none}.btn.loading,.btn.loading:hover{pointer-events:none}.btn.loading:before{margin-right:.5rem;height:1rem;width:1rem;border-radius:9999px;border-width:2px;animation:spin 2s linear infinite;content:"";border-top-color:transparent;border-left-color:transparent;border-bottom-color:currentColor;border-right-color:currentColor}@media (prefers-reduced-motion:reduce){.btn.loading:before{animation:spin 10s linear infinite}}@keyframes spin{from{transform:rotate(0)}to{transform:rotate(360deg)}}.btn-group>input[type=radio].btn{-webkit-appearance:none;-moz-appearance:none;appearance:none}.btn-group>input[type=radio].btn:before{content:attr(data-title)}.card{position:relative;display:flex;flex-direction:column;border-radius:var(--rounded-box,1rem)}.card:focus{outline:2px solid transparent;outline-offset:2px}.card figure{display:flex;align-items:center;justify-content:center}.card.image-full{display:grid}.card.image-full:before{position:relative;content:"";z-index:10;--tw-bg-opacity:1;background-color:hsl(var(--n) / var(--tw-bg-opacity));opacity:.75;border-radius:var(--rounded-box,1rem)}.card.image-full:before,.card.image-full>*{grid-column-start:1;grid-row-start:1}.card.image-full>figure img{height:100%;-o-object-fit:cover;object-fit:cover}.card.image-full>.card-body{position:relative;z-index:20;--tw-text-opacity:1;color:hsl(var(--nc) / var(--tw-text-opacity))}.checkbox{flex-shrink:0;--chkbg:var(--bc);--chkfg:var(--b1);height:1.5rem;width:1.5rem;cursor:pointer;-webkit-appearance:none;-moz-appearance:none;appearance:none;border-width:1px;border-color:hsl(var(--bc) / var(--tw-border-opacity));--tw-border-opacity:0.2;border-radius:var(--rounded-btn,.5rem)}.collapse.collapse{visibility:visible}.collapse{position:relative;display:grid;overflow:hidden}.collapse-content,.collapse-title,.collapse>input[type=checkbox]{grid-column-start:1;grid-row-start:1}.collapse>input[type=checkbox]{-webkit-appearance:none;-moz-appearance:none;appearance:none;opacity:0}.collapse-open .collapse-content,.collapse:focus:not(.collapse-close) .collapse-content,.collapse:not(.collapse-close) input[type=checkbox]:checked~.collapse-content{max-height:9000px}.divider{display:flex;flex-direction:row;align-items:center;align-self:stretch;margin-top:1rem;margin-bottom:1rem;height:1rem;white-space:nowrap}.divider:after,.divider:before{content:"";flex-grow:1;height:.125rem;width:100%}.dropdown{position:relative;display:inline-block}.dropdown>:focus{outline:2px solid transparent;outline-offset:2px}.dropdown .dropdown-content{visibility:hidden;position:absolute;z-index:50;opacity:0;transform-origin:top;--tw-scale-x:.95;--tw-scale-y:.95;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-duration:.2s;transition-timing-function:cubic-bezier(.4,0,.2,1)}.dropdown.dropdown-hover:hover .dropdown-content,.dropdown.dropdown-open .dropdown-content,.dropdown:not(.dropdown-hover):focus .dropdown-content,.dropdown:not(.dropdown-hover):focus-within .dropdown-content{visibility:visible;opacity:1}.footer{display:grid;width:100%;grid-auto-flow:row;place-items:start;row-gap:2.5rem;-moz-column-gap:1rem;column-gap:1rem;font-size:.875rem;line-height:1.25rem}.footer>*{display:grid;place-items:start;gap:.5rem}@media (min-width:48rem){.footer{grid-auto-flow:column}.footer-center{grid-auto-flow:row dense}}.label{display:flex;-webkit-user-select:none;-moz-user-select:none;user-select:none;align-items:center;justify-content:space-between;padding-left:.25rem;padding-right:.25rem;padding-top:.5rem;padding-bottom:.5rem}.indicator{position:relative;display:inline-flex;width:-moz-max-content;width:max-content}.indicator :where(.indicator-item){z-index:1;position:absolute;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.input{flex-shrink:1;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-duration:.2s;transition-timing-function:cubic-bezier(.4,0,.2,1);height:3rem;padding-left:1rem;padding-right:1rem;font-size:1rem;line-height:2;line-height:1.5rem;border-width:1px;border-color:hsl(var(--bc) / var(--tw-border-opacity));--tw-border-opacity:0;--tw-bg-opacity:1;background-color:hsl(var(--b1) / var(--tw-bg-opacity));border-radius:var(--rounded-btn,.5rem)}.input-group>.input{isolation:isolate}.input-group>*,.input-group>.input,.input-group>.select{border-radius:0}.kbd{display:inline-flex;align-items:center;justify-content:center;border-width:1px;border-color:hsl(var(--bc) / var(--tw-border-opacity));--tw-border-opacity:0.2;--tw-bg-opacity:1;background-color:hsl(var(--b2,var(--b1)) / var(--tw-bg-opacity));padding-left:.5rem;padding-right:.5rem;border-radius:var(--rounded-btn,.5rem);border-bottom-width:2px;min-height:2.2em;min-width:2.2em}.link{cursor:pointer;text-decoration-line:underline}.mask{-webkit-mask-size:contain;mask-size:contain;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-position:center;mask-position:center}.menu{display:flex;flex-direction:column;flex-wrap:wrap}.menu.horizontal{display:inline-flex;flex-direction:row}.menu.horizontal :where(li){flex-direction:row}.menu :where(li){position:relative;display:flex;flex-shrink:0;flex-direction:column;flex-wrap:wrap;align-items:stretch}.menu :where(li:not(.menu-title))>:where(:not(ul)){display:flex}.menu :where(li:not(.disabled):not(.menu-title))>:where(:not(ul)){cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none;align-items:center;outline:2px solid transparent;outline-offset:2px}.menu>:where(li>:not(ul):focus){outline:2px solid transparent;outline-offset:2px}.menu>:where(li.disabled>:not(ul):focus){cursor:auto}.menu>:where(li) :where(ul){display:flex;flex-direction:column;align-items:stretch}.menu>:where(li)>:where(ul){position:absolute;display:none;top:initial;left:100%;border-top-left-radius:inherit;border-top-right-radius:inherit;border-bottom-right-radius:inherit;border-bottom-left-radius:inherit}.menu>:where(li:hover)>:where(ul){display:flex}.menu>:where(li:focus)>:where(ul){display:flex}.modal{pointer-events:none;visibility:hidden;position:fixed;top:0;right:0;bottom:0;left:0;display:flex;justify-content:center;opacity:0;z-index:999;background-color:hsl(var(--nf,var(--n)) / var(--tw-bg-opacity));--tw-bg-opacity:0.4;transition-duration:.2s;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-property:transform,opacity;overflow-y:hidden;overscroll-behavior:contain}:where(.modal){align-items:center}.modal-open,.modal-toggle:checked+.modal,.modal:target{pointer-events:auto;visibility:visible;opacity:1}.progress{position:relative;width:100%;-webkit-appearance:none;-moz-appearance:none;appearance:none;overflow:hidden;height:.5rem;border-radius:var(--rounded-box,1rem)}.radio{flex-shrink:0;--chkbg:var(--bc);height:1.5rem;width:1.5rem;cursor:pointer;-webkit-appearance:none;-moz-appearance:none;appearance:none;border-radius:9999px;border-width:1px;border-color:hsl(var(--bc) / var(--tw-border-opacity));--tw-border-opacity:0.2;transition:background,box-shadow var(--animation-input,.2s) ease-in-out}.range{height:1.5rem;width:100%;cursor:pointer;-moz-appearance:none;appearance:none;-webkit-appearance:none;--range-shdw:var(--bc);overflow:hidden;background-color:transparent;border-radius:var(--rounded-box,1rem)}.range:focus{outline:0}.rating{position:relative;display:inline-flex}.rating :where(input){cursor:pointer;animation:rating-pop var(--animation-input,.25s) ease-out;height:1.5rem;width:1.5rem;background-color:hsl(var(--bc) / var(--tw-bg-opacity));--tw-bg-opacity:1}.select{display:inline-flex;flex-shrink:0;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none;-webkit-appearance:none;-moz-appearance:none;appearance:none;height:3rem;padding-left:1rem;padding-right:2.5rem;font-size:.875rem;line-height:1.25rem;line-height:2;min-height:3rem;border-width:1px;border-color:hsl(var(--bc) / var(--tw-border-opacity));--tw-border-opacity:0;--tw-bg-opacity:1;background-color:hsl(var(--b1) / var(--tw-bg-opacity));font-weight:600;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-duration:.2s;transition-timing-function:cubic-bezier(.4,0,.2,1);border-radius:var(--rounded-btn,.5rem);background-image:linear-gradient(45deg,transparent 50%,currentColor 50%),linear-gradient(135deg,currentColor 50%,transparent 50%);background-position:calc(100% - 20px) calc(1px + 50%),calc(100% - 16px) calc(1px + 50%);background-size:4px 4px,4px 4px;background-repeat:no-repeat}.select[multiple]{height:auto}.stack{display:inline-grid;place-items:center;align-items:flex-end}.stack>*{grid-column-start:1;grid-row-start:1;transform:translateY(1rem) scale(.9);z-index:1;width:100%;opacity:.6}.stack>:nth-child(2){transform:translateY(.5rem) scale(.95);z-index:2;opacity:.8}.stack>:nth-child(1){transform:translateY(0) scale(1);z-index:3;opacity:1}.stats{display:inline-grid;--tw-bg-opacity:1;background-color:hsl(var(--b1) / var(--tw-bg-opacity));--tw-text-opacity:1;color:hsl(var(--bc) / var(--tw-text-opacity));border-radius:var(--rounded-box,1rem)}:where(.stats){grid-auto-flow:column;overflow-x:auto}.steps{display:inline-grid;grid-auto-flow:column;overflow:hidden;overflow-x:auto;counter-reset:step;grid-auto-columns:1fr}.steps .step{display:grid;grid-template-columns:repeat(1,minmax(0,1fr));grid-template-columns:auto;grid-template-rows:repeat(2,minmax(0,1fr));grid-template-rows:40px 1fr;place-items:center;text-align:center;min-width:4rem}.swap{position:relative;display:inline-grid;-webkit-user-select:none;-moz-user-select:none;user-select:none;place-content:center;cursor:pointer}.swap>*{grid-column-start:1;grid-row-start:1;transition-duration:.3s;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-property:transform,opacity}.swap input{-webkit-appearance:none;-moz-appearance:none;appearance:none}.swap .swap-indeterminate,.swap .swap-on,.swap input:indeterminate~.swap-on{opacity:0}.swap input:checked~.swap-off,.swap input:indeterminate~.swap-off,.swap.swap-active .swap-off{opacity:0}.swap input:checked~.swap-on,.swap input:indeterminate~.swap-indeterminate,.swap-active .swap-on{opacity:1}.tabs{display:flex;flex-wrap:wrap;align-items:flex-end}.tab{position:relative;display:inline-flex;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none;flex-wrap:wrap;align-items:center;justify-content:center;text-align:center;height:2rem;font-size:.875rem;line-height:1.25rem;line-height:2;--tab-padding:1rem;--tw-text-opacity:0.5;--tab-color:hsla(var(--bc) / var(--tw-text-opacity, 1));--tab-bg:hsla(var(--b1) / var(--tw-bg-opacity, 1));--tab-border-color:hsla(var(--b3) / var(--tw-bg-opacity, 1));color:var(--tab-color);padding-left:var(--tab-padding,1rem);padding-right:var(--tab-padding,1rem)}.table{position:relative;text-align:left}.table th:first-child{position:sticky;position:-webkit-sticky;left:0;z-index:11}.textarea{flex-shrink:1;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-duration:.2s;transition-timing-function:cubic-bezier(.4,0,.2,1);padding-left:1rem;padding-right:1rem;padding-top:.5rem;padding-bottom:.5rem;font-size:.875rem;line-height:1.25rem;line-height:2;min-height:3rem;border-width:1px;border-color:hsl(var(--bc) / var(--tw-border-opacity));--tw-border-opacity:0;--tw-bg-opacity:1;background-color:hsl(var(--b1) / var(--tw-bg-opacity));border-radius:var(--rounded-btn,.5rem)}.toggle{flex-shrink:0;--chkbg:hsla(var(--bc) / 0.2);--handleoffset:1.5rem;height:1.5rem;width:3rem;cursor:pointer;-webkit-appearance:none;-moz-appearance:none;appearance:none;border-width:1px;border-color:hsl(var(--bc) / var(--tw-border-opacity));--tw-border-opacity:0.2;background-color:hsl(var(--bc) / var(--tw-bg-opacity));--tw-bg-opacity:0.2;transition-duration:.3s;transition-timing-function:cubic-bezier(.4,0,.2,1);border-radius:var(--rounded-badge,1.9rem);transition:background,box-shadow var(--animation-input,.2s) ease-in-out;box-shadow:calc(var(--handleoffset) * -1) 0 0 2px hsl(var(--b1)) inset,0 0 0 2px hsl(var(--b1)) inset}.alert-success{--tw-bg-opacity:1;background-color:hsl(var(--su) / var(--tw-bg-opacity));--tw-text-opacity:1;color:hsl(var(--suc,var(--nc)) / var(--tw-text-opacity))}.avatar-group :where(.avatar){overflow:hidden;border-radius:9999px;border-width:4px;--tw-border-opacity:1;border-color:hsl(var(--b1) / var(--tw-border-opacity))}.btn-outline.btn-primary .badge{--tw-border-opacity:1;border-color:hsl(var(--p) / var(--tw-border-opacity));--tw-bg-opacity:1;background-color:hsl(var(--p) / var(--tw-bg-opacity));--tw-text-opacity:1;color:hsl(var(--pc) / var(--tw-text-opacity))}.btn-outline .badge.outline{--tw-border-opacity:1;border-color:hsl(var(--nf,var(--n)) / var(--tw-border-opacity));background-color:transparent}.btn-outline.btn-primary .badge-outline{--tw-border-opacity:1;border-color:hsl(var(--p) / var(--tw-border-opacity));background-color:transparent;--tw-text-opacity:1;color:hsl(var(--p) / var(--tw-text-opacity))}.btn-outline:hover .badge.outline{--tw-border-opacity:1;border-color:hsl(var(--b2,var(--b1)) / var(--tw-border-opacity));--tw-text-opacity:1;color:hsl(var(--nc) / var(--tw-text-opacity))}.btn-outline.btn-primary:hover .badge{--tw-border-opacity:1;border-color:hsl(var(--pc) / var(--tw-border-opacity));--tw-bg-opacity:1;background-color:hsl(var(--pc) / var(--tw-bg-opacity));--tw-text-opacity:1;color:hsl(var(--p) / var(--tw-text-opacity))}.btn-outline.btn-primary:hover .badge.outline{--tw-border-opacity:1;border-color:hsl(var(--pc) / var(--tw-border-opacity));--tw-bg-opacity:1;background-color:hsl(var(--pf,var(--p)) / var(--tw-bg-opacity));--tw-text-opacity:1;color:hsl(var(--pc) / var(--tw-text-opacity))}.btn-outline.btn-secondary:hover .badge.outline{--tw-border-opacity:1;border-color:hsl(var(--sc) / var(--tw-border-opacity));--tw-bg-opacity:1;background-color:hsl(var(--sf,var(--s)) / var(--tw-bg-opacity));--tw-text-opacity:1;color:hsl(var(--sc) / var(--tw-text-opacity))}.btn-outline.btn-accent:hover .badge.outline{--tw-border-opacity:1;border-color:hsl(var(--ac) / var(--tw-border-opacity));--tw-bg-opacity:1;background-color:hsl(var(--af,var(--a)) / var(--tw-bg-opacity));--tw-text-opacity:1;color:hsl(var(--ac) / var(--tw-text-opacity))}.btm-nav>:where(.active){border-top-width:2px;--tw-bg-opacity:1;background-color:hsl(var(--b1) / var(--tw-bg-opacity))}.btm-nav>.disabled,.btm-nav>.disabled:hover,.btm-nav>[disabled],.btm-nav>[disabled]:hover{pointer-events:none;--tw-border-opacity:0;background-color:hsl(var(--n) / var(--tw-bg-opacity));--tw-bg-opacity:0.1;color:hsl(var(--bc) / var(--tw-text-opacity));--tw-text-opacity:0.2}.btm-nav>* .label{font-size:1rem;line-height:1.5rem}.breadcrumbs>ul>li>a:focus{outline:2px solid transparent;outline-offset:2px}.breadcrumbs>ul>li>a:focus-visible{outline:2px solid currentColor;outline-offset:2px}.breadcrumbs>ul>li+:before{content:"";margin-left:.5rem;margin-right:.75rem;display:block;height:.375rem;width:.375rem;--tw-rotate:45deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));opacity:.4;border-top:1px solid;border-right:1px solid;background-color:transparent}[dir=rtl] .breadcrumbs>ul>li+:before{--tw-rotate:-45deg}.btn:active:focus,.btn:active:hover{animation:none;transform:scale(var(--btn-focus-scale,.95))}.btn-active,.btn:hover{--tw-border-opacity:1;border-color:hsl(var(--nf,var(--n)) / var(--tw-border-opacity));--tw-bg-opacity:1;background-color:hsl(var(--nf,var(--n)) / var(--tw-bg-opacity))}.btn:focus-visible{outline:2px solid hsl(var(--nf));outline-offset:2px}.btn-primary{--tw-border-opacity:1;border-color:hsl(var(--p) / var(--tw-border-opacity));--tw-bg-opacity:1;background-color:hsl(var(--p) / var(--tw-bg-opacity));--tw-text-opacity:1;color:hsl(var(--pc) / var(--tw-text-opacity))}.btn-primary.btn-active,.btn-primary:hover{--tw-border-opacity:1;border-color:hsl(var(--pf,var(--p)) / var(--tw-border-opacity));--tw-bg-opacity:1;background-color:hsl(var(--pf,var(--p)) / var(--tw-bg-opacity))}.btn-primary:focus-visible{outline:2px solid hsl(var(--p))}.btn.glass.btn-active,.btn.glass:hover{--glass-opacity:25%;--glass-border-opacity:15%}.btn.glass:focus-visible{outline:2px solid currentColor}.btn-outline.btn-primary{--tw-text-opacity:1;color:hsl(var(--p) / var(--tw-text-opacity))}.btn-outline.btn-primary.btn-active,.btn-outline.btn-primary:hover{--tw-border-opacity:1;border-color:hsl(var(--pf,var(--p)) / var(--tw-border-opacity));--tw-bg-opacity:1;background-color:hsl(var(--pf,var(--p)) / var(--tw-bg-opacity));--tw-text-opacity:1;color:hsl(var(--pc) / var(--tw-text-opacity))}.btn-disabled,.btn-disabled:hover,.btn[disabled],.btn[disabled]:hover{--tw-border-opacity:0;background-color:hsl(var(--n) / var(--tw-bg-opacity));--tw-bg-opacity:0.2;color:hsl(var(--bc) / var(--tw-text-opacity));--tw-text-opacity:0.2}.btn.loading.btn-circle:before,.btn.loading.btn-square:before{margin-right:0}.btn.loading.btn-lg:before,.btn.loading.btn-xl:before{height:1.25rem;width:1.25rem}.btn.loading.btn-sm:before,.btn.loading.btn-xs:before{height:.75rem;width:.75rem}.btn-group>.btn-active,.btn-group>input[type=radio].btn:checked{--tw-border-opacity:1;border-color:hsl(var(--p) / var(--tw-border-opacity));--tw-bg-opacity:1;background-color:hsl(var(--p) / var(--tw-bg-opacity));--tw-text-opacity:1;color:hsl(var(--pc) / var(--tw-text-opacity))}.btn-group>.btn-active:focus-visible,.btn-group>input[type=radio]:checked.btn:focus-visible{outline:2px solid hsl(var(--p))}@keyframes button-pop{0%{transform:scale(var(--btn-focus-scale,.95))}40%{transform:scale(1.02)}100%{transform:scale(1)}}.card :where(figure:first-child){overflow:hidden;border-top-left-radius:inherit;border-top-right-radius:inherit;border-bottom-left-radius:unset;border-bottom-right-radius:unset}.card :where(figure:last-child){overflow:hidden;border-top-left-radius:unset;border-top-right-radius:unset;border-bottom-left-radius:inherit;border-bottom-right-radius:inherit}.card:focus-visible{outline:2px solid currentColor;outline-offset:2px}.card.bordered{border-width:1px;--tw-border-opacity:1;border-color:hsl(var(--b2,var(--b1)) / var(--tw-border-opacity))}.card.compact .card-body{padding:1rem;font-size:.875rem;line-height:1.25rem}.card.image-full :where(figure){overflow:hidden;border-radius:inherit}.checkbox:focus-visible{outline:2px solid hsl(var(--bc));outline-offset:2px}.checkbox:checked,.checkbox[aria-checked=true],.checkbox[checked=true]{--tw-bg-opacity:1;background-color:hsl(var(--bc) / var(--tw-bg-opacity));background-repeat:no-repeat;animation:checkmark var(--animation-input,.2s) ease-in-out;background-image:linear-gradient(-45deg,transparent 65%,hsl(var(--chkbg)) 65.99%),linear-gradient(45deg,transparent 75%,hsl(var(--chkbg)) 75.99%),linear-gradient(-45deg,hsl(var(--chkbg)) 40%,transparent 40.99%),linear-gradient(45deg,hsl(var(--chkbg)) 30%,hsl(var(--chkfg)) 30.99%,hsl(var(--chkfg)) 40%,transparent 40.99%),linear-gradient(-45deg,hsl(var(--chkfg)) 50%,hsl(var(--chkbg)) 50.99%)}.checkbox:indeterminate{--tw-bg-opacity:1;background-color:hsl(var(--bc) / var(--tw-bg-opacity));background-repeat:no-repeat;animation:checkmark var(--animation-input,.2s) ease-in-out;background-image:linear-gradient(90deg,transparent 80%,hsl(var(--chkbg)) 80%),linear-gradient(-90deg,transparent 80%,hsl(var(--chkbg)) 80%),linear-gradient(0deg,hsl(var(--chkbg)) 43%,hsl(var(--chkfg)) 43%,hsl(var(--chkfg)) 57%,hsl(var(--chkbg)) 57%)}.checkbox:disabled{cursor:not-allowed;border-color:transparent;--tw-bg-opacity:1;background-color:hsl(var(--bc) / var(--tw-bg-opacity));opacity:.2}@keyframes checkmark{0%{background-position-y:5px}50%{background-position-y:-2px}100%{background-position-y:0}}[dir=rtl] .checkbox{--chkbg:var(--bc);--chkfg:var(--b1)}[dir=rtl] .checkbox:checked,[dir=rtl] .checkbox[aria-checked=true],[dir=rtl] .checkbox[checked=true]{background-image:linear-gradient(45deg,transparent 65%,hsl(var(--chkbg)) 65.99%),linear-gradient(-45deg,transparent 75%,hsl(var(--chkbg)) 75.99%),linear-gradient(45deg,hsl(var(--chkbg)) 40%,transparent 40.99%),linear-gradient(-45deg,hsl(var(--chkbg)) 30%,hsl(var(--chkfg)) 30.99%,hsl(var(--chkfg)) 40%,transparent 40.99%),linear-gradient(45deg,hsl(var(--chkfg)) 50%,hsl(var(--chkbg)) 50.99%)}.collapse:focus-visible{outline:2px solid hsl(var(--nf));outline-offset:2px}.collapse:not(.collapse-open):not(.collapse-close) .collapse-title,.collapse:not(.collapse-open):not(.collapse-close) input[type=checkbox]{cursor:pointer}.collapse:focus:not(.collapse-open):not(.collapse-close) .collapse-title{cursor:unset}:where(.collapse>input[type=checkbox]){z-index:1}.collapse-title,:where(.collapse>input[type=checkbox]){width:100%;padding:1rem;padding-right:3rem;min-height:3.75rem;transition:background-color .2s ease-in-out}.collapse-open :where(.collapse-content),.collapse:focus:not(.collapse-close) :where(.collapse-content),.collapse:not(.collapse-close) :where(input[type=checkbox]:checked~.collapse-content){padding-bottom:1rem;transition:padding .2s ease-in-out,background-color .2s ease-in-out}.divider:before{background-color:hsl(var(--bc) / var(--tw-bg-opacity));--tw-bg-opacity:0.1}.divider:after{background-color:hsl(var(--bc) / var(--tw-bg-opacity));--tw-bg-opacity:0.1}.divider:not(:empty){gap:1rem}.drawer-toggle:focus-visible~.drawer-content .drawer-button.btn-primary{outline:2px solid hsl(var(--p))}.dropdown.dropdown-hover:hover .dropdown-content,.dropdown.dropdown-open .dropdown-content,.dropdown:focus .dropdown-content,.dropdown:focus-within .dropdown-content{--tw-scale-x:1;--tw-scale-y:1;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.label a:hover{--tw-text-opacity:1;color:hsl(var(--bc) / var(--tw-text-opacity))}.input[list]::-webkit-calendar-picker-indicator{line-height:1em}.input:focus{outline:2px solid hsla(var(--bc) / .2);outline-offset:2px}.input-disabled,.input[disabled]{cursor:not-allowed;--tw-border-opacity:1;border-color:hsl(var(--b2,var(--b1)) / var(--tw-border-opacity));--tw-bg-opacity:1;background-color:hsl(var(--b2,var(--b1)) / var(--tw-bg-opacity));--tw-text-opacity:0.2}.input-disabled::-moz-placeholder,.input[disabled]::-moz-placeholder{color:hsl(var(--bc) / var(--tw-placeholder-opacity));--tw-placeholder-opacity:0.2}.input-disabled::placeholder,.input[disabled]::placeholder{color:hsl(var(--bc) / var(--tw-placeholder-opacity));--tw-placeholder-opacity:0.2}.link:focus{outline:2px solid transparent;outline-offset:2px}.link:focus-visible{outline:2px solid currentColor;outline-offset:2px}.menu.horizontal li.bordered>a,.menu.horizontal li.bordered>button,.menu.horizontal li.bordered>span{border-left-width:0;border-bottom-width:4px;--tw-border-opacity:1;border-color:hsl(var(--p) / var(--tw-border-opacity))}.menu[class*=" p-"] li>*,.menu[class^=p-] li>*{border-radius:var(--rounded-btn,.5rem)}.menu :where(li.bordered>*){border-left-width:4px;--tw-border-opacity:1;border-color:hsl(var(--p) / var(--tw-border-opacity))}.menu :where(li)>:where(:not(ul)){gap:.75rem;padding-left:1rem;padding-right:1rem;padding-top:.75rem;padding-bottom:.75rem;color:currentColor}.menu :where(li:not(.menu-title):not(:empty))>:where(:not(ul):focus),.menu :where(li:not(.menu-title):not(:empty))>:where(:not(ul):hover){background-color:hsl(var(--bc) / var(--tw-bg-opacity));--tw-bg-opacity:0.1}.menu :where(li:not(.menu-title):not(:empty))>:where(:not(ul).active),.menu :where(li:not(.menu-title):not(:empty))>:where(:not(ul):active){--tw-bg-opacity:1;background-color:hsl(var(--p) / var(--tw-bg-opacity));--tw-text-opacity:1;color:hsl(var(--pc) / var(--tw-text-opacity))}.menu :where(li:empty){margin-left:1rem;margin-right:1rem;margin-top:.5rem;margin-bottom:.5rem;height:1px;background-color:hsl(var(--bc) / var(--tw-bg-opacity));--tw-bg-opacity:0.1}.menu li.disabled>*{-webkit-user-select:none;-moz-user-select:none;user-select:none;color:hsl(var(--bc) / var(--tw-text-opacity));--tw-text-opacity:0.2}.menu li.disabled>:hover{background-color:transparent}.menu li.hover-bordered a{border-left-width:4px;border-color:transparent}.menu li.hover-bordered a:hover{--tw-border-opacity:1;border-color:hsl(var(--p) / var(--tw-border-opacity))}.menu.compact li>a,.menu.compact li>span{padding-top:.5rem;padding-bottom:.5rem;font-size:.875rem;line-height:1.25rem}.menu .menu-title>*{padding-top:.25rem;padding-bottom:.25rem;font-size:.75rem;line-height:1rem;font-weight:700;color:hsl(var(--bc) / var(--tw-text-opacity));--tw-text-opacity:0.4}.menu :where(li:not(.disabled))>:where(:not(ul)){outline:2px solid transparent;outline-offset:2px;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-duration:.2s;transition-timing-function:cubic-bezier(.4,0,.2,1)}.menu>:where(li:first-child){border-top-left-radius:inherit;border-top-right-radius:inherit;border-bottom-right-radius:unset;border-bottom-left-radius:unset}.menu>:where(li:first-child)>:where(:not(ul)){border-top-left-radius:inherit;border-top-right-radius:inherit;border-bottom-right-radius:unset;border-bottom-left-radius:unset}.menu>:where(li:last-child){border-top-left-radius:unset;border-top-right-radius:unset;border-bottom-right-radius:inherit;border-bottom-left-radius:inherit}.menu>:where(li:last-child)>:where(:not(ul)){border-top-left-radius:unset;border-top-right-radius:unset;border-bottom-right-radius:inherit;border-bottom-left-radius:inherit}.menu>:where(li)>:where(ul) :where(li){width:100%;white-space:nowrap}.menu>:where(li)>:where(ul) :where(li) :where(ul){padding-left:1rem}.menu>:where(li)>:where(ul) :where(li)>:where(:not(ul)){width:100%;white-space:nowrap}.menu>:where(li)>:where(ul)>:where(li:first-child){border-top-left-radius:inherit;border-top-right-radius:inherit;border-bottom-right-radius:unset;border-bottom-left-radius:unset}.menu>:where(li)>:where(ul)>:where(li:first-child)>:where(:not(ul)){border-top-left-radius:inherit;border-top-right-radius:inherit;border-bottom-right-radius:unset;border-bottom-left-radius:unset}.menu>:where(li)>:where(ul)>:where(li:last-child){border-top-left-radius:unset;border-top-right-radius:unset;border-bottom-right-radius:inherit;border-bottom-left-radius:inherit}.menu>:where(li)>:where(ul)>:where(li:last-child)>:where(:not(ul)){border-top-left-radius:unset;border-top-right-radius:unset;border-bottom-right-radius:inherit;border-bottom-left-radius:inherit}.mockup-phone .display{overflow:hidden;border-radius:40px;margin-top:-25px}.modal-open .modal-box,.modal-toggle:checked+.modal .modal-box,.modal:target .modal-box{--tw-translate-y:0px;--tw-scale-x:1;--tw-scale-y:1;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.progress::-moz-progress-bar{--tw-bg-opacity:1;background-color:hsl(var(--n) / var(--tw-bg-opacity))}.progress:indeterminate::after{--tw-bg-opacity:1;background-color:hsl(var(--n) / var(--tw-bg-opacity));content:"";position:absolute;top:0;bottom:0;left:-40%;width:33.333333%;border-radius:var(--rounded-box,1rem);animation:progress-loading 5s infinite ease-in-out}.progress::-webkit-progress-bar{background-color:hsl(var(--n) / var(--tw-bg-opacity));--tw-bg-opacity:0.2;border-radius:var(--rounded-box,1rem)}.progress::-webkit-progress-value{--tw-bg-opacity:1;background-color:hsl(var(--nf,var(--n)) / var(--tw-bg-opacity));border-radius:var(--rounded-box,1rem)}@keyframes progress-loading{50%{left:107%}}.radio:focus-visible{outline:2px solid hsl(var(--bc));outline-offset:2px}.radio:checked,.radio[aria-checked=true]{--tw-bg-opacity:1;background-color:hsl(var(--bc) / var(--tw-bg-opacity));animation:radiomark var(--animation-input,.2s) ease-in-out;box-shadow:0 0 0 4px hsl(var(--b1)) inset,0 0 0 4px hsl(var(--b1)) inset}.radio:disabled{cursor:not-allowed;opacity:.2}@keyframes radiomark{0%{box-shadow:0 0 0 12px hsl(var(--b1)) inset,0 0 0 12px hsl(var(--b1)) inset}50%{box-shadow:0 0 0 3px hsl(var(--b1)) inset,0 0 0 3px hsl(var(--b1)) inset}100%{box-shadow:0 0 0 4px hsl(var(--b1)) inset,0 0 0 4px hsl(var(--b1)) inset}}.range:focus-visible::-webkit-slider-thumb{--focus-shadow:0 0 0 6px hsl(var(--b1)) inset,0 0 0 2rem hsl(var(--range-shdw)) inset}.range:focus-visible::-moz-range-thumb{--focus-shadow:0 0 0 6px hsl(var(--b1)) inset,0 0 0 2rem hsl(var(--range-shdw)) inset}.range::-webkit-slider-runnable-track{height:.5rem;width:100%;border-radius:var(--rounded-box,1rem);background-color:hsla(var(--bc) / .1)}.range::-moz-range-track{height:.5rem;width:100%;border-radius:var(--rounded-box,1rem);background-color:hsla(var(--bc) / .1)}.range::-webkit-slider-thumb{background-color:hsl(var(--b1));position:relative;height:1.5rem;width:1.5rem;border-style:none;border-radius:var(--rounded-box,1rem);appearance:none;-webkit-appearance:none;top:50%;color:hsl(var(--range-shdw));transform:translateY(-50%);--filler-size:100rem;--filler-offset:0.6rem;box-shadow:0 0 0 3px hsl(var(--range-shdw)) inset,var(--focus-shadow,0 0),calc(var(--filler-size) * -1 - var(--filler-offset)) 0 0 var(--filler-size)}.range::-moz-range-thumb{background-color:hsl(var(--b1));position:relative;height:1.5rem;width:1.5rem;border-style:none;border-radius:var(--rounded-box,1rem);top:50%;color:hsl(var(--range-shdw));--filler-size:100rem;--filler-offset:0.5rem;box-shadow:0 0 0 3px hsl(var(--range-shdw)) inset,var(--focus-shadow,0 0),calc(var(--filler-size) * -1 - var(--filler-offset)) 0 0 var(--filler-size)}.rating input{-moz-appearance:none;appearance:none;-webkit-appearance:none}.rating .rating-hidden{width:.5rem;background-color:transparent}.rating input:checked~input,.rating input[aria-checked=true]~input{--tw-bg-opacity:0.2}.rating input:focus-visible{transition-property:transform;transition-duration:.3s;transition-timing-function:cubic-bezier(.4,0,.2,1);transform:translateY(-.125em)}.rating input:active:focus{animation:none;transform:translateY(-.125em)}@keyframes rating-pop{0%{transform:translateY(-.125em)}40%{transform:translateY(-.125em)}100%{transform:translateY(0)}}.select:focus{outline:2px solid hsla(var(--bc) / .2);outline-offset:2px}.select-disabled,.select[disabled]{cursor:not-allowed;--tw-border-opacity:1;border-color:hsl(var(--b2,var(--b1)) / var(--tw-border-opacity));--tw-bg-opacity:1;background-color:hsl(var(--b2,var(--b1)) / var(--tw-bg-opacity));--tw-text-opacity:0.2}.select-disabled::-moz-placeholder,.select[disabled]::-moz-placeholder{color:hsl(var(--bc) / var(--tw-placeholder-opacity));--tw-placeholder-opacity:0.2}.select-disabled::placeholder,.select[disabled]::placeholder{color:hsl(var(--bc) / var(--tw-placeholder-opacity));--tw-placeholder-opacity:0.2}.select-multiple,.select[multiple],.select[size].select:not([size="1"]){background-image:none;padding-right:1rem}:where(.stats)>:not([hidden])~:not([hidden]){--tw-divide-x-reverse:0;border-right-width:calc(1px * var(--tw-divide-x-reverse));border-left-width:calc(1px * calc(1 - var(--tw-divide-x-reverse)));--tw-divide-y-reverse:0;border-top-width:calc(0px * calc(1 - var(--tw-divide-y-reverse)));border-bottom-width:calc(0px * var(--tw-divide-y-reverse))}.steps .step:before{top:0;grid-column-start:1;grid-row-start:1;height:.5rem;width:100%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));--tw-bg-opacity:1;background-color:hsl(var(--b3,var(--b2)) / var(--tw-bg-opacity));--tw-text-opacity:1;color:hsl(var(--bc) / var(--tw-text-opacity));content:"";margin-left:-100%}.steps .step:after{content:counter(step);counter-increment:step;z-index:1;position:relative;grid-column-start:1;grid-row-start:1;display:grid;height:2rem;width:2rem;place-items:center;place-self:center;border-radius:9999px;--tw-bg-opacity:1;background-color:hsl(var(--b3,var(--b2)) / var(--tw-bg-opacity));--tw-text-opacity:1;color:hsl(var(--bc) / var(--tw-text-opacity))}.steps .step:first-child:before{content:none}.steps .step[data-content]:after{content:attr(data-content)}.steps .step-neutral+.step-neutral:before,.steps .step-neutral:after{--tw-bg-opacity:1;background-color:hsl(var(--n) / var(--tw-bg-opacity));--tw-text-opacity:1;color:hsl(var(--nc) / var(--tw-text-opacity))}.steps .step-primary+.step-primary:before,.steps .step-primary:after{--tw-bg-opacity:1;background-color:hsl(var(--p) / var(--tw-bg-opacity));--tw-text-opacity:1;color:hsl(var(--pc) / var(--tw-text-opacity))}.steps .step-secondary+.step-secondary:before,.steps .step-secondary:after{--tw-bg-opacity:1;background-color:hsl(var(--s) / var(--tw-bg-opacity));--tw-text-opacity:1;color:hsl(var(--sc) / var(--tw-text-opacity))}.steps .step-accent+.step-accent:before,.steps .step-accent:after{--tw-bg-opacity:1;background-color:hsl(var(--a) / var(--tw-bg-opacity));--tw-text-opacity:1;color:hsl(var(--ac) / var(--tw-text-opacity))}.steps .step-info+.step-info:before{--tw-bg-opacity:1;background-color:hsl(var(--in) / var(--tw-bg-opacity))}.steps .step-info:after{--tw-bg-opacity:1;background-color:hsl(var(--in) / var(--tw-bg-opacity));--tw-text-opacity:1;color:hsl(var(--inc,var(--nc)) / var(--tw-text-opacity))}.steps .step-success+.step-success:before{--tw-bg-opacity:1;background-color:hsl(var(--su) / var(--tw-bg-opacity))}.steps .step-success:after{--tw-bg-opacity:1;background-color:hsl(var(--su) / var(--tw-bg-opacity));--tw-text-opacity:1;color:hsl(var(--suc,var(--nc)) / var(--tw-text-opacity))}.steps .step-warning+.step-warning:before{--tw-bg-opacity:1;background-color:hsl(var(--wa) / var(--tw-bg-opacity))}.steps .step-warning:after{--tw-bg-opacity:1;background-color:hsl(var(--wa) / var(--tw-bg-opacity));--tw-text-opacity:1;color:hsl(var(--wac,var(--nc)) / var(--tw-text-opacity))}.steps .step-error+.step-error:before{--tw-bg-opacity:1;background-color:hsl(var(--er) / var(--tw-bg-opacity))}.steps .step-error:after{--tw-bg-opacity:1;background-color:hsl(var(--er) / var(--tw-bg-opacity));--tw-text-opacity:1;color:hsl(var(--erc,var(--nc)) / var(--tw-text-opacity))}.tab:hover{--tw-text-opacity:1}.tab.tab-active{border-color:hsl(var(--bc) / var(--tw-border-opacity));--tw-border-opacity:1;--tw-text-opacity:1}.tab:focus{outline:2px solid transparent;outline-offset:2px}.tab:focus-visible{outline:2px solid currentColor;outline-offset:-3px}.tab:focus-visible.tab-lifted{border-bottom-right-radius:var(--tab-radius,.5rem);border-bottom-left-radius:var(--tab-radius,.5rem)}.table :where(th,td){white-space:nowrap;padding:1rem;vertical-align:middle}.table tr.active td,.table tr.active th,.table tr.active:nth-child(even) td,.table tr.active:nth-child(even) th{--tw-bg-opacity:1;background-color:hsl(var(--b3,var(--b2)) / var(--tw-bg-opacity))}.table tr.hover:hover td,.table tr.hover:hover th,.table tr.hover:nth-child(even):hover td,.table tr.hover:nth-child(even):hover th{--tw-bg-opacity:1;background-color:hsl(var(--b3,var(--b2)) / var(--tw-bg-opacity))}.table:where(:not(.table-zebra)) :where(thead,tbody,tfoot) :where(tr:not(:last-child):where(th,td)){border-bottom-width:1px;--tw-border-opacity:1;border-color:hsl(var(--b2,var(--b1)) / var(--tw-border-opacity))}.table :where(thead,tfoot) :where(th,td){--tw-bg-opacity:1;background-color:hsl(var(--b2,var(--b1)) / var(--tw-bg-opacity));font-size:.75rem;line-height:1rem;font-weight:700;text-transform:uppercase}.table :where(tbodyth,tbodytd){--tw-bg-opacity:1;background-color:hsl(var(--b1) / var(--tw-bg-opacity))}:where(.table:first-child) :where(:first-child) :where(th,td):first-child{border-top-left-radius:.5rem}:where(.table:first-child) :where(:first-child) :where(th,td):last-child{border-top-right-radius:.5rem}:where(.table:last-child) :where(:last-child) :where(th,td):first-child{border-bottom-left-radius:.5rem}:where(.table:last-child) :where(:last-child) :where(th,td):last-child{border-bottom-right-radius:.5rem}.textarea:focus{outline:2px solid hsla(var(--bc) / .2);outline-offset:2px}.textarea-disabled,.textarea[disabled]{cursor:not-allowed;--tw-border-opacity:1;border-color:hsl(var(--b2,var(--b1)) / var(--tw-border-opacity));--tw-bg-opacity:1;background-color:hsl(var(--b2,var(--b1)) / var(--tw-bg-opacity));--tw-text-opacity:0.2}.textarea-disabled::-moz-placeholder,.textarea[disabled]::-moz-placeholder{color:hsl(var(--bc) / var(--tw-placeholder-opacity));--tw-placeholder-opacity:0.2}.textarea-disabled::placeholder,.textarea[disabled]::placeholder{color:hsl(var(--bc) / var(--tw-placeholder-opacity));--tw-placeholder-opacity:0.2}@keyframes toast-pop{0%{transform:scale(.9);opacity:0}100%{transform:scale(1);opacity:1}}.toggle:focus-visible{outline:2px solid hsl(var(--bc));outline-offset:2px}.toggle:checked,.toggle[aria-checked=true],.toggle[checked=true]{--chkbg:hsl(var(--bc));--tw-border-opacity:1;--tw-bg-opacity:1;box-shadow:var(--handleoffset) 0 0 2px hsl(var(--b1)) inset,0 0 0 2px hsl(var(--b1)) inset}[dir=rtl] .toggle:checked,[dir=rtl] .toggle[aria-checked=true],[dir=rtl] .toggle[checked=true]{box-shadow:calc(var(--handleoffset) * 1) 0 0 2px hsl(var(--b1)) inset,0 0 0 2px hsl(var(--b1)) inset}.toggle:indeterminate{--chkbg:hsl(var(--bc));--tw-border-opacity:1;--tw-bg-opacity:1;box-shadow:calc(var(--handleoffset)/ 2) 0 0 2px hsl(var(--b1)) inset,calc(var(--handleoffset)/ -2) 0 0 2px hsl(var(--b1)) inset,0 0 0 2px hsl(var(--b1)) inset}[dir=rtl] .toggle:indeterminate{box-shadow:calc(var(--handleoffset)/ 2) 0 0 2px hsl(var(--b1)) inset,calc(var(--handleoffset)/ -2) 0 0 2px hsl(var(--b1)) inset,0 0 0 2px hsl(var(--b1)) inset}.toggle:disabled{cursor:not-allowed;border-color:transparent;background-color:hsl(var(--bc) / var(--tw-bg-opacity));--tw-bg-opacity:0.2}.artboard.phone{width:320px}.artboard.phone-1.artboard-horizontal,.artboard.phone-1.horizontal{width:568px;height:320px}.artboard.phone-2.artboard-horizontal,.artboard.phone-2.horizontal{width:667px;height:375px}.artboard.phone-3.artboard-horizontal,.artboard.phone-3.horizontal{width:736px;height:414px}.artboard.phone-4.artboard-horizontal,.artboard.phone-4.horizontal{width:812px;height:375px}.artboard.phone-5.artboard-horizontal,.artboard.phone-5.horizontal{width:896px;height:414px}.artboard.phone-6.artboard-horizontal,.artboard.phone-6.horizontal{width:1024px;height:320px}.btm-nav-xs>:where(.active){border-top-width:1px}.btm-nav-sm>:where(.active){border-top-width:2px}.btm-nav-md>:where(.active){border-top-width:2px}.btm-nav-lg>:where(.active){border-top-width:4px}.indicator :where(.indicator-item){right:0;left:auto;top:0;bottom:auto;--tw-translate-x:50%;--tw-translate-y:-50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.indicator :where(.indicator-item.indicator-start){right:auto;left:0;--tw-translate-x:-50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.indicator :where(.indicator-item.indicator-center){right:50%;left:50%;--tw-translate-x:-50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.indicator :where(.indicator-item.indicator-end){right:0;left:auto;--tw-translate-x:50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.indicator :where(.indicator-item.indicator-bottom){top:auto;bottom:0;--tw-translate-y:50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.indicator :where(.indicator-item.indicator-middle){top:50%;bottom:50%;--tw-translate-y:-50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.indicator :where(.indicator-item.indicator-top){top:0;bottom:auto;--tw-translate-y:-50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.steps-horizontal .step{display:grid;grid-template-columns:repeat(1,minmax(0,1fr));grid-template-rows:repeat(2,minmax(0,1fr));place-items:center;text-align:center}.steps-vertical .step{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));grid-template-rows:repeat(1,minmax(0,1fr))}.avatar.online:before{content:"";position:absolute;z-index:10;display:block;border-radius:9999px;--tw-bg-opacity:1;background-color:hsl(var(--su) / var(--tw-bg-opacity));width:15%;height:15%;top:7%;right:7%;box-shadow:0 0 0 2px hsl(var(--b1))}.avatar.offline:before{content:"";position:absolute;z-index:10;display:block;border-radius:9999px;--tw-bg-opacity:1;background-color:hsl(var(--b3,var(--b2)) / var(--tw-bg-opacity));width:15%;height:15%;top:7%;right:7%;box-shadow:0 0 0 2px hsl(var(--b1))}.btn-group .btn:not(:first-child):not(:last-child),.btn-group.btn-group-horizontal .btn:not(:first-child):not(:last-child){border-top-left-radius:0;border-top-right-radius:0;border-bottom-left-radius:0;border-bottom-right-radius:0}.btn-group .btn:first-child:not(:last-child),.btn-group.btn-group-horizontal .btn:first-child:not(:last-child){margin-left:-1px;margin-top:0;border-top-left-radius:var(--rounded-btn,.5rem);border-top-right-radius:0;border-bottom-left-radius:var(--rounded-btn,.5rem);border-bottom-right-radius:0}.btn-group .btn:last-child:not(:first-child),.btn-group.btn-group-horizontal .btn:last-child:not(:first-child){border-top-left-radius:0;border-top-right-radius:var(--rounded-btn,.5rem);border-bottom-left-radius:0;border-bottom-right-radius:var(--rounded-btn,.5rem)}.btn-group.btn-group-vertical .btn:first-child:not(:last-child){margin-left:0;margin-top:-1px;border-top-left-radius:var(--rounded-btn,.5rem);border-top-right-radius:var(--rounded-btn,.5rem);border-bottom-left-radius:0;border-bottom-right-radius:0}.btn-group.btn-group-vertical .btn:last-child:not(:first-child){border-top-left-radius:0;border-top-right-radius:0;border-bottom-left-radius:var(--rounded-btn,.5rem);border-bottom-right-radius:var(--rounded-btn,.5rem)}.steps-horizontal .step{grid-template-rows:40px 1fr;grid-template-columns:auto;min-width:4rem}.steps-horizontal .step:before{height:.5rem;width:100%;--tw-translate-y:0px;--tw-translate-x:0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));content:"";margin-left:-100%}.steps-vertical .step{gap:.5rem;grid-template-columns:40px 1fr;grid-template-rows:auto;min-height:4rem;justify-items:start}.steps-vertical .step:before{height:100%;width:.5rem;--tw-translate-y:-50%;--tw-translate-x:-50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));margin-left:50%}.hover\:bg-white:hover{--tw-bg-opacity:1!important;background-color:rgb(255 255 255 / var(--tw-bg-opacity))!important}.hover\:text-red:hover{--tw-text-opacity:1!important;color:rgb(246 41 88 / var(--tw-text-opacity))!important}@media (min-width:640px){.sm\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))!important}.sm\:space-y-12>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0!important;margin-top:calc(3rem * calc(1 - var(--tw-space-y-reverse)))!important;margin-bottom:calc(3rem * var(--tw-space-y-reverse))!important}}@media (min-width:768px){.md\:gap-x-6{-moz-column-gap:1.5rem!important;column-gap:1.5rem!important}}@media (min-width:1024px){.lg\:mb-0{margin-bottom:0!important}.lg\:h-24{height:6rem!important}.lg\:h-auto{height:auto!important}.lg\:w-24{width:6rem!important}.lg\:max-w-5xl{max-width:64rem!important}.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))!important}.lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))!important}.lg\:flex-nowrap{flex-wrap:nowrap!important}.lg\:gap-x-8{-moz-column-gap:2rem!important;column-gap:2rem!important}.lg\:gap-y-12{row-gap:3rem!important}.lg\:gap-y-0{row-gap:0!important}.lg\:gap-y-8{row-gap:2rem!important}.lg\:gap-y-1{row-gap:.25rem!important}.lg\:p-10{padding:2.5rem!important}.lg\:text-sm{font-size:.875rem!important;line-height:1.25rem!important}.lg\:text-2{font-size:2rem!important}}@media (min-width:1280px){.xl\:mb-0{margin-bottom:0!important}.xl\:grid-cols-5{grid-template-columns:repeat(5,minmax(0,1fr))!important}.xl\:flex-nowrap{flex-wrap:nowrap!important}.xl\:text-5xl{font-size:3rem!important;line-height:1!important}}/*! -======= - */@font-face{font-family:'Font Awesome 5 Pro';font-style:normal;font-weight:900;font-display:auto;src:url(webfonts/fa-solid-900.eot);src:url(webfonts/fa-solid-900.eot?#iefix) format('embedded-opentype'),url(webfonts/fa-solid-900.woff2) format('woff2'),url(webfonts/fa-solid-900.woff) format('woff'),url(webfonts/fa-solid-900.ttf) format('truetype'),url(webfonts/fa-solid-900.svg#fontawesome) format('svg')}.fa,.fas{font-family:'Font Awesome 5 Pro';font-weight:900}@tailwind base;@tailwind utilities;@tailwind components;*,::after,::before{box-sizing:border-box;border-width:0;border-style:solid;border-color:currentColor}::after,::before{--tw-content:''}html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji"}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;font-weight:inherit;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}:root,[data-theme]{background-color:hsla(var(--b1) / var(--tw-bg-opacity,1));color:hsla(var(--bc) / var(--tw-text-opacity,1))}html{-webkit-tap-highlight-color:transparent}:root{color-scheme:light;--pf:259 94% 41%;--sf:314 100% 38%;--af:174 60% 41%;--nf:219 14% 22%;--in:198 93% 60%;--su:158 64% 52%;--wa:43 96% 56%;--er:0 91% 71%;--inc:198 100% 12%;--suc:158 100% 10%;--wac:43 100% 11%;--erc:0 100% 14%;--rounded-box:1rem;--rounded-btn:0.5rem;--rounded-badge:1.9rem;--animation-btn:0.25s;--animation-input:.2s;--btn-text-case:uppercase;--btn-focus-scale:0.95;--border-btn:1px;--tab-border:1px;--tab-radius:0.5rem;--p:259 94% 51%;--pc:0 0% 100%;--s:314 100% 47%;--sc:0 0% 100%;--a:174 60% 51%;--ac:175 44% 15%;--n:219 14% 28%;--nc:0 0% 100%;--b1:0 0% 100%;--b2:0 0% 95%;--b3:180 2% 90%;--bc:215 28% 17%}@media (prefers-color-scheme:dark){:root{color-scheme:dark;--pf:262 80% 40%;--sf:316 70% 40%;--af:175 70% 33%;--in:198 93% 60%;--su:158 64% 52%;--wa:43 96% 56%;--er:0 91% 71%;--inc:198 100% 12%;--suc:158 100% 10%;--wac:43 100% 11%;--erc:0 100% 14%;--rounded-box:1rem;--rounded-btn:0.5rem;--rounded-badge:1.9rem;--animation-btn:0.25s;--animation-input:.2s;--btn-text-case:uppercase;--btn-focus-scale:0.95;--border-btn:1px;--tab-border:1px;--tab-radius:0.5rem;--p:262 80% 50%;--pc:0 0% 100%;--s:316 70% 50%;--sc:0 0% 100%;--a:175 70% 41%;--ac:0 0% 100%;--n:218 18% 12%;--nf:223 17% 8%;--nc:220 13% 69%;--b1:220 18% 20%;--b2:220 17% 17%;--b3:219 18% 15%;--bc:220 13% 69%}}[data-theme=light]{color-scheme:light;--pf:259 94% 41%;--sf:314 100% 38%;--af:174 60% 41%;--nf:219 14% 22%;--in:198 93% 60%;--su:158 64% 52%;--wa:43 96% 56%;--er:0 91% 71%;--inc:198 100% 12%;--suc:158 100% 10%;--wac:43 100% 11%;--erc:0 100% 14%;--rounded-box:1rem;--rounded-btn:0.5rem;--rounded-badge:1.9rem;--animation-btn:0.25s;--animation-input:.2s;--btn-text-case:uppercase;--btn-focus-scale:0.95;--border-btn:1px;--tab-border:1px;--tab-radius:0.5rem;--p:259 94% 51%;--pc:0 0% 100%;--s:314 100% 47%;--sc:0 0% 100%;--a:174 60% 51%;--ac:175 44% 15%;--n:219 14% 28%;--nc:0 0% 100%;--b1:0 0% 100%;--b2:0 0% 95%;--b3:180 2% 90%;--bc:215 28% 17%}[data-theme=dark]{color-scheme:dark;--pf:262 80% 40%;--sf:316 70% 40%;--af:175 70% 33%;--in:198 93% 60%;--su:158 64% 52%;--wa:43 96% 56%;--er:0 91% 71%;--inc:198 100% 12%;--suc:158 100% 10%;--wac:43 100% 11%;--erc:0 100% 14%;--rounded-box:1rem;--rounded-btn:0.5rem;--rounded-badge:1.9rem;--animation-btn:0.25s;--animation-input:.2s;--btn-text-case:uppercase;--btn-focus-scale:0.95;--border-btn:1px;--tab-border:1px;--tab-radius:0.5rem;--p:262 80% 50%;--pc:0 0% 100%;--s:316 70% 50%;--sc:0 0% 100%;--a:175 70% 41%;--ac:0 0% 100%;--n:218 18% 12%;--nf:223 17% 8%;--nc:220 13% 69%;--b1:220 18% 20%;--b2:220 17% 17%;--b3:219 18% 15%;--bc:220 13% 69%}[data-theme=cupcake]{color-scheme:light;--pf:183 47% 47%;--sf:338 71% 62%;--af:39 84% 46%;--nf:280 46% 11%;--in:198 93% 60%;--su:158 64% 52%;--wa:43 96% 56%;--er:0 91% 71%;--pc:183 100% 12%;--sc:338 100% 16%;--ac:39 100% 12%;--nc:280 83% 83%;--inc:198 100% 12%;--suc:158 100% 10%;--wac:43 100% 11%;--erc:0 100% 14%;--rounded-box:1rem;--rounded-badge:1.9rem;--animation-btn:0.25s;--animation-input:.2s;--btn-text-case:uppercase;--btn-focus-scale:0.95;--border-btn:1px;--p:183 47% 59%;--s:338 71% 78%;--a:39 84% 58%;--n:280 46% 14%;--b1:24 33% 97%;--b2:27 22% 92%;--b3:22 14% 89%;--bc:280 46% 14%;--rounded-btn:1.9rem;--tab-border:2px;--tab-radius:.5rem}[data-theme=bumblebee]{color-scheme:light;--pf:41 74% 42%;--sf:50 94% 46%;--af:240 33% 11%;--nf:240 33% 11%;--b2:0 0% 90%;--b3:0 0% 81%;--in:198 93% 60%;--su:158 64% 52%;--wa:43 96% 56%;--er:0 91% 71%;--bc:0 0% 20%;--ac:240 60% 83%;--nc:240 60% 83%;--inc:198 100% 12%;--suc:158 100% 10%;--wac:43 100% 11%;--erc:0 100% 14%;--rounded-box:1rem;--rounded-btn:0.5rem;--rounded-badge:1.9rem;--animation-btn:0.25s;--animation-input:.2s;--btn-text-case:uppercase;--btn-focus-scale:0.95;--border-btn:1px;--tab-border:1px;--tab-radius:0.5rem;--p:41 74% 53%;--pc:240 33% 14%;--s:50 94% 58%;--sc:240 33% 14%;--a:240 33% 14%;--n:240 33% 14%;--b1:0 0% 100%}[data-theme=emerald]{color-scheme:light;--pf:141 50% 48%;--sf:219 96% 48%;--af:10 81% 45%;--nf:219 20% 20%;--b2:0 0% 90%;--b3:0 0% 81%;--in:198 93% 60%;--su:158 64% 52%;--wa:43 96% 56%;--er:0 91% 71%;--inc:198 100% 12%;--suc:158 100% 10%;--wac:43 100% 11%;--erc:0 100% 14%;--rounded-box:1rem;--rounded-btn:0.5rem;--rounded-badge:1.9rem;--btn-text-case:uppercase;--border-btn:1px;--tab-border:1px;--tab-radius:0.5rem;--p:141 50% 60%;--pc:151 28% 19%;--s:219 96% 60%;--sc:210 20% 98%;--a:10 81% 56%;--ac:210 20% 98%;--n:219 20% 25%;--nc:210 20% 98%;--b1:0 0% 100%;--bc:219 20% 25%;--animation-btn:0;--animation-input:0;--btn-focus-scale:1}[data-theme=corporate]{color-scheme:light;--pf:229 96% 51%;--sf:215 26% 47%;--af:154 49% 48%;--nf:233 27% 10%;--b2:0 0% 90%;--b3:0 0% 81%;--in:198 93% 60%;--su:158 64% 52%;--wa:43 96% 56%;--er:0 91% 71%;--pc:229 100% 93%;--sc:215 100% 12%;--ac:154 100% 12%;--inc:198 100% 12%;--suc:158 100% 10%;--wac:43 100% 11%;--erc:0 100% 14%;--btn-text-case:uppercase;--border-btn:1px;--tab-border:1px;--tab-radius:0.5rem;--p:229 96% 64%;--s:215 26% 59%;--a:154 49% 60%;--n:233 27% 13%;--nc:210 38% 95%;--b1:0 0% 100%;--bc:233 27% 13%;--rounded-box:0.25rem;--rounded-btn:.125rem;--rounded-badge:.125rem;--animation-btn:0;--animation-input:0;--btn-focus-scale:1}[data-theme=synthwave]{color-scheme:dark;--pf:321 70% 55%;--sf:197 87% 52%;--af:48 89% 46%;--nf:253 61% 15%;--b2:254 59% 23%;--b3:254 59% 21%;--pc:321 100% 14%;--sc:197 100% 13%;--ac:48 100% 11%;--rounded-box:1rem;--rounded-btn:0.5rem;--rounded-badge:1.9rem;--animation-btn:0.25s;--animation-input:.2s;--btn-text-case:uppercase;--btn-focus-scale:0.95;--border-btn:1px;--tab-border:1px;--tab-radius:0.5rem;--p:321 70% 69%;--s:197 87% 65%;--a:48 89% 57%;--n:253 61% 19%;--nc:260 60% 98%;--b1:254 59% 26%;--bc:260 60% 98%;--in:199 87% 64%;--inc:257 63% 17%;--su:168 74% 68%;--suc:257 63% 17%;--wa:48 89% 57%;--wac:257 63% 17%;--er:352 74% 57%;--erc:260 60% 98%}[data-theme=retro]{color-scheme:light;--pf:3 74% 61%;--sf:145 27% 58%;--af:49 67% 61%;--nf:42 17% 34%;--inc:221 100% 91%;--suc:142 100% 87%;--wac:32 100% 9%;--erc:0 100% 90%;--animation-btn:0.25s;--animation-input:.2s;--btn-text-case:uppercase;--btn-focus-scale:0.95;--border-btn:1px;--tab-border:1px;--tab-radius:0.5rem;--p:3 74% 76%;--pc:345 5% 15%;--s:145 27% 72%;--sc:345 5% 15%;--a:49 67% 76%;--ac:345 5% 15%;--n:42 17% 42%;--nc:45 47% 80%;--b1:45 47% 80%;--b2:45 37% 72%;--b3:42 36% 65%;--bc:345 5% 15%;--in:221 83% 53%;--su:142 76% 36%;--wa:32 95% 44%;--er:0 72% 51%;--rounded-box:0.4rem;--rounded-btn:0.4rem;--rounded-badge:0.4rem}[data-theme=cyberpunk]{color-scheme:light;--pf:345 100% 58%;--sf:195 80% 56%;--af:276 74% 57%;--nf:57 100% 10%;--b2:56 100% 45%;--b3:56 100% 41%;--in:198 93% 60%;--su:158 64% 52%;--wa:43 96% 56%;--er:0 91% 71%;--bc:56 100% 10%;--pc:345 100% 15%;--sc:195 100% 14%;--ac:276 100% 14%;--inc:198 100% 12%;--suc:158 100% 10%;--wac:43 100% 11%;--erc:0 100% 14%;--animation-btn:0.25s;--animation-input:.2s;--btn-text-case:uppercase;--btn-focus-scale:0.95;--border-btn:1px;--tab-border:1px;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;--p:345 100% 73%;--s:195 80% 70%;--a:276 74% 71%;--n:57 100% 13%;--nc:56 100% 50%;--b1:56 100% 50%;--rounded-box:0;--rounded-btn:0;--rounded-badge:0;--tab-radius:0}[data-theme=valentine]{color-scheme:light;--pf:353 74% 54%;--sf:254 86% 61%;--af:181 56% 56%;--nf:336 43% 38%;--b2:318 46% 80%;--b3:318 46% 72%;--pc:353 100% 13%;--sc:254 100% 15%;--ac:181 100% 14%;--inc:221 100% 91%;--suc:142 100% 87%;--wac:32 100% 9%;--erc:0 100% 90%;--rounded-box:1rem;--rounded-badge:1.9rem;--animation-btn:0.25s;--animation-input:.2s;--btn-text-case:uppercase;--btn-focus-scale:0.95;--border-btn:1px;--tab-border:1px;--tab-radius:0.5rem;--p:353 74% 67%;--s:254 86% 77%;--a:181 56% 70%;--n:336 43% 48%;--nc:318 46% 89%;--b1:318 46% 89%;--bc:344 38% 28%;--in:221 83% 53%;--su:142 76% 36%;--wa:32 95% 44%;--er:0 72% 51%;--rounded-btn:1.9rem}[data-theme=halloween]{color-scheme:dark;--pf:32 89% 42%;--sf:271 46% 34%;--af:91 100% 26%;--nf:180 4% 9%;--b2:0 0% 12%;--b3:0 0% 10%;--bc:0 0% 83%;--sc:271 100% 88%;--ac:91 100% 7%;--nc:180 5% 82%;--inc:221 100% 91%;--suc:142 100% 87%;--wac:32 100% 9%;--erc:0 100% 90%;--rounded-box:1rem;--rounded-btn:0.5rem;--rounded-badge:1.9rem;--animation-btn:0.25s;--animation-input:.2s;--btn-text-case:uppercase;--btn-focus-scale:0.95;--border-btn:1px;--tab-border:1px;--tab-radius:0.5rem;--p:32 89% 52%;--pc:180 7% 8%;--s:271 46% 42%;--a:91 100% 33%;--n:180 4% 11%;--b1:0 0% 13%;--in:221 83% 53%;--su:142 76% 36%;--wa:32 95% 44%;--er:0 72% 51%}[data-theme=garden]{color-scheme:light;--pf:139 16% 34%;--sf:97 37% 75%;--af:0 68% 75%;--nf:0 4% 28%;--b2:0 4% 82%;--b3:0 4% 74%;--in:198 93% 60%;--su:158 64% 52%;--wa:43 96% 56%;--er:0 91% 71%;--pc:139 100% 89%;--inc:198 100% 12%;--suc:158 100% 10%;--wac:43 100% 11%;--erc:0 100% 14%;--rounded-box:1rem;--rounded-btn:0.5rem;--rounded-badge:1.9rem;--animation-btn:0.25s;--animation-input:.2s;--btn-text-case:uppercase;--btn-focus-scale:0.95;--border-btn:1px;--tab-border:1px;--tab-radius:0.5rem;--p:139 16% 43%;--s:97 37% 93%;--sc:96 32% 15%;--a:0 68% 94%;--ac:0 22% 16%;--n:0 4% 35%;--nc:0 4% 91%;--b1:0 4% 91%;--bc:0 3% 6%}[data-theme=forest]{color-scheme:dark;--pf:141 72% 34%;--sf:141 75% 38%;--af:35 69% 42%;--nf:0 10% 5%;--b2:0 12% 7%;--b3:0 12% 7%;--in:198 93% 60%;--su:158 64% 52%;--wa:43 96% 56%;--er:0 91% 71%;--bc:0 12% 82%;--sc:141 100% 10%;--ac:35 100% 10%;--nc:0 7% 81%;--inc:198 100% 12%;--suc:158 100% 10%;--wac:43 100% 11%;--erc:0 100% 14%;--rounded-box:1rem;--rounded-badge:1.9rem;--animation-btn:0.25s;--animation-input:.2s;--btn-text-case:uppercase;--btn-focus-scale:0.95;--border-btn:1px;--tab-border:1px;--tab-radius:0.5rem;--p:141 72% 42%;--pc:141 100% 88%;--s:141 75% 48%;--a:35 69% 52%;--n:0 10% 6%;--b1:0 12% 8%;--rounded-btn:1.9rem}[data-theme=aqua]{color-scheme:dark;--pf:182 93% 40%;--sf:274 31% 45%;--af:47 100% 64%;--nf:205 54% 40%;--b2:219 53% 39%;--b3:219 53% 35%;--bc:219 100% 89%;--sc:274 100% 91%;--ac:47 100% 16%;--nc:205 100% 90%;--inc:221 100% 91%;--suc:142 100% 87%;--wac:32 100% 9%;--erc:0 100% 90%;--rounded-box:1rem;--rounded-btn:0.5rem;--rounded-badge:1.9rem;--animation-btn:0.25s;--animation-input:.2s;--btn-text-case:uppercase;--btn-focus-scale:0.95;--border-btn:1px;--tab-border:1px;--tab-radius:0.5rem;--p:182 93% 49%;--pc:181 100% 17%;--s:274 31% 57%;--a:47 100% 80%;--n:205 54% 50%;--b1:219 53% 43%;--in:221 83% 53%;--su:142 76% 36%;--wa:32 95% 44%;--er:0 72% 51%}[data-theme=lofi]{color-scheme:light;--pf:0 0% 4%;--sf:0 2% 8%;--af:0 0% 12%;--nf:0 0% 0%;--btn-text-case:uppercase;--border-btn:1px;--tab-border:1px;--p:0 0% 5%;--pc:0 0% 100%;--s:0 2% 10%;--sc:0 0% 100%;--a:0 0% 15%;--ac:0 0% 100%;--n:0 0% 0%;--nc:0 0% 100%;--b1:0 0% 100%;--b2:0 0% 95%;--b3:0 2% 90%;--bc:0 0% 0%;--in:212 100% 48%;--inc:0 0% 100%;--su:137 72% 46%;--suc:0 0% 100%;--wa:5 100% 66%;--wac:0 0% 100%;--er:325 78% 49%;--erc:0 0% 100%;--rounded-box:0.25rem;--rounded-btn:0.125rem;--rounded-badge:0.125rem;--animation-btn:0;--animation-input:0;--btn-focus-scale:1;--tab-radius:0}[data-theme=pastel]{color-scheme:light;--pf:284 22% 64%;--sf:352 70% 70%;--af:158 55% 65%;--nf:199 44% 49%;--in:198 93% 60%;--su:158 64% 52%;--wa:43 96% 56%;--er:0 91% 71%;--bc:0 0% 20%;--pc:284 59% 16%;--sc:352 100% 18%;--ac:158 100% 16%;--nc:199 100% 12%;--inc:198 100% 12%;--suc:158 100% 10%;--wac:43 100% 11%;--erc:0 100% 14%;--rounded-box:1rem;--rounded-badge:1.9rem;--animation-btn:0.25s;--animation-input:.2s;--btn-text-case:uppercase;--btn-focus-scale:0.95;--border-btn:1px;--tab-border:1px;--tab-radius:0.5rem;--p:284 22% 80%;--s:352 70% 88%;--a:158 55% 81%;--n:199 44% 61%;--b1:0 0% 100%;--b2:210 20% 98%;--b3:216 12% 84%;--rounded-btn:1.9rem}[data-theme=fantasy]{color-scheme:light;--pf:296 83% 20%;--sf:200 100% 30%;--af:31 94% 41%;--nf:215 28% 13%;--b2:0 0% 90%;--b3:0 0% 81%;--in:198 93% 60%;--su:158 64% 52%;--wa:43 96% 56%;--er:0 91% 71%;--pc:296 100% 85%;--sc:200 100% 87%;--ac:31 100% 10%;--nc:215 62% 83%;--inc:198 100% 12%;--suc:158 100% 10%;--wac:43 100% 11%;--erc:0 100% 14%;--rounded-box:1rem;--rounded-btn:0.5rem;--rounded-badge:1.9rem;--animation-btn:0.25s;--animation-input:.2s;--btn-text-case:uppercase;--btn-focus-scale:0.95;--border-btn:1px;--tab-border:1px;--tab-radius:0.5rem;--p:296 83% 25%;--s:200 100% 37%;--a:31 94% 51%;--n:215 28% 17%;--b1:0 0% 100%;--bc:215 28% 17%}[data-theme=wireframe]{color-scheme:light;--pf:0 0% 58%;--sf:0 0% 58%;--af:0 0% 58%;--nf:0 0% 74%;--bc:0 0% 20%;--pc:0 0% 14%;--sc:0 0% 14%;--ac:0 0% 14%;--nc:0 0% 18%;--inc:240 100% 90%;--suc:120 100% 85%;--wac:60 100% 10%;--erc:0 100% 90%;--animation-btn:0.25s;--animation-input:.2s;--btn-text-case:uppercase;--btn-focus-scale:0.95;--border-btn:1px;--tab-border:1px;font-family:Chalkboard,comic sans ms,sanssecondaryerif;--p:0 0% 72%;--s:0 0% 72%;--a:0 0% 72%;--n:0 0% 92%;--b1:0 0% 100%;--b2:0 0% 93%;--b3:0 0% 87%;--in:240 100% 50%;--su:120 100% 25%;--wa:60 30% 50%;--er:0 100% 50%;--rounded-box:0.2rem;--rounded-btn:0.2rem;--rounded-badge:0.2rem;--tab-radius:0.2rem}[data-theme=black]{color-scheme:dark;--pf:0 2% 16%;--sf:0 2% 16%;--af:0 2% 16%;--bc:0 0% 80%;--pc:0 5% 84%;--sc:0 5% 84%;--ac:0 5% 84%;--nc:0 3% 83%;--inc:240 100% 90%;--suc:120 100% 85%;--wac:60 100% 10%;--erc:0 100% 90%;--border-btn:1px;--tab-border:1px;--p:0 2% 20%;--s:0 2% 20%;--a:0 2% 20%;--b1:0 0% 0%;--b2:0 0% 5%;--b3:0 2% 10%;--n:0 1% 15%;--nf:0 2% 20%;--in:240 100% 50%;--su:120 100% 25%;--wa:60 100% 50%;--er:0 100% 50%;--rounded-box:0;--rounded-btn:0;--rounded-badge:0;--animation-btn:0;--animation-input:0;--btn-text-case:lowercase;--btn-focus-scale:1;--tab-radius:0}[data-theme=luxury]{color-scheme:dark;--pf:0 0% 80%;--sf:218 54% 14%;--af:319 22% 21%;--nf:270 4% 7%;--pc:0 0% 20%;--sc:218 100% 84%;--ac:319 85% 85%;--inc:202 100% 14%;--suc:89 100% 10%;--wac:54 100% 13%;--erc:0 100% 14%;--rounded-box:1rem;--rounded-btn:0.5rem;--rounded-badge:1.9rem;--animation-btn:0.25s;--animation-input:.2s;--btn-text-case:uppercase;--btn-focus-scale:0.95;--border-btn:1px;--tab-border:1px;--tab-radius:0.5rem;--p:0 0% 100%;--s:218 54% 18%;--a:319 22% 26%;--n:270 4% 9%;--nc:37 67% 58%;--b1:240 10% 4%;--b2:270 4% 9%;--b3:270 2% 18%;--bc:37 67% 58%;--in:202 100% 70%;--su:89 62% 52%;--wa:54 69% 64%;--er:0 100% 72%}[data-theme=dracula]{color-scheme:dark;--pf:326 100% 59%;--sf:265 89% 62%;--af:31 100% 57%;--nf:230 15% 24%;--b2:231 15% 17%;--b3:231 15% 15%;--pc:326 100% 15%;--sc:265 100% 16%;--ac:31 100% 14%;--nc:230 71% 86%;--inc:191 100% 15%;--suc:135 100% 13%;--wac:65 100% 15%;--erc:0 100% 93%;--rounded-box:1rem;--rounded-btn:0.5rem;--rounded-badge:1.9rem;--animation-btn:0.25s;--animation-input:.2s;--btn-text-case:uppercase;--btn-focus-scale:0.95;--border-btn:1px;--tab-border:1px;--tab-radius:0.5rem;--p:326 100% 74%;--s:265 89% 78%;--a:31 100% 71%;--n:230 15% 30%;--b1:231 15% 18%;--bc:60 30% 96%;--in:191 97% 77%;--su:135 94% 65%;--wa:65 92% 76%;--er:0 100% 67%}[data-theme=cmyk]{color-scheme:light;--pf:203 83% 48%;--sf:335 78% 48%;--af:56 100% 48%;--nf:0 0% 8%;--b2:0 0% 90%;--b3:0 0% 81%;--bc:0 0% 20%;--pc:203 100% 12%;--sc:335 100% 92%;--ac:56 100% 12%;--nc:0 0% 82%;--inc:192 100% 10%;--suc:291 100% 88%;--wac:25 100% 11%;--erc:4 100% 91%;--rounded-box:1rem;--rounded-btn:0.5rem;--rounded-badge:1.9rem;--animation-btn:0.25s;--animation-input:.2s;--btn-text-case:uppercase;--btn-focus-scale:0.95;--border-btn:1px;--tab-border:1px;--tab-radius:0.5rem;--p:203 83% 60%;--s:335 78% 60%;--a:56 100% 60%;--n:0 0% 10%;--b1:0 0% 100%;--in:192 48% 52%;--su:291 48% 38%;--wa:25 85% 57%;--er:4 81% 56%}[data-theme=autumn]{color-scheme:light;--pf:344 96% 22%;--sf:0 63% 47%;--af:27 56% 50%;--nf:22 17% 35%;--b2:0 0% 85%;--b3:0 0% 77%;--bc:0 0% 19%;--pc:344 100% 86%;--sc:0 100% 92%;--ac:27 100% 13%;--nc:22 100% 89%;--inc:187 100% 10%;--suc:165 100% 9%;--wac:30 100% 10%;--erc:354 100% 90%;--rounded-box:1rem;--rounded-btn:0.5rem;--rounded-badge:1.9rem;--animation-btn:0.25s;--animation-input:.2s;--btn-text-case:uppercase;--btn-focus-scale:0.95;--border-btn:1px;--tab-border:1px;--tab-radius:0.5rem;--p:344 96% 28%;--s:0 63% 58%;--a:27 56% 63%;--n:22 17% 44%;--b1:0 0% 95%;--in:187 48% 50%;--su:165 34% 43%;--wa:30 84% 50%;--er:354 79% 49%}[data-theme=business]{color-scheme:dark;--pf:210 64% 24%;--sf:200 13% 44%;--af:13 80% 48%;--nf:213 14% 13%;--b2:0 0% 11%;--b3:0 0% 10%;--bc:0 0% 83%;--pc:210 100% 86%;--sc:200 100% 11%;--ac:13 100% 12%;--nc:213 28% 83%;--inc:199 100% 88%;--suc:144 100% 11%;--wac:39 100% 12%;--erc:6 100% 89%;--animation-btn:0.25s;--animation-input:.2s;--btn-text-case:uppercase;--btn-focus-scale:0.95;--border-btn:1px;--tab-border:1px;--tab-radius:0.5rem;--p:210 64% 31%;--s:200 13% 55%;--a:13 80% 60%;--n:213 14% 16%;--b1:0 0% 13%;--in:199 100% 42%;--su:144 31% 56%;--wa:39 64% 60%;--er:6 56% 43%;--rounded-box:0.25rem;--rounded-btn:.125rem;--rounded-badge:.125rem}[data-theme=acid]{color-scheme:light;--pf:303 100% 40%;--sf:27 100% 40%;--af:72 98% 40%;--nf:238 43% 14%;--b2:0 0% 88%;--b3:0 0% 79%;--bc:0 0% 20%;--pc:303 100% 90%;--sc:27 100% 10%;--ac:72 100% 10%;--nc:238 99% 83%;--inc:210 100% 12%;--suc:149 100% 12%;--wac:53 100% 11%;--erc:1 100% 89%;--animation-btn:0.25s;--animation-input:.2s;--btn-text-case:uppercase;--btn-focus-scale:0.95;--border-btn:1px;--tab-border:1px;--tab-radius:0.5rem;--p:303 100% 50%;--s:27 100% 50%;--a:72 98% 50%;--n:238 43% 17%;--b1:0 0% 98%;--in:210 92% 58%;--su:149 50% 58%;--wa:53 93% 57%;--er:1 100% 45%;--rounded-box:1.25rem;--rounded-btn:1rem;--rounded-badge:1rem}[data-theme=lemonade]{color-scheme:light;--pf:89 96% 24%;--sf:60 81% 44%;--af:63 80% 71%;--nf:238 43% 14%;--b2:0 0% 90%;--b3:0 0% 81%;--bc:0 0% 20%;--pc:89 100% 86%;--sc:60 100% 11%;--ac:63 100% 18%;--nc:238 99% 83%;--inc:192 79% 17%;--suc:74 100% 16%;--wac:50 100% 15%;--erc:1 100% 17%;--rounded-box:1rem;--rounded-btn:0.5rem;--rounded-badge:1.9rem;--animation-btn:0.25s;--animation-input:.2s;--btn-text-case:uppercase;--btn-focus-scale:0.95;--border-btn:1px;--tab-border:1px;--tab-radius:0.5rem;--p:89 96% 31%;--s:60 81% 55%;--a:63 80% 88%;--n:238 43% 17%;--b1:0 0% 100%;--in:192 39% 85%;--su:74 76% 79%;--wa:50 87% 75%;--er:1 70% 83%}[data-theme=night]{color-scheme:dark;--pf:198 93% 48%;--sf:234 89% 59%;--af:329 86% 56%;--b2:222 47% 10%;--b3:222 47% 9%;--bc:222 66% 82%;--pc:198 100% 12%;--sc:234 100% 15%;--ac:329 100% 14%;--nc:217 76% 83%;--inc:198 100% 10%;--suc:172 100% 10%;--wac:41 100% 13%;--erc:351 100% 14%;--rounded-box:1rem;--rounded-btn:0.5rem;--rounded-badge:1.9rem;--animation-btn:0.25s;--animation-input:.2s;--btn-text-case:uppercase;--btn-focus-scale:0.95;--border-btn:1px;--tab-border:1px;--tab-radius:0.5rem;--p:198 93% 60%;--s:234 89% 74%;--a:329 86% 70%;--n:217 33% 17%;--nf:217 30% 22%;--b1:222 47% 11%;--in:198 90% 48%;--su:172 66% 50%;--wa:41 88% 64%;--er:351 95% 71%}[data-theme=coffee]{color-scheme:dark;--pf:30 67% 46%;--sf:182 25% 16%;--af:194 74% 20%;--nf:300 20% 5%;--b2:306 19% 10%;--b3:306 19% 9%;--pc:30 100% 12%;--sc:182 67% 84%;--ac:194 100% 85%;--nc:300 14% 81%;--inc:171 100% 13%;--suc:93 100% 12%;--wac:43 100% 14%;--erc:10 100% 15%;--rounded-box:1rem;--rounded-btn:0.5rem;--rounded-badge:1.9rem;--animation-btn:0.25s;--animation-input:.2s;--btn-text-case:uppercase;--btn-focus-scale:0.95;--border-btn:1px;--tab-border:1px;--tab-radius:0.5rem;--p:30 67% 58%;--s:182 25% 20%;--a:194 74% 25%;--n:300 20% 6%;--b1:306 19% 11%;--bc:37 8% 42%;--in:171 37% 67%;--su:93 25% 62%;--wa:43 100% 69%;--er:10 95% 75%}[data-theme=winter]{color-scheme:light;--pf:212 100% 41%;--sf:247 47% 35%;--af:310 49% 42%;--nf:217 92% 8%;--pc:212 100% 90%;--sc:247 100% 89%;--ac:310 100% 90%;--nc:217 100% 82%;--inc:192 100% 16%;--suc:182 100% 13%;--wac:32 100% 17%;--erc:0 100% 14%;--rounded-box:1rem;--rounded-btn:0.5rem;--rounded-badge:1.9rem;--animation-btn:0.25s;--animation-input:.2s;--btn-text-case:uppercase;--btn-focus-scale:0.95;--border-btn:1px;--tab-border:1px;--tab-radius:0.5rem;--p:212 100% 51%;--s:247 47% 43%;--a:310 49% 52%;--n:217 92% 10%;--b1:0 0% 100%;--b2:217 100% 97%;--b3:219 44% 92%;--bc:214 30% 32%;--in:192 93% 78%;--su:182 47% 66%;--wa:32 62% 84%;--er:0 63% 72%}*,::after,::before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-scroll-snap-strictness:proximity;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgb(59 130 246 / 0.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000}::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-scroll-snap-strictness:proximity;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgb(59 130 246 / 0.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000}.visible{visibility:visible!important}.invisible{visibility:hidden!important}.collapse{visibility:collapse!important}.static{position:static!important}.fixed{position:fixed!important}.absolute{position:absolute!important}.relative{position:relative!important}.sticky{position:sticky!important}.inset-0{top:0!important;right:0!important;bottom:0!important;left:0!important}.top-0\.5{top:.125rem!important}.left-0{left:0!important}.top-0{top:0!important}.m-0{margin:0!important}.mx-auto{margin-left:auto!important;margin-right:auto!important}.my-8{margin-top:2rem!important;margin-bottom:2rem!important}.mb-1{margin-bottom:.25rem!important}.mt-12{margin-top:3rem!important}.mr-10{margin-right:2.5rem!important}.mb-4{margin-bottom:1rem!important}.mb-0{margin-bottom:0!important}.mt-0{margin-top:0!important}.mr-2\.5{margin-right:.625rem!important}.mr-2{margin-right:.5rem!important}.ml-2{margin-left:.5rem!important}.mb-7{margin-bottom:1.75rem!important}.mt-3{margin-top:.75rem!important}.mb-2{margin-bottom:.5rem!important}.mb-12{margin-bottom:3rem!important}.mr-4{margin-right:1rem!important}.ml-auto{margin-left:auto!important}.block{display:block!important}.inline-block{display:inline-block!important}.inline{display:inline!important}.flex{display:flex!important}.inline-flex{display:inline-flex!important}.table{display:table!important}.grid{display:grid!important}.contents{display:contents!important}.hidden{display:none!important}.h-20{height:5rem!important}.h-64{height:16rem!important}.h-12{height:3rem!important}.h-56{height:14rem!important}.h-full{height:100%!important}.h-16{height:4rem!important}.h-screen{height:100vh!important}.w-20{width:5rem!important}.w-64{width:16rem!important}.w-16{width:4rem!important}.w-full{width:100%!important}.max-w-7xl{max-width:80rem!important}.max-w-full{max-width:100%!important}.max-w-md{max-width:28rem!important}.flex-shrink-0{flex-shrink:0!important}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.resize{resize:both!important}.list-disc{list-style-type:disc!important}.list-decimal{list-style-type:decimal!important}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))!important}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))!important}.flex-col{flex-direction:column!important}.flex-wrap{flex-wrap:wrap!important}.items-center{align-items:center!important}.justify-start{justify-content:flex-start!important}.justify-center{justify-content:center!important}.gap-x-4{-moz-column-gap:1rem!important;column-gap:1rem!important}.gap-y-8{row-gap:2rem!important}.gap-x-6{-moz-column-gap:1.5rem!important;column-gap:1.5rem!important}.gap-y-6{row-gap:1.5rem!important}.gap-x-7{-moz-column-gap:1.75rem!important;column-gap:1.75rem!important}.gap-y-7{row-gap:1.75rem!important}.gap-y-4{row-gap:1rem!important}.gap-y-1{row-gap:.25rem!important}.space-y-8>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0!important;margin-top:calc(2rem * calc(1 - var(--tw-space-y-reverse)))!important;margin-bottom:calc(2rem * var(--tw-space-y-reverse))!important}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0!important;margin-top:calc(1rem * calc(1 - var(--tw-space-y-reverse)))!important;margin-bottom:calc(1rem * var(--tw-space-y-reverse))!important}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0!important;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)))!important;margin-bottom:calc(.5rem * var(--tw-space-y-reverse))!important}.overflow-hidden{overflow:hidden!important}.truncate{overflow:hidden!important;text-overflow:ellipsis!important;white-space:nowrap!important}.rounded-full{border-radius:9999px!important}.rounded-lg{border-radius:.5rem!important}.rounded-5{border-radius:5px!important}.rounded{border-radius:.25rem!important}.border{border-width:1px!important}.border-t{border-top-width:1px!important}.border-solid{border-style:solid!important}.border-none{border-style:none!important}.border-red{--tw-border-opacity:1!important;border-color:rgb(246 41 88 / var(--tw-border-opacity))!important}.border-gray{--tw-border-opacity:1!important;border-color:rgb(229 230 240 / var(--tw-border-opacity))!important}.bg-white{--tw-bg-opacity:1!important;background-color:rgb(255 255 255 / var(--tw-bg-opacity))!important}.bg-red{--tw-bg-opacity:1!important;background-color:rgb(246 41 88 / var(--tw-bg-opacity))!important}.bg-purple-new{--tw-bg-opacity:1!important;background-color:rgb(65 49 133 / var(--tw-bg-opacity))!important}.bg-purple-lightest{--tw-bg-opacity:1!important;background-color:rgb(232 232 249 / var(--tw-bg-opacity))!important}.bg-purple-regular{--tw-bg-opacity:1!important;background-color:rgb(120 125 242 / var(--tw-bg-opacity))!important}.bg-purple-tint{--tw-bg-opacity:1!important;background-color:rgb(216 213 238 / var(--tw-bg-opacity))!important}.p-8{padding:2rem!important}.p-0{padding:0!important}.p-4{padding:1rem!important}.py-12{padding-top:3rem!important;padding-bottom:3rem!important}.px-4{padding-left:1rem!important;padding-right:1rem!important}.px-7{padding-left:1.75rem!important;padding-right:1.75rem!important}.py-9{padding-top:2.25rem!important;padding-bottom:2.25rem!important}.py-3\.5{padding-top:.875rem!important;padding-bottom:.875rem!important}.py-3{padding-top:.75rem!important;padding-bottom:.75rem!important}.px-6{padding-left:1.5rem!important;padding-right:1.5rem!important}.py-4{padding-top:1rem!important;padding-bottom:1rem!important}.px-3{padding-left:.75rem!important;padding-right:.75rem!important}.py-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.pb-7{padding-bottom:1.75rem!important}.pt-5{padding-top:1.25rem!important}.pl-5{padding-left:1.25rem!important}.pr-6{padding-right:1.5rem!important}.pt-0{padding-top:0!important}.pt-6{padding-top:1.5rem!important}.pl-7{padding-left:1.75rem!important}.pt-4{padding-top:1rem!important}.pb-2{padding-bottom:.5rem!important}.text-center{text-align:center!important}.text-xs{font-size:.75rem!important;line-height:1rem!important}.text-2{font-size:2rem!important}.text-base{font-size:1rem!important;line-height:1.5rem!important}.text-3xl{font-size:1.875rem!important;line-height:2.25rem!important}.text-4xl{font-size:2.25rem!important;line-height:2.5rem!important}.text-2xl{font-size:1.5rem!important;line-height:2rem!important}.text-sm{font-size:.875rem!important;line-height:1.25rem!important}.text-7xl{font-size:4.5rem!important;line-height:1!important}.text-1\.75{font-size:1.75rem!important}.text-lg{font-size:1.125rem!important;line-height:1.75rem!important}.text-xl{font-size:1.25rem!important;line-height:1.75rem!important}.font-medium{font-weight:500!important}.font-bold{font-weight:700!important}.font-normal{font-weight:400!important}.font-semibold{font-weight:600!important}.uppercase{text-transform:uppercase!important}.lowercase{text-transform:lowercase!important}.capitalize{text-transform:capitalize!important}.italic{font-style:italic!important}.ordinal{--tw-ordinal:ordinal!important;font-variant-numeric:var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure) var(--tw-numeric-spacing) var(--tw-numeric-fraction)!important}.leading-snug{line-height:1.375!important}.leading-none{line-height:1!important}.tracking-wider{letter-spacing:.05em!important}.text-indigo-600{--tw-text-opacity:1!important;color:rgb(79 70 229 / var(--tw-text-opacity))!important}.text-red{--tw-text-opacity:1!important;color:rgb(246 41 88 / var(--tw-text-opacity))!important}.text-purple-dark{--tw-text-opacity:1!important;color:rgb(53 38 112 / var(--tw-text-opacity))!important}.text-white{--tw-text-opacity:1!important;color:rgb(255 255 255 / var(--tw-text-opacity))!important}.text-brand{--tw-text-opacity:1!important;color:rgb(65 49 133 / var(--tw-text-opacity))!important}.text-black{--tw-text-opacity:1!important;color:rgb(0 0 0 / var(--tw-text-opacity))!important}.text-purple-light{--tw-text-opacity:1!important;color:rgb(83 121 218 / var(--tw-text-opacity))!important}.text-current{color:currentColor!important}.text-indigo-500{--tw-text-opacity:1!important;color:rgb(99 102 241 / var(--tw-text-opacity))!important}.text-opacity-80{--tw-text-opacity:0.8!important}.text-opacity-50{--tw-text-opacity:0.5!important}.text-opacity-70{--tw-text-opacity:0.7!important}.underline{text-decoration-line:underline!important}.shadow-custom-black{--tw-shadow:0 2px 25px rgba(0,0,0,0.1)!important;--tw-shadow-colored:0 2px 25px var(--tw-shadow-color)!important;box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)!important}.shadow-lg{--tw-shadow:0 10px 15px -3px rgb(0 0 0 / 0.1),0 4px 6px -4px rgb(0 0 0 / 0.1)!important;--tw-shadow-colored:0 10px 15px -3px var(--tw-shadow-color),0 4px 6px -4px var(--tw-shadow-color)!important;box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)!important}.shadow{--tw-shadow:0 1px 3px 0 rgb(0 0 0 / 0.1),0 1px 2px -1px rgb(0 0 0 / 0.1)!important;--tw-shadow-colored:0 1px 3px 0 var(--tw-shadow-color),0 1px 2px -1px var(--tw-shadow-color)!important;box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)!important}.outline{outline-style:solid!important}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)!important}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter!important;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter!important;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter!important;transition-timing-function:cubic-bezier(.4,0,.2,1)!important;transition-duration:150ms!important}.\[make\:migration\]{make:migration}.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.alert{display:flex;width:100%;flex-direction:column;align-items:center;justify-content:space-between;gap:1rem;--tw-bg-opacity:1;background-color:hsl(var(--b2,var(--b1)) / var(--tw-bg-opacity));padding:1rem;border-radius:var(--rounded-box,1rem)}.alert>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}@media (min-width:768px){.alert{flex-direction:row}.alert>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(0px * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(0px * var(--tw-space-y-reverse))}}.alert>:where(*){display:flex;align-items:center;gap:.5rem}.avatar{position:relative;display:inline-flex}.avatar>div{display:block;aspect-ratio:1/1;overflow:hidden}.avatar img{height:100%;width:100%;-o-object-fit:cover;object-fit:cover}.avatar.placeholder>div{display:flex;align-items:center;justify-content:center}.breadcrumbs{max-width:100%;overflow-x:auto;padding-top:.5rem;padding-bottom:.5rem}.breadcrumbs>ul{display:flex;align-items:center;white-space:nowrap;min-height:-moz-min-content;min-height:min-content}.breadcrumbs>ul>li{display:flex;align-items:center}.breadcrumbs>ul>li>a{display:flex;cursor:pointer;align-items:center}.breadcrumbs>ul>li>a:hover{text-decoration-line:underline}.btn{display:inline-flex;flex-shrink:0;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none;flex-wrap:wrap;align-items:center;justify-content:center;border-color:transparent;border-color:hsl(var(--n) / var(--tw-border-opacity));text-align:center;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-duration:.2s;transition-timing-function:cubic-bezier(.4,0,.2,1);border-radius:var(--rounded-btn,.5rem);height:3rem;padding-left:1rem;padding-right:1rem;font-size:.875rem;line-height:1.25rem;line-height:1em;min-height:3rem;font-weight:600;text-transform:uppercase;text-transform:var(--btn-text-case,uppercase);text-decoration-line:none;border-width:var(--border-btn,1px);animation:button-pop var(--animation-btn,.25s) ease-out;--tw-border-opacity:1;--tw-bg-opacity:1;background-color:hsl(var(--n) / var(--tw-bg-opacity));--tw-text-opacity:1;color:hsl(var(--nc) / var(--tw-text-opacity))}.btn-disabled,.btn[disabled]{pointer-events:none}.btn.loading,.btn.loading:hover{pointer-events:none}.btn.loading:before{margin-right:.5rem;height:1rem;width:1rem;border-radius:9999px;border-width:2px;animation:spin 2s linear infinite;content:"";border-top-color:transparent;border-left-color:transparent;border-bottom-color:currentColor;border-right-color:currentColor}@media (prefers-reduced-motion:reduce){.btn.loading:before{animation:spin 10s linear infinite}}@keyframes spin{from{transform:rotate(0)}to{transform:rotate(360deg)}}.btn-group>input[type=radio].btn{-webkit-appearance:none;-moz-appearance:none;appearance:none}.btn-group>input[type=radio].btn:before{content:attr(data-title)}.card{position:relative;display:flex;flex-direction:column;border-radius:var(--rounded-box,1rem)}.card:focus{outline:2px solid transparent;outline-offset:2px}.card figure{display:flex;align-items:center;justify-content:center}.card.image-full{display:grid}.card.image-full:before{position:relative;content:"";z-index:10;--tw-bg-opacity:1;background-color:hsl(var(--n) / var(--tw-bg-opacity));opacity:.75;border-radius:var(--rounded-box,1rem)}.card.image-full:before,.card.image-full>*{grid-column-start:1;grid-row-start:1}.card.image-full>figure img{height:100%;-o-object-fit:cover;object-fit:cover}.card.image-full>.card-body{position:relative;z-index:20;--tw-text-opacity:1;color:hsl(var(--nc) / var(--tw-text-opacity))}.checkbox{flex-shrink:0;--chkbg:var(--bc);--chkfg:var(--b1);height:1.5rem;width:1.5rem;cursor:pointer;-webkit-appearance:none;-moz-appearance:none;appearance:none;border-width:1px;border-color:hsl(var(--bc) / var(--tw-border-opacity));--tw-border-opacity:0.2;border-radius:var(--rounded-btn,.5rem)}.collapse.collapse{visibility:visible}.collapse{position:relative;display:grid;overflow:hidden}.collapse-content,.collapse-title,.collapse>input[type=checkbox]{grid-column-start:1;grid-row-start:1}.collapse>input[type=checkbox]{-webkit-appearance:none;-moz-appearance:none;appearance:none;opacity:0}.collapse-open .collapse-content,.collapse:focus:not(.collapse-close) .collapse-content,.collapse:not(.collapse-close) input[type=checkbox]:checked~.collapse-content{max-height:9000px}.divider{display:flex;flex-direction:row;align-items:center;align-self:stretch;margin-top:1rem;margin-bottom:1rem;height:1rem;white-space:nowrap}.divider:after,.divider:before{content:"";flex-grow:1;height:.125rem;width:100%}.dropdown{position:relative;display:inline-block}.dropdown>:focus{outline:2px solid transparent;outline-offset:2px}.dropdown .dropdown-content{visibility:hidden;position:absolute;z-index:50;opacity:0;transform-origin:top;--tw-scale-x:.95;--tw-scale-y:.95;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-duration:.2s;transition-timing-function:cubic-bezier(.4,0,.2,1)}.dropdown.dropdown-hover:hover .dropdown-content,.dropdown.dropdown-open .dropdown-content,.dropdown:not(.dropdown-hover):focus .dropdown-content,.dropdown:not(.dropdown-hover):focus-within .dropdown-content{visibility:visible;opacity:1}.footer{display:grid;width:100%;grid-auto-flow:row;place-items:start;row-gap:2.5rem;-moz-column-gap:1rem;column-gap:1rem;font-size:.875rem;line-height:1.25rem}.footer>*{display:grid;place-items:start;gap:.5rem}@media (min-width:48rem){.footer{grid-auto-flow:column}.footer-center{grid-auto-flow:row dense}}.label{display:flex;-webkit-user-select:none;-moz-user-select:none;user-select:none;align-items:center;justify-content:space-between;padding-left:.25rem;padding-right:.25rem;padding-top:.5rem;padding-bottom:.5rem}.indicator{position:relative;display:inline-flex;width:-moz-max-content;width:max-content}.indicator :where(.indicator-item){z-index:1;position:absolute;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.input{flex-shrink:1;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-duration:.2s;transition-timing-function:cubic-bezier(.4,0,.2,1);height:3rem;padding-left:1rem;padding-right:1rem;font-size:1rem;line-height:2;line-height:1.5rem;border-width:1px;border-color:hsl(var(--bc) / var(--tw-border-opacity));--tw-border-opacity:0;--tw-bg-opacity:1;background-color:hsl(var(--b1) / var(--tw-bg-opacity));border-radius:var(--rounded-btn,.5rem)}.input-group>.input{isolation:isolate}.input-group>*,.input-group>.input,.input-group>.select{border-radius:0}.kbd{display:inline-flex;align-items:center;justify-content:center;border-width:1px;border-color:hsl(var(--bc) / var(--tw-border-opacity));--tw-border-opacity:0.2;--tw-bg-opacity:1;background-color:hsl(var(--b2,var(--b1)) / var(--tw-bg-opacity));padding-left:.5rem;padding-right:.5rem;border-radius:var(--rounded-btn,.5rem);border-bottom-width:2px;min-height:2.2em;min-width:2.2em}.link{cursor:pointer;text-decoration-line:underline}.mask{-webkit-mask-size:contain;mask-size:contain;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-position:center;mask-position:center}.menu{display:flex;flex-direction:column;flex-wrap:wrap}.menu.horizontal{display:inline-flex;flex-direction:row}.menu.horizontal :where(li){flex-direction:row}.menu :where(li){position:relative;display:flex;flex-shrink:0;flex-direction:column;flex-wrap:wrap;align-items:stretch}.menu :where(li:not(.menu-title))>:where(:not(ul)){display:flex}.menu :where(li:not(.disabled):not(.menu-title))>:where(:not(ul)){cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none;align-items:center;outline:2px solid transparent;outline-offset:2px}.menu>:where(li>:not(ul):focus){outline:2px solid transparent;outline-offset:2px}.menu>:where(li.disabled>:not(ul):focus){cursor:auto}.menu>:where(li) :where(ul){display:flex;flex-direction:column;align-items:stretch}.menu>:where(li)>:where(ul){position:absolute;display:none;top:initial;left:100%;border-top-left-radius:inherit;border-top-right-radius:inherit;border-bottom-right-radius:inherit;border-bottom-left-radius:inherit}.menu>:where(li:hover)>:where(ul){display:flex}.menu>:where(li:focus)>:where(ul){display:flex}.modal{pointer-events:none;visibility:hidden;position:fixed;top:0;right:0;bottom:0;left:0;display:flex;justify-content:center;opacity:0;z-index:999;background-color:hsl(var(--nf,var(--n)) / var(--tw-bg-opacity));--tw-bg-opacity:0.4;transition-duration:.2s;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-property:transform,opacity;overflow-y:hidden;overscroll-behavior:contain}:where(.modal){align-items:center}.modal-open,.modal-toggle:checked+.modal,.modal:target{pointer-events:auto;visibility:visible;opacity:1}.progress{position:relative;width:100%;-webkit-appearance:none;-moz-appearance:none;appearance:none;overflow:hidden;height:.5rem;border-radius:var(--rounded-box,1rem)}.radio{flex-shrink:0;--chkbg:var(--bc);height:1.5rem;width:1.5rem;cursor:pointer;-webkit-appearance:none;-moz-appearance:none;appearance:none;border-radius:9999px;border-width:1px;border-color:hsl(var(--bc) / var(--tw-border-opacity));--tw-border-opacity:0.2;transition:background,box-shadow var(--animation-input,.2s) ease-in-out}.range{height:1.5rem;width:100%;cursor:pointer;-moz-appearance:none;appearance:none;-webkit-appearance:none;--range-shdw:var(--bc);overflow:hidden;background-color:transparent;border-radius:var(--rounded-box,1rem)}.range:focus{outline:0}.rating{position:relative;display:inline-flex}.rating :where(input){cursor:pointer;animation:rating-pop var(--animation-input,.25s) ease-out;height:1.5rem;width:1.5rem;background-color:hsl(var(--bc) / var(--tw-bg-opacity));--tw-bg-opacity:1}.select{display:inline-flex;flex-shrink:0;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none;-webkit-appearance:none;-moz-appearance:none;appearance:none;height:3rem;padding-left:1rem;padding-right:2.5rem;font-size:.875rem;line-height:1.25rem;line-height:2;min-height:3rem;border-width:1px;border-color:hsl(var(--bc) / var(--tw-border-opacity));--tw-border-opacity:0;--tw-bg-opacity:1;background-color:hsl(var(--b1) / var(--tw-bg-opacity));font-weight:600;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-duration:.2s;transition-timing-function:cubic-bezier(.4,0,.2,1);border-radius:var(--rounded-btn,.5rem);background-image:linear-gradient(45deg,transparent 50%,currentColor 50%),linear-gradient(135deg,currentColor 50%,transparent 50%);background-position:calc(100% - 20px) calc(1px + 50%),calc(100% - 16px) calc(1px + 50%);background-size:4px 4px,4px 4px;background-repeat:no-repeat}.select[multiple]{height:auto}.stack{display:inline-grid;place-items:center;align-items:flex-end}.stack>*{grid-column-start:1;grid-row-start:1;transform:translateY(1rem) scale(.9);z-index:1;width:100%;opacity:.6}.stack>:nth-child(2){transform:translateY(.5rem) scale(.95);z-index:2;opacity:.8}.stack>:nth-child(1){transform:translateY(0) scale(1);z-index:3;opacity:1}.stats{display:inline-grid;--tw-bg-opacity:1;background-color:hsl(var(--b1) / var(--tw-bg-opacity));--tw-text-opacity:1;color:hsl(var(--bc) / var(--tw-text-opacity));border-radius:var(--rounded-box,1rem)}:where(.stats){grid-auto-flow:column;overflow-x:auto}.steps{display:inline-grid;grid-auto-flow:column;overflow:hidden;overflow-x:auto;counter-reset:step;grid-auto-columns:1fr}.steps .step{display:grid;grid-template-columns:repeat(1,minmax(0,1fr));grid-template-columns:auto;grid-template-rows:repeat(2,minmax(0,1fr));grid-template-rows:40px 1fr;place-items:center;text-align:center;min-width:4rem}.swap{position:relative;display:inline-grid;-webkit-user-select:none;-moz-user-select:none;user-select:none;place-content:center;cursor:pointer}.swap>*{grid-column-start:1;grid-row-start:1;transition-duration:.3s;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-property:transform,opacity}.swap input{-webkit-appearance:none;-moz-appearance:none;appearance:none}.swap .swap-indeterminate,.swap .swap-on,.swap input:indeterminate~.swap-on{opacity:0}.swap input:checked~.swap-off,.swap input:indeterminate~.swap-off,.swap.swap-active .swap-off{opacity:0}.swap input:checked~.swap-on,.swap input:indeterminate~.swap-indeterminate,.swap-active .swap-on{opacity:1}.tabs{display:flex;flex-wrap:wrap;align-items:flex-end}.tab{position:relative;display:inline-flex;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none;flex-wrap:wrap;align-items:center;justify-content:center;text-align:center;height:2rem;font-size:.875rem;line-height:1.25rem;line-height:2;--tab-padding:1rem;--tw-text-opacity:0.5;--tab-color:hsla(var(--bc) / var(--tw-text-opacity, 1));--tab-bg:hsla(var(--b1) / var(--tw-bg-opacity, 1));--tab-border-color:hsla(var(--b3) / var(--tw-bg-opacity, 1));color:var(--tab-color);padding-left:var(--tab-padding,1rem);padding-right:var(--tab-padding,1rem)}.table{position:relative;text-align:left}.table th:first-child{position:sticky;position:-webkit-sticky;left:0;z-index:11}.textarea{flex-shrink:1;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-duration:.2s;transition-timing-function:cubic-bezier(.4,0,.2,1);padding-left:1rem;padding-right:1rem;padding-top:.5rem;padding-bottom:.5rem;font-size:.875rem;line-height:1.25rem;line-height:2;min-height:3rem;border-width:1px;border-color:hsl(var(--bc) / var(--tw-border-opacity));--tw-border-opacity:0;--tw-bg-opacity:1;background-color:hsl(var(--b1) / var(--tw-bg-opacity));border-radius:var(--rounded-btn,.5rem)}.toggle{flex-shrink:0;--chkbg:hsla(var(--bc) / 0.2);--handleoffset:1.5rem;height:1.5rem;width:3rem;cursor:pointer;-webkit-appearance:none;-moz-appearance:none;appearance:none;border-width:1px;border-color:hsl(var(--bc) / var(--tw-border-opacity));--tw-border-opacity:0.2;background-color:hsl(var(--bc) / var(--tw-bg-opacity));--tw-bg-opacity:0.2;transition-duration:.3s;transition-timing-function:cubic-bezier(.4,0,.2,1);border-radius:var(--rounded-badge,1.9rem);transition:background,box-shadow var(--animation-input,.2s) ease-in-out;box-shadow:calc(var(--handleoffset) * -1) 0 0 2px hsl(var(--b1)) inset,0 0 0 2px hsl(var(--b1)) inset}.alert-success{--tw-bg-opacity:1;background-color:hsl(var(--su) / var(--tw-bg-opacity));--tw-text-opacity:1;color:hsl(var(--suc,var(--nc)) / var(--tw-text-opacity))}.avatar-group :where(.avatar){overflow:hidden;border-radius:9999px;border-width:4px;--tw-border-opacity:1;border-color:hsl(var(--b1) / var(--tw-border-opacity))}.btn-outline.btn-primary .badge{--tw-border-opacity:1;border-color:hsl(var(--p) / var(--tw-border-opacity));--tw-bg-opacity:1;background-color:hsl(var(--p) / var(--tw-bg-opacity));--tw-text-opacity:1;color:hsl(var(--pc) / var(--tw-text-opacity))}.btn-outline .badge.outline{--tw-border-opacity:1;border-color:hsl(var(--nf,var(--n)) / var(--tw-border-opacity));background-color:transparent}.btn-outline.btn-primary .badge-outline{--tw-border-opacity:1;border-color:hsl(var(--p) / var(--tw-border-opacity));background-color:transparent;--tw-text-opacity:1;color:hsl(var(--p) / var(--tw-text-opacity))}.btn-outline:hover .badge.outline{--tw-border-opacity:1;border-color:hsl(var(--b2,var(--b1)) / var(--tw-border-opacity));--tw-text-opacity:1;color:hsl(var(--nc) / var(--tw-text-opacity))}.btn-outline.btn-primary:hover .badge{--tw-border-opacity:1;border-color:hsl(var(--pc) / var(--tw-border-opacity));--tw-bg-opacity:1;background-color:hsl(var(--pc) / var(--tw-bg-opacity));--tw-text-opacity:1;color:hsl(var(--p) / var(--tw-text-opacity))}.btn-outline.btn-primary:hover .badge.outline{--tw-border-opacity:1;border-color:hsl(var(--pc) / var(--tw-border-opacity));--tw-bg-opacity:1;background-color:hsl(var(--pf,var(--p)) / var(--tw-bg-opacity));--tw-text-opacity:1;color:hsl(var(--pc) / var(--tw-text-opacity))}.btn-outline.btn-secondary:hover .badge.outline{--tw-border-opacity:1;border-color:hsl(var(--sc) / var(--tw-border-opacity));--tw-bg-opacity:1;background-color:hsl(var(--sf,var(--s)) / var(--tw-bg-opacity));--tw-text-opacity:1;color:hsl(var(--sc) / var(--tw-text-opacity))}.btn-outline.btn-accent:hover .badge.outline{--tw-border-opacity:1;border-color:hsl(var(--ac) / var(--tw-border-opacity));--tw-bg-opacity:1;background-color:hsl(var(--af,var(--a)) / var(--tw-bg-opacity));--tw-text-opacity:1;color:hsl(var(--ac) / var(--tw-text-opacity))}.btm-nav>:where(.active){border-top-width:2px;--tw-bg-opacity:1;background-color:hsl(var(--b1) / var(--tw-bg-opacity))}.btm-nav>.disabled,.btm-nav>.disabled:hover,.btm-nav>[disabled],.btm-nav>[disabled]:hover{pointer-events:none;--tw-border-opacity:0;background-color:hsl(var(--n) / var(--tw-bg-opacity));--tw-bg-opacity:0.1;color:hsl(var(--bc) / var(--tw-text-opacity));--tw-text-opacity:0.2}.btm-nav>* .label{font-size:1rem;line-height:1.5rem}.breadcrumbs>ul>li>a:focus{outline:2px solid transparent;outline-offset:2px}.breadcrumbs>ul>li>a:focus-visible{outline:2px solid currentColor;outline-offset:2px}.breadcrumbs>ul>li+:before{content:"";margin-left:.5rem;margin-right:.75rem;display:block;height:.375rem;width:.375rem;--tw-rotate:45deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));opacity:.4;border-top:1px solid;border-right:1px solid;background-color:transparent}[dir=rtl] .breadcrumbs>ul>li+:before{--tw-rotate:-45deg}.btn:active:focus,.btn:active:hover{animation:none;transform:scale(var(--btn-focus-scale,.95))}.btn-active,.btn:hover{--tw-border-opacity:1;border-color:hsl(var(--nf,var(--n)) / var(--tw-border-opacity));--tw-bg-opacity:1;background-color:hsl(var(--nf,var(--n)) / var(--tw-bg-opacity))}.btn:focus-visible{outline:2px solid hsl(var(--nf));outline-offset:2px}.btn-primary{--tw-border-opacity:1;border-color:hsl(var(--p) / var(--tw-border-opacity));--tw-bg-opacity:1;background-color:hsl(var(--p) / var(--tw-bg-opacity));--tw-text-opacity:1;color:hsl(var(--pc) / var(--tw-text-opacity))}.btn-primary.btn-active,.btn-primary:hover{--tw-border-opacity:1;border-color:hsl(var(--pf,var(--p)) / var(--tw-border-opacity));--tw-bg-opacity:1;background-color:hsl(var(--pf,var(--p)) / var(--tw-bg-opacity))}.btn-primary:focus-visible{outline:2px solid hsl(var(--p))}.btn.glass.btn-active,.btn.glass:hover{--glass-opacity:25%;--glass-border-opacity:15%}.btn.glass:focus-visible{outline:2px solid currentColor}.btn-outline.btn-primary{--tw-text-opacity:1;color:hsl(var(--p) / var(--tw-text-opacity))}.btn-outline.btn-primary.btn-active,.btn-outline.btn-primary:hover{--tw-border-opacity:1;border-color:hsl(var(--pf,var(--p)) / var(--tw-border-opacity));--tw-bg-opacity:1;background-color:hsl(var(--pf,var(--p)) / var(--tw-bg-opacity));--tw-text-opacity:1;color:hsl(var(--pc) / var(--tw-text-opacity))}.btn-disabled,.btn-disabled:hover,.btn[disabled],.btn[disabled]:hover{--tw-border-opacity:0;background-color:hsl(var(--n) / var(--tw-bg-opacity));--tw-bg-opacity:0.2;color:hsl(var(--bc) / var(--tw-text-opacity));--tw-text-opacity:0.2}.btn.loading.btn-circle:before,.btn.loading.btn-square:before{margin-right:0}.btn.loading.btn-lg:before,.btn.loading.btn-xl:before{height:1.25rem;width:1.25rem}.btn.loading.btn-sm:before,.btn.loading.btn-xs:before{height:.75rem;width:.75rem}.btn-group>.btn-active,.btn-group>input[type=radio].btn:checked{--tw-border-opacity:1;border-color:hsl(var(--p) / var(--tw-border-opacity));--tw-bg-opacity:1;background-color:hsl(var(--p) / var(--tw-bg-opacity));--tw-text-opacity:1;color:hsl(var(--pc) / var(--tw-text-opacity))}.btn-group>.btn-active:focus-visible,.btn-group>input[type=radio]:checked.btn:focus-visible{outline:2px solid hsl(var(--p))}@keyframes button-pop{0%{transform:scale(var(--btn-focus-scale,.95))}40%{transform:scale(1.02)}100%{transform:scale(1)}}.card :where(figure:first-child){overflow:hidden;border-top-left-radius:inherit;border-top-right-radius:inherit;border-bottom-left-radius:unset;border-bottom-right-radius:unset}.card :where(figure:last-child){overflow:hidden;border-top-left-radius:unset;border-top-right-radius:unset;border-bottom-left-radius:inherit;border-bottom-right-radius:inherit}.card:focus-visible{outline:2px solid currentColor;outline-offset:2px}.card.bordered{border-width:1px;--tw-border-opacity:1;border-color:hsl(var(--b2,var(--b1)) / var(--tw-border-opacity))}.card.compact .card-body{padding:1rem;font-size:.875rem;line-height:1.25rem}.card.image-full :where(figure){overflow:hidden;border-radius:inherit}.checkbox:focus-visible{outline:2px solid hsl(var(--bc));outline-offset:2px}.checkbox:checked,.checkbox[aria-checked=true],.checkbox[checked=true]{--tw-bg-opacity:1;background-color:hsl(var(--bc) / var(--tw-bg-opacity));background-repeat:no-repeat;animation:checkmark var(--animation-input,.2s) ease-in-out;background-image:linear-gradient(-45deg,transparent 65%,hsl(var(--chkbg)) 65.99%),linear-gradient(45deg,transparent 75%,hsl(var(--chkbg)) 75.99%),linear-gradient(-45deg,hsl(var(--chkbg)) 40%,transparent 40.99%),linear-gradient(45deg,hsl(var(--chkbg)) 30%,hsl(var(--chkfg)) 30.99%,hsl(var(--chkfg)) 40%,transparent 40.99%),linear-gradient(-45deg,hsl(var(--chkfg)) 50%,hsl(var(--chkbg)) 50.99%)}.checkbox:indeterminate{--tw-bg-opacity:1;background-color:hsl(var(--bc) / var(--tw-bg-opacity));background-repeat:no-repeat;animation:checkmark var(--animation-input,.2s) ease-in-out;background-image:linear-gradient(90deg,transparent 80%,hsl(var(--chkbg)) 80%),linear-gradient(-90deg,transparent 80%,hsl(var(--chkbg)) 80%),linear-gradient(0deg,hsl(var(--chkbg)) 43%,hsl(var(--chkfg)) 43%,hsl(var(--chkfg)) 57%,hsl(var(--chkbg)) 57%)}.checkbox:disabled{cursor:not-allowed;border-color:transparent;--tw-bg-opacity:1;background-color:hsl(var(--bc) / var(--tw-bg-opacity));opacity:.2}@keyframes checkmark{0%{background-position-y:5px}50%{background-position-y:-2px}100%{background-position-y:0}}[dir=rtl] .checkbox{--chkbg:var(--bc);--chkfg:var(--b1)}[dir=rtl] .checkbox:checked,[dir=rtl] .checkbox[aria-checked=true],[dir=rtl] .checkbox[checked=true]{background-image:linear-gradient(45deg,transparent 65%,hsl(var(--chkbg)) 65.99%),linear-gradient(-45deg,transparent 75%,hsl(var(--chkbg)) 75.99%),linear-gradient(45deg,hsl(var(--chkbg)) 40%,transparent 40.99%),linear-gradient(-45deg,hsl(var(--chkbg)) 30%,hsl(var(--chkfg)) 30.99%,hsl(var(--chkfg)) 40%,transparent 40.99%),linear-gradient(45deg,hsl(var(--chkfg)) 50%,hsl(var(--chkbg)) 50.99%)}.collapse:focus-visible{outline:2px solid hsl(var(--nf));outline-offset:2px}.collapse:not(.collapse-open):not(.collapse-close) .collapse-title,.collapse:not(.collapse-open):not(.collapse-close) input[type=checkbox]{cursor:pointer}.collapse:focus:not(.collapse-open):not(.collapse-close) .collapse-title{cursor:unset}:where(.collapse>input[type=checkbox]){z-index:1}.collapse-title,:where(.collapse>input[type=checkbox]){width:100%;padding:1rem;padding-right:3rem;min-height:3.75rem;transition:background-color .2s ease-in-out}.collapse-open :where(.collapse-content),.collapse:focus:not(.collapse-close) :where(.collapse-content),.collapse:not(.collapse-close) :where(input[type=checkbox]:checked~.collapse-content){padding-bottom:1rem;transition:padding .2s ease-in-out,background-color .2s ease-in-out}.divider:before{background-color:hsl(var(--bc) / var(--tw-bg-opacity));--tw-bg-opacity:0.1}.divider:after{background-color:hsl(var(--bc) / var(--tw-bg-opacity));--tw-bg-opacity:0.1}.divider:not(:empty){gap:1rem}.drawer-toggle:focus-visible~.drawer-content .drawer-button.btn-primary{outline:2px solid hsl(var(--p))}.dropdown.dropdown-hover:hover .dropdown-content,.dropdown.dropdown-open .dropdown-content,.dropdown:focus .dropdown-content,.dropdown:focus-within .dropdown-content{--tw-scale-x:1;--tw-scale-y:1;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.label a:hover{--tw-text-opacity:1;color:hsl(var(--bc) / var(--tw-text-opacity))}.input[list]::-webkit-calendar-picker-indicator{line-height:1em}.input:focus{outline:2px solid hsla(var(--bc) / .2);outline-offset:2px}.input-disabled,.input[disabled]{cursor:not-allowed;--tw-border-opacity:1;border-color:hsl(var(--b2,var(--b1)) / var(--tw-border-opacity));--tw-bg-opacity:1;background-color:hsl(var(--b2,var(--b1)) / var(--tw-bg-opacity));--tw-text-opacity:0.2}.input-disabled::-moz-placeholder,.input[disabled]::-moz-placeholder{color:hsl(var(--bc) / var(--tw-placeholder-opacity));--tw-placeholder-opacity:0.2}.input-disabled::placeholder,.input[disabled]::placeholder{color:hsl(var(--bc) / var(--tw-placeholder-opacity));--tw-placeholder-opacity:0.2}.link:focus{outline:2px solid transparent;outline-offset:2px}.link:focus-visible{outline:2px solid currentColor;outline-offset:2px}.menu.horizontal li.bordered>a,.menu.horizontal li.bordered>button,.menu.horizontal li.bordered>span{border-left-width:0;border-bottom-width:4px;--tw-border-opacity:1;border-color:hsl(var(--p) / var(--tw-border-opacity))}.menu[class*=" p-"] li>*,.menu[class^=p-] li>*{border-radius:var(--rounded-btn,.5rem)}.menu :where(li.bordered>*){border-left-width:4px;--tw-border-opacity:1;border-color:hsl(var(--p) / var(--tw-border-opacity))}.menu :where(li)>:where(:not(ul)){gap:.75rem;padding-left:1rem;padding-right:1rem;padding-top:.75rem;padding-bottom:.75rem;color:currentColor}.menu :where(li:not(.menu-title):not(:empty))>:where(:not(ul):focus),.menu :where(li:not(.menu-title):not(:empty))>:where(:not(ul):hover){background-color:hsl(var(--bc) / var(--tw-bg-opacity));--tw-bg-opacity:0.1}.menu :where(li:not(.menu-title):not(:empty))>:where(:not(ul).active),.menu :where(li:not(.menu-title):not(:empty))>:where(:not(ul):active){--tw-bg-opacity:1;background-color:hsl(var(--p) / var(--tw-bg-opacity));--tw-text-opacity:1;color:hsl(var(--pc) / var(--tw-text-opacity))}.menu :where(li:empty){margin-left:1rem;margin-right:1rem;margin-top:.5rem;margin-bottom:.5rem;height:1px;background-color:hsl(var(--bc) / var(--tw-bg-opacity));--tw-bg-opacity:0.1}.menu li.disabled>*{-webkit-user-select:none;-moz-user-select:none;user-select:none;color:hsl(var(--bc) / var(--tw-text-opacity));--tw-text-opacity:0.2}.menu li.disabled>:hover{background-color:transparent}.menu li.hover-bordered a{border-left-width:4px;border-color:transparent}.menu li.hover-bordered a:hover{--tw-border-opacity:1;border-color:hsl(var(--p) / var(--tw-border-opacity))}.menu.compact li>a,.menu.compact li>span{padding-top:.5rem;padding-bottom:.5rem;font-size:.875rem;line-height:1.25rem}.menu .menu-title>*{padding-top:.25rem;padding-bottom:.25rem;font-size:.75rem;line-height:1rem;font-weight:700;color:hsl(var(--bc) / var(--tw-text-opacity));--tw-text-opacity:0.4}.menu :where(li:not(.disabled))>:where(:not(ul)){outline:2px solid transparent;outline-offset:2px;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-duration:.2s;transition-timing-function:cubic-bezier(.4,0,.2,1)}.menu>:where(li:first-child){border-top-left-radius:inherit;border-top-right-radius:inherit;border-bottom-right-radius:unset;border-bottom-left-radius:unset}.menu>:where(li:first-child)>:where(:not(ul)){border-top-left-radius:inherit;border-top-right-radius:inherit;border-bottom-right-radius:unset;border-bottom-left-radius:unset}.menu>:where(li:last-child){border-top-left-radius:unset;border-top-right-radius:unset;border-bottom-right-radius:inherit;border-bottom-left-radius:inherit}.menu>:where(li:last-child)>:where(:not(ul)){border-top-left-radius:unset;border-top-right-radius:unset;border-bottom-right-radius:inherit;border-bottom-left-radius:inherit}.menu>:where(li)>:where(ul) :where(li){width:100%;white-space:nowrap}.menu>:where(li)>:where(ul) :where(li) :where(ul){padding-left:1rem}.menu>:where(li)>:where(ul) :where(li)>:where(:not(ul)){width:100%;white-space:nowrap}.menu>:where(li)>:where(ul)>:where(li:first-child){border-top-left-radius:inherit;border-top-right-radius:inherit;border-bottom-right-radius:unset;border-bottom-left-radius:unset}.menu>:where(li)>:where(ul)>:where(li:first-child)>:where(:not(ul)){border-top-left-radius:inherit;border-top-right-radius:inherit;border-bottom-right-radius:unset;border-bottom-left-radius:unset}.menu>:where(li)>:where(ul)>:where(li:last-child){border-top-left-radius:unset;border-top-right-radius:unset;border-bottom-right-radius:inherit;border-bottom-left-radius:inherit}.menu>:where(li)>:where(ul)>:where(li:last-child)>:where(:not(ul)){border-top-left-radius:unset;border-top-right-radius:unset;border-bottom-right-radius:inherit;border-bottom-left-radius:inherit}.mockup-phone .display{overflow:hidden;border-radius:40px;margin-top:-25px}.modal-open .modal-box,.modal-toggle:checked+.modal .modal-box,.modal:target .modal-box{--tw-translate-y:0px;--tw-scale-x:1;--tw-scale-y:1;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.progress::-moz-progress-bar{--tw-bg-opacity:1;background-color:hsl(var(--n) / var(--tw-bg-opacity))}.progress:indeterminate::after{--tw-bg-opacity:1;background-color:hsl(var(--n) / var(--tw-bg-opacity));content:"";position:absolute;top:0;bottom:0;left:-40%;width:33.333333%;border-radius:var(--rounded-box,1rem);animation:progress-loading 5s infinite ease-in-out}.progress::-webkit-progress-bar{background-color:hsl(var(--n) / var(--tw-bg-opacity));--tw-bg-opacity:0.2;border-radius:var(--rounded-box,1rem)}.progress::-webkit-progress-value{--tw-bg-opacity:1;background-color:hsl(var(--nf,var(--n)) / var(--tw-bg-opacity));border-radius:var(--rounded-box,1rem)}@keyframes progress-loading{50%{left:107%}}.radio:focus-visible{outline:2px solid hsl(var(--bc));outline-offset:2px}.radio:checked,.radio[aria-checked=true]{--tw-bg-opacity:1;background-color:hsl(var(--bc) / var(--tw-bg-opacity));animation:radiomark var(--animation-input,.2s) ease-in-out;box-shadow:0 0 0 4px hsl(var(--b1)) inset,0 0 0 4px hsl(var(--b1)) inset}.radio:disabled{cursor:not-allowed;opacity:.2}@keyframes radiomark{0%{box-shadow:0 0 0 12px hsl(var(--b1)) inset,0 0 0 12px hsl(var(--b1)) inset}50%{box-shadow:0 0 0 3px hsl(var(--b1)) inset,0 0 0 3px hsl(var(--b1)) inset}100%{box-shadow:0 0 0 4px hsl(var(--b1)) inset,0 0 0 4px hsl(var(--b1)) inset}}.range:focus-visible::-webkit-slider-thumb{--focus-shadow:0 0 0 6px hsl(var(--b1)) inset,0 0 0 2rem hsl(var(--range-shdw)) inset}.range:focus-visible::-moz-range-thumb{--focus-shadow:0 0 0 6px hsl(var(--b1)) inset,0 0 0 2rem hsl(var(--range-shdw)) inset}.range::-webkit-slider-runnable-track{height:.5rem;width:100%;border-radius:var(--rounded-box,1rem);background-color:hsla(var(--bc) / .1)}.range::-moz-range-track{height:.5rem;width:100%;border-radius:var(--rounded-box,1rem);background-color:hsla(var(--bc) / .1)}.range::-webkit-slider-thumb{background-color:hsl(var(--b1));position:relative;height:1.5rem;width:1.5rem;border-style:none;border-radius:var(--rounded-box,1rem);appearance:none;-webkit-appearance:none;top:50%;color:hsl(var(--range-shdw));transform:translateY(-50%);--filler-size:100rem;--filler-offset:0.6rem;box-shadow:0 0 0 3px hsl(var(--range-shdw)) inset,var(--focus-shadow,0 0),calc(var(--filler-size) * -1 - var(--filler-offset)) 0 0 var(--filler-size)}.range::-moz-range-thumb{background-color:hsl(var(--b1));position:relative;height:1.5rem;width:1.5rem;border-style:none;border-radius:var(--rounded-box,1rem);top:50%;color:hsl(var(--range-shdw));--filler-size:100rem;--filler-offset:0.5rem;box-shadow:0 0 0 3px hsl(var(--range-shdw)) inset,var(--focus-shadow,0 0),calc(var(--filler-size) * -1 - var(--filler-offset)) 0 0 var(--filler-size)}.rating input{-moz-appearance:none;appearance:none;-webkit-appearance:none}.rating .rating-hidden{width:.5rem;background-color:transparent}.rating input:checked~input,.rating input[aria-checked=true]~input{--tw-bg-opacity:0.2}.rating input:focus-visible{transition-property:transform;transition-duration:.3s;transition-timing-function:cubic-bezier(.4,0,.2,1);transform:translateY(-.125em)}.rating input:active:focus{animation:none;transform:translateY(-.125em)}@keyframes rating-pop{0%{transform:translateY(-.125em)}40%{transform:translateY(-.125em)}100%{transform:translateY(0)}}.select:focus{outline:2px solid hsla(var(--bc) / .2);outline-offset:2px}.select-disabled,.select[disabled]{cursor:not-allowed;--tw-border-opacity:1;border-color:hsl(var(--b2,var(--b1)) / var(--tw-border-opacity));--tw-bg-opacity:1;background-color:hsl(var(--b2,var(--b1)) / var(--tw-bg-opacity));--tw-text-opacity:0.2}.select-disabled::-moz-placeholder,.select[disabled]::-moz-placeholder{color:hsl(var(--bc) / var(--tw-placeholder-opacity));--tw-placeholder-opacity:0.2}.select-disabled::placeholder,.select[disabled]::placeholder{color:hsl(var(--bc) / var(--tw-placeholder-opacity));--tw-placeholder-opacity:0.2}.select-multiple,.select[multiple],.select[size].select:not([size="1"]){background-image:none;padding-right:1rem}:where(.stats)>:not([hidden])~:not([hidden]){--tw-divide-x-reverse:0;border-right-width:calc(1px * var(--tw-divide-x-reverse));border-left-width:calc(1px * calc(1 - var(--tw-divide-x-reverse)));--tw-divide-y-reverse:0;border-top-width:calc(0px * calc(1 - var(--tw-divide-y-reverse)));border-bottom-width:calc(0px * var(--tw-divide-y-reverse))}.steps .step:before{top:0;grid-column-start:1;grid-row-start:1;height:.5rem;width:100%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));--tw-bg-opacity:1;background-color:hsl(var(--b3,var(--b2)) / var(--tw-bg-opacity));--tw-text-opacity:1;color:hsl(var(--bc) / var(--tw-text-opacity));content:"";margin-left:-100%}.steps .step:after{content:counter(step);counter-increment:step;z-index:1;position:relative;grid-column-start:1;grid-row-start:1;display:grid;height:2rem;width:2rem;place-items:center;place-self:center;border-radius:9999px;--tw-bg-opacity:1;background-color:hsl(var(--b3,var(--b2)) / var(--tw-bg-opacity));--tw-text-opacity:1;color:hsl(var(--bc) / var(--tw-text-opacity))}.steps .step:first-child:before{content:none}.steps .step[data-content]:after{content:attr(data-content)}.steps .step-neutral+.step-neutral:before,.steps .step-neutral:after{--tw-bg-opacity:1;background-color:hsl(var(--n) / var(--tw-bg-opacity));--tw-text-opacity:1;color:hsl(var(--nc) / var(--tw-text-opacity))}.steps .step-primary+.step-primary:before,.steps .step-primary:after{--tw-bg-opacity:1;background-color:hsl(var(--p) / var(--tw-bg-opacity));--tw-text-opacity:1;color:hsl(var(--pc) / var(--tw-text-opacity))}.steps .step-secondary+.step-secondary:before,.steps .step-secondary:after{--tw-bg-opacity:1;background-color:hsl(var(--s) / var(--tw-bg-opacity));--tw-text-opacity:1;color:hsl(var(--sc) / var(--tw-text-opacity))}.steps .step-accent+.step-accent:before,.steps .step-accent:after{--tw-bg-opacity:1;background-color:hsl(var(--a) / var(--tw-bg-opacity));--tw-text-opacity:1;color:hsl(var(--ac) / var(--tw-text-opacity))}.steps .step-info+.step-info:before{--tw-bg-opacity:1;background-color:hsl(var(--in) / var(--tw-bg-opacity))}.steps .step-info:after{--tw-bg-opacity:1;background-color:hsl(var(--in) / var(--tw-bg-opacity));--tw-text-opacity:1;color:hsl(var(--inc,var(--nc)) / var(--tw-text-opacity))}.steps .step-success+.step-success:before{--tw-bg-opacity:1;background-color:hsl(var(--su) / var(--tw-bg-opacity))}.steps .step-success:after{--tw-bg-opacity:1;background-color:hsl(var(--su) / var(--tw-bg-opacity));--tw-text-opacity:1;color:hsl(var(--suc,var(--nc)) / var(--tw-text-opacity))}.steps .step-warning+.step-warning:before{--tw-bg-opacity:1;background-color:hsl(var(--wa) / var(--tw-bg-opacity))}.steps .step-warning:after{--tw-bg-opacity:1;background-color:hsl(var(--wa) / var(--tw-bg-opacity));--tw-text-opacity:1;color:hsl(var(--wac,var(--nc)) / var(--tw-text-opacity))}.steps .step-error+.step-error:before{--tw-bg-opacity:1;background-color:hsl(var(--er) / var(--tw-bg-opacity))}.steps .step-error:after{--tw-bg-opacity:1;background-color:hsl(var(--er) / var(--tw-bg-opacity));--tw-text-opacity:1;color:hsl(var(--erc,var(--nc)) / var(--tw-text-opacity))}.tab:hover{--tw-text-opacity:1}.tab.tab-active{border-color:hsl(var(--bc) / var(--tw-border-opacity));--tw-border-opacity:1;--tw-text-opacity:1}.tab:focus{outline:2px solid transparent;outline-offset:2px}.tab:focus-visible{outline:2px solid currentColor;outline-offset:-3px}.tab:focus-visible.tab-lifted{border-bottom-right-radius:var(--tab-radius,.5rem);border-bottom-left-radius:var(--tab-radius,.5rem)}.table :where(th,td){white-space:nowrap;padding:1rem;vertical-align:middle}.table tr.active td,.table tr.active th,.table tr.active:nth-child(even) td,.table tr.active:nth-child(even) th{--tw-bg-opacity:1;background-color:hsl(var(--b3,var(--b2)) / var(--tw-bg-opacity))}.table tr.hover:hover td,.table tr.hover:hover th,.table tr.hover:nth-child(even):hover td,.table tr.hover:nth-child(even):hover th{--tw-bg-opacity:1;background-color:hsl(var(--b3,var(--b2)) / var(--tw-bg-opacity))}.table:where(:not(.table-zebra)) :where(thead,tbody,tfoot) :where(tr:not(:last-child):where(th,td)){border-bottom-width:1px;--tw-border-opacity:1;border-color:hsl(var(--b2,var(--b1)) / var(--tw-border-opacity))}.table :where(thead,tfoot) :where(th,td){--tw-bg-opacity:1;background-color:hsl(var(--b2,var(--b1)) / var(--tw-bg-opacity));font-size:.75rem;line-height:1rem;font-weight:700;text-transform:uppercase}.table :where(tbodyth,tbodytd){--tw-bg-opacity:1;background-color:hsl(var(--b1) / var(--tw-bg-opacity))}:where(.table:first-child) :where(:first-child) :where(th,td):first-child{border-top-left-radius:.5rem}:where(.table:first-child) :where(:first-child) :where(th,td):last-child{border-top-right-radius:.5rem}:where(.table:last-child) :where(:last-child) :where(th,td):first-child{border-bottom-left-radius:.5rem}:where(.table:last-child) :where(:last-child) :where(th,td):last-child{border-bottom-right-radius:.5rem}.textarea:focus{outline:2px solid hsla(var(--bc) / .2);outline-offset:2px}.textarea-disabled,.textarea[disabled]{cursor:not-allowed;--tw-border-opacity:1;border-color:hsl(var(--b2,var(--b1)) / var(--tw-border-opacity));--tw-bg-opacity:1;background-color:hsl(var(--b2,var(--b1)) / var(--tw-bg-opacity));--tw-text-opacity:0.2}.textarea-disabled::-moz-placeholder,.textarea[disabled]::-moz-placeholder{color:hsl(var(--bc) / var(--tw-placeholder-opacity));--tw-placeholder-opacity:0.2}.textarea-disabled::placeholder,.textarea[disabled]::placeholder{color:hsl(var(--bc) / var(--tw-placeholder-opacity));--tw-placeholder-opacity:0.2}@keyframes toast-pop{0%{transform:scale(.9);opacity:0}100%{transform:scale(1);opacity:1}}.toggle:focus-visible{outline:2px solid hsl(var(--bc));outline-offset:2px}.toggle:checked,.toggle[aria-checked=true],.toggle[checked=true]{--chkbg:hsl(var(--bc));--tw-border-opacity:1;--tw-bg-opacity:1;box-shadow:var(--handleoffset) 0 0 2px hsl(var(--b1)) inset,0 0 0 2px hsl(var(--b1)) inset}[dir=rtl] .toggle:checked,[dir=rtl] .toggle[aria-checked=true],[dir=rtl] .toggle[checked=true]{box-shadow:calc(var(--handleoffset) * 1) 0 0 2px hsl(var(--b1)) inset,0 0 0 2px hsl(var(--b1)) inset}.toggle:indeterminate{--chkbg:hsl(var(--bc));--tw-border-opacity:1;--tw-bg-opacity:1;box-shadow:calc(var(--handleoffset)/ 2) 0 0 2px hsl(var(--b1)) inset,calc(var(--handleoffset)/ -2) 0 0 2px hsl(var(--b1)) inset,0 0 0 2px hsl(var(--b1)) inset}[dir=rtl] .toggle:indeterminate{box-shadow:calc(var(--handleoffset)/ 2) 0 0 2px hsl(var(--b1)) inset,calc(var(--handleoffset)/ -2) 0 0 2px hsl(var(--b1)) inset,0 0 0 2px hsl(var(--b1)) inset}.toggle:disabled{cursor:not-allowed;border-color:transparent;background-color:hsl(var(--bc) / var(--tw-bg-opacity));--tw-bg-opacity:0.2}.artboard.phone{width:320px}.artboard.phone-1.artboard-horizontal,.artboard.phone-1.horizontal{width:568px;height:320px}.artboard.phone-2.artboard-horizontal,.artboard.phone-2.horizontal{width:667px;height:375px}.artboard.phone-3.artboard-horizontal,.artboard.phone-3.horizontal{width:736px;height:414px}.artboard.phone-4.artboard-horizontal,.artboard.phone-4.horizontal{width:812px;height:375px}.artboard.phone-5.artboard-horizontal,.artboard.phone-5.horizontal{width:896px;height:414px}.artboard.phone-6.artboard-horizontal,.artboard.phone-6.horizontal{width:1024px;height:320px}.btm-nav-xs>:where(.active){border-top-width:1px}.btm-nav-sm>:where(.active){border-top-width:2px}.btm-nav-md>:where(.active){border-top-width:2px}.btm-nav-lg>:where(.active){border-top-width:4px}.indicator :where(.indicator-item){right:0;left:auto;top:0;bottom:auto;--tw-translate-x:50%;--tw-translate-y:-50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.indicator :where(.indicator-item.indicator-start){right:auto;left:0;--tw-translate-x:-50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.indicator :where(.indicator-item.indicator-center){right:50%;left:50%;--tw-translate-x:-50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.indicator :where(.indicator-item.indicator-end){right:0;left:auto;--tw-translate-x:50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.indicator :where(.indicator-item.indicator-bottom){top:auto;bottom:0;--tw-translate-y:50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.indicator :where(.indicator-item.indicator-middle){top:50%;bottom:50%;--tw-translate-y:-50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.indicator :where(.indicator-item.indicator-top){top:0;bottom:auto;--tw-translate-y:-50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.steps-horizontal .step{display:grid;grid-template-columns:repeat(1,minmax(0,1fr));grid-template-rows:repeat(2,minmax(0,1fr));place-items:center;text-align:center}.steps-vertical .step{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));grid-template-rows:repeat(1,minmax(0,1fr))}.avatar.online:before{content:"";position:absolute;z-index:10;display:block;border-radius:9999px;--tw-bg-opacity:1;background-color:hsl(var(--su) / var(--tw-bg-opacity));width:15%;height:15%;top:7%;right:7%;box-shadow:0 0 0 2px hsl(var(--b1))}.avatar.offline:before{content:"";position:absolute;z-index:10;display:block;border-radius:9999px;--tw-bg-opacity:1;background-color:hsl(var(--b3,var(--b2)) / var(--tw-bg-opacity));width:15%;height:15%;top:7%;right:7%;box-shadow:0 0 0 2px hsl(var(--b1))}.btn-group .btn:not(:first-child):not(:last-child),.btn-group.btn-group-horizontal .btn:not(:first-child):not(:last-child){border-top-left-radius:0;border-top-right-radius:0;border-bottom-left-radius:0;border-bottom-right-radius:0}.btn-group .btn:first-child:not(:last-child),.btn-group.btn-group-horizontal .btn:first-child:not(:last-child){margin-left:-1px;margin-top:0;border-top-left-radius:var(--rounded-btn,.5rem);border-top-right-radius:0;border-bottom-left-radius:var(--rounded-btn,.5rem);border-bottom-right-radius:0}.btn-group .btn:last-child:not(:first-child),.btn-group.btn-group-horizontal .btn:last-child:not(:first-child){border-top-left-radius:0;border-top-right-radius:var(--rounded-btn,.5rem);border-bottom-left-radius:0;border-bottom-right-radius:var(--rounded-btn,.5rem)}.btn-group.btn-group-vertical .btn:first-child:not(:last-child){margin-left:0;margin-top:-1px;border-top-left-radius:var(--rounded-btn,.5rem);border-top-right-radius:var(--rounded-btn,.5rem);border-bottom-left-radius:0;border-bottom-right-radius:0}.btn-group.btn-group-vertical .btn:last-child:not(:first-child){border-top-left-radius:0;border-top-right-radius:0;border-bottom-left-radius:var(--rounded-btn,.5rem);border-bottom-right-radius:var(--rounded-btn,.5rem)}.steps-horizontal .step{grid-template-rows:40px 1fr;grid-template-columns:auto;min-width:4rem}.steps-horizontal .step:before{height:.5rem;width:100%;--tw-translate-y:0px;--tw-translate-x:0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));content:"";margin-left:-100%}.steps-vertical .step{gap:.5rem;grid-template-columns:40px 1fr;grid-template-rows:auto;min-height:4rem;justify-items:start}.steps-vertical .step:before{height:100%;width:.5rem;--tw-translate-y:-50%;--tw-translate-x:-50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));margin-left:50%}.hover\:bg-white:hover{--tw-bg-opacity:1!important;background-color:rgb(255 255 255 / var(--tw-bg-opacity))!important}.hover\:text-red:hover{--tw-text-opacity:1!important;color:rgb(246 41 88 / var(--tw-text-opacity))!important}@media (min-width:640px){.sm\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))!important}.sm\:space-y-12>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0!important;margin-top:calc(3rem * calc(1 - var(--tw-space-y-reverse)))!important;margin-bottom:calc(3rem * var(--tw-space-y-reverse))!important}}@media (min-width:768px){.md\:gap-x-6{-moz-column-gap:1.5rem!important;column-gap:1.5rem!important}}@media (min-width:1024px){.lg\:mb-0{margin-bottom:0!important}.lg\:h-24{height:6rem!important}.lg\:h-auto{height:auto!important}.lg\:w-24{width:6rem!important}.lg\:max-w-5xl{max-width:64rem!important}.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))!important}.lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))!important}.lg\:flex-nowrap{flex-wrap:nowrap!important}.lg\:gap-x-8{-moz-column-gap:2rem!important;column-gap:2rem!important}.lg\:gap-y-12{row-gap:3rem!important}.lg\:gap-y-0{row-gap:0!important}.lg\:gap-y-8{row-gap:2rem!important}.lg\:gap-y-1{row-gap:.25rem!important}.lg\:p-10{padding:2.5rem!important}.lg\:text-sm{font-size:.875rem!important;line-height:1.25rem!important}.lg\:text-2{font-size:2rem!important}}@media (min-width:1280px){.xl\:mb-0{margin-bottom:0!important}.xl\:grid-cols-5{grid-template-columns:repeat(5,minmax(0,1fr))!important}.xl\:flex-nowrap{flex-wrap:nowrap!important}.xl\:text-5xl{font-size:3rem!important;line-height:1!important}}/*! ->>>>>>> new-addon-structure * This source file is part of the open source project * ExpressionEngine User Guide (https://github.com/ExpressionEngine/ExpressionEngine-User-Guide) * @@ -73,18 +69,14 @@ * @link https://expressionengine.com/ * @copyright Copyright (c) 2003-2020, EllisLab, Inc. (https://packettide.com) * @license https://expressionengine.com/license Licensed under Apache License, Version 2.0 - */.sidebar-container{position:fixed;left:0;top:50px;bottom:0;background-color:#f7f7fa;width:100%;z-index:30;display:none}@media only screen and (min-width:900px){.sidebar-container{display:block;height:calc(100% - 70px);top:70px;width:360px}}@media only screen and (min-width:1200px){.sidebar-container{min-width:360px;width:360px;width:calc(((100% - 1400px)/ 2) + 360px)}}.sidebar{margin-left:0;width:100%;height:100%;position:relative;background-color:#f7f7fa;display:flex;flex-direction:column}@media only screen and (min-width:900px){.sidebar{width:360px;float:right}}.sidebar-toc{overflow-y:auto;margin:0 0 1px 0;position:relative;padding-bottom:15px}.sidebar-search{width:100%;padding:20px}.sidebar-search .algolia-autocomplete{width:100%}.search-input{width:100%;padding:6px 30px 6px 10px;border-radius:5px;border:1px solid #e6e6f0;outline:0;background:url(images/search.svg) no-repeat;background-position:right 10px center;transition:border 150ms ease,box-shadow 150ms ease;background-color:#fff}.search-input:focus{border:1px solid #4a62ff;box-shadow:0 8px 10px -10px rgba(52,22,201,.1),0 5px 10px rgba(19,114,198,.04)}.search-input::placeholder{color:#747f8f;opacity:1}.sidebar-sponsor{display:none;padding-top:20px}.sidebar-sponsor p{font-weight:700}.sidebar-sponsor img{padding-top:10px}.sidebar ul{list-style-type:none;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Roboto,Arial,sans-serif}.sidebar-toc{font-size:.98em}.sidebar-toc>ul{padding:0 0 0 40px;margin:0}.sidebar-toc li ul{margin:8px 0 0 1em;padding-left:1em;border-left:1px solid #e2e5ee}.sidebar-toc li{margin-bottom:5px;color:#544e72;padding-right:10px}.sidebar-toc>ul>li{margin:0 0 10px 0;font-size:.95em}.sidebar-toc>ul>li>a{color:#757698;font-weight:700}.sidebar-toc>li>li{font-size:.95em}.sidebar-toc li.active>a{color:#f62958}.sidebar-toc li a{transition:all 250ms ease;display:block}.sidebar-toc li a:hover{color:#f62958;cursor:pointer}.sidebar-toc li[data-active-page=true]>a{color:#f62958}.sidebar h2{text-transform:uppercase;letter-spacing:1px;font-size:90%;color:#afb0bf;font-weight:700;margin-bottom:15px;padding-left:20px;padding-right:10px}.sidebar h2:not(:first-of-type){margin-top:30px}/*! + */.sidebar-container{position:fixed;left:0;top:50px;bottom:0;background-color:#f7f7fa;width:100%;z-index:30;display:none}@media only screen and (min-width:900px){.sidebar-container{display:block;height:calc(100% - 70px);top:70px;width:280px}}@media only screen and (min-width:1200px){.sidebar-container{min-width:280px;width:280px;width:calc(((100% - 1200px)/ 2) + 280px)}}.sidebar{margin-left:0;width:100%;height:100%;position:relative;background-color:#f7f7fa;display:flex;flex-direction:column}@media only screen and (min-width:900px){.sidebar{width:280px;float:right}}.sidebar-toc{overflow-y:auto;margin:0 0 1px 0;position:relative;padding-bottom:15px}.sidebar-search{width:100%;padding:20px}.sidebar-search .algolia-autocomplete{width:100%}.search-input{width:100%;padding:6px 30px 6px 10px;border-radius:5px;border:1px solid #e6e6f0;outline:0;background:url(images/search.svg) no-repeat;background-position:right 10px center;transition:border 150ms ease,box-shadow 150ms ease;background-color:#fff}.search-input:focus{border:1px solid #4a62ff;box-shadow:0 8px 10px -10px rgba(52,22,201,.1),0 5px 10px rgba(19,114,198,.04)}.search-input::placeholder{color:#747f8f;opacity:1}.sidebar-sponsor{display:none;padding-top:20px}.sidebar-sponsor p{font-weight:700}.sidebar-sponsor img{padding-top:10px}.sidebar ul{list-style-type:none;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Roboto,Arial,sans-serif}.sidebar-toc{font-size:.98em}.sidebar-toc>ul{padding:0 0 0 40px;margin:0}.sidebar-toc li ul{margin:8px 0 0 1em;padding-left:1em;border-left:1px solid #e2e5ee}.sidebar-toc li{margin-bottom:5px;color:#544e72;padding-right:10px}.sidebar-toc>ul>li{margin:0 0 10px 0;font-size:.95em}.sidebar-toc>ul>li>a{color:#757698;font-weight:700}.sidebar-toc>li>li{font-size:.95em}.sidebar-toc li.active>a{color:#f62958}.sidebar-toc li a{transition:all 250ms ease;display:block}.sidebar-toc li a:hover{color:#f62958;cursor:pointer}.sidebar-toc li[data-active-page=true]>a{color:#f62958}.sidebar h2{text-transform:uppercase;letter-spacing:1px;font-size:90%;color:#afb0bf;font-weight:700;margin-bottom:15px;padding-left:20px;padding-right:10px}.sidebar h2:not(:first-of-type){margin-top:30px}/*! * This source file is part of the open source project * ExpressionEngine User Guide (https://github.com/ExpressionEngine/ExpressionEngine-User-Guide) * * @link https://expressionengine.com/ * @copyright Copyright (c) 2003-2020, EllisLab, Inc. (https://packettide.com) * @license https://expressionengine.com/license Licensed under Apache License, Version 2.0 -<<<<<<< HEAD */.docs-content-wrapper{margin:0 auto 0 auto;padding-top:50px;height:100%;min-height:100vh;width:100%;max-width:1200px}.docs-content-wrapper .content{padding:16px 30px 20px 30px}@media only screen and (min-width:900px){.docs-content-wrapper{padding-top:70px;padding-left:280px}.docs-content-wrapper .content{padding:32px 40px 40px 40px;padding:45px 80px 40px 80px}}.docs-content-wrapper .content{font-size:1rem}@media only screen and (min-width:1200px){.docs-content-wrapper .content{font-size:.97rem}}.docs-content-wrapper .content blockquote,.docs-content-wrapper .content div,.docs-content-wrapper .content h1,.docs-content-wrapper .content h2,.docs-content-wrapper .content h3,.docs-content-wrapper .content h4,.docs-content-wrapper .content h5,.docs-content-wrapper .content h6,.docs-content-wrapper .content img,.docs-content-wrapper .content ol,.docs-content-wrapper .content p,.docs-content-wrapper .content pre,.docs-content-wrapper .content table,.docs-content-wrapper .content ul{margin-bottom:1.5em}.docs-content-wrapper .content .copyright{color:#9099a6;margin:0;font-size:small}.docs-content-wrapper .content .copyright a{color:inherit}.docs-content-wrapper .content .copyright a:hover{color:#225aba}.docs-content-wrapper .content h1,.docs-content-wrapper .content h2,.docs-content-wrapper .content h3,.docs-content-wrapper .content h4,.docs-content-wrapper .content h5,.docs-content-wrapper .content h6{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Roboto,Arial,sans-serif;margin-top:2em;font-weight:700;line-height:1.2;margin-bottom:.75em}.docs-content-wrapper .content h1 a,.docs-content-wrapper .content h2 a,.docs-content-wrapper .content h3 a,.docs-content-wrapper .content h4 a,.docs-content-wrapper .content h5 a,.docs-content-wrapper .content h6 a{color:inherit}.docs-content-wrapper .content h1 code,.docs-content-wrapper .content h2 code,.docs-content-wrapper .content h3 code,.docs-content-wrapper .content h4 code,.docs-content-wrapper .content h5 code,.docs-content-wrapper .content h6 code{font-family:"Source Code Pro",SFMono-Regular,Menlo,"Courier New",monospace;font-weight:600;font-size:.9em;background:0 0;color:inherit;padding:0}.docs-content-wrapper .content h1 .anchor,.docs-content-wrapper .content h2 .anchor,.docs-content-wrapper .content h3 .anchor,.docs-content-wrapper .content h4 .anchor,.docs-content-wrapper .content h5 .anchor,.docs-content-wrapper .content h6 .anchor{display:block;position:relative;top:-150px;visibility:hidden}.docs-content-wrapper .content h1{font-size:2.5em}.docs-content-wrapper .content h2{font-weight:600;font-size:1.75em;margin-top:50px;padding-top:50px;border-top:1px solid #ebecf4}.docs-content-wrapper .content h3{font-size:1.5em}.docs-content-wrapper .content h4{font-size:1.33em}.docs-content-wrapper .content h5,.docs-content-wrapper .content h6{font-size:1.165em}.docs-content-wrapper .content p{margin-bottom:20px;font-size:1em}.docs-content-wrapper .content small,.docs-content-wrapper .content small code{font-size:.85em!important}.docs-content-wrapper .content div{margin-bottom:20px}.docs-content-wrapper .content a{color:#5379da}.docs-content-wrapper .content a:hover{border-bottom:1px dotted #225aba}.docs-content-wrapper .content img{border-radius:5px;max-width:100%}.docs-content-wrapper .content code{font-family:"Source Code Pro",SFMono-Regular,Menlo,"Courier New",monospace;padding:.2em .4em;word-wrap:break-word;overflow-wrap:break-word;border-radius:3px;background-color:#f3f5f8;font-size:.9em;color:#484963}.docs-content-wrapper .content pre{font-family:"Source Code Pro",SFMono-Regular,Menlo,"Courier New",monospace;overflow-x:auto;padding:1rem 1.5rem;border-radius:3px;display:block;background:#f3f5f8;color:#585470!important;position:relative;font-size:.9em}.docs-content-wrapper .content pre ::selection{color:inherit;background:#a7abcd}.docs-content-wrapper .content pre ::-moz-selection{color:inherit;background:#a7abcd}.docs-content-wrapper .content pre .hljs-title{color:#845cc1;font-weight:400}.docs-content-wrapper .content pre .hljs-name{color:#d8d5ed;color:#f78c6c}.docs-content-wrapper .content pre .hljs-tag{color:#585470}.docs-content-wrapper .content pre .hljs-tag .hljs-name{color:#f44c71}.docs-content-wrapper .content pre .hljs-template-tag{color:#585470}.docs-content-wrapper .content pre .hljs-template-tag .hljs-name{color:#4271ae}.docs-content-wrapper .content pre .hljs-attr{color:#7d4dff}.docs-content-wrapper .content pre .hljs-built_in,.docs-content-wrapper .content pre .hljs-section,.docs-content-wrapper .content pre .hljs-selector-tag{color:#fb9e00}.docs-content-wrapper .content pre .hljs-keyword{color:#fb9e00;color:#fc2658}.docs-content-wrapper .content pre .hljs,.docs-content-wrapper .content pre .hljs-subst{color:#e3dfff}.docs-content-wrapper .content pre .hljs-string{color:#3e8a51!important}.docs-content-wrapper .content pre .hljs-addition,.docs-content-wrapper .content pre .hljs-attribute,.docs-content-wrapper .content pre .hljs-bullet,.docs-content-wrapper .content pre .hljs-code,.docs-content-wrapper .content pre .hljs-deletion,.docs-content-wrapper .content pre .hljs-quote,.docs-content-wrapper .content pre .hljs-regexp,.docs-content-wrapper .content pre .hljs-selector-attr,.docs-content-wrapper .content pre .hljs-selector-pseudo,.docs-content-wrapper .content pre .hljs-string,.docs-content-wrapper .content pre .hljs-symbol{color:#918baf}.docs-content-wrapper .content pre .hljs-addition{color:#55a532;background-color:#eaffea}.docs-content-wrapper .content pre .hljs-deletion{color:#bd2c00;background-color:#ffecec}.docs-content-wrapper .content pre .hljs-selector-class{color:#e4931a}.docs-content-wrapper .content pre .hljs-selector-id{color:#f06634}.docs-content-wrapper .content pre .hljs-meta,.docs-content-wrapper .content pre .hljs-meta-string{color:#fb9e00}.docs-content-wrapper .content pre .hljs-comment{color:#a2a2ba}.docs-content-wrapper .content pre .hljs-literal,.docs-content-wrapper .content pre .hljs-name,.docs-content-wrapper .content pre .hljs-selector-tag,.docs-content-wrapper .content pre .hljs-strong{color:#fc2658;color:#7748ce;font-weight:400}.docs-content-wrapper .content pre .hljs-literal,.docs-content-wrapper .content pre .hljs-number{color:#fa658d}.docs-content-wrapper .content pre .hljs-emphasis{font-style:italic;font-style:normal!important}.docs-content-wrapper .content pre .hljs-strong{font-weight:700}.docs-content-wrapper .content pre code{padding:0;border:none;font-size:inherit;color:inherit;word-wrap:initial;background:0 0;overflow-wrap:initial}.docs-content-wrapper .content pre code{padding:0;border:none;font-size:inherit;color:inherit;word-wrap:initial;background:0 0}.docs-content-wrapper .content .table-wrapper{overflow-x:auto;max-width:100%;border:1px solid #ebecf4;margin-bottom:20px}.docs-content-wrapper .content .table-wrapper table{margin-bottom:0}.docs-content-wrapper .content table{width:100%;border-collapse:collapse;font-size:.8em}@media only screen and (min-width:900px){.docs-content-wrapper .content table{font-size:.95em}}.docs-content-wrapper .content table td,.docs-content-wrapper .content table th{border-bottom:1px solid #ebecf4;border-right:1px solid #ebecf4;padding:10px 16px}.docs-content-wrapper .content table td:last-child,.docs-content-wrapper .content table th:last-child{border-right:none}.docs-content-wrapper .content table th{text-align:left;background-color:#f7f8fa}.docs-content-wrapper .content table tr:last-child td{border-bottom:none}.docs-content-wrapper .content ol,.docs-content-wrapper .content ul{list-style-position:outside;margin:0 0 20px 20px;padding:0}.docs-content-wrapper .content ol ol,.docs-content-wrapper .content ol ul,.docs-content-wrapper .content ul ol,.docs-content-wrapper .content ul ul{margin-top:10px;margin-bottom:0}.docs-content-wrapper .content li{margin-bottom:10px}.docs-content-wrapper .content blockquote{padding-left:1.25em;font-size:1.1em}.docs-content-wrapper .content blockquote,.docs-content-wrapper .content blockquote p{color:#8b879a;margin-top:0;line-height:1.5;border-left:4px solid #ebecf4}.docs-content-wrapper .content hr{border:none;height:1px;border-top:1px solid #ebecf4;margin:2em 0}.docs-content-wrapper .content .video-wrapper{position:relative;padding-bottom:56.25%;padding-top:25px;height:0}.docs-content-wrapper .content .video-wrapper iframe{position:absolute;top:0;left:0;width:100%;height:100%}.docs-content-wrapper .content .table-of-contents{margin-bottom:20px}.docs-content-wrapper .content .table-of-contents.collapse{display:inline-block!important}.docs-content-wrapper .content .table-of-contents>ul{list-style-type:none;margin:20px 0 0 20px;visibility:visible!important}.docs-content-wrapper .content .table-of-contents>ul li{margin-bottom:5px}.docs-content-wrapper .content .table-of-contents>ul li code{padding:0;border:none;color:inherit;background:0 0}.docs-content-wrapper .content .table-of-contents>ul li::before{content:'# ';color:rgba(83,121,218,.9);font-weight:100!important}.docs-content-wrapper .content .table-of-contents>ul li ul{list-style-type:none;margin:5px 0 0 20px}.docs-content-wrapper .content .table-of-contents .toc-overview-button{display:inline-block;border:1px solid #225aba;border-radius:5px;color:#225aba;font-weight:500;padding:5px 14px;cursor:pointer;visibility:visible!important}.docs-content-wrapper .content h1:first-child{margin-top:.2em}.docs-content-wrapper .content .docs-footer{display:flex;align-items:center;font-size:1em}.docs-content-wrapper .content .docs-contribute-to-page-button{transition:all 250ms ease;background:#f3f5fa;margin-left:auto;border-radius:5px;padding:6px 10px}.docs-content-wrapper .content .docs-contribute-to-page-button i{margin-right:5px}.docs-content-wrapper .content .docs-contribute-to-page-button:hover{border-bottom:0!important;background:#5379da;color:#fff}.code-parameter h1,.code-parameter h2,.code-parameter h3,.code-parameter h4,.code-parameter h5,.code-parameter h6{font-family:"Source Code Pro",SFMono-Regular,Menlo,"Courier New",monospace!important;font-size:.95em!important;margin-bottom:.5em!important;margin-top:0!important}.code-parameter p{font-size:.95em!important}.code-parameter+.code-parameter{padding-top:1.5em;border-top:1px solid #ebecf4}.code-example{display:flex;flex-direction:column;border-radius:5px;overflow:hidden}.code-example pre{margin:0!important;border-radius:0!important}.code-example .code-example__example{border:1px solid #ebecf4;padding:1.5rem;margin:0;border-radius:5px;border-bottom-right-radius:0;border-bottom-left-radius:0;border-bottom-width:0}.code-example .code-example__example:nth-child(1):nth-last-child(1){border-bottom-right-radius:5px;border-bottom-left-radius:5px;border-bottom-width:1px}.code-example .code-example__title{border-radius:5px;border-bottom-right-radius:0;border-bottom-left-radius:0;margin:0!important;padding:.5em 1em;background:rgba(209,217,232,.5);text-transform:uppercase;font-size:.85rem;color:#544e72}.code-example pre+.code-example__example{border-top:none;border-bottom-width:1px;border-top-right-radius:0;border-top-left-radius:0;border-bottom-right-radius:5px;border-bottom-left-radius:5px}.alert.alert--hint{border-radius:3px;border:1px solid #7cca6d;padding:1.5em 2em;box-shadow:0 5px 30px -20px rgba(46,98,35,.2);color:#544e72;line-height:1.5;margin-bottom:20px;display:block;background:0 0}.alert.alert--hint>*{display:initial}.alert.alert--hint .alert__title{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Roboto,Arial,sans-serif;display:block;margin-bottom:5px;color:#489a37;font-weight:700}.alert.alert--success{border-radius:3px;border:1px solid #7cca6d;padding:1.5em 2em;box-shadow:0 5px 30px -20px rgba(46,98,35,.2);color:#544e72;line-height:1.5;margin-bottom:20px;display:block;background:0 0}.alert.alert--success>*{display:initial}.alert.alert--success .alert__title{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Roboto,Arial,sans-serif;display:block;margin-bottom:5px;color:#489a37;font-weight:700}.alert.alert--warn{border-radius:3px;border:1px solid #ffd042;padding:1.5em 2em;box-shadow:0 5px 30px -20px rgba(143,107,0,.2);color:#544e72;line-height:1.5;margin-bottom:20px;display:block;background:0 0}.alert.alert--warn>*{display:initial}.alert.alert--warn .alert__title{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Roboto,Arial,sans-serif;display:block;margin-bottom:5px;color:#dba400;font-weight:700}.alert.alert--error{border-radius:3px;border:1px solid #fb8484;padding:1.5em 2em;box-shadow:0 5px 30px -20px rgba(199,6,6,.2);color:#544e72;line-height:1.5;margin-bottom:20px;display:block;background:0 0}.alert.alert--error>*{display:initial}.alert.alert--error .alert__title{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Roboto,Arial,sans-serif;display:block;margin-bottom:5px;color:#f92020;font-weight:700}.alert.alert--empty{border-radius:3px;border:1px solid #7e9ee2;padding:1.5em 2em;box-shadow:0 5px 30px -20px rgba(32,67,141,.2);color:#544e72;line-height:1.5;margin-bottom:20px;display:block;background:0 0;text-align:center;border-style:dashed}.alert.alert--empty>*{display:initial}.alert.alert--empty .alert__title{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Roboto,Arial,sans-serif;display:block;margin-bottom:5px;color:#2f61cb;font-weight:700}/*! -======= - */.docs-content-wrapper{margin:0 auto 0 auto;padding-top:50px;height:100%;min-height:100vh;width:100%;max-width:1200px}.docs-content-wrapper .content{padding:16px 30px 20px 30px}@media only screen and (min-width:900px){.docs-content-wrapper{padding-top:70px;padding-left:360px}.docs-content-wrapper .content{padding:32px 40px 40px 40px;padding:0 80px 40px 20px}}.docs-content-wrapper .content{font-size:1rem}@media only screen and (min-width:1200px){.docs-content-wrapper .content{font-size:.97rem;padding:0 80px 40px 0}}.docs-content-wrapper .content blockquote,.docs-content-wrapper .content div,.docs-content-wrapper .content h1,.docs-content-wrapper .content h2,.docs-content-wrapper .content h3,.docs-content-wrapper .content h4,.docs-content-wrapper .content h5,.docs-content-wrapper .content h6,.docs-content-wrapper .content img,.docs-content-wrapper .content ol,.docs-content-wrapper .content p,.docs-content-wrapper .content pre,.docs-content-wrapper .content table,.docs-content-wrapper .content ul{margin-bottom:1.5em}.docs-content-wrapper .content .copyright{color:#9099a6;margin:0;font-size:small}.docs-content-wrapper .content .copyright a{color:inherit}.docs-content-wrapper .content .copyright a:hover{color:#225aba}.docs-content-wrapper .content h1,.docs-content-wrapper .content h2,.docs-content-wrapper .content h3,.docs-content-wrapper .content h4,.docs-content-wrapper .content h5,.docs-content-wrapper .content h6{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Roboto,Arial,sans-serif;margin-top:2em;font-weight:700;line-height:1.2;margin-bottom:.75em}.docs-content-wrapper .content h1 a,.docs-content-wrapper .content h2 a,.docs-content-wrapper .content h3 a,.docs-content-wrapper .content h4 a,.docs-content-wrapper .content h5 a,.docs-content-wrapper .content h6 a{color:inherit}.docs-content-wrapper .content h1 code,.docs-content-wrapper .content h2 code,.docs-content-wrapper .content h3 code,.docs-content-wrapper .content h4 code,.docs-content-wrapper .content h5 code,.docs-content-wrapper .content h6 code{font-family:"Source Code Pro",SFMono-Regular,Menlo,"Courier New",monospace;font-weight:600;font-size:.9em;background:0 0;color:inherit;padding:0}.docs-content-wrapper .content h1 .anchor,.docs-content-wrapper .content h2 .anchor,.docs-content-wrapper .content h3 .anchor,.docs-content-wrapper .content h4 .anchor,.docs-content-wrapper .content h5 .anchor,.docs-content-wrapper .content h6 .anchor{display:block;position:relative;top:-150px;visibility:hidden}.docs-content-wrapper .content h1{font-size:2.5em}.docs-content-wrapper .content h2{font-weight:600;font-size:1.75em;margin-top:50px;padding-top:50px;border-top:1px solid #ebecf4}.docs-content-wrapper .content h3{font-size:1.5em}.docs-content-wrapper .content h4{font-size:1.33em}.docs-content-wrapper .content h5,.docs-content-wrapper .content h6{font-size:1.165em}.docs-content-wrapper .content p{margin-bottom:20px;font-size:1em}.docs-content-wrapper .content small,.docs-content-wrapper .content small code{font-size:.85em!important}.docs-content-wrapper .content div{margin-bottom:20px}.docs-content-wrapper .content a{color:#5379da}.docs-content-wrapper .content a:hover{border-bottom:1px dotted #225aba}.docs-content-wrapper .content img{border-radius:5px;max-width:100%}.docs-content-wrapper .content code{font-family:"Source Code Pro",SFMono-Regular,Menlo,"Courier New",monospace;padding:.2em .4em;word-wrap:break-word;overflow-wrap:break-word;border-radius:3px;background-color:#f3f5f8;font-size:.9em;color:#484963}.docs-content-wrapper .content pre{font-family:"Source Code Pro",SFMono-Regular,Menlo,"Courier New",monospace;overflow-x:auto;padding:1rem 1.5rem;border-radius:3px;display:block;background:#f3f5f8;color:#585470!important;position:relative;font-size:.9em}.docs-content-wrapper .content pre ::selection{color:inherit;background:#a7abcd}.docs-content-wrapper .content pre ::-moz-selection{color:inherit;background:#a7abcd}.docs-content-wrapper .content pre .hljs-title{color:#845cc1;font-weight:400}.docs-content-wrapper .content pre .hljs-name{color:#d8d5ed;color:#f78c6c}.docs-content-wrapper .content pre .hljs-tag{color:#585470}.docs-content-wrapper .content pre .hljs-tag .hljs-name{color:#f44c71}.docs-content-wrapper .content pre .hljs-template-tag{color:#585470}.docs-content-wrapper .content pre .hljs-template-tag .hljs-name{color:#4271ae}.docs-content-wrapper .content pre .hljs-attr{color:#7d4dff}.docs-content-wrapper .content pre .hljs-built_in,.docs-content-wrapper .content pre .hljs-section,.docs-content-wrapper .content pre .hljs-selector-tag{color:#fb9e00}.docs-content-wrapper .content pre .hljs-keyword{color:#fb9e00;color:#fc2658}.docs-content-wrapper .content pre .hljs,.docs-content-wrapper .content pre .hljs-subst{color:#e3dfff}.docs-content-wrapper .content pre .hljs-string{color:#3e8a51!important}.docs-content-wrapper .content pre .hljs-addition,.docs-content-wrapper .content pre .hljs-attribute,.docs-content-wrapper .content pre .hljs-bullet,.docs-content-wrapper .content pre .hljs-code,.docs-content-wrapper .content pre .hljs-deletion,.docs-content-wrapper .content pre .hljs-quote,.docs-content-wrapper .content pre .hljs-regexp,.docs-content-wrapper .content pre .hljs-selector-attr,.docs-content-wrapper .content pre .hljs-selector-pseudo,.docs-content-wrapper .content pre .hljs-string,.docs-content-wrapper .content pre .hljs-symbol{color:#918baf}.docs-content-wrapper .content pre .hljs-addition{color:#55a532;background-color:#eaffea}.docs-content-wrapper .content pre .hljs-deletion{color:#bd2c00;background-color:#ffecec}.docs-content-wrapper .content pre .hljs-selector-class{color:#e4931a}.docs-content-wrapper .content pre .hljs-selector-id{color:#f06634}.docs-content-wrapper .content pre .hljs-meta,.docs-content-wrapper .content pre .hljs-meta-string{color:#fb9e00}.docs-content-wrapper .content pre .hljs-comment{color:#a2a2ba}.docs-content-wrapper .content pre .hljs-literal,.docs-content-wrapper .content pre .hljs-name,.docs-content-wrapper .content pre .hljs-selector-tag,.docs-content-wrapper .content pre .hljs-strong{color:#fc2658;color:#7748ce;font-weight:400}.docs-content-wrapper .content pre .hljs-literal,.docs-content-wrapper .content pre .hljs-number{color:#fa658d}.docs-content-wrapper .content pre .hljs-emphasis{font-style:italic;font-style:normal!important}.docs-content-wrapper .content pre .hljs-strong{font-weight:700}.docs-content-wrapper .content pre code{padding:0;border:none;font-size:inherit;color:inherit;word-wrap:initial;background:0 0;overflow-wrap:initial}.docs-content-wrapper .content pre code{padding:0;border:none;font-size:inherit;color:inherit;word-wrap:initial;background:0 0}.docs-content-wrapper .content .table-wrapper{overflow-x:auto;max-width:100%;border:1px solid #ebecf4;margin-bottom:20px}.docs-content-wrapper .content .table-wrapper table{margin-bottom:0}.docs-content-wrapper .content table{width:100%;border-collapse:collapse;font-size:.8em}@media only screen and (min-width:900px){.docs-content-wrapper .content table{font-size:.95em}}.docs-content-wrapper .content table td,.docs-content-wrapper .content table th{border-bottom:1px solid #ebecf4;border-right:1px solid #ebecf4;padding:10px 16px}.docs-content-wrapper .content table td:last-child,.docs-content-wrapper .content table th:last-child{border-right:none}.docs-content-wrapper .content table th{text-align:left;background-color:#f7f8fa}.docs-content-wrapper .content table tr:last-child td{border-bottom:none}.docs-content-wrapper .content ol,.docs-content-wrapper .content ul{list-style-position:outside;margin:0 0 20px 20px;padding:0}.docs-content-wrapper .content ol ol,.docs-content-wrapper .content ol ul,.docs-content-wrapper .content ul ol,.docs-content-wrapper .content ul ul{margin-top:10px;margin-bottom:0}.docs-content-wrapper .content li{margin-bottom:10px}.docs-content-wrapper .content blockquote{padding-left:1.25em;font-size:1.1em}.docs-content-wrapper .content blockquote,.docs-content-wrapper .content blockquote p{color:#8b879a;margin-top:0;line-height:1.5;border-left:4px solid #ebecf4}.docs-content-wrapper .content hr{border:none;height:1px;border-top:1px solid #ebecf4;margin:2em 0}.docs-content-wrapper .content .video-wrapper{position:relative;padding-bottom:56.25%;padding-top:25px;height:0}.docs-content-wrapper .content .video-wrapper iframe{position:absolute;top:0;left:0;width:100%;height:100%}.docs-content-wrapper .content .table-of-contents{margin-bottom:20px}.docs-content-wrapper .content .table-of-contents>ul{list-style-type:none;margin:20px 0 0 20px}.docs-content-wrapper .content .table-of-contents>ul li{margin-bottom:5px}.docs-content-wrapper .content .table-of-contents>ul li code{padding:0;border:none;color:inherit;background:0 0}.docs-content-wrapper .content .table-of-contents>ul li::before{content:'# ';color:rgba(83,121,218,.9);font-weight:100!important}.docs-content-wrapper .content .table-of-contents>ul li ul{list-style-type:none;margin:5px 0 0 20px}.docs-content-wrapper .content .table-of-contents .toc-overview-button{display:inline-block;border:1px solid #225aba;border-radius:5px;color:#225aba;font-weight:500;padding:5px 14px;cursor:pointer}.docs-content-wrapper .content h1:first-child{margin-top:.2em}.docs-content-wrapper .content .docs-footer{display:flex;align-items:center;font-size:1em}.docs-content-wrapper .content .docs-contribute-to-page-button{transition:all 250ms ease;background:#f3f5fa;margin-left:auto;border-radius:5px;padding:6px 10px}.docs-content-wrapper .content .docs-contribute-to-page-button i{margin-right:5px}.docs-content-wrapper .content .docs-contribute-to-page-button:hover{border-bottom:0!important;background:#5379da;color:#fff}.code-parameter h1,.code-parameter h2,.code-parameter h3,.code-parameter h4,.code-parameter h5,.code-parameter h6{font-family:"Source Code Pro",SFMono-Regular,Menlo,"Courier New",monospace!important;font-size:.95em!important;margin-bottom:.5em!important;margin-top:0!important}.code-parameter p{font-size:.95em!important}.code-parameter+.code-parameter{padding-top:1.5em;border-top:1px solid #ebecf4}.code-example{display:flex;flex-direction:column;border-radius:5px;overflow:hidden}.code-example pre{margin:0!important;border-radius:0!important}.code-example .code-example__example{border:1px solid #ebecf4;padding:1.5rem;margin:0;border-radius:5px;border-bottom-right-radius:0;border-bottom-left-radius:0;border-bottom-width:0}.code-example .code-example__example:nth-child(1):nth-last-child(1){border-bottom-right-radius:5px;border-bottom-left-radius:5px;border-bottom-width:1px}.code-example .code-example__title{border-radius:5px;border-bottom-right-radius:0;border-bottom-left-radius:0;margin:0!important;padding:.5em 1em;background:rgba(209,217,232,.5);text-transform:uppercase;font-size:.85rem;color:#544e72}.code-example pre+.code-example__example{border-top:none;border-bottom-width:1px;border-top-right-radius:0;border-top-left-radius:0;border-bottom-right-radius:5px;border-bottom-left-radius:5px}.alert.alert--hint{border-radius:3px;border:1px solid #7cca6d;padding:1.5em 2em;box-shadow:0 5px 30px -20px rgba(46,98,35,.2);color:#544e72;line-height:1.5;margin-bottom:20px;display:block;background:0 0}.alert.alert--hint>*{display:initial}.alert.alert--hint .alert__title{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Roboto,Arial,sans-serif;display:block;margin-bottom:5px;color:#489a37;font-weight:700}.alert.alert--success{border-radius:3px;border:1px solid #7cca6d;padding:1.5em 2em;box-shadow:0 5px 30px -20px rgba(46,98,35,.2);color:#544e72;line-height:1.5;margin-bottom:20px;display:block;background:0 0}.alert.alert--success>*{display:initial}.alert.alert--success .alert__title{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Roboto,Arial,sans-serif;display:block;margin-bottom:5px;color:#489a37;font-weight:700}.alert.alert--warn{border-radius:3px;border:1px solid #ffd042;padding:1.5em 2em;box-shadow:0 5px 30px -20px rgba(143,107,0,.2);color:#544e72;line-height:1.5;margin-bottom:20px;display:block;background:0 0}.alert.alert--warn>*{display:initial}.alert.alert--warn .alert__title{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Roboto,Arial,sans-serif;display:block;margin-bottom:5px;color:#dba400;font-weight:700}.alert.alert--error{border-radius:3px;border:1px solid #fb8484;padding:1.5em 2em;box-shadow:0 5px 30px -20px rgba(199,6,6,.2);color:#544e72;line-height:1.5;margin-bottom:20px;display:block;background:0 0}.alert.alert--error>*{display:initial}.alert.alert--error .alert__title{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Roboto,Arial,sans-serif;display:block;margin-bottom:5px;color:#f92020;font-weight:700}.alert.alert--empty{border-radius:3px;border:1px solid #7e9ee2;padding:1.5em 2em;box-shadow:0 5px 30px -20px rgba(32,67,141,.2);color:#544e72;line-height:1.5;margin-bottom:20px;display:block;background:0 0;text-align:center;border-style:dashed}.alert.alert--empty>*{display:initial}.alert.alert--empty .alert__title{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Roboto,Arial,sans-serif;display:block;margin-bottom:5px;color:#2f61cb;font-weight:700}/*! ->>>>>>> new-addon-structure * This source file is part of the open source project * ExpressionEngine User Guide (https://github.com/ExpressionEngine/ExpressionEngine-User-Guide) * diff --git a/theme/assets-src/styles/_tailwind_build.css b/theme/assets-src/styles/_tailwind_build.css index d74370552..9b929b732 100644 --- a/theme/assets-src/styles/_tailwind_build.css +++ b/theme/assets-src/styles/_tailwind_build.css @@ -1931,14 +1931,6 @@ html { resize: both !important; } -.list-disc { - list-style-type: disc !important; -} - -.list-decimal { - list-style-type: decimal !important; -} - .grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)) !important; } diff --git a/theme/assets-src/styles/docs-content.less b/theme/assets-src/styles/docs-content.less index a175371b8..2ce1e90bb 100755 --- a/theme/assets-src/styles/docs-content.less +++ b/theme/assets-src/styles/docs-content.less @@ -25,10 +25,9 @@ .content { padding: @p-lg * @p-y @p-lg @p-lg @p-lg; - padding: 0px 80px 40px 20px; + padding: 45px 80px 40px 80px; } } - } @@ -40,7 +39,6 @@ @media @m-desktop { font-size: 0.97rem; - padding: 0px 80px 40px 0px; } p, h1, h2, h3, h4, h5, h6, img, ul, ol, pre, div, table, blockquote { diff --git a/theme/assets-src/styles/sidebar.less b/theme/assets-src/styles/sidebar.less index be6859ca4..cf7abbcd0 100755 --- a/theme/assets-src/styles/sidebar.less +++ b/theme/assets-src/styles/sidebar.less @@ -31,7 +31,7 @@ @media @m-desktop { min-width: @sidebar-width; width: @sidebar-width; - width: calc(((100% - 1400px) / 2) + @sidebar-width); + width: calc(((100% - 1200px) / 2) + @sidebar-width); } } diff --git a/theme/assets-src/styles/variables.less b/theme/assets-src/styles/variables.less index 49f289d04..44e35adca 100755 --- a/theme/assets-src/styles/variables.less +++ b/theme/assets-src/styles/variables.less @@ -68,7 +68,7 @@ @corner-radius: 5px; @type-padding: 20px; -@sidebar-width: 360px; +@sidebar-width: 280px; @p-xs: 5px; @p-sm: 10px;