diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 34b34a64..a548fd86 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -1,4 +1,4 @@ -name: CI +name: PHP8.0-CI on: push: diff --git a/.github/workflows/php5.6.yml b/.github/workflows/php5.6.yml new file mode 100644 index 00000000..8a81edff --- /dev/null +++ b/.github/workflows/php5.6.yml @@ -0,0 +1,34 @@ +name: PHP5.6-CI + +on: + push: + branches: [ php56-backport ] + pull_request: + branches: [ php56-backport ] + +jobs: + build: + + runs-on: ubuntu-20.04 + + steps: + - uses: actions/checkout@v2 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '5.6' + extensions: mbstring, xml, curl + tools: phpunit, composer + + - name: Validate composer.json and composer.lock + run: composer validate --strict + + - name: Install dependencies + run: composer install --prefer-dist --no-progress + + # Add a test script to composer.json, for instance: "test": "vendor/bin/phpunit" + # Docs: https://getcomposer.org/doc/articles/scripts.md + + - name: Run test suite + run: composer run-script test diff --git a/.github/workflows/php7.4.yml b/.github/workflows/php7.4.yml new file mode 100644 index 00000000..b15192e9 --- /dev/null +++ b/.github/workflows/php7.4.yml @@ -0,0 +1,34 @@ +name: PHP7.4-CI + +on: + push: + branches: [ php56-backport ] + pull_request: + branches: [ php56-backport ] + +jobs: + build: + + runs-on: ubuntu-20.04 + + steps: + - uses: actions/checkout@v2 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '7.4' + extensions: mbstring, xml, curl + tools: phpunit, composer + + - name: Validate composer.json and composer.lock + run: composer validate --strict + + - name: Install dependencies + run: composer install --prefer-dist --no-progress + + # Add a test script to composer.json, for instance: "test": "vendor/bin/phpunit" + # Docs: https://getcomposer.org/doc/articles/scripts.md + + - name: Run test suite + run: composer run-script test diff --git a/.github/workflows/php8.1.yml b/.github/workflows/php8.1.yml new file mode 100644 index 00000000..9347e48b --- /dev/null +++ b/.github/workflows/php8.1.yml @@ -0,0 +1,34 @@ +name: PHP8.1-CI + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + + runs-on: ubuntu-20.04 + + steps: + - uses: actions/checkout@v2 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.1' + extensions: mbstring, xml, curl + tools: phpunit, composer + + - name: Validate composer.json and composer.lock + run: composer validate --strict + + - name: Install dependencies + run: composer install --prefer-dist --no-progress + + # Add a test script to composer.json, for instance: "test": "vendor/bin/phpunit" + # Docs: https://getcomposer.org/doc/articles/scripts.md + + - name: Run test suite + run: composer run-script test diff --git a/.github/workflows/php8.2.yml b/.github/workflows/php8.2.yml new file mode 100644 index 00000000..2f7c0c5e --- /dev/null +++ b/.github/workflows/php8.2.yml @@ -0,0 +1,34 @@ +name: PHP8.2-CI + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + + runs-on: ubuntu-20.04 + + steps: + - uses: actions/checkout@v2 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.2' + extensions: mbstring, xml, curl + tools: phpunit, composer + + - name: Validate composer.json and composer.lock + run: composer validate --strict + + - name: Install dependencies + run: composer install --prefer-dist --no-progress + + # Add a test script to composer.json, for instance: "test": "vendor/bin/phpunit" + # Docs: https://getcomposer.org/doc/articles/scripts.md + + - name: Run test suite + run: composer run-script test diff --git a/README.md b/README.md index 6caa8653..4b5fae47 100755 --- a/README.md +++ b/README.md @@ -31,10 +31,16 @@ A CSS parser, beautifier and minifier written in PHP. It supports the following install using [Composer](https://getcomposer.org/) +### PHP version >= 8.0 ```bash $ composer require tbela99/css ``` +### PHP version >= 5.6 +```bash +$ composer require "tbela99/css:dev-php56-backport" +``` + ## Requirements - PHP version >= 8.0 on master branch. @@ -578,6 +584,19 @@ $stylesheet->append('https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/ ## Performance +### Utility methods + +The renderer class provides utility methods to format css data + +```php + +$css = \TBela\CSS\Renderer::fromFile($url_or_file, $renderOptions = [], $parseOptions = []); +# +$css = \TBela\CSS\Renderer::fromString($css, $renderOptions = [], $parseOptions = []); +``` + +### Manual parsing and rendering + parsing and rendering ast is 3x faster than parsing an element. ```php @@ -592,9 +611,12 @@ echo (string) $parser; // or render minified css $renderer = new Renderer(['compress' => true]); -echo $renderer->renderAst($parser->getAst()); -// slower - will build an Element +echo $renderer->renderAst($parser); +# or +echo $renderer->renderAst($parser->getAst()); +# or +// slower - will build a stylesheet object echo $renderer->render($parser->parse()); ``` ## Parser Options diff --git a/benchmark/parseast.php b/benchmark/parseast.php index 26eddb52..6a1b6434 100755 --- a/benchmark/parseast.php +++ b/benchmark/parseast.php @@ -13,4 +13,4 @@ $parser = (new Parser($css, $options)); -//$ast = $parser->getAst(); \ No newline at end of file +$ast = $parser->getAst(); \ No newline at end of file diff --git a/bin/benchmark.sh b/bin/benchmark.sh index b1e6fe45..cab9d1d7 100755 --- a/bin/benchmark.sh +++ b/bin/benchmark.sh @@ -1,4 +1,16 @@ ##!/bin/bash -x -v + +# css files +if [ $# -gt 0 ]; then + + files=() + + while [ $# -gt 0 ]; do + files+=($(realpath "$1")) + shift + done +fi + cd $(dirname "$0") cd ../benchmark @@ -8,8 +20,9 @@ if [ ! -d ./vendor/sabberworm ]; then exit 1 fi -# css files -files=$(ls -d ../test/perf_files/*.css) +if [ -z "$files" ]; then + files=$(ls -d ../test/perf_files/*.css) +fi # files size sizes=() # measure test time @@ -25,7 +38,7 @@ size() { # shellcheck disable=SC2046 result=$($(echo "$prog" | cut -f1 -d ' ') $(echo "$prog" | cut -f2 -s -d ' ') "$@" 2>&1) output=$(convert_file_size "${#result}") -# echo $(lpad "$output" 6)" ("$(lpad $(echo "scale=2; ${#result} * 100 / "$(stat -c%s $(echo "$@" | rev | cut -d ' ' -f1 | rev)) | bc) 5)"%)" + # echo $(lpad "$output" 6)" ("$(lpad $(echo "scale=2; ${#result} * 100 / "$(stat -c%s $(echo "$@" | rev | cut -d ' ' -f1 | rev)) | bc) 5)"%)" echo $(lpad "$output" 9) } # left pad with '\ ', will pipe to sed 's/\\ //g' to display the actual space character diff --git a/bin/runtest.sh b/bin/runtest.sh index 44a604bc..0fb505c3 100755 --- a/bin/runtest.sh +++ b/bin/runtest.sh @@ -9,7 +9,7 @@ ## #set -x DIR=$(cd -P -- "$(dirname $(readlink -f "$0"))" && pwd -P) -cd "$DIR" +cd "$DIR" || exit 1 if [ ! -f "../vendor/bin/phpunit" ]; then echo "please go to "$(dirname "$DIR")" and run 'composer install'" @@ -17,6 +17,17 @@ if [ ! -f "../vendor/bin/phpunit" ]; then fi unset DIR +TEST_PCNTL=$(php -m | grep pcntl) + +php=$(command -v php"$PHP_VER") + +#test_timeout=60 + +if [ -z "$php" ] +then + echo "php$PHP_VER is not installed" + exit 1 +fi # # #../phpunit.phar --bootstrap autoload.php src/*.php @@ -33,7 +44,22 @@ run() { # # set -x - php -dmemory_limit=512M ../vendor/bin/phpunit -v --enforce-time-limit --colors=always --bootstrap autoload.php --testdox --fail-on-skipped --fail-on-risky --fail-on-incomplete "$@" + result="0" +# timeout $test_timeout + PROCESS_ENGINE="process" "$php" ../vendor/bin/phpunit -v --colors=always --bootstrap autoload.php --testdox --fail-on-skipped --fail-on-risky --fail-on-incomplete "$@" || result="1" + + if [ "$result" -gt 0 ] + then + return "$result" + fi + + if [ -n "$TEST_PCNTL" ]; then +# timeout $test_timeout + PROCESS_ENGINE="thread" "$php" ../vendor/bin/phpunit -v --colors=always --bootstrap autoload.php --testdox --fail-on-skipped --fail-on-risky --fail-on-incomplete "$@" || result="1" + # unset $PROCESS_ENGINE + fi + + return "$result" # set +x } @@ -42,12 +68,12 @@ testName() { fname=$(basename "$1" | awk -F . '{print $1}') # strip the Test suffix - echo ""${fname%Test} + echo "${fname%Test}" } # # -cd ../test +cd ../test || exit 1 #pwd # # diff --git a/cli/css-parser b/cli/css-parser index 45b60911..71e78f40 100755 --- a/cli/css-parser +++ b/cli/css-parser @@ -10,7 +10,7 @@ use TBela\CSS\Renderer; require __DIR__ . '/../vendor/autoload.php'; // only use from the cli -if (php_sapi_name() != 'cli') { +if (PHP_SAPI != 'cli') { fwrite(STDERR, 'this program must be executed from the cli'); exit(1); @@ -34,28 +34,32 @@ spl_autoload_register(function ($name) { } }); -$exe = basename($argv[0]); $cli = new Args($argv); +$exe = $cli->getExe(); try { + $data = json_decode(file_get_contents(__DIR__ . '/../package.json'), JSON_OBJECT_AS_ARRAY); + $metadata = json_decode(file_get_contents(__DIR__ . '/../composer.json'), JSON_OBJECT_AS_ARRAY); + + $version = sprintf("%s (version %s) +Copyright (C) %s %s. +Dual licensed under MIT or LGPL v3\n", $cli->getExe(), $data['version'], date('Y'), implode(', ', array_map(function ($author) { + + return $author['name']; + }, $metadata['authors']))); + $cli-> - setStrict(true)-> - add('version', 'print version number', Option::BOOL, 'v', multiple: false) - ->addGroup('internal', "internal commands are used by the multithreading feature:\n", true) - ->add('parse-ast-src', 'src value of the ast nodes', Option::STRING, multiple: false, group: 'internal') - ->add('parse-ast-position-index', 'initial index position of the ast nodes', Option::INT, multiple: false, group: 'internal') - ->add('parse-ast-position-line', 'initial line number of the ast nodes', Option::INT, multiple: false, group: 'internal') - ->add('parse-ast-position-column', 'initial column number of the ast nodes', Option::INT, multiple: false, group: 'internal') + setStrict(true) ->addGroup('parse', "Parse options:\n") ->add('capture-errors', 'ignore parse error', Option::BOOL, 'e', multiple: false, group: 'parse') ->add('flatten-import', 'process @import', Option::BOOL, 'm', multiple: false, group: 'parse') ->add('parse-allow-duplicate-rules', 'allow duplicate rule', Option::BOOL, 'p', multiple: false, group: 'parse') ->add('parse-allow-duplicate-declarations', 'allow duplicate declaration', type: Option::AUTO, alias: 'd', multiple: false, group: 'parse') - ->add('file', 'input css file or url', Option::STRING, 'f', multiple: true, group: 'parse') + ->add('file', 'input css file or url', Option::STRING, 'f', group: 'parse') ->add('parse-multi-processing', 'enable multi-processing parser', Option::BOOL, 'M', multiple: false, defaultValue: true, group: 'parse') ->add('parse-children-process', 'maximum children process', Option::INT, 'P', multiple: false, defaultValue: 20, group: 'parse') - ->add('input-format', 'input format: json (ast), serialize (PHP serialized ast)', Option::STRING, 'I', multiple: false, defaultValue: Option::STRING, options: [Option::STRING, 'json', 'serialize'], group: 'parse') + ->add('input-format', 'input format: json (ast), string (plain css)', Option::STRING, 'I', multiple: false, defaultValue: 'string', options: ['string', 'json'], group: 'parse') ->addGroup('render', "Render options:\n") ->add('css-level', 'css color module', Option::INT, 'l', multiple: false, defaultValue: 4, options: [3, 4], group: 'render') ->add('charset', 'remove @charset', Option::BOOL, 'S', multiple: false, defaultValue: false, group: 'render') @@ -70,8 +74,11 @@ try { ->add('convert-color', 'convert colors', Option::AUTO, 't', multiple: false, options: [true, false, 'hex', 'rgb', 'rgba', 'hsl', 'hsla', 'hwb', 'device-cmyk'], group: 'render') ->add('output', 'output file name', Option::STRING, 'o', multiple: false, group: 'render') ->add('ast', 'dump ast as JSON', Option::BOOL, 'a', multiple: false, group: 'render') - ->add('output-format', "output export format. string (css), json (ast), serialize (PHP serialized ast), json-array, serialize-array", Option::STRING, 'F', multiple: false, defaultValue: Option::STRING, options: [Option::STRING, 'json', 'serialize', 'json-array', 'serialize-array'], dependsOn: 'input-format', group: 'render') ->add('render-multi-processing', "enable multi-processing renderer", Option::BOOL, 'R', multiple: false, defaultValue: true, group: 'render') + ->setVersion($version) + ->alias('version', 'v') + ->help() + ->alias('help', 'h') ->parse(); $parseOptions = []; @@ -80,19 +87,6 @@ try { $groups = $cli->getGroups(); $args = $cli->getArguments(); - if (isset($args['version'])) { - - $data = json_decode(file_get_contents(__DIR__ . '/../package.json'), JSON_OBJECT_AS_ARRAY); - $metadata = json_decode(file_get_contents(__DIR__ . '/../composer.json'), JSON_OBJECT_AS_ARRAY); - - echo sprintf("%s (version %s) -Copyright (C) %s %s. -Dual licensed under MIT or LGPL v3\n", $exe, $data['version'], date('Y'), implode(', ', array_map(function ($author) { - - return $author['name']; - }, $metadata['authors']))); - exit (0); - } $pipeIn = !stream_isatty(STDIN); $pipeOut = !stream_isatty(STDOUT); @@ -109,7 +103,7 @@ Dual licensed under MIT or LGPL v3\n", $exe, $data['version'], date('Y'), implod if (!empty($args['_'])) { - fwrite(STDERR, "> notice: ignoring inline css\n".json_encode($args['_'], JSON_PRETTY_PRINT)."\n"); + fwrite(STDERR, "> notice: ignoring inline css\n" . json_encode($args['_'], JSON_PRETTY_PRINT) . "\n"); } } @@ -134,20 +128,6 @@ Dual licensed under MIT or LGPL v3\n", $exe, $data['version'], date('Y'), implod } } - foreach (array_keys($groups['internal']['arguments']) as $key) { - - if (isset($args[$key])) { - - if (str_starts_with($key, 'parse-')) { - - $parseOptions[str_replace(['parse-', '-'], ['', '_'], $key)] = $args[$key]; - } else if (str_starts_with($key, 'render-')) { - - $renderOptions[str_replace(['render-', '-'], ['', '_'], $key)] = $args[$key]; - } - } - } - function read_input(array $parseOptions, $inFile): Generator { @@ -156,13 +136,10 @@ Dual licensed under MIT or LGPL v3\n", $exe, $data['version'], date('Y'), implod $data = file_get_contents('php://stdin'); yield match ($parseOptions['input_format']) { - 'serialize' => unserialize($data), 'json' => json_decode($data), default => $data, }; - } - - else { + } else { foreach ((array)$inFile as $file) { @@ -170,14 +147,12 @@ Dual licensed under MIT or LGPL v3\n", $exe, $data['version'], date('Y'), implod $data = file_get_contents($file); - } - else { + } else { $data = Parser\Helper::fetchContent($file); } yield $file => match ($parseOptions['input_format']) { - 'serialize' => unserialize($data), 'json' => json_decode($data), default => $data, }; @@ -188,7 +163,7 @@ Dual licensed under MIT or LGPL v3\n", $exe, $data['version'], date('Y'), implod $ast = []; $parser = new Parser(options: $parseOptions); - if ($parseOptions['input_format'] == Option::STRING) { + if ($parseOptions['input_format'] == 'string') { if ($inFile) { @@ -212,14 +187,12 @@ Dual licensed under MIT or LGPL v3\n", $exe, $data['version'], date('Y'), implod } $ast = [$parser->getAst()]; - } - - else { + } else { // ast foreach (read_input($parseOptions, $inFile) as $data) { - $ast[] = $data; // $renderer->renderAst($data); + $ast[] = $data; } } @@ -238,41 +211,12 @@ Dual licensed under MIT or LGPL v3\n", $exe, $data['version'], date('Y'), implod if ($outFile != STDOUT) { $renderer->save($root, $outFile); - } - - else if (in_array($renderOptions['output_format'], ['json-array', 'serialize-array'])) { - - $output = []; - - foreach ($root->children as $node) { - - $nodes = $node->type == 'Stylesheet' ? $node->children : [$node]; + } else { - foreach ($nodes as $nod) { - - $css = $renderer->renderAst($nod); - - if (isset($output[$css])) { - - unset($output[$css]); - } - - $output[$css] = $css; - } - } - - fwrite($outFile, $renderOptions['output_format'] == 'serialize-array' ? serialize($output) : json_encode($output, empty($renderOptions['compress']) ? JSON_PRETTY_PRINT : 0)); - } - - else { - - fwrite($outFile, match ($renderOptions['output_format']) { - 'json' => json_encode(count($ast) == 1 ? $ast[0] : $root, empty($renderOptions['compress']) ? JSON_PRETTY_PRINT : 0), - 'serialize' => serialize(count($ast) == 1 ? $ast[0] : $root), - default => $renderer->renderAst($root)}); + fwrite($outFile, !empty($renderOptions['ast']) ? json_encode(count($ast) == 1 ? $ast[0] : $root, empty($renderOptions['compress']) ? JSON_PRETTY_PRINT : 0) + : $renderer->renderAst($root)); } - } catch (ValueError|UnexpectedValueException|InvalidArgumentException $e) { fwrite(STDERR, $e->getMessage() . "\n"); @@ -283,7 +227,7 @@ Dual licensed under MIT or LGPL v3\n", $exe, $data['version'], date('Y'), implod fwrite(STDERR, sprintf("%s: %s\nTry '%s --help'\n", $exe, $e->getMessage(), $exe)); $code = $e->getCode(); exit($code == 0 ? 1 : $code); -} catch (Exception $e) { +} catch (Throwable $e) { fwrite(STDERR, $e->getMessage() . "\n"); exit(1); diff --git a/composer.json b/composer.json index 847e9603..5321a013 100755 --- a/composer.json +++ b/composer.json @@ -7,6 +7,8 @@ "parser", "minifier", "beautifier", + "css-parser", + "stylesheet", "Ast", "PHP" ], @@ -36,14 +38,16 @@ } }, "suggest": { - "ext-curl": "*" + "ext-curl": "*", + "ext-sockets": "*", + "ext-pcntl": "*" }, "require": { "php": ">=8.0", "axy/sourcemap": "^0.1.5", "ext-json": "*", "ext-mbstring": "*", - "symfony/process": "^6.0" + "opis/closure": "^3.6" }, "scripts": { "test": "./bin/runtest.sh" diff --git a/docs/README.md b/docs/README.md index 539f8b22..4d3470a7 100755 --- a/docs/README.md +++ b/docs/README.md @@ -31,10 +31,16 @@ A CSS parser, beautifier and minifier written in PHP. It supports the following install using [Composer](https://getcomposer.org/) +### PHP version >= 8.0 ```bash $ composer require tbela99/css ``` +### PHP version >= 5.6 +```bash +$ composer require "tbela99/css:dev-php56-backport" +``` + ## Requirements - PHP version >= 8.0 on master branch. @@ -578,6 +584,19 @@ $stylesheet->append('https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/ ## Performance +### Utility methods + +The renderer class provides utility methods to format css data + +```php + +$css = \TBela\CSS\Renderer::fromFile($url_or_file, $renderOptions = [], $parseOptions = []); +# +$css = \TBela\CSS\Renderer::fromString($css, $renderOptions = [], $parseOptions = []); +``` + +### Manual parsing and rendering + parsing and rendering ast is 3x faster than parsing an element. ```php @@ -592,9 +611,12 @@ echo (string) $parser; // or render minified css $renderer = new Renderer(['compress' => true]); -echo $renderer->renderAst($parser->getAst()); -// slower - will build an Element +echo $renderer->renderAst($parser); +# or +echo $renderer->renderAst($parser->getAst()); +# or +// slower - will build a stylesheet object echo $renderer->render($parser->parse()); ``` ## Parser Options diff --git a/docs/api/html/d0/d18/classTBela_1_1CSS_1_1Value_1_1Color.png b/docs/api/html/d0/d18/classTBela_1_1CSS_1_1Value_1_1Color.png index 7db2f456..84ede509 100644 Binary files a/docs/api/html/d0/d18/classTBela_1_1CSS_1_1Value_1_1Color.png and b/docs/api/html/d0/d18/classTBela_1_1CSS_1_1Value_1_1Color.png differ diff --git a/docs/api/html/d0/d24/interfaceTBela_1_1CSS_1_1Event_1_1EventInterface.png b/docs/api/html/d0/d24/interfaceTBela_1_1CSS_1_1Event_1_1EventInterface.png index 3df2767b..413d28d2 100644 Binary files a/docs/api/html/d0/d24/interfaceTBela_1_1CSS_1_1Event_1_1EventInterface.png and b/docs/api/html/d0/d24/interfaceTBela_1_1CSS_1_1Event_1_1EventInterface.png differ diff --git a/docs/api/html/d0/d91/classTBela_1_1CSS_1_1Value_1_1BackgroundSize.png b/docs/api/html/d0/d91/classTBela_1_1CSS_1_1Value_1_1BackgroundSize.png index 94fa7779..c692b547 100644 Binary files a/docs/api/html/d0/d91/classTBela_1_1CSS_1_1Value_1_1BackgroundSize.png and b/docs/api/html/d0/d91/classTBela_1_1CSS_1_1Value_1_1BackgroundSize.png differ diff --git a/docs/api/html/d0/da5/interfaceTBela_1_1CSS_1_1Interfaces_1_1InvalidTokenInterface.png b/docs/api/html/d0/da5/interfaceTBela_1_1CSS_1_1Interfaces_1_1InvalidTokenInterface.png index 4cf47e91..067d351b 100644 Binary files a/docs/api/html/d0/da5/interfaceTBela_1_1CSS_1_1Interfaces_1_1InvalidTokenInterface.png and b/docs/api/html/d0/da5/interfaceTBela_1_1CSS_1_1Interfaces_1_1InvalidTokenInterface.png differ diff --git a/docs/api/html/d0/df9/classTBela_1_1CSS_1_1Query_1_1TokenSelectorValueAttributeFunctionGeneric.png b/docs/api/html/d0/df9/classTBela_1_1CSS_1_1Query_1_1TokenSelectorValueAttributeFunctionGeneric.png index 743bf8a9..56469d1f 100644 Binary files a/docs/api/html/d0/df9/classTBela_1_1CSS_1_1Query_1_1TokenSelectorValueAttributeFunctionGeneric.png and b/docs/api/html/d0/df9/classTBela_1_1CSS_1_1Query_1_1TokenSelectorValueAttributeFunctionGeneric.png differ diff --git a/docs/api/html/d1/d24/classTBela_1_1CSS_1_1Value_1_1BackgroundClip.png b/docs/api/html/d1/d24/classTBela_1_1CSS_1_1Value_1_1BackgroundClip.png index 5be40841..87b96547 100644 Binary files a/docs/api/html/d1/d24/classTBela_1_1CSS_1_1Value_1_1BackgroundClip.png and b/docs/api/html/d1/d24/classTBela_1_1CSS_1_1Value_1_1BackgroundClip.png differ diff --git a/docs/api/html/d1/d3e/interfaceTBela_1_1CSS_1_1Interfaces_1_1RenderablePropertyInterface.png b/docs/api/html/d1/d3e/interfaceTBela_1_1CSS_1_1Interfaces_1_1RenderablePropertyInterface.png index 35e8dc9e..789169fb 100644 Binary files a/docs/api/html/d1/d3e/interfaceTBela_1_1CSS_1_1Interfaces_1_1RenderablePropertyInterface.png and b/docs/api/html/d1/d3e/interfaceTBela_1_1CSS_1_1Interfaces_1_1RenderablePropertyInterface.png differ diff --git a/docs/api/html/d1/d43/interfaceTBela_1_1CSS_1_1Interfaces_1_1RenderableInterface.png b/docs/api/html/d1/d43/interfaceTBela_1_1CSS_1_1Interfaces_1_1RenderableInterface.png index 570081b2..31dfc85a 100644 Binary files a/docs/api/html/d1/d43/interfaceTBela_1_1CSS_1_1Interfaces_1_1RenderableInterface.png and b/docs/api/html/d1/d43/interfaceTBela_1_1CSS_1_1Interfaces_1_1RenderableInterface.png differ diff --git a/docs/api/html/d1/d60/classTBela_1_1CSS_1_1Value_1_1Separator.png b/docs/api/html/d1/d60/classTBela_1_1CSS_1_1Value_1_1Separator.png index b8b6142d..041fa896 100644 Binary files a/docs/api/html/d1/d60/classTBela_1_1CSS_1_1Value_1_1Separator.png and b/docs/api/html/d1/d60/classTBela_1_1CSS_1_1Value_1_1Separator.png differ diff --git a/docs/api/html/d1/d6f/classTBela_1_1CSS_1_1Query_1_1TokenSelectorValueAttributeIndex.png b/docs/api/html/d1/d6f/classTBela_1_1CSS_1_1Query_1_1TokenSelectorValueAttributeIndex.png index 25f39ba1..34c6d65e 100644 Binary files a/docs/api/html/d1/d6f/classTBela_1_1CSS_1_1Query_1_1TokenSelectorValueAttributeIndex.png and b/docs/api/html/d1/d6f/classTBela_1_1CSS_1_1Query_1_1TokenSelectorValueAttributeIndex.png differ diff --git a/docs/api/html/d1/d70/classTBela_1_1CSS_1_1Value_1_1BackgroundOrigin.png b/docs/api/html/d1/d70/classTBela_1_1CSS_1_1Value_1_1BackgroundOrigin.png index 4c6d1a9a..e85c6809 100644 Binary files a/docs/api/html/d1/d70/classTBela_1_1CSS_1_1Value_1_1BackgroundOrigin.png and b/docs/api/html/d1/d70/classTBela_1_1CSS_1_1Value_1_1BackgroundOrigin.png differ diff --git a/docs/api/html/d1/d75/classTBela_1_1CSS_1_1Query_1_1TokenSelectorValueWhitespace.png b/docs/api/html/d1/d75/classTBela_1_1CSS_1_1Query_1_1TokenSelectorValueWhitespace.png index 5f050ac3..e324c872 100644 Binary files a/docs/api/html/d1/d75/classTBela_1_1CSS_1_1Query_1_1TokenSelectorValueWhitespace.png and b/docs/api/html/d1/d75/classTBela_1_1CSS_1_1Query_1_1TokenSelectorValueWhitespace.png differ diff --git a/docs/api/html/d1/d80/classTBela_1_1CSS_1_1Value_1_1CssAttribute.png b/docs/api/html/d1/d80/classTBela_1_1CSS_1_1Value_1_1CssAttribute.png index bcdfa1cb..6a53430b 100644 Binary files a/docs/api/html/d1/d80/classTBela_1_1CSS_1_1Value_1_1CssAttribute.png and b/docs/api/html/d1/d80/classTBela_1_1CSS_1_1Value_1_1CssAttribute.png differ diff --git a/docs/api/html/d1/dab/classTBela_1_1CSS_1_1Query_1_1TokenSelectorValueAttributeFunctionEmpty.png b/docs/api/html/d1/dab/classTBela_1_1CSS_1_1Query_1_1TokenSelectorValueAttributeFunctionEmpty.png index 4ef4bdcf..73c72391 100644 Binary files a/docs/api/html/d1/dab/classTBela_1_1CSS_1_1Query_1_1TokenSelectorValueAttributeFunctionEmpty.png and b/docs/api/html/d1/dab/classTBela_1_1CSS_1_1Query_1_1TokenSelectorValueAttributeFunctionEmpty.png differ diff --git a/docs/api/html/d1/db9/classTBela_1_1CSS_1_1Query_1_1TokenSelectorValue.png b/docs/api/html/d1/db9/classTBela_1_1CSS_1_1Query_1_1TokenSelectorValue.png index d9176875..1b1eb0b0 100644 Binary files a/docs/api/html/d1/db9/classTBela_1_1CSS_1_1Query_1_1TokenSelectorValue.png and b/docs/api/html/d1/db9/classTBela_1_1CSS_1_1Query_1_1TokenSelectorValue.png differ diff --git a/docs/api/html/d1/dcc/classTBela_1_1CSS_1_1Query_1_1TokenSelectorValueAttributeTest.png b/docs/api/html/d1/dcc/classTBela_1_1CSS_1_1Query_1_1TokenSelectorValueAttributeTest.png index 1cc5c11c..84e3372f 100644 Binary files a/docs/api/html/d1/dcc/classTBela_1_1CSS_1_1Query_1_1TokenSelectorValueAttributeTest.png and b/docs/api/html/d1/dcc/classTBela_1_1CSS_1_1Query_1_1TokenSelectorValueAttributeTest.png differ diff --git a/docs/api/html/d1/de4/classTBela_1_1CSS_1_1Value_1_1FontStretch.png b/docs/api/html/d1/de4/classTBela_1_1CSS_1_1Value_1_1FontStretch.png index eb60246d..72853771 100644 Binary files a/docs/api/html/d1/de4/classTBela_1_1CSS_1_1Value_1_1FontStretch.png and b/docs/api/html/d1/de4/classTBela_1_1CSS_1_1Value_1_1FontStretch.png differ diff --git a/docs/api/html/d2/d6b/classTBela_1_1CSS_1_1Element_1_1Stylesheet.png b/docs/api/html/d2/d6b/classTBela_1_1CSS_1_1Element_1_1Stylesheet.png index 4bfd8308..5fec3fcf 100644 Binary files a/docs/api/html/d2/d6b/classTBela_1_1CSS_1_1Element_1_1Stylesheet.png and b/docs/api/html/d2/d6b/classTBela_1_1CSS_1_1Element_1_1Stylesheet.png differ diff --git a/docs/api/html/d2/da5/classTBela_1_1CSS_1_1Value_1_1FontFamily.png b/docs/api/html/d2/da5/classTBela_1_1CSS_1_1Value_1_1FontFamily.png index ea5689e6..c444ec54 100644 Binary files a/docs/api/html/d2/da5/classTBela_1_1CSS_1_1Value_1_1FontFamily.png and b/docs/api/html/d2/da5/classTBela_1_1CSS_1_1Value_1_1FontFamily.png differ diff --git a/docs/api/html/d2/dbb/classTBela_1_1CSS_1_1Value_1_1BackgroundPosition.png b/docs/api/html/d2/dbb/classTBela_1_1CSS_1_1Value_1_1BackgroundPosition.png index 8266d55d..58ede5f0 100644 Binary files a/docs/api/html/d2/dbb/classTBela_1_1CSS_1_1Value_1_1BackgroundPosition.png and b/docs/api/html/d2/dbb/classTBela_1_1CSS_1_1Value_1_1BackgroundPosition.png differ diff --git a/docs/api/html/d2/dc7/interfaceTBela_1_1CSS_1_1Interfaces_1_1RuleListInterface.png b/docs/api/html/d2/dc7/interfaceTBela_1_1CSS_1_1Interfaces_1_1RuleListInterface.png index 5fb3b2dc..1979a367 100644 Binary files a/docs/api/html/d2/dc7/interfaceTBela_1_1CSS_1_1Interfaces_1_1RuleListInterface.png and b/docs/api/html/d2/dc7/interfaceTBela_1_1CSS_1_1Interfaces_1_1RuleListInterface.png differ diff --git a/docs/api/html/d2/de2/classTBela_1_1CSS_1_1Element_1_1NestingRule.png b/docs/api/html/d2/de2/classTBela_1_1CSS_1_1Element_1_1NestingRule.png index 310af984..4b046405 100644 Binary files a/docs/api/html/d2/de2/classTBela_1_1CSS_1_1Element_1_1NestingRule.png and b/docs/api/html/d2/de2/classTBela_1_1CSS_1_1Element_1_1NestingRule.png differ diff --git a/docs/api/html/d2/dff/classTBela_1_1CSS_1_1Query_1_1TokenSelectorValueAttributeFunction.png b/docs/api/html/d2/dff/classTBela_1_1CSS_1_1Query_1_1TokenSelectorValueAttributeFunction.png index 01576b8b..ecd17683 100644 Binary files a/docs/api/html/d2/dff/classTBela_1_1CSS_1_1Query_1_1TokenSelectorValueAttributeFunction.png and b/docs/api/html/d2/dff/classTBela_1_1CSS_1_1Query_1_1TokenSelectorValueAttributeFunction.png differ diff --git a/docs/api/html/d3/d53/classTBela_1_1CSS_1_1Query_1_1TokenSelectorValueString.png b/docs/api/html/d3/d53/classTBela_1_1CSS_1_1Query_1_1TokenSelectorValueString.png index 14efd22d..f6e87fc9 100644 Binary files a/docs/api/html/d3/d53/classTBela_1_1CSS_1_1Query_1_1TokenSelectorValueString.png and b/docs/api/html/d3/d53/classTBela_1_1CSS_1_1Query_1_1TokenSelectorValueString.png differ diff --git a/docs/api/html/d3/d54/classTBela_1_1CSS_1_1Value_1_1CssString.png b/docs/api/html/d3/d54/classTBela_1_1CSS_1_1Value_1_1CssString.png index bb9b7f99..08a87af6 100644 Binary files a/docs/api/html/d3/d54/classTBela_1_1CSS_1_1Value_1_1CssString.png and b/docs/api/html/d3/d54/classTBela_1_1CSS_1_1Value_1_1CssString.png differ diff --git a/docs/api/html/d3/dd5/classTBela_1_1CSS_1_1Cli_1_1Exceptions_1_1DuplicateArgumentException.png b/docs/api/html/d3/dd5/classTBela_1_1CSS_1_1Cli_1_1Exceptions_1_1DuplicateArgumentException.png index ac2687b7..193b45b9 100644 Binary files a/docs/api/html/d3/dd5/classTBela_1_1CSS_1_1Cli_1_1Exceptions_1_1DuplicateArgumentException.png and b/docs/api/html/d3/dd5/classTBela_1_1CSS_1_1Cli_1_1Exceptions_1_1DuplicateArgumentException.png differ diff --git a/docs/api/html/d4/d84/classTBela_1_1CSS_1_1Value_1_1InvalidCssFunction.png b/docs/api/html/d4/d84/classTBela_1_1CSS_1_1Value_1_1InvalidCssFunction.png index 19d33322..eaf95376 100644 Binary files a/docs/api/html/d4/d84/classTBela_1_1CSS_1_1Value_1_1InvalidCssFunction.png and b/docs/api/html/d4/d84/classTBela_1_1CSS_1_1Value_1_1InvalidCssFunction.png differ diff --git a/docs/api/html/d4/dff/interfaceTBela_1_1CSS_1_1Query_1_1TokenSelectorValueInterface.png b/docs/api/html/d4/dff/interfaceTBela_1_1CSS_1_1Query_1_1TokenSelectorValueInterface.png index 33263278..ab97f620 100644 Binary files a/docs/api/html/d4/dff/interfaceTBela_1_1CSS_1_1Query_1_1TokenSelectorValueInterface.png and b/docs/api/html/d4/dff/interfaceTBela_1_1CSS_1_1Query_1_1TokenSelectorValueInterface.png differ diff --git a/docs/api/html/d5/d3b/interfaceTBela_1_1CSS_1_1Query_1_1TokenSelectorInterface.png b/docs/api/html/d5/d3b/interfaceTBela_1_1CSS_1_1Query_1_1TokenSelectorInterface.png index 437b51e7..3005df91 100644 Binary files a/docs/api/html/d5/d3b/interfaceTBela_1_1CSS_1_1Query_1_1TokenSelectorInterface.png and b/docs/api/html/d5/d3b/interfaceTBela_1_1CSS_1_1Query_1_1TokenSelectorInterface.png differ diff --git a/docs/api/html/d5/d56/classTBela_1_1CSS_1_1Value_1_1FontSize.png b/docs/api/html/d5/d56/classTBela_1_1CSS_1_1Value_1_1FontSize.png index 0579b5b7..817f387d 100644 Binary files a/docs/api/html/d5/d56/classTBela_1_1CSS_1_1Value_1_1FontSize.png and b/docs/api/html/d5/d56/classTBela_1_1CSS_1_1Value_1_1FontSize.png differ diff --git a/docs/api/html/d5/d9e/interfaceTBela_1_1CSS_1_1Interfaces_1_1ElementInterface.png b/docs/api/html/d5/d9e/interfaceTBela_1_1CSS_1_1Interfaces_1_1ElementInterface.png index 9de2f741..9a6c622b 100644 Binary files a/docs/api/html/d5/d9e/interfaceTBela_1_1CSS_1_1Interfaces_1_1ElementInterface.png and b/docs/api/html/d5/d9e/interfaceTBela_1_1CSS_1_1Interfaces_1_1ElementInterface.png differ diff --git a/docs/api/html/d5/dcb/classTBela_1_1CSS_1_1Query_1_1TokenSelectorValueAttributeFunctionContains.png b/docs/api/html/d5/dcb/classTBela_1_1CSS_1_1Query_1_1TokenSelectorValueAttributeFunctionContains.png index 403da13f..37d001ad 100644 Binary files a/docs/api/html/d5/dcb/classTBela_1_1CSS_1_1Query_1_1TokenSelectorValueAttributeFunctionContains.png and b/docs/api/html/d5/dcb/classTBela_1_1CSS_1_1Query_1_1TokenSelectorValueAttributeFunctionContains.png differ diff --git a/docs/api/html/d5/df3/classTBela_1_1CSS_1_1Value_1_1OutlineWidth.png b/docs/api/html/d5/df3/classTBela_1_1CSS_1_1Value_1_1OutlineWidth.png index 756fb179..720d5108 100644 Binary files a/docs/api/html/d5/df3/classTBela_1_1CSS_1_1Value_1_1OutlineWidth.png and b/docs/api/html/d5/df3/classTBela_1_1CSS_1_1Value_1_1OutlineWidth.png differ diff --git a/docs/api/html/d6/d0a/classTBela_1_1CSS_1_1Property_1_1Comment.png b/docs/api/html/d6/d0a/classTBela_1_1CSS_1_1Property_1_1Comment.png index 40dbd788..a889de54 100644 Binary files a/docs/api/html/d6/d0a/classTBela_1_1CSS_1_1Property_1_1Comment.png and b/docs/api/html/d6/d0a/classTBela_1_1CSS_1_1Property_1_1Comment.png differ diff --git a/docs/api/html/d6/d13/interfaceTBela_1_1CSS_1_1Interfaces_1_1ParsableInterface.png b/docs/api/html/d6/d13/interfaceTBela_1_1CSS_1_1Interfaces_1_1ParsableInterface.png index 7b0b097b..923a65fd 100644 Binary files a/docs/api/html/d6/d13/interfaceTBela_1_1CSS_1_1Interfaces_1_1ParsableInterface.png and b/docs/api/html/d6/d13/interfaceTBela_1_1CSS_1_1Interfaces_1_1ParsableInterface.png differ diff --git a/docs/api/html/d6/d63/classTBela_1_1CSS_1_1Query_1_1TokenSelectorValueSeparator.png b/docs/api/html/d6/d63/classTBela_1_1CSS_1_1Query_1_1TokenSelectorValueSeparator.png index 067f3188..16c523be 100644 Binary files a/docs/api/html/d6/d63/classTBela_1_1CSS_1_1Query_1_1TokenSelectorValueSeparator.png and b/docs/api/html/d6/d63/classTBela_1_1CSS_1_1Query_1_1TokenSelectorValueSeparator.png differ diff --git a/docs/api/html/d6/d73/classTBela_1_1CSS_1_1Query_1_1TokenSelectorValueAttributeExpression.png b/docs/api/html/d6/d73/classTBela_1_1CSS_1_1Query_1_1TokenSelectorValueAttributeExpression.png index 146e301f..e2caf14e 100644 Binary files a/docs/api/html/d6/d73/classTBela_1_1CSS_1_1Query_1_1TokenSelectorValueAttributeExpression.png and b/docs/api/html/d6/d73/classTBela_1_1CSS_1_1Query_1_1TokenSelectorValueAttributeExpression.png differ diff --git a/docs/api/html/d6/d86/classTBela_1_1CSS_1_1Value_1_1Whitespace.png b/docs/api/html/d6/d86/classTBela_1_1CSS_1_1Value_1_1Whitespace.png index 52f67db1..0e434194 100644 Binary files a/docs/api/html/d6/d86/classTBela_1_1CSS_1_1Value_1_1Whitespace.png and b/docs/api/html/d6/d86/classTBela_1_1CSS_1_1Value_1_1Whitespace.png differ diff --git a/docs/api/html/d6/ddb/interfaceTBela_1_1CSS_1_1Interfaces_1_1ObjectInterface.png b/docs/api/html/d6/ddb/interfaceTBela_1_1CSS_1_1Interfaces_1_1ObjectInterface.png index 2cfffaa8..ef8493dc 100644 Binary files a/docs/api/html/d6/ddb/interfaceTBela_1_1CSS_1_1Interfaces_1_1ObjectInterface.png and b/docs/api/html/d6/ddb/interfaceTBela_1_1CSS_1_1Interfaces_1_1ObjectInterface.png differ diff --git a/docs/api/html/d6/ddc/classTBela_1_1CSS_1_1Value_1_1OutlineColor.png b/docs/api/html/d6/ddc/classTBela_1_1CSS_1_1Value_1_1OutlineColor.png index 2e01068f..9a9e9a62 100644 Binary files a/docs/api/html/d6/ddc/classTBela_1_1CSS_1_1Value_1_1OutlineColor.png and b/docs/api/html/d6/ddc/classTBela_1_1CSS_1_1Value_1_1OutlineColor.png differ diff --git a/docs/api/html/d6/dde/classTBela_1_1CSS_1_1Value_1_1BackgroundRepeat.png b/docs/api/html/d6/dde/classTBela_1_1CSS_1_1Value_1_1BackgroundRepeat.png index 665bb89d..9683c211 100644 Binary files a/docs/api/html/d6/dde/classTBela_1_1CSS_1_1Value_1_1BackgroundRepeat.png and b/docs/api/html/d6/dde/classTBela_1_1CSS_1_1Value_1_1BackgroundRepeat.png differ diff --git a/docs/api/html/d6/dfd/classTBela_1_1CSS_1_1Element_1_1Comment.png b/docs/api/html/d6/dfd/classTBela_1_1CSS_1_1Element_1_1Comment.png index ff58d52e..dfcebe65 100644 Binary files a/docs/api/html/d6/dfd/classTBela_1_1CSS_1_1Element_1_1Comment.png and b/docs/api/html/d6/dfd/classTBela_1_1CSS_1_1Element_1_1Comment.png differ diff --git a/docs/api/html/d7/d0d/classTBela_1_1CSS_1_1Element_1_1AtRule.png b/docs/api/html/d7/d0d/classTBela_1_1CSS_1_1Element_1_1AtRule.png index 86adfac2..a32f4fd6 100644 Binary files a/docs/api/html/d7/d0d/classTBela_1_1CSS_1_1Element_1_1AtRule.png and b/docs/api/html/d7/d0d/classTBela_1_1CSS_1_1Element_1_1AtRule.png differ diff --git a/docs/api/html/d7/d52/classTBela_1_1CSS_1_1Element_1_1RuleList.png b/docs/api/html/d7/d52/classTBela_1_1CSS_1_1Element_1_1RuleList.png index 2c0c03fe..afb9e26e 100644 Binary files a/docs/api/html/d7/d52/classTBela_1_1CSS_1_1Element_1_1RuleList.png and b/docs/api/html/d7/d52/classTBela_1_1CSS_1_1Element_1_1RuleList.png differ diff --git a/docs/api/html/d7/d86/interfaceTBela_1_1CSS_1_1Query_1_1TokenSelectInterface.png b/docs/api/html/d7/d86/interfaceTBela_1_1CSS_1_1Query_1_1TokenSelectInterface.png index fde50c97..7b6d123a 100644 Binary files a/docs/api/html/d7/d86/interfaceTBela_1_1CSS_1_1Query_1_1TokenSelectInterface.png and b/docs/api/html/d7/d86/interfaceTBela_1_1CSS_1_1Query_1_1TokenSelectInterface.png differ diff --git a/docs/api/html/d8/d0d/classTBela_1_1CSS_1_1Value_1_1BackgroundAttachment.png b/docs/api/html/d8/d0d/classTBela_1_1CSS_1_1Value_1_1BackgroundAttachment.png index d47263c9..2f8a8561 100644 Binary files a/docs/api/html/d8/d0d/classTBela_1_1CSS_1_1Value_1_1BackgroundAttachment.png and b/docs/api/html/d8/d0d/classTBela_1_1CSS_1_1Value_1_1BackgroundAttachment.png differ diff --git a/docs/api/html/d8/d23/classTBela_1_1CSS_1_1Element.png b/docs/api/html/d8/d23/classTBela_1_1CSS_1_1Element.png index e5fa5844..3ae90e35 100644 Binary files a/docs/api/html/d8/d23/classTBela_1_1CSS_1_1Element.png and b/docs/api/html/d8/d23/classTBela_1_1CSS_1_1Element.png differ diff --git a/docs/api/html/d8/d56/classTBela_1_1CSS_1_1Value_1_1FontWeight.png b/docs/api/html/d8/d56/classTBela_1_1CSS_1_1Value_1_1FontWeight.png index ad357521..240d4ad3 100644 Binary files a/docs/api/html/d8/d56/classTBela_1_1CSS_1_1Value_1_1FontWeight.png and b/docs/api/html/d8/d56/classTBela_1_1CSS_1_1Value_1_1FontWeight.png differ diff --git a/docs/api/html/d8/d5c/classTBela_1_1CSS_1_1Value_1_1ShortHand.png b/docs/api/html/d8/d5c/classTBela_1_1CSS_1_1Value_1_1ShortHand.png index c6709114..dba103d3 100644 Binary files a/docs/api/html/d8/d5c/classTBela_1_1CSS_1_1Value_1_1ShortHand.png and b/docs/api/html/d8/d5c/classTBela_1_1CSS_1_1Value_1_1ShortHand.png differ diff --git a/docs/api/html/d8/d61/classTBela_1_1CSS_1_1Query_1_1TokenSelectorValueAttributeFunctionEndswith.png b/docs/api/html/d8/d61/classTBela_1_1CSS_1_1Query_1_1TokenSelectorValueAttributeFunctionEndswith.png index a65128eb..290354ea 100644 Binary files a/docs/api/html/d8/d61/classTBela_1_1CSS_1_1Query_1_1TokenSelectorValueAttributeFunctionEndswith.png and b/docs/api/html/d8/d61/classTBela_1_1CSS_1_1Query_1_1TokenSelectorValueAttributeFunctionEndswith.png differ diff --git a/docs/api/html/d8/d66/classTBela_1_1CSS_1_1Value_1_1Comment.png b/docs/api/html/d8/d66/classTBela_1_1CSS_1_1Value_1_1Comment.png index d7f06b73..2f780356 100644 Binary files a/docs/api/html/d8/d66/classTBela_1_1CSS_1_1Value_1_1Comment.png and b/docs/api/html/d8/d66/classTBela_1_1CSS_1_1Value_1_1Comment.png differ diff --git a/docs/api/html/d8/da7/classTBela_1_1CSS_1_1Element_1_1Declaration.png b/docs/api/html/d8/da7/classTBela_1_1CSS_1_1Element_1_1Declaration.png index dc6ff597..fca6f1e6 100644 Binary files a/docs/api/html/d8/da7/classTBela_1_1CSS_1_1Element_1_1Declaration.png and b/docs/api/html/d8/da7/classTBela_1_1CSS_1_1Element_1_1Declaration.png differ diff --git a/docs/api/html/d8/dab/classTBela_1_1CSS_1_1Value_1_1InvalidComment.png b/docs/api/html/d8/dab/classTBela_1_1CSS_1_1Value_1_1InvalidComment.png index 7ef7d58e..7678d841 100644 Binary files a/docs/api/html/d8/dab/classTBela_1_1CSS_1_1Value_1_1InvalidComment.png and b/docs/api/html/d8/dab/classTBela_1_1CSS_1_1Value_1_1InvalidComment.png differ diff --git a/docs/api/html/d8/db9/classTBela_1_1CSS_1_1Value_1_1FontStyle.png b/docs/api/html/d8/db9/classTBela_1_1CSS_1_1Value_1_1FontStyle.png index f17964e0..945f72a9 100644 Binary files a/docs/api/html/d8/db9/classTBela_1_1CSS_1_1Value_1_1FontStyle.png and b/docs/api/html/d8/db9/classTBela_1_1CSS_1_1Value_1_1FontStyle.png differ diff --git a/docs/api/html/d9/d03/classTBela_1_1CSS_1_1Query_1_1TokenSelectorValueAttributeSelector.png b/docs/api/html/d9/d03/classTBela_1_1CSS_1_1Query_1_1TokenSelectorValueAttributeSelector.png index 3a1bcda8..3a37d2e5 100644 Binary files a/docs/api/html/d9/d03/classTBela_1_1CSS_1_1Query_1_1TokenSelectorValueAttributeSelector.png and b/docs/api/html/d9/d03/classTBela_1_1CSS_1_1Query_1_1TokenSelectorValueAttributeSelector.png differ diff --git a/docs/api/html/d9/d40/classTBela_1_1CSS_1_1Value_1_1CssSrcFormat.png b/docs/api/html/d9/d40/classTBela_1_1CSS_1_1Value_1_1CssSrcFormat.png index 368fd3d3..f55c6c4d 100644 Binary files a/docs/api/html/d9/d40/classTBela_1_1CSS_1_1Value_1_1CssSrcFormat.png and b/docs/api/html/d9/d40/classTBela_1_1CSS_1_1Value_1_1CssSrcFormat.png differ diff --git a/docs/api/html/d9/d4a/classTBela_1_1CSS_1_1Cli_1_1Exceptions_1_1UnknownParameterException.png b/docs/api/html/d9/d4a/classTBela_1_1CSS_1_1Cli_1_1Exceptions_1_1UnknownParameterException.png index 5c9749f9..df6d7d36 100644 Binary files a/docs/api/html/d9/d4a/classTBela_1_1CSS_1_1Cli_1_1Exceptions_1_1UnknownParameterException.png and b/docs/api/html/d9/d4a/classTBela_1_1CSS_1_1Cli_1_1Exceptions_1_1UnknownParameterException.png differ diff --git a/docs/api/html/d9/d56/classTBela_1_1CSS_1_1Element_1_1RuleSet.png b/docs/api/html/d9/d56/classTBela_1_1CSS_1_1Element_1_1RuleSet.png index f1deb3b0..349ae4cb 100644 Binary files a/docs/api/html/d9/d56/classTBela_1_1CSS_1_1Element_1_1RuleSet.png and b/docs/api/html/d9/d56/classTBela_1_1CSS_1_1Element_1_1RuleSet.png differ diff --git a/docs/api/html/d9/d5b/classTBela_1_1CSS_1_1Value_1_1CssFunction.png b/docs/api/html/d9/d5b/classTBela_1_1CSS_1_1Value_1_1CssFunction.png index 34955b26..03b3e71b 100644 Binary files a/docs/api/html/d9/d5b/classTBela_1_1CSS_1_1Value_1_1CssFunction.png and b/docs/api/html/d9/d5b/classTBela_1_1CSS_1_1Value_1_1CssFunction.png differ diff --git a/docs/api/html/d9/d61/classTBela_1_1CSS_1_1Query_1_1TokenSelect.png b/docs/api/html/d9/d61/classTBela_1_1CSS_1_1Query_1_1TokenSelect.png index 8f542b7c..2861506c 100644 Binary files a/docs/api/html/d9/d61/classTBela_1_1CSS_1_1Query_1_1TokenSelect.png and b/docs/api/html/d9/d61/classTBela_1_1CSS_1_1Query_1_1TokenSelect.png differ diff --git a/docs/api/html/d9/d7d/classTBela_1_1CSS_1_1Element_1_1NestingMediaRule.png b/docs/api/html/d9/d7d/classTBela_1_1CSS_1_1Element_1_1NestingMediaRule.png index d570a4c2..b46bf835 100644 Binary files a/docs/api/html/d9/d7d/classTBela_1_1CSS_1_1Element_1_1NestingMediaRule.png and b/docs/api/html/d9/d7d/classTBela_1_1CSS_1_1Element_1_1NestingMediaRule.png differ diff --git a/docs/api/html/da/d44/classTBela_1_1CSS_1_1Value_1_1Number.png b/docs/api/html/da/d44/classTBela_1_1CSS_1_1Value_1_1Number.png index dd2469f4..5decc360 100644 Binary files a/docs/api/html/da/d44/classTBela_1_1CSS_1_1Value_1_1Number.png and b/docs/api/html/da/d44/classTBela_1_1CSS_1_1Value_1_1Number.png differ diff --git a/docs/api/html/da/db5/classTBela_1_1CSS_1_1Query_1_1TokenSelectorValueAttributeFunctionComment.png b/docs/api/html/da/db5/classTBela_1_1CSS_1_1Query_1_1TokenSelectorValueAttributeFunctionComment.png index 4aaade66..95bcef3f 100644 Binary files a/docs/api/html/da/db5/classTBela_1_1CSS_1_1Query_1_1TokenSelectorValueAttributeFunctionComment.png and b/docs/api/html/da/db5/classTBela_1_1CSS_1_1Query_1_1TokenSelectorValueAttributeFunctionComment.png differ diff --git a/docs/api/html/da/db6/classTBela_1_1CSS_1_1Property_1_1Property.png b/docs/api/html/da/db6/classTBela_1_1CSS_1_1Property_1_1Property.png index c43f7b72..cdcb4a1d 100644 Binary files a/docs/api/html/da/db6/classTBela_1_1CSS_1_1Property_1_1Property.png and b/docs/api/html/da/db6/classTBela_1_1CSS_1_1Property_1_1Property.png differ diff --git a/docs/api/html/da/db6/classTBela_1_1CSS_1_1Value_1_1Outline.png b/docs/api/html/da/db6/classTBela_1_1CSS_1_1Value_1_1Outline.png index 7dd7134c..1410fc9b 100644 Binary files a/docs/api/html/da/db6/classTBela_1_1CSS_1_1Value_1_1Outline.png and b/docs/api/html/da/db6/classTBela_1_1CSS_1_1Value_1_1Outline.png differ diff --git a/docs/api/html/da/db8/classTBela_1_1CSS_1_1Cli_1_1Exceptions_1_1MissingParameterException.png b/docs/api/html/da/db8/classTBela_1_1CSS_1_1Cli_1_1Exceptions_1_1MissingParameterException.png index ce4b7ffa..4b50c0ad 100644 Binary files a/docs/api/html/da/db8/classTBela_1_1CSS_1_1Cli_1_1Exceptions_1_1MissingParameterException.png and b/docs/api/html/da/db8/classTBela_1_1CSS_1_1Cli_1_1Exceptions_1_1MissingParameterException.png differ diff --git a/docs/api/html/da/dca/classTBela_1_1CSS_1_1Value_1_1CssUrl.png b/docs/api/html/da/dca/classTBela_1_1CSS_1_1Value_1_1CssUrl.png index 7265a0ba..70deff70 100644 Binary files a/docs/api/html/da/dca/classTBela_1_1CSS_1_1Value_1_1CssUrl.png and b/docs/api/html/da/dca/classTBela_1_1CSS_1_1Value_1_1CssUrl.png differ diff --git a/docs/api/html/da/dff/classTBela_1_1CSS_1_1Query_1_1TokenSelectorValueAttributeFunctionEquals.png b/docs/api/html/da/dff/classTBela_1_1CSS_1_1Query_1_1TokenSelectorValueAttributeFunctionEquals.png index 2371f20a..33b27340 100644 Binary files a/docs/api/html/da/dff/classTBela_1_1CSS_1_1Query_1_1TokenSelectorValueAttributeFunctionEquals.png and b/docs/api/html/da/dff/classTBela_1_1CSS_1_1Query_1_1TokenSelectorValueAttributeFunctionEquals.png differ diff --git a/docs/api/html/db/d5f/classTBela_1_1CSS_1_1Value_1_1Operator.png b/docs/api/html/db/d5f/classTBela_1_1CSS_1_1Value_1_1Operator.png index 1f0c9655..c1ed84d3 100644 Binary files a/docs/api/html/db/d5f/classTBela_1_1CSS_1_1Value_1_1Operator.png and b/docs/api/html/db/d5f/classTBela_1_1CSS_1_1Value_1_1Operator.png differ diff --git a/docs/api/html/db/d67/interfaceTBela_1_1CSS_1_1Interfaces_1_1ValidatorInterface.png b/docs/api/html/db/d67/interfaceTBela_1_1CSS_1_1Interfaces_1_1ValidatorInterface.png index c57cc0ed..dd7544fd 100644 Binary files a/docs/api/html/db/d67/interfaceTBela_1_1CSS_1_1Interfaces_1_1ValidatorInterface.png and b/docs/api/html/db/d67/interfaceTBela_1_1CSS_1_1Interfaces_1_1ValidatorInterface.png differ diff --git a/docs/api/html/db/d84/interfaceTBela_1_1CSS_1_1Query_1_1TokenInterface.png b/docs/api/html/db/d84/interfaceTBela_1_1CSS_1_1Query_1_1TokenInterface.png index 75f5648d..fbf5d2c8 100644 Binary files a/docs/api/html/db/d84/interfaceTBela_1_1CSS_1_1Query_1_1TokenInterface.png and b/docs/api/html/db/d84/interfaceTBela_1_1CSS_1_1Query_1_1TokenInterface.png differ diff --git a/docs/api/html/db/d88/classTBela_1_1CSS_1_1Element_1_1NestingAtRule.png b/docs/api/html/db/d88/classTBela_1_1CSS_1_1Element_1_1NestingAtRule.png index 97d208aa..6fa8f233 100644 Binary files a/docs/api/html/db/d88/classTBela_1_1CSS_1_1Element_1_1NestingAtRule.png and b/docs/api/html/db/d88/classTBela_1_1CSS_1_1Element_1_1NestingAtRule.png differ diff --git a/docs/api/html/db/daf/classTBela_1_1CSS_1_1Parser_1_1Validator_1_1NestingMedialRule.png b/docs/api/html/db/daf/classTBela_1_1CSS_1_1Parser_1_1Validator_1_1NestingMedialRule.png index 5e4e9051..3b4995cd 100644 Binary files a/docs/api/html/db/daf/classTBela_1_1CSS_1_1Parser_1_1Validator_1_1NestingMedialRule.png and b/docs/api/html/db/daf/classTBela_1_1CSS_1_1Parser_1_1Validator_1_1NestingMedialRule.png differ diff --git a/docs/api/html/db/de0/classTBela_1_1CSS_1_1Value_1_1CssParenthesisExpression.png b/docs/api/html/db/de0/classTBela_1_1CSS_1_1Value_1_1CssParenthesisExpression.png index aff63467..73d5332b 100644 Binary files a/docs/api/html/db/de0/classTBela_1_1CSS_1_1Value_1_1CssParenthesisExpression.png and b/docs/api/html/db/de0/classTBela_1_1CSS_1_1Value_1_1CssParenthesisExpression.png differ diff --git a/docs/api/html/db/df6/classTBela_1_1CSS_1_1Value_1_1InvalidCssString.png b/docs/api/html/db/df6/classTBela_1_1CSS_1_1Value_1_1InvalidCssString.png index 6204d77a..73283db2 100644 Binary files a/docs/api/html/db/df6/classTBela_1_1CSS_1_1Value_1_1InvalidCssString.png and b/docs/api/html/db/df6/classTBela_1_1CSS_1_1Value_1_1InvalidCssString.png differ diff --git a/docs/api/html/dc/d0a/classTBela_1_1CSS_1_1Value_1_1Font.png b/docs/api/html/dc/d0a/classTBela_1_1CSS_1_1Value_1_1Font.png index 2961a728..52226c75 100644 Binary files a/docs/api/html/dc/d0a/classTBela_1_1CSS_1_1Value_1_1Font.png and b/docs/api/html/dc/d0a/classTBela_1_1CSS_1_1Value_1_1Font.png differ diff --git a/docs/api/html/dc/d9b/classTBela_1_1CSS_1_1Value_1_1BackgroundImage.png b/docs/api/html/dc/d9b/classTBela_1_1CSS_1_1Value_1_1BackgroundImage.png index 0668ebcb..370867f1 100644 Binary files a/docs/api/html/dc/d9b/classTBela_1_1CSS_1_1Value_1_1BackgroundImage.png and b/docs/api/html/dc/d9b/classTBela_1_1CSS_1_1Value_1_1BackgroundImage.png differ diff --git a/docs/api/html/dc/dc7/classTBela_1_1CSS_1_1Event_1_1Event.png b/docs/api/html/dc/dc7/classTBela_1_1CSS_1_1Event_1_1Event.png index 6453908f..b2663a0a 100644 Binary files a/docs/api/html/dc/dc7/classTBela_1_1CSS_1_1Event_1_1Event.png and b/docs/api/html/dc/dc7/classTBela_1_1CSS_1_1Event_1_1Event.png differ diff --git a/docs/api/html/dc/dd7/classTBela_1_1CSS_1_1Query_1_1TokenSelectorValueAttributeFunctionColor.png b/docs/api/html/dc/dd7/classTBela_1_1CSS_1_1Query_1_1TokenSelectorValueAttributeFunctionColor.png index 9310023c..5d99bb05 100644 Binary files a/docs/api/html/dc/dd7/classTBela_1_1CSS_1_1Query_1_1TokenSelectorValueAttributeFunctionColor.png and b/docs/api/html/dc/dd7/classTBela_1_1CSS_1_1Query_1_1TokenSelectorValueAttributeFunctionColor.png differ diff --git a/docs/api/html/dd/d6e/classTBela_1_1CSS_1_1Query_1_1TokenSelector.png b/docs/api/html/dd/d6e/classTBela_1_1CSS_1_1Query_1_1TokenSelector.png index 7fc10df9..7126255d 100644 Binary files a/docs/api/html/dd/d6e/classTBela_1_1CSS_1_1Query_1_1TokenSelector.png and b/docs/api/html/dd/d6e/classTBela_1_1CSS_1_1Query_1_1TokenSelector.png differ diff --git a/docs/api/html/dd/dae/classTBela_1_1CSS_1_1Query_1_1TokenSelectorValueAttributeFunctionNot.png b/docs/api/html/dd/dae/classTBela_1_1CSS_1_1Query_1_1TokenSelectorValueAttributeFunctionNot.png index 8d950454..f12ba6da 100644 Binary files a/docs/api/html/dd/dae/classTBela_1_1CSS_1_1Query_1_1TokenSelectorValueAttributeFunctionNot.png and b/docs/api/html/dd/dae/classTBela_1_1CSS_1_1Query_1_1TokenSelectorValueAttributeFunctionNot.png differ diff --git a/docs/api/html/dd/db4/classTBela_1_1CSS_1_1Query_1_1Token.png b/docs/api/html/dd/db4/classTBela_1_1CSS_1_1Query_1_1Token.png index 5633a4da..6a49de62 100644 Binary files a/docs/api/html/dd/db4/classTBela_1_1CSS_1_1Query_1_1Token.png and b/docs/api/html/dd/db4/classTBela_1_1CSS_1_1Query_1_1Token.png differ diff --git a/docs/api/html/dd/dbc/classTBela_1_1CSS_1_1Value_1_1BackgroundColor.png b/docs/api/html/dd/dbc/classTBela_1_1CSS_1_1Value_1_1BackgroundColor.png index 06e4eabe..97d06005 100644 Binary files a/docs/api/html/dd/dbc/classTBela_1_1CSS_1_1Value_1_1BackgroundColor.png and b/docs/api/html/dd/dbc/classTBela_1_1CSS_1_1Value_1_1BackgroundColor.png differ diff --git a/docs/api/html/dd/dca/classTBela_1_1CSS_1_1Value.png b/docs/api/html/dd/dca/classTBela_1_1CSS_1_1Value.png index 0647ee3e..15e6414b 100644 Binary files a/docs/api/html/dd/dca/classTBela_1_1CSS_1_1Value.png and b/docs/api/html/dd/dca/classTBela_1_1CSS_1_1Value.png differ diff --git a/docs/api/html/dd/dcf/classTBela_1_1CSS_1_1Value_1_1Unit.png b/docs/api/html/dd/dcf/classTBela_1_1CSS_1_1Value_1_1Unit.png index e630026e..c3ebc199 100644 Binary files a/docs/api/html/dd/dcf/classTBela_1_1CSS_1_1Value_1_1Unit.png and b/docs/api/html/dd/dcf/classTBela_1_1CSS_1_1Value_1_1Unit.png differ diff --git a/docs/api/html/dd/df5/classTBela_1_1CSS_1_1Ast_1_1Traverser.png b/docs/api/html/dd/df5/classTBela_1_1CSS_1_1Ast_1_1Traverser.png index e2fa85d7..a8778667 100644 Binary files a/docs/api/html/dd/df5/classTBela_1_1CSS_1_1Ast_1_1Traverser.png and b/docs/api/html/dd/df5/classTBela_1_1CSS_1_1Ast_1_1Traverser.png differ diff --git a/docs/api/html/dd/dfa/classTBela_1_1CSS_1_1Value_1_1LineHeight.png b/docs/api/html/dd/dfa/classTBela_1_1CSS_1_1Value_1_1LineHeight.png index d978668d..9774df71 100644 Binary files a/docs/api/html/dd/dfa/classTBela_1_1CSS_1_1Value_1_1LineHeight.png and b/docs/api/html/dd/dfa/classTBela_1_1CSS_1_1Value_1_1LineHeight.png differ diff --git a/docs/api/html/de/d7b/classTBela_1_1CSS_1_1Query_1_1TokenSelectorValueAttribute.png b/docs/api/html/de/d7b/classTBela_1_1CSS_1_1Query_1_1TokenSelectorValueAttribute.png index f8702612..1ce7f4a1 100644 Binary files a/docs/api/html/de/d7b/classTBela_1_1CSS_1_1Query_1_1TokenSelectorValueAttribute.png and b/docs/api/html/de/d7b/classTBela_1_1CSS_1_1Query_1_1TokenSelectorValueAttribute.png differ diff --git a/docs/api/html/de/dc6/classTBela_1_1CSS_1_1Query_1_1TokenWhitespace.png b/docs/api/html/de/dc6/classTBela_1_1CSS_1_1Query_1_1TokenWhitespace.png index f8a358d6..2c80549a 100644 Binary files a/docs/api/html/de/dc6/classTBela_1_1CSS_1_1Query_1_1TokenWhitespace.png and b/docs/api/html/de/dc6/classTBela_1_1CSS_1_1Query_1_1TokenWhitespace.png differ diff --git a/docs/api/html/df/d1e/classTBela_1_1CSS_1_1Value_1_1FontVariant.png b/docs/api/html/df/d1e/classTBela_1_1CSS_1_1Value_1_1FontVariant.png index 4f5871dc..fd5cbc1a 100644 Binary files a/docs/api/html/df/d1e/classTBela_1_1CSS_1_1Value_1_1FontVariant.png and b/docs/api/html/df/d1e/classTBela_1_1CSS_1_1Value_1_1FontVariant.png differ diff --git a/docs/api/html/df/d55/classTBela_1_1CSS_1_1Value_1_1OutlineStyle.png b/docs/api/html/df/d55/classTBela_1_1CSS_1_1Value_1_1OutlineStyle.png index 87768944..30619e88 100644 Binary files a/docs/api/html/df/d55/classTBela_1_1CSS_1_1Value_1_1OutlineStyle.png and b/docs/api/html/df/d55/classTBela_1_1CSS_1_1Value_1_1OutlineStyle.png differ diff --git a/docs/api/html/df/d73/classTBela_1_1CSS_1_1Element_1_1Rule.png b/docs/api/html/df/d73/classTBela_1_1CSS_1_1Element_1_1Rule.png index 31e366a7..e616cfec 100644 Binary files a/docs/api/html/df/d73/classTBela_1_1CSS_1_1Element_1_1Rule.png and b/docs/api/html/df/d73/classTBela_1_1CSS_1_1Element_1_1Rule.png differ diff --git a/docs/api/html/df/d86/classTBela_1_1CSS_1_1Parser_1_1Validator_1_1InvalidDeclaration.png b/docs/api/html/df/d86/classTBela_1_1CSS_1_1Parser_1_1Validator_1_1InvalidDeclaration.png index 39c25eed..f15557e5 100644 Binary files a/docs/api/html/df/d86/classTBela_1_1CSS_1_1Parser_1_1Validator_1_1InvalidDeclaration.png and b/docs/api/html/df/d86/classTBela_1_1CSS_1_1Parser_1_1Validator_1_1InvalidDeclaration.png differ diff --git a/docs/api/html/df/d88/classTBela_1_1CSS_1_1Query_1_1TokenSelectorValueAttributeString.png b/docs/api/html/df/d88/classTBela_1_1CSS_1_1Query_1_1TokenSelectorValueAttributeString.png index e1419e2f..66addd26 100644 Binary files a/docs/api/html/df/d88/classTBela_1_1CSS_1_1Query_1_1TokenSelectorValueAttributeString.png and b/docs/api/html/df/d88/classTBela_1_1CSS_1_1Query_1_1TokenSelectorValueAttributeString.png differ diff --git a/docs/api/html/df/d90/classTBela_1_1CSS_1_1Value_1_1Background.png b/docs/api/html/df/d90/classTBela_1_1CSS_1_1Value_1_1Background.png index dc6487bb..b3ca2212 100644 Binary files a/docs/api/html/df/d90/classTBela_1_1CSS_1_1Value_1_1Background.png and b/docs/api/html/df/d90/classTBela_1_1CSS_1_1Value_1_1Background.png differ diff --git a/docs/api/html/df/d9f/interfaceTBela_1_1CSS_1_1Query_1_1QueryInterface.png b/docs/api/html/df/d9f/interfaceTBela_1_1CSS_1_1Query_1_1QueryInterface.png index 8cfd8124..dc45fe4f 100644 Binary files a/docs/api/html/df/d9f/interfaceTBela_1_1CSS_1_1Query_1_1QueryInterface.png and b/docs/api/html/df/d9f/interfaceTBela_1_1CSS_1_1Query_1_1QueryInterface.png differ diff --git a/docs/api/html/df/dec/classTBela_1_1CSS_1_1Query_1_1TokenSelectorValueAttributeFunctionBeginswith.png b/docs/api/html/df/dec/classTBela_1_1CSS_1_1Query_1_1TokenSelectorValueAttributeFunctionBeginswith.png index 446eacce..4a36fcf5 100644 Binary files a/docs/api/html/df/dec/classTBela_1_1CSS_1_1Query_1_1TokenSelectorValueAttributeFunctionBeginswith.png and b/docs/api/html/df/dec/classTBela_1_1CSS_1_1Query_1_1TokenSelectorValueAttributeFunctionBeginswith.png differ diff --git a/docs/api/html/doc.png b/docs/api/html/doc.png index 921848ee..d5ffffdf 100644 Binary files a/docs/api/html/doc.png and b/docs/api/html/doc.png differ diff --git a/docs/api/html/doxygen.png b/docs/api/html/doxygen.png index c44cfe43..88ef4f21 100644 Binary files a/docs/api/html/doxygen.png and b/docs/api/html/doxygen.png differ diff --git a/package.json b/package.json index 4ada8f5e..0f30a4dc 100755 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "version": "0.3.9", + "version": "0.3.10", "devDependencies": { "docsify-cli": "^4.4.3" } diff --git a/src/Cli/Args.php b/src/Cli/Args.php index 2fcb22a2..a4640b0f 100644 --- a/src/Cli/Args.php +++ b/src/Cli/Args.php @@ -3,410 +3,463 @@ namespace TBela\CSS\Cli; +use InvalidArgumentException; +use TBela\CSS\Cli\Exceptions\DuplicateArgumentException; use TBela\CSS\Cli\Exceptions\MissingParameterException; +use UnexpectedValueException; +use ValueError; class Args { - // reject unknown flags - protected bool $strict = false; + // reject unknown flags + protected bool $strict = false; - // command groups - protected array $groups = [ - 'default' => [ - 'description' => "\nUsage: \n\$ %s [OPTIONS] [PARAMETERS]\n" - ] - ]; + // command groups + protected array $groups = [ + 'default' => [ + 'description' => "\nUsage: \n\$ %s [OPTIONS] [PARAMETERS]\n" + ] + ]; - // command line args - /** - * @var Option[] - * @ignore - */ - protected array $flags = []; + // command line args + /** + * @var Option[] + * @ignore + */ + protected array $flags = []; - /** - * @var array - * @ignore - */ - protected array $settings = []; + /** + * @var array + * @ignore + */ + protected array $settings = []; - // short flags - protected array $alias = []; + // short flags + protected array $alias = []; - // command line params - protected array $argv; - protected array $args = []; + // command line params + protected array $argv; + protected array $args = []; + protected string $version; - public function __construct(array $argv) - { + protected string $exe = ''; - $this->argv = $argv; - } + public function __construct(array $argv) + { - public function setDescription(string $description): static - { + $this->argv = $argv; + $this->exe = basename($argv[0]); + } - $this->groups['default']['description'] = sprintf($description, basename($this->argv[0])); - return $this; - } + public function setDescription(string $description): static + { - public function setStrict(bool $strict): static - { + $this->groups['default']['description'] = sprintf($description, basename($this->argv[0])); + return $this; + } - $this->strict = $strict; - return $this; - } + /** + * enable or disable strict mode. in strict mode, all arguments must be declared + * @param bool $strict + * @return $this + */ + public function setStrict(bool $strict): static + { - public function getGroups() - { + $this->strict = $strict; + return $this; + } - return $this->groups; - } + public function getGroups(): array + { - public function addGroup(string $group, string $description, bool $internal = false): static - { + return $this->groups; + } - $this->groups[$group]['description'] = $description; - $this->groups[$group]['internal'] = $internal; - return $this; - } + public function addGroup(string $group, string $description, bool $internal = false): static + { - /** - * @throws Exceptions\DuplicateArgumentException - */ - public function add(string $name, string $description, string $type, array|string $alias = null, $multiple = true, $required = false, $defaultValue = null, ?array $options = [], array|string|null $dependsOn = null, $group = 'default'): static - { + $this->groups[$group]['description'] = $description; + $this->groups[$group]['internal'] = $internal; + return $this; + } - if (isset($this->flags[$name])) { + /** + * @throws Exceptions\DuplicateArgumentException + */ + public function add(string $name, string $description, string $type, array|string $alias = null, $multiple = true, $required = false, $defaultValue = null, ?array $options = [], array|string|null $dependsOn = null, $group = 'default'): static + { - $exe = basename($this->argv[0]); - throw new Exceptions\DuplicateArgumentException(sprintf("%s: duplicate flag: '%s'\nTry '%s --help' for more information", $exe, $name, $exe)); - } + if (isset($this->flags[$name])) { - $this->flags[$name] = new Option($type, $multiple, $required, $defaultValue, $options); + $exe = basename($this->argv[0]); + throw new Exceptions\DuplicateArgumentException(sprintf("%s: duplicate flag: '%s'\nTry '%s --help' for more information", $exe, $name, $exe)); + } - $this->groups[$group]['arguments'][$name]['description'] = $description; + $this->flags[$name] = new Option($type, $multiple, $required, $defaultValue, $options); - if (!empty($dependsOn)) { + $this->groups[$group]['arguments'][$name]['description'] = $description; - $this->settings['requires'][$name] = (array)$dependsOn; - } + if (!empty($dependsOn)) { - if (!is_null($alias) && $alias !== '' && $alias != []) { + $this->settings['requires'][$name] = (array)$dependsOn; + } - foreach ((array)$alias as $a) { + if (!is_null($alias) && $alias !== '' && $alias !== []) { - if (!preg_match('#^[a-zA-Z]$#', $a)) { + $this->alias($name, $alias); + } - throw new \InvalidArgumentException(sprintf("command option must be a letter [a-zA-Z]: '%s'", $a)); - } + return $this; + } - if (isset($this->alias[$a])) { + /** + * @throws Exceptions\MissingParameterException + * @throws Exceptions\UnknownParameterException + */ + public function parse(): static + { - throw new \InvalidArgumentException(sprintf("duplicated alias '%s' for the flag '%s' is already defined by command '%s'", $a, $name, $this->alias[$a])); - } + $argc = count($this->argv); - $this->alias[$a] = $name; - } - } + $args = []; + $flags = []; + $this->args = []; - return $this; - } + $flagReg = '#^((--?)([a-zA-Z][a-zA-Z\d_-]*))(=.*)?$#'; - /** - * @throws Exceptions\MissingParameterException - * @throws Exceptions\UnknownParameterException - */ - public function parse(): static - { + $i = 0; - $argc = count($this->argv); + /** + * @var Option[] $dynamicArgs + */ + $dynamicArgs = []; - for ($i = 1; $i < $argc; $i++) { + while ($i++ < $argc - 1) { - if (in_array($this->argv[$i], ['-h', '--help'])) { + if (preg_match($flagReg, $this->argv[$i], $matches)) { - echo $this->help($this->argv[$i] == '--help'); - exit; - } - } + $value = str_starts_with($matches[4] ?? '', '=') ? substr($matches[4], 1) : null; + $name = $matches[3]; + $names = []; - $args = []; - $this->args = []; - $exe = basename($this->argv[0]); + if ($matches[2] == '-') { - $flagReg = '#^((--?)([a-zA-Z][a-zA-Z\d_-]*))(=.*)?$#'; + if (is_null($value)) { - $i = 0; + $j = strlen($name); - /** - * @var Option[] - */ - $dynamicArgs = []; + for ($k = 1; $k < $j; $k++) { - while ($i++ < $argc - 1) { + if (!isset($this->alias[$name[$k]])) { - if (preg_match($flagReg, $this->argv[$i], $matches)) { + break; + } + } - // print help and exit - if (in_array($matches[1], ['-h', '--help'])) { + $value = substr($name, $k); + $name = substr($name, 0, $k); - echo $this->help($matches[1] == '--help'); - exit; - } + if ($value === '') { - $value = str_starts_with($matches[4] ?? '', '=') ? substr($matches[4], 1) : null; - $name = $matches[3]; - $names = []; + $value = null; + } + } - if ($matches[2] == '-') { + $names = str_split($name); + $name = array_pop($names); + $flags[$name] = $name; + } - if (is_null($value)) { + try { - $j = strlen($name); + $option = $this->parseFlag($name, $dynamicArgs); - for ($k = 1; $k < $j; $k++) { + if (is_null($value) && $option->getType() != 'bool' && $i < $argc - 1 && !preg_match($flagReg, $this->argv[$i + 1])) { - if (!isset($this->alias[$name[$k]])) { + $option->addValue($this->argv[++$i]); + } else { - break; - } - } + $option->addValue(is_null($value) && in_array($option->getType(), ['auto', 'bool']) ? true : $value); + } - $value = substr($name, $k); - $name = substr($name, 0, $k); + $k = count($names); - if ($value === '') { + while ($k--) { - $value = null; - } - } + $name = $names[$k]; + $flags[$name] = $name; - $names = str_split($name); - $name = array_pop($names); - } + $option = $this->parseFlag($name, $dynamicArgs); - try { + $option->addValue(true); + } + } catch (ValueError) { - $option = $this->parseFlag($name, $dynamicArgs); + throw new ValueError(sprintf("%s: invalid value specified for -- '%s'\nTry '%s --help'\n", $this->exe, $name, $this->exe), 1); + } catch (UnexpectedValueException) { - if (is_null($value) && $option->getType() != 'bool' && $i < $argc - 1 && !preg_match($flagReg, $this->argv[$i + 1])) { + throw new UnexpectedValueException(sprintf("%s: invalid value specified for -- '%s'\nTry '%s --help'\n", $this->exe, $name, $this->exe), 1); + } catch (Exceptions\UnknownParameterException) { - $option->addValue($this->argv[++$i]); - } else { + throw new Exceptions\UnknownParameterException(sprintf("%s: unknown parameter -- '%s'\nTry '%s --help'\n", $this->exe, $name, $this->exe), 1); + } catch (InvalidArgumentException) { - $option->addValue(is_null($value) && in_array($option->getType(), ['auto', 'bool']) ? true : $value); - } + throw new InvalidArgumentException(sprintf("%s: expected string value -- '%s'\nTry '%s --help'\n", $this->exe, $name, $this->exe), 1); + } - $k = count($names); + } else { - while ($k--) { + $args[] = $this->argv[$i]; + } + } - $name = $names[$k]; + $result = []; + foreach (array_merge($this->flags, $dynamicArgs) as $name => $option) { - $option = $this->parseFlag($name, $dynamicArgs); + if (!$option->isValueSet()) { - $option->addValue(true); - } - } catch (\ValueError $e) { + if (!$option->isRequired()) { - throw new \ValueError(sprintf("%s: invalid value specified for -- '%s'\nTry '%s --help'\n", $exe, $name, $exe), 1); - } catch (\UnexpectedValueException $e) { + continue; + } - throw new \UnexpectedValueException(sprintf("%s: invalid value specified for -- '%s'\nTry '%s --help'\n", $exe, $name, $exe), 1); - } catch (Exceptions\UnknownParameterException $e) { + throw new Exceptions\MissingParameterException(sprintf("%s: missing required parameter -- '%s'\nTry '%s --help'\n", $this->exe, $name, $this->exe), 1); + } - throw new Exceptions\UnknownParameterException(sprintf("%s: unknown parameter -- '%s'\nTry '%s --help'\n", $exe, $name, $exe), 1); - } catch (\InvalidArgumentException $e) { + $result[$name] = $option->getValue(); + } - throw new \InvalidArgumentException(sprintf("%s: expected string value -- '%s'\nTry '%s --help'\n", $exe, $name, $exe), 1); - } + foreach ($result as $name => $value) { - } else { + if (!empty($this->settings['requires'][$name])) { - $args[] = $this->argv[$i]; - } - } + foreach ($this->settings['requires'][$name] as $required) { - $result = []; - foreach (array_merge($this->flags, $dynamicArgs) as $name => $option) { + if (!array_key_exists($required, $result)) { - if (!$option->isValueSet()) { + throw new MissingParameterException(sprintf("%s: missing required parameter -- '%s", $this->exe, $required), 400); + } + } + } + } - if (!$option->isRequired()) { - continue; - } + if (array_key_exists('help', $result) ) { - $exe = basename($this->argv[0]); - throw new Exceptions\MissingParameterException(sprintf("%s: missing required parameter -- '%s'\nTry '%s --help'\n", $exe, $name, $exe), 1); - } + echo $this->showHelp(!array_key_exists('h', $flags)); + exit; + } - $result[$name] = $option->getValue(); - } + else if (!empty($this->version) && array_key_exists('version', $result)) { - foreach ($result as $name => $value) { + echo $this->version; + exit; + } - if (!empty($this->settings['requires'][$name])) { + $this->args = $result; + $this->args['_'] = $args; - foreach ($this->settings['requires'][$name] as $required) { + return $this; + } - if (!array_key_exists($required, $result)) { + public function getArguments(): array + { - throw new MissingParameterException(sprintf("%s: missing required parameter -- '%s", $exe, $required), 400); - } - } - } - } + return $this->args; + } - $this->args = $result; - $this->args['_'] = $args; + /** + * @throws DuplicateArgumentException + */ + public function setVersion(string $info, string $description = 'display version info', string $flag = 'version'): static + { - return $this; - } + unset($this->flags[$flag]); - public function getArguments(): array - { + $this->add($flag, $description, Option::BOOL); + $this->version = $info; - return $this->args; - } + return $this; + } - public function help($extended = false): string - { + public function getExe(): string + { - $output = ''; - $groups = $this->groups; + return $this->exe; + } - if (isset($groups['default'])) { + /** + * @throws DuplicateArgumentException + */ + public function help(string $flag = 'help', string $description = 'display this help menu'): static + { - $output .= $this->printGroupHelp($groups['default'], $extended) . "\n"; - } + unset($this->flags[$flag]); - $output .= "-h\tprint help\n--help\tprint extended help\n\n"; + $this->add($flag, $description, Option::BOOL); + return $this; + } - unset($groups['default']); + public function showHelp($extended = false): string + { - foreach ($groups as $def) { + $output = ''; + $groups = $this->groups; - if (!empty($def['internal'])) { + if (isset($groups['default'])) { - continue; - } + $output .= $this->printGroupHelp($groups['default'], $extended) . "\n"; + } - $output .= $this->printGroupHelp($def, $extended) . "\n\n"; - } + unset($groups['default']); - return $output; - } + foreach ($groups as $def) { - protected function printGroupHelp(array $group, bool $extended): string - { + if (!empty($def['internal'])) { - $output = ''; + continue; + } - if (isset($group['description'])) { + $output .= $this->printGroupHelp($def, $extended) . "\n\n"; + } - $output .= sprintf($group['description'], basename($this->argv[0])); - } + return $output; + } - $args = []; - $length = 0; + protected function printGroupHelp(array $group, bool $extended): string + { - $flags = $group['arguments'] ?? []; + $output = ''; - ksort($flags); + if (isset($group['description'])) { - foreach ($flags as $option => $conf) { + $output .= sprintf($group['description'], basename($this->argv[0])); + } - $rev = array_keys(array_filter($this->alias, function ($name) use ($option) { + $args = []; + $length = 0; - return $name == $option; - })); + $flags = $group['arguments'] ?? []; - $description = $conf['description']; + ksort($flags); - if (!empty($this->settings['requires'][$option])) { + foreach ($flags as $option => $conf) { - $description .= ', requires --' . implode(', --', $this->settings['requires'][$option]); - } + $rev = array_keys(array_filter($this->alias, function ($name) use ($option) { - $args[] = ['flags' => "\n" . ($rev ? '-' . implode(', -', $rev) . ', ' : '') . '--' . $option, 'description' => $description]; - $length = max($length, strlen(end($args)['flags'])); + return $name == $option; + })); - if ($extended) { + $description = $conf['description']; - $args[] = ['flags' => " type", 'description' => $this->flags[$option]->getType()]; - $length = max($length, strlen(end($args)['flags'])); + if (!empty($this->settings['requires'][$option])) { - $args[] = ['flags' => " required", 'description' => $this->flags[$option]->isRequired() ? 'yes' : 'no']; - $length = max($length, strlen(end($args)['flags'])); + $description .= ', requires --' . implode(', --', $this->settings['requires'][$option]); + } - $args[] = ['flags' => " multiple", 'description' => $this->flags[$option]->isMultiple() ? 'yes' : 'no']; - $length = max($length, strlen(end($args)['flags'])); + $args[] = ['flags' => "\n" . ($rev ? '-' . implode(', -', $rev) . ', ' : '') . '--' . $option, 'description' => $description]; + $length = max($length, strlen(end($args)['flags'])); - $options = $this->flags[$option]->getOptions(); + if ($extended) { - if (!empty($options)) { + $args[] = ['flags' => " type", 'description' => $this->flags[$option]->getType()]; + $length = max($length, strlen(end($args)['flags'])); - $args[] = ['flags' => " valid options", 'description' => implode(', ', $options)]; - $length = max($length, strlen(end($args)['flags'])); - } + $args[] = ['flags' => " required", 'description' => $this->flags[$option]->isRequired() ? 'yes' : 'no']; + $length = max($length, strlen(end($args)['flags'])); - $defaultValue = $this->flags[$option]->getDefaultValue(); + $args[] = ['flags' => " multiple", 'description' => $this->flags[$option]->isMultiple() ? 'yes' : 'no']; + $length = max($length, strlen(end($args)['flags'])); - if (isset($defaultValue)) { + $options = $this->flags[$option]->getOptions(); - $args[] = ['flags' => " default value", 'description' => json_encode($defaultValue)]; - $length = max($length, strlen(end($args)['flags'])); - } - } - } + if (!empty($options)) { - foreach ($args as $arg) { + $args[] = ['flags' => " valid options", 'description' => implode(', ', $options)]; + $length = max($length, strlen(end($args)['flags'])); + } - $output .= str_pad($arg['flags'], $length, " ") . "\t" . $arg['description'] . "\n"; - } + $defaultValue = $this->flags[$option]->getDefaultValue(); - return rtrim($output); - } + if (isset($defaultValue)) { - /** - * @param string $name - * @param array $dynamicArgs - * @return Option|null - * @throws Exceptions\UnknownParameterException - */ - protected function parseFlag(string &$name, array &$dynamicArgs): ?Option - { - if (isset($this->alias[$name])) { + $args[] = ['flags' => " default value", 'description' => json_encode($defaultValue)]; + $length = max($length, strlen(end($args)['flags'])); + } + } + } - $name = $this->alias[$name]; - } + foreach ($args as $arg) { - $option = $this->flags[$name] ?? null; + $output .= str_pad($arg['flags'], $length) . "\t" . $arg['description'] . "\n"; + } - if (is_null($option)) { + return rtrim($output); + } - if ($this->strict) { + /** + * @param string $name + * @param array $dynamicArgs + * @return Option|null + * @throws Exceptions\UnknownParameterException + */ + protected function parseFlag(string &$name, array &$dynamicArgs): ?Option + { + if (isset($this->alias[$name])) { - throw new Exceptions\UnknownParameterException(sprintf("%s: invalid option -- '%s'", basename($this->argv[0]), $name)); - } else { + $name = $this->alias[$name]; + } - if (!isset($dynamicArgs[$name])) { + $option = $this->flags[$name] ?? null; - $dynamicArgs[$name] = $option; - } + if (is_null($option)) { - $option = $dynamicArgs[$name] ?? new Option(); + if ($this->strict) { - if (!isset($dynamicArgs[$name])) { + throw new Exceptions\UnknownParameterException(sprintf("%s: invalid option -- '%s'", basename($this->argv[0]), $name)); + } else { - $dynamicArgs[$name] = $option; - } - } - } + if (!isset($dynamicArgs[$name])) { - return $option; - } + $dynamicArgs[$name] = $option; + } + + $option = $dynamicArgs[$name] ?? new Option(); + + if (!isset($dynamicArgs[$name])) { + + $dynamicArgs[$name] = $option; + } + } + } + + return $option; + } + + /** + * @param array|string $alias + * @param string $name + * @return Args + */ + public function alias(string $name, array|string $alias): static + { + foreach ((array)$alias as $a) { + + if (!preg_match('#^[a-zA-Z]$#', $a)) { + + throw new InvalidArgumentException(sprintf("command option must be a single letter [a-zA-Z]: '%s'", $a)); + } + + if (isset($this->alias[$a])) { + + throw new InvalidArgumentException(sprintf("duplicated alias '%s' for the flag '%s' is already defined by command '%s'", $a, $name, $this->alias[$a])); + } + + $this->alias[$a] = $name; + } + + return $this; + } } \ No newline at end of file diff --git a/src/Element.php b/src/Element.php index e7ac2196..4983effd 100755 --- a/src/Element.php +++ b/src/Element.php @@ -18,7 +18,7 @@ * Css node base class * @package TBela\CSS */ -abstract class Element implements ElementInterface { +abstract class Element implements ElementInterface, \Stringable { use ArrayTrait; @@ -621,7 +621,7 @@ public function jsonSerialize () { /** * @inheritDoc */ - public function __toString() + public function __toString(): string { try { diff --git a/src/Event/EventInterface.php b/src/Event/EventInterface.php index d63203c4..26cefb5c 100755 --- a/src/Event/EventInterface.php +++ b/src/Event/EventInterface.php @@ -4,9 +4,9 @@ interface EventInterface { - public function on(string $event, callable $callable); + public function on(string $event, callable $callable): static; - public function off(string $event, callable $callable); + public function off(string $event, callable $callable): static; public function emit(string $event, ...$args): array; } \ No newline at end of file diff --git a/src/Event/EventTrait.php b/src/Event/EventTrait.php index 80b10916..89eca92d 100755 --- a/src/Event/EventTrait.php +++ b/src/Event/EventTrait.php @@ -16,7 +16,7 @@ trait EventTrait { * @param callable $callable * @return $this */ - public function on(string $event, callable $callable) { + public function on(string $event, callable $callable): static { $this->events[strtolower($event)][] = $callable; return $this; @@ -31,7 +31,7 @@ public function on(string $event, callable $callable) { * @param callable|null $callable * @return $this */ - public function off(string $event = null, callable $callable = null) { + public function off(string $event = null, callable $callable = null): static { if (is_null($event)) { diff --git a/src/Parser.php b/src/Parser.php index a178b0a7..e6c155ec 100755 --- a/src/Parser.php +++ b/src/Parser.php @@ -2,29 +2,30 @@ namespace TBela\CSS; -use Closure; use Exception; -use Symfony\Component\Process\PhpExecutableFinder; -use Symfony\Component\Process\Process; +use Generator; +use ReflectionException; +use RuntimeException; +use Stringable; use TBela\CSS\Exceptions\IOException; use TBela\CSS\Interfaces\ParsableInterface; use TBela\CSS\Interfaces\RuleListInterface; use TBela\CSS\Interfaces\ValidatorInterface; use TBela\CSS\Parser\Helper; use TBela\CSS\Parser\Lexer; -use TBela\CSS\Parser\MultiprocessingTrait; use TBela\CSS\Parser\ParserTrait; use TBela\CSS\Parser\SyntaxError; -use TBela\CSS\Process\Pool; +use TBela\CSS\Process\Exceptions\UnhandledException; +use TBela\CSS\Process\Pool as ProcessPool; /** * Css Parser * @package TBela\CSS * ok */ -class Parser implements ParsableInterface +class Parser implements ParsableInterface, Stringable { - use ParserTrait, MultiprocessingTrait; + use ParserTrait; /** * @var ValidatorInterface[] @@ -54,7 +55,7 @@ class Parser implements ParsableInterface 'allow_duplicate_declarations' => true, 'multi_processing' => true, // buffer size 32k. higher values may break the execution - 'multi_processing_threshold' => 32768, + 'multi_processing_threshold' => 64 * 1024, // 'children_process' => 20, 'ast_src' => '', 'ast_position_line' => 1, @@ -69,20 +70,18 @@ class Parser implements ParsableInterface */ protected ?int $lastDedupIndex = null; - /** - * @var string serialize | json - */ - // TODO: remove serialize as it seams to produce warning - protected string $format = 'serialize'; + protected array $processPoolEvents = []; + + protected array $queue = []; /** * Parser constructor. * @param string $css * @param array $options - * @throws IOException - * @throws SyntaxError + * @param string $media + * @throws Exception */ - public function __construct(string $css = '', array $options = []) + public function __construct(string $css = '', array $options = [], string $media = '') { $this->setOptions($options); $this->lexer = (new Lexer())-> @@ -92,13 +91,11 @@ public function __construct(string $css = '', array $options = []) 'column' => $this->options['ast_position_column'], 'index' => $this->options['ast_position_index'], 'src' => $this->options['ast_src'] - ])-> - on('enter', Closure::fromCallable([$this, 'enterNode']))-> - on('exit', Closure::fromCallable([$this, 'exitNode'])); + ]); if ($css !== '') { - $this->setContent($css); + $this->setContent($css, $media); } } @@ -110,7 +107,15 @@ public function __construct(string $css = '', array $options = []) public function on(string $event, callable $callable): static { - $this->lexer->on($event, $callable); + if (str_starts_with($event, 'pool.')) { + + $this->processPoolEvents[substr($event, 5)][] = $callable; + + } else { + + $this->lexer->on($event, $callable); + } + return $this; } @@ -122,109 +127,91 @@ public function on(string $event, callable $callable): static public function off(string $event, callable $callable): static { - $this->lexer->off($event, $callable); - return $this; - } - - public function getCliArgs(array $options, $src, $position): array - { - - $args = [ - '-ac', - '--parse-multi-processing=off', - sprintf('--parse-ast-src=%s', $src) - ]; + if (str_starts_with($event, 'pool.')) { - if (!empty($position)) { + $event = substr($event, 5); - array_push($args, - sprintf('--parse-ast-position-index=%s', max(0, $position->index - 1)), - sprintf('--parse-ast-position-line=%s', $position->line), - sprintf('--parse-ast-position-column=%s', $position->column)); - } - - if (!$options['capture_errors']) { - // default is on - $args[] = '--capture-errors=off'; - } - - foreach ([ - 'capture_errors' => true, - 'flatten_import' => false, - 'allow_duplicate_rules' => ['font-face'], // set to true for speed - 'allow_duplicate_declarations' => true, - 'multi_processing' => true, - // 65k - 'multi_processing_threshold' => 32768, -// 'children_process' => 20, - 'ast_src' => '', - 'ast_position_line' => 1, - 'ast_position_column' => 1, - 'ast_position_index' => 0 - ] as $key => $value) { + if (isset($this->processPoolEvents[$event])) { - if (in_array($key, ['multi_processing', 'multi_processing_threshold', 'ast_src', 'ast_position_line', 'ast_position_column', 'ast_position_index'])) { + $this->processPoolEvents[$event] = array_filter($this->processPoolEvents[$event], function ($val) use ($callable) { - continue; - } + return $val != $callable; + }); - if ($key == 'allow_duplicate_rules') { + if (empty($this->processPoolEvents[$event])) { - if (!$options['allow_duplicate_rules']) { - // default is on - $args[] = '--parse-allow-duplicate-rules==off'; + unset($this->processPoolEvents[$event]); } - } else if ($key == 'allow_duplicate_declarations') { + } - if (!$options['allow_duplicate_declarations']) { - // default is on - $args[] = '--parse-allow-duplicate-declarations==off'; - } - } else if (isset($options[$key]) && $options[$key] !== $value) { + } else { - $args[] = sprintf('--%s=%s', str_replace('_', '-', $key), is_bool($options[$key]) ? ($options[$key] ? 'on' : 'off') : $options[$key]); - } + $this->lexer->on($event, $callable); } - $args[] = sprintf('--output-format=%s', $this->format); - - return $args; + return $this; } - /** * @param string $content + /** + * @param string $content * @param object $root * @param string $file * @return void + * @throws UnhandledException + * @throws ReflectionException */ - protected function stream(string $content, object $root, string $file): void + public function parallelize(string $content, object $root, string $file): void { + $data = []; - $file = Helper::absolutePath($file, Helper::getCurrentDirectory()); - $size = max(min($this->options['multi_processing_threshold'], strlen($content) / 2), 1); + $processPool = (new ProcessPool()); - foreach ($this->slice($content, $size, $root->location->end) as $index => $data) { + foreach ($this->processPoolEvents as $event => $callables) { - $this->enQueue($data[0], $this->getCliArgs($this->options, $file, $data[1])); + foreach ($callables as $callable) { + + $processPool->on($event, $callable); + } } - $this->pool->wait(); + $len = strlen($content); + $options = $this->options; - if (!empty($this->output)) { + // min 65k + $size = min($this->options['multi_processing_threshold'], $len / 2); + $options['multi_processing'] = false; - if (!isset($root->children)) { + foreach ($this->slice($content, $size, $root->location->end) as $slice) { - $root->children = []; - } + $processPool->add(function () use ($file, $slice, $options): array { - foreach ($this->output as $token) { + $parser = new Parser($slice[0], array_merge($options, [ + 'ast_src' => $file, + 'ast_position_line' => $slice[1]->line, + 'ast_position_column' => $slice[1]->column, + 'ast_position_index' => $slice[1]->index + ])); - if (isset($token->children)) { + return $parser->getAst()->children ?? []; + })-> + then(function (array $result, int $index) use (&$data) { - array_splice($root->children, count($root->children), 0, $token->children); - } - } + $data[$index] = $result; + }); + } + + $processPool->wait(); + + ksort($data); + + if (!isset($root->children)) { - $this->output = []; + $root->children = []; + } + + foreach ($data as $datum) { + + array_splice($root->children, count($root->children), 0, $datum); } } @@ -263,70 +250,115 @@ public function append(string $file, string $media = ''): static throw new IOException(sprintf('File Not Found "%s" => \'%s:%s:%s\'', $file, $context->location->src ?? null, $context->location->end->line ?? null, $context->location->end->column ?? null), 404); } - $rule = null; + $this->queue[] = [ + 'content' => $content, + 'media' => $media !== '' && $media != 'all' ? $media : '', + 'file' => $file + ]; - $newContext = $this->lexer->setParentOffset((object)[ - 'line' => 1, - 'column' => 1, - 'index' => 0, - 'src' => $file - ])->createContext(); + return $this; + } - if (is_null($this->ast)) { + /** + * @throws ReflectionException + * @throws UnhandledException + * @throws SyntaxError + * @throws Exception + */ + protected function doParse() + { + + if (empty($this->queue)) { - $this->ast = $newContext; + return; } - if ($media !== '' && $media != 'all') { + $this->lexer->emit('start', $this->getContext()); - $rule = (object)[ +// $multi; + foreach ($this->queue as $data) { - 'type' => 'AtRule', - 'name' => 'media', - 'value' => Value::parse($media, null, true, '', '', true) - ]; + $file = $data['file'] ?? ''; + $media = $data['media'] ?? ''; - $rule->location = (object)[ - 'start' => (object)[ - 'line' => 1, - 'column' => 1, - 'index' => 0 - ], - 'end' => (object)[ + $rule = null; + $context = null; + $content = $data['content']; + + if ($media !== '' && $media != 'all') { + + $rule = (object)[ + + 'type' => 'AtRule', + 'name' => 'media', + 'value' => Value::parse($media) + ]; + + $rule->location = (object)[ + 'start' => (object)[ + 'line' => 1, + 'column' => 1, + 'index' => 0 + ], + 'end' => (object)[ + 'line' => 1, + 'column' => 1, + 'index' => 0 + ] + ]; + + $rule->src = $this->options['ast_src']; + } + + $context = $this->lexer->createContext(); + + if ($file !== '') { + + $newContext = $this->lexer->setParentOffset((object)[ 'line' => 1, 'column' => 1, - 'index' => 0 - ] - ]; + 'index' => 0, + 'src' => $file + ])->createContext(); - $rule->src = $file; + $context = $newContext; + } - $this->ast->children[] = $rule; - $this->pushContext($rule); - } + if (is_null($this->ast)) { - if (function_exists('\\proc_open') && $this->options['multi_processing'] && strlen($content) > $this->options['multi_processing_threshold']) { + $this->ast = $context; + } - $root = $this->getContext(); - $this->stream($content, $root, $file); - } else { + if ($rule) { - $this->lexer->setContent($content)->setContext($newContext)->tokenize(); - } + $this->ast->children[] = $rule; + $this->pushContext($rule); + } - if ($rule) { + if (ProcessPool::isSupported() && $this->options['multi_processing'] && strlen($content) > $this->options['multi_processing_threshold']) { - $this->popContext(); - } + $this->parallelize($content, $this->getContext(), $file); + } else { - if (!empty($this->ast->children)) { + $this->lexer->setContent($content)->setContext($context); + $this->tokenize(); + } - $this->parseImport(); - $this->deduplicate($this->ast, $this->lastDedupIndex); - $this->lastDedupIndex = max(0, count($this->ast->children) - 2); + if ($rule) { + + $this->popContext(); + } + + if (!empty($this->ast->children)) { + + $this->parseImport(); + $this->deduplicate($this->ast, $this->lastDedupIndex); + $this->lastDedupIndex = max(0, count($this->ast->children) - 2); + } } - return $this; + $this->lexer->emit('end', $this->getContext()); + $this->queue = []; } /** @@ -334,40 +366,16 @@ public function append(string $file, string $media = ''): static * @param string $css * @param string $media * @return Parser - * @throws SyntaxError|Exception */ public function appendContent(string $css, string $media = ''): static { - if ($media !== '' && $media != 'all') { - - $css = '@media ' . $media . ' { ' . rtrim($css) . ' }'; - } - if (!$this->ast) { - - $this->ast = $this->lexer->createContext(); - $this->lexer->setContext($this->ast); - } else { - - $this->lexer->setContext($this->lexer->createContext()); - } + $this->queue[] = [ - if (function_exists('\\proc_open') && $this->options['multi_processing'] && strlen($css) > $this->options['multi_processing_threshold']) { - - $this->stream($css, $this->getContext(), $this->options['ast_src']); - } else { - - $this->lexer-> - setContent($css)-> - tokenize(); - } - - if (!empty($this->ast->children)) { - - $this->parseImport(); - $this->deduplicate($this->ast, $this->lastDedupIndex); - $this->lastDedupIndex = max(0, count($this->ast->children) - 2); - } + 'file' => $this->options['ast_src'], + 'content' => $css, + 'media' => $media !== '' && $media != 'all' ? $media : '' + ]; return $this; } @@ -404,17 +412,12 @@ public function merge(Parser $parser): static * @param string $css * @param string $media * @return Parser - * @throws SyntaxError + * @throws Exception */ public function setContent(string $css, string $media = ''): static { - if ($media !== '' && $media != 'all') { - - $css = '@media ' . $media . '{ ' . rtrim($css) . ' }'; - } - - $this->reset()->appendContent($css); + $this->reset()->appendContent($css, $media); return $this; } @@ -425,6 +428,7 @@ protected function reset(): static $this->ast = null; $this->errors = []; $this->context = []; + $this->queue = []; $this->lastDedupIndex = null; return $this; @@ -522,10 +526,10 @@ public function parse(): ?RuleListInterface public function getAst(): ?object { - if (is_null($this->ast)) { - $this->ast = $this->lexer->createContext(); - $this->lexer->setContext($this->ast)->tokenize(); + if (!empty($this->queue)) { + + $this->doParse(); } if (!empty($this->ast->children)) { @@ -534,105 +538,15 @@ public function getAst(): ?object $this->lastDedupIndex = max(0, count($this->ast->children) - 2); } - return $this->ast; - } - - - public function slice($css, $size, $position): \Generator - { - - $i = ($position->index ?? 0) - 1; - $j = strlen($css) - 1; - - $buffer = ''; - - while ($i++ < $j) { - - $string = Parser::substr($css, $i, $j, ['{']); - - if ($string === false) { - - $buffer = substr($css, $i); - - $pos = clone $position; - $pos->index++; - - yield [$buffer, $pos]; - - $this->update($position, $buffer); - $position->index += strlen($buffer); - - $buffer = ''; - break; - } - - $string .= Parser::_close($css, '}', '{', $i + strlen($string), $j); - $buffer .= $string; - - if (strlen($buffer) >= $size) { - - $k = 0; - while (static::is_whitespace($buffer[$k])) { - - $k++; - } - - if ($k > 0) { - - $this->update($position, substr($buffer, 0, $k)); - $position->index += $k; - - $buffer = substr($buffer, $k); - } - - $pos = clone $position; - $pos->index++; - - yield [$buffer, $pos]; - - $this->update($position, $buffer); - $position->index += strlen($buffer); - - $buffer = ''; - } - - $i += strlen($string) - 1; - } - - if ($buffer) { - - $k = 0; - $l = strlen($buffer); - while ($k < $l && Parser::is_whitespace($buffer[$k])) { - - $k++; - } - - if ($k > 0) { - - $this->update($position, substr($buffer, 0, $k)); - $position->index += $k; - - $buffer = substr($buffer, $k); - } - } - - if (trim($buffer) !== '') { - - $pos = clone $position; - $pos->index++; + if (is_null($this->ast)) { - yield [$buffer, $pos]; - $this->update($position, $buffer); - $position->index += strlen($buffer) - 1; + $this->ast = $this->lexer->createContext(); } - $position->index = max(0, $position->index - 1); - $position->column = max(1, $position->column - 1); + return $this->ast; } /** - * @throws IOException * @throws SyntaxError * @throws Exception */ @@ -653,7 +567,7 @@ protected function parseImport() if ($child->name == 'import') { - $imports[$i] = clone $child; + $imports[$i] = $child; } continue; @@ -669,9 +583,11 @@ protected function parseImport() $pool = null; - $phpExe = (new PhpExecutableFinder())->find(); + krsort($imports); + + $options = $this->options; - foreach (array_reverse($imports, true) as $key => $token) { + foreach ($imports as $token) { preg_match('#^((["\']?)([^\\2]+)\\2)(.*?$)#', is_array($token->value) ? Value::renderTokens($token->value) : $token->value, $matches); @@ -684,64 +600,57 @@ protected function parseImport() $file = Helper::absolutePath($matches[3], dirname($token->src ?? '')); - $token->value = $media; - - unset($token->isLeaf); - - $token->name = 'media'; - - if (count($imports) > 2 && function_exists('\\proc_open')) { - - $args = $this->getCliArgs($this->options, $file, null); - - array_unshift($args, $phpExe, '-f', __DIR__ . '/../cli/css-parser', '--'); - - $args[] = '--file=' . $file; - $args[] = '--output-format=json'; - $args[] = '-c'; - - $process = new Process($args); - $process->setPty(true); + if (count($imports) > 2 && ProcessPool::isSupported()) { if (!isset($pool)) { - $pool = (new Pool()); + $pool = new ProcessPool(); } - $pool->add($process)->then(function (Process $process, $stdout, $stderr) use ($token, $file) { + $pool->add(function () use ($file, $options) { + + return (new Parser('', $options))->load($file)->getAst()->children ?? []; + })->then(function ($result, $index, $stderr, $exitCode /*, $thread */) use ($media, $token, $file) { - if ($process->getExitCode() != 0) { + if ($exitCode != 0) { - throw new \RuntimeException(sprintf("cannot resolve @import#%s\n%s", $file, $stderr)); + throw new RuntimeException(sprintf("cannot resolve @import#%s (exit code #%s\n%s", $file, $exitCode, $stderr)); } - if (!empty($stdout)) { + $token->children = $result; + $token->value = $media; - $ast = /* $this->format == 'serialize' ? unserialize($payload) : */ - json_decode($stdout); + unset($token->isLeaf); + + $token->name = 'media'; - $token->children = $ast->children ?? []; - } }); + + } else { $parser = new static(options: $this->options); $parser->ast = $token; $parser->append($file); + + $token->children = $parser->getAst()->children ?? []; + $token->value = $media; + + unset($token->isLeaf); + + $token->name = 'media'; + } } $pool?->wait(); - foreach (array_reverse($imports, true) as $key => $token) { + foreach ($imports as $key => $token) { if (empty($token->value)) { array_splice($this->ast->children, $key, 1, $token->children ?? []); - } else { - - $this->ast->children[$key] = $token; } } } @@ -1053,13 +962,12 @@ protected function validate(object $token, object $parentRule, object $parentSty /** * get the current parent node * @return object|null - * @throws SyntaxError * @ignore */ protected function getContext(): ?object { - return end($this->context) ?: ($this->ast ?? $this->getAst()); + return end($this->context) ?: $this->ast; } /** @@ -1082,7 +990,7 @@ protected function pushContext(object $context): void protected function popContext(): void { - array_pop($this->context);; + array_pop($this->context); } /** @@ -1155,7 +1063,6 @@ protected function enterNode(object $token, object $parentRule, object $parentSt * parse event handler * @param object $token * @return void - * @throws SyntaxError * @ignore */ protected function exitNode(object $token): void @@ -1186,7 +1093,8 @@ protected function exitNode(object $token): void $context = $this->getContext(); array_pop($context->children); - array_splice($context->children, count($context->children), 0, $token->children); +// array_splice($context->children, count($context->children), 0, $token->children); + $context->children = array_merge($context->children, $token->children); } } @@ -1212,24 +1120,243 @@ protected function doValidate(object $token, object $context, object $parentStyl return $status; } - public function __toString() + public function slice($css, $size, $position): Generator + { + + $i = -1; // ($position->index ?? 0) - 1; + $j = strlen($css) - 1; + + $buffer = ''; + + while ($i++ < $j) { + + $string = Parser::substr($css, $i, $j, ['{']); + + if ($string === false) { + + $buffer = substr($css, $i); + + $pos = clone $position; + $pos->index++; + + yield [$buffer, $pos]; + + $this->update($position, $buffer); + $position->index += strlen($buffer); + + $buffer = ''; + break; + } + + $string .= Parser::_close($css, '}', '{', $i + strlen($string), $j); + $buffer .= $string; + + if (strlen($buffer) >= $size) { + + $k = 0; + while ($k < $j && static::is_whitespace($buffer[$k])) { + + $k++; + } + + if ($k > 0) { + + $this->update($position, substr($buffer, 0, $k)); + $position->index += $k; + + $buffer = substr($buffer, $k); + } + + $pos = clone $position; + + $pos->index = max(0, $pos->index - 1); + + yield [$buffer, $pos]; + + $this->update($position, $buffer); + $position->index += strlen($buffer); + + $buffer = ''; + } + + $i += strlen($string) - 1; + } + + if ($buffer) { + + $k = 0; + $l = strlen($buffer); + while ($k < $l && Parser::is_whitespace($buffer[$k])) { + + $k++; + } + + if ($k > 0) { + + $this->update($position, substr($buffer, 0, $k)); + $position->index += $k; + + $buffer = substr($buffer, $k); + } + } + + if (trim($buffer) !== '') { + + $pos = clone $position; + $pos->index = max(0, $pos->index - 1); + + yield [$buffer, $pos]; + $this->update($position, $buffer); + $position->index += strlen($buffer); + } + } + + public function __toString(): string { + try { - $this->getAst(); + if (empty($this->ast)) { + + if (empty($this->queue)) { + + return ''; + } + + if ((count($this->queue) > 1 || (strlen($this->queue[0]['content']) > $this->options['multi_processing_threshold'] * .8) && ProcessPool::isSupported() && $this->options['multi_processing'])) { + + $processPool = new ProcessPool(); + + $options = $this->options; + $web = PHP_SAPI != 'cli'; + $currentDirectory = Helper::getCurrentDirectory(); + $css = []; + + $options['multi_processing'] = false; + $threshold = $options['multi_processing_threshold']; + + foreach ($this->queue as $data) { + + $file = $data['file'] ?? ''; + $content = $data['content']; + $media = $datum['media'] ?? ''; + + $root = $currentDirectory == '/' ? '/' : $currentDirectory . '/'; + $size = min($threshold, strlen($content) / 2); + + foreach ($this->slice($content, $size, (object)[ + 'line' => 1, + 'column' => 1, + 'index' => 0, + 'src' => $file + ]) as $slice) { - if (isset($this->ast)) { + $processPool->add(function () use ($root, $currentDirectory, $web, $media, $file, $slice, $options): array { - return (new Renderer())->renderAst($this->ast); + $parser = (new Parser($slice[0], array_merge($options, [ + 'ast_src' => $file, + 'ast_position_line' => $slice[1]->line, + 'ast_position_column' => $slice[1]->column, + 'ast_position_index' => $slice[1]->index + ]), $media)); + + $renderer = new Renderer(); + + $children = $parser->getAst()->children ?? []; + $result = []; + + foreach ($children as $child) { + + $css = $renderer->renderAst($child); + + if ($css !== '') { + + if ($child->type == 'Comment') { + + $result[] = (object)[ + 'type' => $child->type, + 'css' => $css + ]; + continue; + } + + if (isset($result[$css])) { + + unset($result[$css]); + } + + $result[$css] = (object)[ + 'type' => $child->type, + 'css' => $css + ]; + } + } + + return array_values($result); + })-> + then(function (array $result, int $index) use (&$css) { + + $css[$index] = $result; + }); + } + } + + $processPool->wait(); + + ksort($css); + + $result = []; + + foreach ($css as $data) { + + foreach ($data as $datum) { + + if ($datum->type == 'Comment') { + + $result[] = $datum->css; + continue; + } + + if (isset($result[$datum->css])) { + + unset($result[$datum->css]); + } + + $result[$datum->css] = $datum->css; + } + } + + return implode((new Renderer())->getOptions('glue'), $result); + } } + $this->doParse(); + + return (new Renderer())->renderAst($this->ast); + } catch (Exception $ex) { - fwrite(STDERR, $ex); error_log($ex); } return ''; } + + /** + * @throws Exception + */ + protected function tokenize() + { + foreach ($this->lexer->tokenize() as $event => $data) { + + $token = $data[0]; + $status = call_user_func_array([$this, $event . 'Node'], $data); + + if ($event == 'enter' && $status != ValidatorInterface::VALID) { + + $token->type = 'Invalid' . $token->type; + } + } + } } \ No newline at end of file diff --git a/src/Parser/Helper.php b/src/Parser/Helper.php index 27d54c13..cbf503b0 100755 --- a/src/Parser/Helper.php +++ b/src/Parser/Helper.php @@ -57,7 +57,7 @@ protected static function doParseUrl($url): array public static function getCurrentDirectory() { - if (php_sapi_name() == 'cli') { + if (PHP_SAPI == 'cli') { return static::format(getcwd()); } @@ -141,7 +141,7 @@ public static function resolvePath(string $file, string $path = '') public static function absolutePath(string $file, string $ref): string { - if (php_sapi_name() != 'cli' && preg_match('#^/([^/]|$)#', $file) && is_file($file)) { + if (PHP_SAPI != 'cli' && preg_match('#^/([^/]|$)#', $file) && is_file($file)) { $file = preg_replace('#'.preg_quote(getcwd()).'#', '', $file); } @@ -150,7 +150,7 @@ public static function absolutePath(string $file, string $ref): string $ref = static::format($ref); // web server environment - if (str_starts_with($ref, '/') && php_sapi_name() != 'cli') { + if (str_starts_with($ref, '/') && PHP_SAPI != 'cli') { if (str_starts_with($file, '/') && substr($file, 1, 1) != '/') { diff --git a/src/Parser/Lexer.php b/src/Parser/Lexer.php index 2defc96d..aabfdd64 100755 --- a/src/Parser/Lexer.php +++ b/src/Parser/Lexer.php @@ -4,9 +4,11 @@ use Exception; +use Generator; use TBela\CSS\Event\EventTrait; -use TBela\CSS\Interfaces\ValidatorInterface; +use TBela\CSS\Interfaces\InvalidTokenInterface; use TBela\CSS\Value; + class Lexer { @@ -73,21 +75,33 @@ public function setContext(object $context): Lexer return $this; } - /** - * @return Lexer - * @throws Exception - */ + /** + * @return Lexer|Generator + * @throws Exception + */ - public function tokenize(): static + public function tokenize(): Lexer|Generator { - return $this->doTokenize($this->css, $this->src, $this->recover, $this->context, $this->parentStylesheet, $this->parentMediaRule); + foreach($this->doTokenize($this->css, $this->src, $this->recover, $this->context, $this->parentStylesheet, $this->parentMediaRule) as $key => $value) { + + yield $key => $value; + } + + $this->css = ''; } /** + * @param $css + * @param $src + * @param $recover + * @param $context + * @param $parentStylesheet + * @param $parentMediaRule + * @return Generator * @throws Exception */ - public function doTokenize($css, $src, $recover, $context, $parentStylesheet, $parentMediaRule): static + public function doTokenize($css, $src, $recover, $context, $parentStylesheet, $parentMediaRule): Generator { $position = $context->location->end; @@ -183,7 +197,7 @@ public function doTokenize($css, $src, $recover, $context, $parentStylesheet, $p $token->src = $src; } - $this->emit('enter', $token, $context, $parentStylesheet); + yield 'enter' => [$token, $context, $parentStylesheet]; $this->update($position, $comment); $position->index += strlen($comment); @@ -237,7 +251,7 @@ public function doTokenize($css, $src, $recover, $context, $parentStylesheet, $p 'value' => rtrim($declaration, "\n\r\t ") ]; - $this->emit('enter', $token, $context, $parentStylesheet); + yield 'enter' => [$token, $context, $parentStylesheet]; } else { @@ -346,6 +360,9 @@ public function doTokenize($css, $src, $recover, $context, $parentStylesheet, $p // invalid declaration if ($isValidDeclaration) { + /** + * @var InvalidTokenInterface $className + */ $className = Value::getClassName($value->type); $data[$key] = $className::doRecover($value); } @@ -360,8 +377,8 @@ public function doTokenize($css, $src, $recover, $context, $parentStylesheet, $p } } - $this->emit('enter', $declaration, $context, $parentStylesheet); - } + yield 'enter' => [$declaration, $context, $parentStylesheet]; + } } $this->update($position, $name); @@ -489,7 +506,7 @@ public function doTokenize($css, $src, $recover, $context, $parentStylesheet, $p $rule->location->end->line = max(1, $rule->location->end->line - 1); $rule->location->end->column = max($rule->location->end->column - 1, 1); - $this->emit('enter', $rule, $context, $parentStylesheet); + yield 'enter' => [$rule, $context, $parentStylesheet]; $i += strlen($name) - 1; continue; @@ -504,7 +521,8 @@ public function doTokenize($css, $src, $recover, $context, $parentStylesheet, $p $rule->location->end->index = max(1, $rule->location->end->index - 1); $this->parseComments($rule); - $this->emit('enter', $rule, $context, $parentStylesheet); + + yield 'enter' => [$rule, $context, $parentStylesheet]; $i += strlen($name) - 1; continue; @@ -563,7 +581,8 @@ public function doTokenize($css, $src, $recover, $context, $parentStylesheet, $p $validRule = false; $rule->type = 'InvalidRule'; $rule->value = $body; - $this->emit('enter', $rule, $context, $parentStylesheet); + + yield 'enter' => [$rule, $context, $parentStylesheet]; } } @@ -573,7 +592,8 @@ public function doTokenize($css, $src, $recover, $context, $parentStylesheet, $p if (!$ignoreRule) { - $validRule = $this->getStatus('enter', $rule, $context, $parentStylesheet) == ValidatorInterface::VALID; + yield 'enter' => [$rule, $context, $parentStylesheet]; + $validRule = !str_starts_with($rule->type, 'Invalid'); } } @@ -609,7 +629,10 @@ public function doTokenize($css, $src, $recover, $context, $parentStylesheet, $p $parentStylesheet->type = 'NestingRule'; } - $this->doTokenize($recover ? $body : substr($body, 0, -1), $src, $recover, $rule, $newParentStyleSheet, $newParentMediaRule); + foreach ($this->doTokenize($recover ? $body : substr($body, 0, -1), $src, $recover, $rule, $newParentStyleSheet, $newParentMediaRule) as $event => $data) { + + yield $event => $data; + } } $string = $name . $body; @@ -625,15 +648,14 @@ public function doTokenize($css, $src, $recover, $context, $parentStylesheet, $p if (!$ignoreRule) { $this->parseComments($rule); - $this->emit('exit', $rule, $context, $parentStylesheet); + + yield 'exit' => [$rule, $context, $parentStylesheet]; } } } $context->location->end->index = max(1, $context->location->end->index - 1); $context->location->end->column = max($context->location->end->column - 1, 1); - - return $this; } public function setParentOffset(object $parentOffset): static @@ -796,24 +818,4 @@ protected function parseVendor(string $str): array return ['name' => $str]; } - - /** - * @param string $event - * @param object $rule - * @param $context - * @param $parentStylesheet - * @return int - */ - protected function getStatus(string $event, object $rule, $context, $parentStylesheet): int - { - foreach ($this->emit($event, $rule, $context, $parentStylesheet) as $status) { - - if (is_int($status)) { - - return $status; - } - } - - return ValidatorInterface::VALID; - } } \ No newline at end of file diff --git a/src/Parser/MultiprocessingTrait.php b/src/Parser/MultiprocessingTrait.php deleted file mode 100644 index f71bfaf8..00000000 --- a/src/Parser/MultiprocessingTrait.php +++ /dev/null @@ -1,58 +0,0 @@ -pool)) { - - $this->pool = (new Pool())-> - on('finish', function (Process $process, $stdout, $stderr, $key) { - - if ($process->getExitCode() != 0) { - - throw new \RuntimeException($stderr, $process->getExitCode()); - } - - if (isset($this->format)) { - - $this->output[$key] = in_array($this->format, ['serialize', 'serialize-array']) ? unserialize($stdout) : json_decode($stdout); - } - - else { - - $this->output[$key] = $stdout; - } - }); - } - - $phpPath = (new PhpExecutableFinder())->find(); - $cmd = array_merge([ - $phpPath, - '-f', - __DIR__ . '/../../cli/css-parser', - '--' - ], $cliArgs); - - $this->output[] = null; - - $process = new Process($cmd); - - $process->setInput($buffer); - $this->pool->add($process); - } -} \ No newline at end of file diff --git a/src/Parser/ParserTrait.php b/src/Parser/ParserTrait.php index 555fe7d2..85f44747 100755 --- a/src/Parser/ParserTrait.php +++ b/src/Parser/ParserTrait.php @@ -2,6 +2,7 @@ namespace TBela\CSS\Parser; +use TBela\CSS\Parser; use TBela\CSS\Property\Config; trait ParserTrait @@ -13,8 +14,8 @@ trait ParserTrait * @return object * @ignore */ - protected function update(object $position, string $string) - { + protected function update(object $position, string $string): object + { $j = strlen($string); @@ -38,8 +39,8 @@ protected function update(object $position, string $string) * @param bool $force * @return false|string */ - public static function stripQuotes(string $string, bool $force = false) - { + public static function stripQuotes(string $string, bool $force = false): bool|string + { $q = substr($string, 0, 1); @@ -54,24 +55,19 @@ public static function stripQuotes(string $string, bool $force = false) return $string; } - public static function match_comment($string, $start, $end) - { + public static function match_comment($string, $start, $end): bool|string + { $i = $start + 1; while ($i++ < $end) { - switch ($string[$i]) { + if ($string[$i] == '*') { + if ($string[$i + 1] == '/') { - case '*': - - if ($string[$i + 1] == '/') { - - return is_array($string) ? implode('', array_slice($string, $start, $i + 2 - $start)) : substr($string, $start, $i + 2 - $start); - } - - break; - } + return is_array($string) ? implode('', array_slice($string, $start, $i + 2 - $start)) : substr($string, $start, $i + 2 - $start); + } + } } // unterminated comment is still a valid comment @@ -86,8 +82,8 @@ public static function match_comment($string, $start, $end) * @param array $char_stop * @return false|string */ - public static function substr(string $string, int $startPosition, int $endPosition, array $char_stop) - { + public static function substr(string $string, int $startPosition, int $endPosition, array $char_stop): bool|string + { if ($startPosition < 0) { @@ -262,8 +258,8 @@ public static function _close($string, $search, $reset, $start, $end) * @param int $limit * @return array */ - public static function split($string, $separator = '', int $limit = PHP_INT_MAX) - { + public static function split(string $string, string $separator = '', int $limit = PHP_INT_MAX): array + { $result = []; @@ -419,8 +415,8 @@ public static function splitValues(array $values, string $property): array { return $result; } - public static function is_whitespace($char) - { + public static function is_whitespace($char): bool + { return preg_match("#^\s$#", $char); } diff --git a/src/Process/AbstractProcess.php b/src/Process/AbstractProcess.php new file mode 100644 index 00000000..08b7322c --- /dev/null +++ b/src/Process/AbstractProcess.php @@ -0,0 +1,202 @@ +started; + } + + public function isRunning(): bool + { + + return $this->running; + } + + public function isTerminated(): bool + { + + return $this->terminated; + } + + public function getPid(): ?int + { + + return $this->pid; + } + + public function kill(int $pid): bool { + + if (!is_null($this->pid)) { + + if (strncasecmp(PHP_OS, "win", 3) == 0) { + + exec(sprintf('taskkill /F /T /PID %d 2>&1', $pid), $output, $exitCode); + } + + else { + + exec(sprintf('kill -9 -%d 2>&1', $pid), $output, $exitCode); + } + + if ($exitCode > 1 && $this->isRunning()) { + + return false; + } + + return true; + } + + return false; + } + + public function setTimeout(float $timeout): void + { + $this->timeout = $timeout; + } + + public function getTimeout(): ?float + { + return $this->timeout; + } + + /** + * @return void + * @throws TimeoutException + */ + public function checkTimeout(): void + { + if ($this->timeout > 0 && microtime(true) - $this->startTime >= $this->timeout) { + + $this->stop(); + + throw new TimeoutException('the task has timed out', 500); + } + } + + public function getStartTime(): ?float + { + return $this->startTime; + } + + public function getEndTime(): ?float + { + return $this->endTime; + } + + public function getExitCode(): ?int + { + return $this->exitCode; + } + + public function getDuration(): ?string + { + + if (is_null($this->endTime)) { + + return null; + } + + $duration = $this->endTime - $this->startTime; + + return sprintf("%.2f%s", $duration < 1 ? $duration * 1000 : $duration, $duration < 1 ? 'ms' : 's'); + } + + public function getData() + { + return $this->data; + } + + public function getStdErr(): string + { + return $this->err; + } + + public function isStopped(): bool + { + return $this->stopped; + } + /** + * @param int $waitTimeout timeout in nanoseconds + * @return Generator + * @throws IllegalStateException + * @throws RuntimeException|TimeoutException + */ + + public function check(int $waitTimeout): Generator + { + + if (!$this->started || !$this->running) { + + throw new IllegalStateException('thread must be started', 503); + } + + if ($this->terminated) { + + return; + } + + foreach ($this->ipc->read($waitTimeout) as $data) { + + if ($data !== true && $data !== "done") { + + $this->checkTimeout(); + + yield $data; + } + } + + $buffer = $this->ipc->getData(); + $this->data = $this->serializer->decode($buffer); + + $this->stop(); + + if (is_null($this->data) && !empty($buffer)) { + + throw new RuntimeException(sprintf("invalid %s data?\n%s\n\n", $this->serializer->getName(), $buffer), 500); + } + + $this->ipc->release(); + + $this->cleanup(); + + yield true; + } +} \ No newline at end of file diff --git a/src/Process/Exceptions/IllegalStateException.php b/src/Process/Exceptions/IllegalStateException.php new file mode 100644 index 00000000..efef4928 --- /dev/null +++ b/src/Process/Exceptions/IllegalStateException.php @@ -0,0 +1,8 @@ +socket = socket_create(AF_UNIX, strcasecmp(substr(PHP_OS, 0, 3), 'win') === 0 ? SOCK_STREAM : SOCK_DGRAM, 0))) { + + throw new RuntimeException("Can't create socket", 500); + } + + if (is_null($path)) { + + $this->path = tempnam(sys_get_temp_dir(), 'css-'); + unlink($this->path); + } + + else { + + $this->path = $path; + } + + register_shutdown_function(function () { + + @unlink($this->path); + }); + + if (!socket_bind($this->socket, $this->path)) { + + throw new RuntimeException("socket can't bind to $this->path", 500); + } + + if (!socket_set_block($this->socket)) { + + throw new RuntimeException("socket can't set blocking socket", 500); + } + + $this->server = $server; + } + + public function getKey(): string + { + + return $this->path; + } + + public static function isSupported(): bool + { + + return function_exists('\\socket_create'); + } + + public function write(string $data): void + { + + $j = strlen($data) - 1; + + $i = 0; + + while($i < $j) { + + $bytes_sent = socket_sendto($this->socket, substr($data, $i, static::BUFFER_SIZE), static::BUFFER_SIZE, 0, $this->server); + + if ($bytes_sent === false) { + + throw new RuntimeException(sprintf("can't write to socket: %s", socket_strerror(socket_last_error($this->socket))), 500); + } + + + else { + + $i += $bytes_sent; + } + } + } + + public function read(int $waitTimeout = 1): Generator + { + } + + public function getData(): string + { + + return $this->data; + } + + public function release() + { + if (!empty($this->socket)) { + + socket_close($this->socket); + $this->socket = null; + } + } + + public function __destruct() + { + + $this->release(); + } +} \ No newline at end of file diff --git a/src/Process/IPC/Transport/IPCSocketPair.php b/src/Process/IPC/Transport/IPCSocketPair.php new file mode 100644 index 00000000..624893d6 --- /dev/null +++ b/src/Process/IPC/Transport/IPCSocketPair.php @@ -0,0 +1,118 @@ +pair)) { + + throw new RuntimeException("Can't create sockets pair", 500); + } + } + + public static function isSupported(): bool + { + + return function_exists('\\socket_create_pair'); + } + + public function write(string $data): void + { + + $this->data = ''; + + $i = 0; + $j = strlen($data); + + while ($i < $j) { + + $written = socket_write($this->pair[1], substr($data, $i, static::BUFFER_SIZE)); + + if ($written === false) { + + break; + } + + $i += $written; + } + } + + public function read(int $waitTimeout = 1): Generator + { + + $buffer = ''; + $seconds = floor($waitTimeout / 1000); + + while (true) { + + $read = [$this->pair[0]]; + $write = null; + $except = null; + + $changed = @socket_select($read, $write, $except, $seconds, $waitTimeout - 1000 * $seconds); + + if ($changed === false) { + + $status = socket_last_error($this->pair[0]); + + if ($status) { + + throw new RuntimeException(socket_strerror($status), 500); + } + } + + $data = socket_read($this->pair[0], static::BUFFER_SIZE); + + if (is_string($data)) { + + $buffer .= $data; + + if (strlen($data) < static::BUFFER_SIZE) { + + break; + } + + yield "reading"; + } else { + + yield "waiting"; + } + } + + $this->data = $buffer; + yield "done"; + } + + public function getData(): string + { + + return $this->data; + } + + public function release() + { + if (!empty($this->pair)) { + + socket_close($this->pair[1]); + socket_close($this->pair[0]); + + $this->pair = []; + } + } + + public function __destruct() { + + $this->release(); + } +} \ No newline at end of file diff --git a/src/Process/IPC/Transport/IPCSocketServer.php b/src/Process/IPC/Transport/IPCSocketServer.php new file mode 100644 index 00000000..d9beb845 --- /dev/null +++ b/src/Process/IPC/Transport/IPCSocketServer.php @@ -0,0 +1,131 @@ +socket = socket_create(AF_UNIX, strcasecmp(substr(PHP_OS, 0, 3), 'win') === 0 ? SOCK_STREAM : SOCK_DGRAM, 0))) { + + throw new RuntimeException("Can't create socket", 500); + } + + if (is_null($path)) { + + $this->path = tempnam(sys_get_temp_dir(), 'css-'); + unlink($this->path); + } else { + + $this->path = $path; + } + + register_shutdown_function(function () { + + @unlink($this->path); + }); + + if (!socket_bind($this->socket, $this->path)) { + + throw new RuntimeException("socket can't bind to $this->path", 500); + } + + if (!socket_set_nonblock($this->socket)) { + + throw new RuntimeException("socket can't set non blocking socket", 500); + } + } + + public function getKey() + { + + return $this->path; + } + + public static function isSupported(): bool + { + + return function_exists('\\socket_create'); + } + + public function write(string $data): void + { + } + + public function read(int $waitTimeout = 1): Generator + { + + $buffer = ''; + + while (true) { + + $read = [$this->socket]; + $write = null; + $except = null; + + $changed = socket_select($read, $write, $except, 0, $waitTimeout * 20000); + + if ($changed === false) { + + throw new RuntimeException(socket_strerror(socket_last_error($this->socket)), 500); + } + + if (empty($read)) { + + yield "waiting"; + continue; + } + + $bytes_received = socket_recvfrom($this->socket, $data, static::BUFFER_SIZE, 0, $from); + + if ($bytes_received === false) { + + throw new RuntimeException(sprintf("Can't read from socket: %s", socket_strerror(socket_last_error($this->socket))), 500); + } + + $buffer .= $data; + + if (strlen($data) < static::BUFFER_SIZE) { + + break; + } + + yield "reading"; + } + + $this->data = $buffer; + yield "done"; + } + + public function getData(): string + { + + return $this->data; + } + + public function release() + { + if (!empty($this->socket)) { + + socket_close($this->socket); + $this->socket = null; + } + } + + public function __destruct() + { + + $this->release(); + } +} \ No newline at end of file diff --git a/src/Process/MultiProcessing/Process.php b/src/Process/MultiProcessing/Process.php new file mode 100644 index 00000000..5e1e276e --- /dev/null +++ b/src/Process/MultiProcessing/Process.php @@ -0,0 +1,185 @@ +getReflector()->getUseVariables(); + + foreach ($vars as $key => $var) { + + $script .= "\n\$$key = " . var_export($var, true) . ";"; + } + + $code = $serialized->getReflector()->getCode(); + + $this->ipc = new IPCSocketServer(); + $this->serializer = Serializer::getInstance(); + + $key = $this->ipc->getKey(); + + $script .= sprintf(" + + try { + \$data = call_user_func(%s); + \$ipc = new %s(null, '%s'); + + \$serializer = new %s(); + \$ipc->write(\$serializer->encode(\$data)); + } + + catch (Throwable \$e) { + + fwrite(STDERR, \$e); + } + ", $code, IPCSocketClient::class, $key, $this->serializer::class); + + $script .= "exit;"; + + $file = tempnam(sys_get_temp_dir(), 'csr-'); + + register_shutdown_function(function () use ($file) { + + @unlink($file); + $this->cleanup(); + }); + + file_put_contents($file, ""); + $this->command = [PHP_BINARY, '-f', escapeshellarg($file)]; + } + + public static function isSupported(): bool + { + return function_exists('\\proc_open') && extension_loaded('sockets'); + } + + public function start(): void + { + $this->startTime = microtime(true); + + $descriptorspec = [ + 0 => array("pipe", "r"), // stdin is a pipe that the child will read from + 1 => array("pipe", "wb"), // stdout is a pipe that the child will write to + 2 => array("pipe", "wb") // stderr is a file to write to + ]; + + $this->process = proc_open(implode(' ', $this->command), $descriptorspec, $this->pipes); + + if ($this->process === false) { + + throw new RuntimeException(sprintf("failed to run command: '%'", implode(' ', $this->command)), 500); + } + + $this->startTime = microtime(true); + + fclose($this->pipes[0]); + + $this->status = proc_get_status($this->process); + + $this->pid = $this->status['pid']; + + $this->started = true; + $this->running = true; + } + + /** + * @throws IllegalStateException + */ + public function stop(float $timeout = 10, int $signal = null): void + { + if (!$this->started) { + + throw new IllegalStateException('process must be started', 500); + } + + if ($this->stopped || $this->terminated) { + + return; + } + + $this->status = proc_get_status($this->process); + + if ($this->status['running']) { + + if (!$this->kill($this->pid)) { + + proc_terminate($this->process, 9); + + $this->status = proc_get_status($this->process); + + if ($this->status['running']) { + + throw new RuntimeException(sprintf("cannot kill process #%s", $this->getPid())); + } + } + } + + $this->cleanup(); + } + + /** + * @return void + */ + public function cleanup(): void + { + if ($this->process) { + + $this->running = false; + $this->terminated = true; + $this->endTime = microtime(true); + + $this->exitCode = max(0, $this->status['exitcode']); + + $this->err = stream_get_contents($this->pipes[2]); + + fclose($this->pipes[1]); + fclose($this->pipes[2]); + + proc_close($this->process); + $this->process = null; + $this->pid = null; + + $this->pipes = []; + } + } +} \ No newline at end of file diff --git a/src/Process/Pool.php b/src/Process/Pool.php index 862acf91..30187306 100644 --- a/src/Process/Pool.php +++ b/src/Process/Pool.php @@ -2,127 +2,230 @@ namespace TBela\CSS\Process; -use Symfony\Component\Process\Process; -use TBela\CSS\Event\EventInterface; +use Closure; +use Opis\Closure\SerializableClosure; +use ReflectionClass; +use ReflectionException; +use ReflectionNamedType; +use ReflectionType; +use ReflectionUnionType; +use SplObjectStorage; use TBela\CSS\Event\EventTrait; +use TBela\CSS\Process\Exceptions\TimeoutException; +use TBela\CSS\Process\Exceptions\UnhandledException; +use TBela\CSS\Process\MultiProcessing\Process; +use TBela\CSS\Process\Thread\PCNTL\Thread; +use Throwable; /** - * A simple Pool manager around symphony Process component. + * Simple thread pool manager using pcntl extension * * Usage: - * * - * $pool = new Pool(); + * if (Pool::isSupported()) { * - * $pool->on('finish', function (Process $process, $position) { - * echo "process #$position completed!"; - * }); + * $pool = new Pool(); * - * $pool->add(new Process(...)); + * $pool->on('finish', function (mixed $data, int $index, ?string $stderr, int $exitCode, ProcessInterface $process) { * - * $pool->add(new Process(...)); + * echo "sprintf(thread $index completed in %dns\n", $process->getDuration()); + * var_dump($data); + * } * - * $pool->add(new Process(...)); + * $names = [ 'joe', 'jane', 'romney', 'suzan', 'bruce', 'jim', 'tania']; * - * $pool->wait(); - * + * for ($i = 0; $i < 10; $i++) { + * + * $pool->add(function () use($names) { + * + * // do something + * $index = random_int(1, count($names) - 1); + * + * sleep($index); + * return sprintf("%s is having good sleep", $names[$index]); + * }); + * } + * + * $pool->wait(); + * } */ -class Pool implements EventInterface +class Pool implements PoolInterface { - use EventTrait; - protected int $concurrency = 20; + /** + * @var string|null default engine + */ + protected static ?string $defaultEngine = null; + protected ?ProcessInterface $current = null; + protected ?int $startTime = null; protected int $count = 0; + protected int $concurrency = 25; - protected int $sleepTime = 33; + /** + * @var int time in nanoseconds + */ + protected int $sleepTime = 33000; + protected SplObjectStorage $storage; - protected \SplObjectStorage $storage; - - protected ?Process $current = null; + protected int $timeout = 60; + protected string $engine; public function __construct() { - $this->concurrency = max(20, ceil(Helper::getCPUCount() * 2.5)); - $this->storage = new \SplObjectStorage(); + $this->storage = new SplObjectStorage(); + $this->concurrency = Helper::getCPUCount() * 2; + $this->engine = static::getDefaultEngine(); } - public function add(Process $process): static + public static function isSupported(): bool { - $this->current = $process; - $this->storage[$process] = (object)['data' => (object)['index' => $this->count++, 'stdout' => '', 'stderr' => ''], 'next' => []]; + return Thread::isSupported() || Process::isSupported(); + } - $this->check(); - return $this; + public static function getAvailableEngines(): array + { + + $result = []; + + if (Thread::isSupported()) { + + $result[] = 'thread'; + } + + if (Process::isSupported()) { + + $result[] = 'process'; + } + + return $result; } - public function then(\Closure $callable): static + public function setEngine(string $engine): static { - $process = $this->current; + if (in_array($engine, static::getAvailableEngines())) { - $data = $this->storage[$process]; - $data->next[] = $callable; - $this->storage[$process] = $data; + $this->engine = $engine; + } return $this; } - public function setConcurrency(int $concurrency): static + public function getEngine(): string { - $this->concurrency = $concurrency; - return $this; + return $this->engine; } - public function setSleepTime(int $sleepTime): static + public static function getDefaultEngine(): ?string { - $this->sleepTime = $sleepTime; + return static::$defaultEngine ?? current(static::getAvailableEngines()); + } + + public static function setDefaultEngine(?string $engine) + { + + if ($engine == 'thread' && Thread::isSupported()) { + + static::$defaultEngine = 'thread'; + } else if ($engine == 'process' && Process::isSupported()) { + + static::$defaultEngine = 'process'; + } else if (is_null($engine)) { + + static::$defaultEngine = null; + } + } + + /** + * @param Closure $closure + * @return ProcessInterface + */ + public function createProcess(Closure $closure): ProcessInterface + { + return match ($this->engine) { + 'thread' => new Thread($closure), + default => new Process($closure), + }; + } + + /** + * @throws ReflectionException + * @throws UnhandledException + */ + public function add(Closure $closure): static + { + + $this->current = $this->createProcess($closure); + + $this->current->setTimeout($this->timeout); + + $this->storage[$this->current] = (object)['data' => (object)['index' => $this->count++, 'stdout' => '', 'stderr' => '', 'counter' => 0], 'next' => [], 'error' => []]; + + $this->check(false); return $this; } - protected function check(): bool + /** + * @throws ReflectionException + * @throws UnhandledException + */ + protected function check($collect = true): bool { $running = 0; /** - * @var Process $process + * @var ProcessInterface $thread */ - foreach ($this->storage as $process) { + foreach ($this->storage as $thread) { + + if ($running >= $this->concurrency) { - $data = $this->storage[$process]->data; + break; + } - if ($process->isTerminated()) { + $data = $this->storage[$thread]->data; - $running = max(0, $running--); + if ($collect && $thread->isTerminated()) { - foreach ($this->storage[$process]->next as $callable) { + $this->collect($thread); + } else if ($collect && $thread->isRunning()) { - call_user_func($callable, $process, $data->stdout, $data->stderr, $data->index); - } + try { - $this->emit('finish', $process, $data->stdout, $data->stderr, $data->index); - $this->storage->detach($process); - continue; - } + foreach ($thread->check(1) as $status) { - if ($process->isRunning()) { + if ($status === "waiting") { - $running++; - } else if ($running >= $this->concurrency) { + break; + } - break; - } else if (!$process->isStarted()) { + if ($status === true) { + + $this->collect($thread); + $running = max(0, $running - 1); + } + } + } catch (Throwable $e) { + + if ($e instanceof TimeoutException) { + + $this->storage->detach($thread); + } - $process->start(function ($type, $buffer) use ($data) { + $this->handleException($e, $data); + } + + } else if (!$thread->isStarted()) { - $data->{'std' . $type} .= $buffer; - }); + $thread->start(); + $this->emit('start', $data->index, $thread); $running++; } } @@ -130,20 +233,163 @@ protected function check(): bool return $this->storage->count() > 0; } + protected function collect(ProcessInterface $thread) + { + + if (!$this->storage->contains($thread)) { + + return; + } + + $data = $this->storage[$thread]; + $result = $thread->getData(); + $stderr = $thread->getStdErr(); + $exitCode = $thread->getExitCode(); + $duration = $thread->getDuration(); + $index = $data->data->index; + + foreach ($data->next as $callable) { + + call_user_func($callable, $result, $index, $stderr, $exitCode, $duration, $thread); + } + + $this->storage->detach($thread); + $this->emit('finish', $result, $index, $stderr, $exitCode, $duration, $thread); + } + + public function then(Closure $callable): static + { + + $process = $this->current; + + $data = $this->storage[$process]; + $data->next[] = $callable; + $this->storage[$process] = $data; + + return $this; + } + + public function catch(Closure $callable): static + { + + $process = $this->current; + + $data = $this->storage[$process]; + + $parameters = (new SerializableClosure($callable))->getReflector()->getParameters(); + + $this->assignErrorHandler($data, $callable, $parameters[0]?->getType()); + + $this->storage[$process] = $data; + + return $this; + } + + protected function assignErrorHandler(object $data, Closure $callable, ?ReflectionType $class) + { + + if (is_null($class)) { + + $data->error['generic'] = $callable; + } else if ($class instanceof ReflectionNamedType) { + + $data->error[$class->getName()] = $callable; + } else if ($class instanceof ReflectionUnionType || (class_exists('\\ReflectionIntersectionType') && $data instanceof \ReflectionIntersectionType)) { + + foreach ($class->getTypes() as $type) { + + $this->assignErrorHandler($data, $callable, $type); + } + } + } + + public function setConcurrency(int $concurrency): static + { + + $this->concurrency = $concurrency; + return $this; + } + + public function getConcurrency(): int + { + + return $this->concurrency; + } + + public function setSleepTime(int $sleepTime): static + { + + $this->sleepTime = $sleepTime; + return $this; + } + + /** + * @throws ReflectionException + * @throws UnhandledException + */ public function wait(): static { while ($this->check()) { - usleep($this->sleepTime); + time_nanosleep(0, $this->sleepTime); } $this->count = 0; $this->current = null; - $this->storage = new \SplObjectStorage(); return $this; } -} + public function cancel(): static + { + foreach ($this->storage as $thread) { + + $data = $this->storage[$thread]; + + $this->storage->detach($thread); + $thread->stop(); + + $this->emit('cancel', $data->data->index, $thread); + } + + return $this; + } + + /** + * @param Throwable $e + * @param $data + * @return void + * @throws UnhandledException + * @throws ReflectionException + */ + protected function handleException(Throwable $e, $data): void + { + $class = new ReflectionClass($e::class); + + while ($class) { + + if (isset($data->error[$class->getName()])) { + + break; + } + + $class = $class->getParentClass(); + } + + if ($class === false) { + + $class = null; + } + + $handler = $data->error[$class?->getName()] ?? $data->error['generic'] ?? null; + if (is_callable($handler)) { + + call_user_func($handler, $e); + } else { + + throw new UnhandledException(sprintf("unhandled exception in task #%d", $data->index), $e->getCode(), $e); + } + } +} \ No newline at end of file diff --git a/src/Process/PoolInterface.php b/src/Process/PoolInterface.php new file mode 100644 index 00000000..22ce2207 --- /dev/null +++ b/src/Process/PoolInterface.php @@ -0,0 +1,12 @@ +task = $task; + $this->ipc = IPC::getInstance(true); + $this->serializer = Serializer::getInstance(); + + register_shutdown_function(function () { + + $this->ipc->release(); + }); + } + + public static function isSupported(): bool + { + + return 'cli' == PHP_SAPI && extension_loaded('pcntl'); + } + + /** + * @throws IllegalStateException + * @throws TimeoutException + */ + public function start(): void + { + + if ($this->started) { + + throw new IllegalStateException('thread is already started', 503); + } + + $this->running = true; + $this->started = true; + $this->terminated = false; + $this->startTime = microtime(true); + + pcntl_signal(SIGCHLD, function () { + + if ($this->running) { + + $this->stopped = true; + } + }); + + $this->pid = pcntl_fork(); + + if ($this->pid == -1) { + + throw new RuntimeException('Cannot fork process', 500); + } + + if ($this->pid === 0) { + + $this->ipc->write($this->serializer->encode(call_user_func($this->task))); + exit; + } + } + + public function stop(float $timeout = 10, int $signal = null): void + { + if ($this->stopped || $this->terminated) { + + return; + } + + if ($this->pid > 0) { + + $this->kill($this->pid); + pcntl_waitpid($this->pid, $this->exitCode); + } + + $this->stopped = true; + $this->terminated = true; + $this->running = false; + $this->pid = null; + } + + /** + * @return void + */ + public function cleanup(): void + { + $this->running = false; + $this->terminated = true; + $this->endTime = microtime(true); + + if (!$this->stopped) { + + pcntl_waitpid($this->pid, $this->exitCode); + } + } +} \ No newline at end of file diff --git a/src/Property/PropertyList.php b/src/Property/PropertyList.php index b54a0665..d9fde4bf 100755 --- a/src/Property/PropertyList.php +++ b/src/Property/PropertyList.php @@ -108,7 +108,7 @@ public function remove($property): static /** * set property * @param string|null $name - * @param Value|string $value + * @param object[]|string $value * @param string|null $propertyType * @param array|null $leadingcomments * @param array|null $trailingcomments @@ -117,7 +117,7 @@ public function remove($property): static * @return $this */ - public function set(?string $name, $value, ?string $propertyType = null, ?array $leadingcomments = null, ?array $trailingcomments = null, ?string $src = '', ?string $vendor = null): PropertyList + public function set(?string $name, $value, ?string $propertyType = null, ?array $leadingcomments = null, ?array $trailingcomments = null, ?string $src = null, ?string $vendor = null): PropertyList { if ($propertyType == 'Comment') { @@ -220,13 +220,13 @@ public function set(?string $name, $value, ?string $propertyType = null, ?array $this->properties[$shorthand] = new PropertySet($shorthand, $config); - if (!is_null($src)) { - - $this->properties[$shorthand]->setSrc($src); - } +// if (!is_null($src)) { +// +// $this->properties[$shorthand]->setSrc($src); +// } } - $this->properties[$shorthand]->set($name, $value, $leadingcomments, $trailingcomments, $vendor); + $this->properties[$shorthand]->set($name, $value, $leadingcomments, $trailingcomments, $vendor, $src); } else { @@ -242,13 +242,13 @@ public function set(?string $name, $value, ?string $propertyType = null, ?array $this->properties[$shorthand] = new PropertyMap($shorthand, $config); - if (!is_null($src)) { - - $this->properties[$shorthand]->setSrc($src); - } +// if (!is_null($src)) { +// +// $this->properties[$shorthand]->setSrc($src); +// } } - $this->properties[$shorthand]->set($name, $value, $leadingcomments, $trailingcomments); + $this->properties[$shorthand]->set($name, $value, $leadingcomments, $trailingcomments, $src); } else { @@ -266,6 +266,11 @@ public function set(?string $name, $value, ?string $propertyType = null, ?array $property->setVendor($vendor); } + if (!is_null($src)) { + + $property->setSrc($src); + } + if (!empty($leadingcomments)) { $property->setLeadingComments($leadingcomments); diff --git a/src/Property/PropertyMap.php b/src/Property/PropertyMap.php index bb7fc84d..44133808 100755 --- a/src/Property/PropertyMap.php +++ b/src/Property/PropertyMap.php @@ -87,12 +87,12 @@ public function remove($property): static /** * set property value * @param string $name - * @param array|string $value + * @param object[]|string $value * @param array|null $leadingcomments * @param array|null $trailingcomments * @return PropertyMap */ - public function set(string $name, $value, ?array $leadingcomments = null, ?array $trailingcomments = null) + public function set(string $name, $value, ?array $leadingcomments = null, ?array $trailingcomments = null, $src = null) { // is valid property @@ -114,6 +114,11 @@ public function set(string $name, $value, ?array $leadingcomments = null, ?array $this->properties[$name] = new Property($name); } + if ($src !== null) { + + $this->properties[$name]->setSrc($src); + } + $this->properties[$name]->setValue($value)-> setLeadingComments($leadingcomments)-> setTrailingComments($trailingcomments); @@ -125,7 +130,12 @@ public function set(string $name, $value, ?array $leadingcomments = null, ?array setLeadingComments($leadingcomments)-> setTrailingComments($trailingcomments); - $separator = Config::getPath('map.' . $this->shorthand . '.separator'); + if ($src !== null) { + + $this->properties[$name]->setSrc($src); + } + + $separator = Config::getPath('map.' . $this->shorthand . '.separator'); $all = []; diff --git a/src/Property/PropertySet.php b/src/Property/PropertySet.php index 5be5294e..27b58618 100755 --- a/src/Property/PropertySet.php +++ b/src/Property/PropertySet.php @@ -87,7 +87,7 @@ public function remove($property): static * @param null $vendor * @return PropertySet */ - public function set(string $name, $value, ?array $leadingcomments = null, ?array $trailingcomments = null, $vendor = null) + public function set(string $name, $value, ?array $leadingcomments = null, ?array $trailingcomments = null, $vendor = null, $src = null) { $propertyName = ($vendor ? '-' . $vendor . '-' : '') . $name; @@ -115,7 +115,7 @@ public function set(string $name, $value, ?array $leadingcomments = null, ?array if ($result === false) { - $this->setProperty($name, $value, $vendor); + $this->setProperty($name, $value, $vendor, $src); return $this; } @@ -125,7 +125,7 @@ public function set(string $name, $value, ?array $leadingcomments = null, ?array unset($this->properties[$this->shorthand]); } else { - $this->setProperty($name, $value, $vendor); + $this->setProperty($name, $value, $vendor, $src); if (!is_null($leadingcomments)) { @@ -160,7 +160,7 @@ public function set(string $name, $value, ?array $leadingcomments = null, ?array unset($this->properties[$this->shorthand]); } - $this->setProperty($name, $value, $vendor); + $this->setProperty($name, $value, $vendor, $src); if (!is_null($leadingcomments)) { @@ -357,7 +357,7 @@ protected function expand($value) * @return PropertySet * @ignore */ - protected function setProperty($name, $value, $vendor = null) + protected function setProperty($name, $value, $vendor = null, $src = null) { $propertyName = ($vendor && substr($name, 0, strlen($vendor) + 2) != '-' . $vendor . '-' ? '-' . $vendor . '-' : '') . $name; @@ -367,11 +367,16 @@ protected function setProperty($name, $value, $vendor = null) $this->properties[$propertyName] = new Property($name); } - if ($vendor) { + if ($vendor !== null) { $this->properties[$propertyName]->setVendor($vendor); } + if ($src !== null) { + + $this->properties[$propertyName]->setSrc($src); + } + $this->properties[$propertyName]->setValue($value); return $this; diff --git a/src/Renderer.php b/src/Renderer.php index cb66c7a5..941a0092 100755 --- a/src/Renderer.php +++ b/src/Renderer.php @@ -4,15 +4,15 @@ use axy\sourcemap\SourceMap; use Exception; -use Generator; +use stdClass; use TBela\CSS\Ast\Traverser; use TBela\CSS\Exceptions\IOException; use TBela\CSS\Interfaces\ParsableInterface; use TBela\CSS\Interfaces\RenderableInterface; use TBela\CSS\Interfaces\ElementInterface; use TBela\CSS\Parser\Helper; -use TBela\CSS\Parser\MultiprocessingTrait; use TBela\CSS\Parser\SyntaxError; +use TBela\CSS\Process\Pool as ProcessPool; use TBela\CSS\Property\PropertyList; use function is_string; @@ -23,8 +23,6 @@ class Renderer { - use MultiprocessingTrait; - protected array $options = [ 'glue' => "\n", 'indent' => ' ', @@ -41,7 +39,7 @@ class Renderer 'remove_empty_nodes' => false, 'allow_duplicate_declarations' => false, 'multi_processing' => false, - 'multi_processing_threshold' => 400 + 'multi_processing_threshold' => 1500 ]; /** @@ -53,9 +51,6 @@ class Renderer protected array $indents = []; protected ?SourceMap $sourcemap; - // 'serialize-array' or 'json-array' - protected string $format = 'serialize-array'; - /** * Identity constructor. * @param array $options @@ -67,143 +62,11 @@ public function __construct(array $options = []) $this->setOptions($options); } - public function getCliArgs(array $parseOptions, array $renderOptions): array - { - - $args = [ - '--parse-multi-processing=off' - ]; - - if (empty($parseOptions['capture_errors'])) { - // default is on - $args[] = '--capture-errors=off'; - } - - if (!empty($parseOptions['ast_src'])) { - // default is on - $args[] = sprintf('--parse-ast-src=%s', $parseOptions['ast_src']); - } - - if (!empty($parseOptions['flatten_import'])) { - // default is off - $args[] = '--flatten-import=on'; - } - - if ($parseOptions['ast_src'] ?? '') { - // default is off - $args[] = '--parse-ast-src=' . $parseOptions['ast_src']; - } - - if (empty($parseOptions['allow_duplicate_declarations'])) { - // default is on - $args[] = '--parse-allow-duplicate-declarations==off'; - } - - $args[] = '--render-multi-processing=off'; - - foreach ([ -// 'glue' => "\n", -// 'indent' => ' ', -// 'css_level' => 4, -// 'separator' => ' ', - 'charset' => false, - 'compress' => false, -// 'sourcemap' => false, - 'convert_color' => false, - 'remove_comments' => false, - 'preserve_license' => false, - 'legacy_rendering' => false, - 'compute_shorthand' => true, - 'remove_empty_nodes' => false, - 'allow_duplicate_declarations' => false, - 'multi_processing' => true - ] as $key => $value) { - - if (!isset($renderOptions[$key]) || $renderOptions[$key] === $value) { - - continue; - } - - $args[] = sprintf('--%s%s=%s', $key == 'allow_duplicate_declarations' ? 'render-' : '', str_replace('_', '-', $key), is_bool($renderOptions[$key]) ? ($renderOptions[$key] ? 'on' : 'off') : $renderOptions[$key]); - } - - $args[] = '--output-format=' . $this->format; - - return $args; - } - - public function slice($css, $size): Generator - { - - $i = -1; - $j = strlen($css) - 1; - - $buffer = ''; - - while ($i++ < $j) { - - $string = Parser::substr($css, $i, $j, ['{']); - - if ($string === false) { - - $buffer = substr($css, $i); - - yield $buffer; - - $buffer = ''; - break; - } - - $string .= Parser::_close($css, '}', '{', $i + strlen($string), $j); - $buffer .= $string; - - if (strlen($buffer) >= $size) { - - $k = 0; - while (Parser::is_whitespace($buffer[$k])) { - - $k++; - } - - if ($k > 0) { - - $buffer = substr($buffer, $k); - } - - yield $buffer; - $buffer = ''; - } - - $i += strlen($string) - 1; - } - - if ($buffer) { - - $k = 0; - $l = strlen($buffer); - while ($k < $l && Parser::is_whitespace($buffer[$k])) { - - $k++; - } - - if ($k > 0) { - - $buffer = substr($buffer, $k); - } - } - - if (trim($buffer) !== '') { - - yield $buffer; - } - } - /** * @param string $css * @param array $renderOptions * @param array $parseOptions * @return string - * @throws IOException * @throws SyntaxError * @throws Exception */ @@ -211,38 +74,41 @@ public static function fromString(string $css, array $renderOptions = [], array { $parser = new Parser(options: $parseOptions); - $renderer = new static($renderOptions); + $renderer = new Renderer($renderOptions); $size = max(1, min($parser->getOptions('multi_processing_threshold'), strlen($css) / 2)); - if (function_exists('\\proc_open') && $renderer->options['multi_processing'] && empty($renderer->options['sourcemap']) && strlen($css) > $size) { + if (ProcessPool::isSupported() && strlen($css) > $size) { - $args = $renderer->getCliArgs($parser->getOptions(), $renderer->options); + $processPool = new ProcessPool(); - foreach ($renderer->slice($css, $size) as $buffer) { + $parseOptions['multi_processing'] = false; + $renderOptions['multi_processing'] = false; - $renderer->enQueue($buffer, $args); - } - - $renderer->pool->wait(); + $data = []; + $root = $parser->getAst(); - $result = []; + foreach ($parser->slice($css, $size, $root->location->end) as $slice) { - foreach ($renderer->output as $sets) { + $processPool->add(function () use ($slice, $parseOptions, $renderOptions): array { - foreach ($sets as $key => $value) { + $parser = new Parser($slice[0], array_merge($parseOptions, [ +// 'ast_src' => $file, + 'ast_position_line' => $slice[1]->line, + 'ast_position_column' => $slice[1]->column, + 'ast_position_index' => $slice[1]->index + ])); - if (isset($result[$key])) { - - unset($result[$key]); - } + return (new Renderer($renderOptions))->renderChildNodes($parser->getAst()->children ?? []); + })-> + then(function (array $result, int $index) use (&$data) { - $result[$key] = $value; - } + $data[$index] = $result; + }); } - $renderer->output = []; + $processPool->wait(); - return implode($renderer->options['glue'], $result); + return $renderer->renderChildData($data); } return $renderer->renderAst($parser->setContent($css)); @@ -278,6 +144,38 @@ public static function fromFile(string $file, array $renderOptions = [], array $ return static::fromString($content, $renderOptions, array_merge($parseOptions, ['ast_src' => $file])); } + /** + * @param array $data + * @return string + */ + public function renderChildData(array $data): string + { + ksort($data); + + $output = []; + + foreach ($data as $results) { + + foreach ($results as $r) { + + if ($r->type != 'Comment') { + + if (isset($output[$r->css])) { + + unset($output[$r->css]); + } + + $output[$r->css] = $r->css; + } else { + + $output[] = $r->css; + } + } + } + + return implode($this->options['glue'], $output); + } + /** * render an ElementInterface or a Property * @param RenderableInterface $element the element to render @@ -299,13 +197,13 @@ public function render(RenderableInterface $element, ?int $level = null, bool $p } /** - * @param \stdClass|ParsableInterface $ast + * @param stdClass|ParsableInterface $ast * @param int|null $level * @return string * @throws Exception */ - public function renderAst(\stdClass|ParsableInterface $ast, ?int $level = null): string + public function renderAst(stdClass|ParsableInterface $ast, ?int $level = null): string { $this->outFile = ''; @@ -320,43 +218,36 @@ public function renderAst(\stdClass|ParsableInterface $ast, ?int $level = null): $ast = $this->flatten($ast); } - $block_size = 1500; - - if (function_exists('\\proc_open') && $this->options['multi_processing'] && empty($this->options['sourcemap']) && $ast->type == 'Stylesheet' && isset($ast->children) && count($ast->children) > $block_size) { - - $args = $this->getCliArgs([], $this->options); - - $args[] = '--input-format=serialize'; + $block_size = $this->options['multi_processing_threshold']; - $j = count($ast->children); - $i = 0; + if (isset($ast->children) && count($ast->children) > $block_size) { - while ($i < $j) { + $block_size = min($block_size, count($ast->children) / 2); - $this->enQueue(serialize((object)['type' => 'Stylesheet', 'children' => array_slice($ast->children, $i, $block_size)]), $args); - $i += $block_size; - } + $processPool = new ProcessPool(); - $this->pool->wait(); + $data = []; - $result = []; + $renderer = $this; - foreach ($this->output as $sets) { + $options = $this->options; + $options['multi_processing'] = false; - foreach ($sets as $key => $value) { + foreach (array_chunk($ast->children, $block_size) as $slice) { - if (isset($result[$key])) { + $processPool->add(function () use ($slice, $options): array { - unset($result[$key]); - } + return (new Renderer($options))->renderChildNodes($slice); + })-> + then(function (array $result, int $index) use (&$data) { - $result[$key] = $value; - } + $data[$index] = $result; + }); } - $this->output = []; + $processPool->wait(); - return implode($this->options['glue'], $result); + return $renderer->renderChildData($data); } switch ($ast->type) { @@ -560,6 +451,48 @@ protected function walk(array $tree, object $position, ?int $level = 0): void } } + /** + * @param array $children + * @return array + * @throws Exception + */ + public function renderChildNodes(array $children): array + { + + $result = []; + + foreach ($children as $child) { + + $css = $this->renderAst($child); + + if ($css !== '') { + + if ($child->type != 'Comment') { + + if (isset($result[$css])) { + + unset($result[$css]); + } + + $result[$css] = (object)[ + + 'type' => $child->type, + 'css' => $css + ]; + } else { + + $result[] = (object)[ + + 'type' => $child->type, + 'css' => $css + ]; + } + } + } + + return array_values($result); + } + /** * @param object $ast * @param int|null $level @@ -718,7 +651,7 @@ protected function renderCollection(object $ast, ?int $level): string $properties->remove($name); } - $properties->set($name, $child->value, $child->type, $child->leadingcomments ?? null, $child->trailingcomments ?? null, null, $child->vendor ?? null); + $properties->set($name, $child->value, $child->type, $child->leadingcomments ?? null, $child->trailingcomments ?? null, $child->src ?? null, $child->vendor ?? null); } else { $children[] = $child; @@ -761,12 +694,20 @@ protected function renderCollection(object $ast, ?int $level): string $output .= in_array($el->type, ['Declaration', 'Property']) ? ';' : ''; - if (isset($result[$output])) { + if ($el->type != 'Comment') { + + if (isset($result[$output])) { - unset($result[$output]); + unset($result[$output]); + } + + $result[$output] = [$output, $el]; } - $result[$output] = [$output, $el]; + else { + + $result[] = [$output, $el]; + } } if ($this->options['remove_empty_nodes'] && $count == 0) { @@ -890,7 +831,7 @@ protected function renderSelector(object $ast, ?int $level): string if (empty($selector)) { // the selector is empty! - throw new \Exception(sprintf('the selector is empty: %s:%s:%s', $ast->src ?? '', $ast->position->line ?? '', $ast->position->column ?? ''), 400); + throw new Exception(sprintf('the selector is empty: %s:%s:%s', $ast->src ?? '', $ast->position->line ?? '', $ast->position->column ?? ''), 400); } if (is_string($selector[0])) { @@ -901,7 +842,7 @@ protected function renderSelector(object $ast, ?int $level): string if (is_string($selector)) { - $selector = Value::parse($selector, null, true, '', ''); + $selector = Value::parse($selector); } $result = $indent . Value::renderTokens($selector, ['omit_unit' => false, 'compress' => $this->options['compress']], $this->options['glue'] . $indent); @@ -927,14 +868,14 @@ protected function renderSelector(object $ast, ?int $level): string } /** - * @param \stdClass $ast + * @param stdClass $ast * @param int|null $level * @return string * @throws Exception * @ignore */ - protected function renderDeclaration($ast, ?int $level): string + protected function renderDeclaration(stdClass $ast, ?int $level): string { return $this->renderProperty($ast, $level); @@ -975,13 +916,18 @@ protected function renderProperty(object $ast, ?int $level): string } } else if (in_array($name, ['background', 'background-image', 'src'])) { - $value = preg_replace_callback('#(^|\s)url\(\s*(["\']?)([^)\\2]+)\\2\)#', function ($matches) { + $value = preg_replace_callback('#(^|\s)url\(\s*(["\']?)([^)\\2]+)\\2\)#', function ($matches) use ($ast) { - if (str_contains($matches[3], 'data:')) { + if (str_starts_with($matches[3], 'data:')) { return $matches[0]; } + if (!empty($ast->src)) { + + $matches[3] = Helper::absolutePath($matches[3], $ast->src); + } + return $matches[1] . 'url(' . Helper::relativePath($matches[3], $this->outFile === '' ? Helper::getCurrentDirectory() : dirname($this->outFile)) . ')'; }, $value); } @@ -1044,7 +990,7 @@ protected function renderName(object $ast): string protected function renderValue(object $ast): string { - $result = Value::renderTokens(is_string($ast->value) ? Value::parse($ast->value, in_array($ast->type, ['Property', 'Declaration']) ? $ast->name : null, true, '', '') : $ast->value, $this->options); + $result = Value::renderTokens(is_string($ast->value) ? Value::parse($ast->value, in_array($ast->type, ['Property', 'Declaration']) ? $ast->name : null) : $ast->value, $this->options); if (!$this->options['remove_comments'] && !empty($ast->trailingcomments)) { @@ -1118,9 +1064,9 @@ public function setOptions(array $options): static * return the options * @param string|null $name * @param mixed|null $default return value - * @return array + * @return array|string|bool */ - public function getOptions(string $name = null, mixed $default = null): array + public function getOptions(string $name = null, mixed $default = null): array|string|bool { if (is_null($name)) { @@ -1273,7 +1219,7 @@ public function flatten(object $node): object if (isset($child->value)) { - $value = Value::renderTokens(is_string($child->value) ? Value::parse($child->value, null, true, '', '') : $child->value, $this->options); + $value = Value::renderTokens(is_string($child->value) ? Value::parse($child->value) : $child->value, $this->options); if ($value !== '' && $value != 'all') { diff --git a/test/autoload.php b/test/autoload.php index 4fda08d1..5fc425d5 100755 --- a/test/autoload.php +++ b/test/autoload.php @@ -16,6 +16,12 @@ if (is_file($path)) { - require ($path); + require_once ($path); } -}); \ No newline at end of file +}); + +// force multithreading or multiprocessing +\TBela\CSS\Process\Pool::setDefaultEngine(getenv('PROCESS_ENGINE')); + +fwrite(STDERR, sprintf("current engine: %s\n", \TBela\CSS\Process\Pool::getDefaultEngine())); +putenv('PROCESS_ENGINE'); \ No newline at end of file diff --git a/test/multiprocessing/bs.4-slice.json b/test/multiprocessing/bs.4-slice.json new file mode 100644 index 00000000..be80588d --- /dev/null +++ b/test/multiprocessing/bs.4-slice.json @@ -0,0 +1,26 @@ +[ + [ + "\/*!\n * Bootstrap v4.3.1 (https:\/\/getbootstrap.com\/)\n * Copyright 2011-2019 The Bootstrap Authors\n * Copyright 2011-2019 Twitter, Inc.\n * Licensed under MIT (https:\/\/github.com\/twbs\/bootstrap\/blob\/master\/LICENSE)\n *\/\n:root {\n --blue: #0d6efd;\n --indigo: #6610f2;\n --purple: #6f42c1;\n --pink: #d63384;\n --red: #dc3545;\n --orange: #fd7e14;\n --yellow: #ffc107;\n --green: #28a745;\n --teal: #20c997;\n --cyan: #17a2b8;\n --white: #fff;\n --gray: #6c757d;\n --gray-dark: #343a40;\n --primary: #0d6efd;\n --secondary: #6c757d;\n --success: #28a745;\n --info: #17a2b8;\n --warning: #ffc107;\n --danger: #dc3545;\n --light: #f8f9fa;\n --dark: #343a40;\n --font-family-sans-serif: -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\";\n --font-family-monospace: SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace;\n}\n\n*,\n*::before,\n*::after {\n box-sizing: border-box;\n}\n\nbody {\n margin: 0;\n font-family: -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\";\n font-size: 1rem;\n font-weight: 400;\n line-height: 1.5;\n color: #212529;\n background-color: #fff;\n -webkit-text-size-adjust: 100%;\n -webkit-tap-highlight-color: rgba(0, 0, 0, 0);\n}\n\n[tabindex=\"-1\"]:focus:not(:focus-visible) {\n outline: 0 !important;\n}\n\nhr {\n margin: 1rem 0;\n color: inherit;\n background-color: currentColor;\n border: 0;\n opacity: 0.25;\n}\n\nhr:not([size]) {\n height: 1px;\n}\n\nh1, .h1, h2, .h2, h3, .h3, h4, .h4, h5, .h5, h6, .h6 {\n margin-top: 0;\n margin-bottom: 0.5rem;\n font-weight: 500;\n line-height: 1.2;\n}\n\nh1, .h1 {\n font-size: 2.5rem;\n}\n\nh2, .h2 {\n font-size: 2rem;\n}\n\nh3, .h3 {\n font-size: 1.75rem;\n}\n\nh4, .h4 {\n font-size: 1.5rem;\n}\n\nh5, .h5 {\n font-size: 1.25rem;\n}\n\nh6, .h6 {\n font-size: 1rem;\n}\n\np {\n margin-top: 0;\n margin-bottom: 1rem;\n}\n\nabbr[title],\nabbr[data-original-title] {\n text-decoration: underline;\n -webkit-text-decoration: underline dotted;\n text-decoration: underline dotted;\n cursor: help;\n -webkit-text-decoration-skip-ink: none;\n text-decoration-skip-ink: none;\n}\n\naddress {\n margin-bottom: 1rem;\n font-style: normal;\n line-height: inherit;\n}\n\nol,\nul {\n padding-left: 2rem;\n}\n\nol,\nul,\ndl {\n margin-top: 0;\n margin-bottom: 1rem;\n}\n\nol ol,\nul ul,\nol ul,\nul ol {\n margin-bottom: 0;\n}\n\ndt {\n font-weight: 700;\n}\n\ndd {\n margin-bottom: .5rem;\n margin-left: 0;\n}\n\nblockquote {\n margin: 0 0 1rem;\n}\n\nb,\nstrong {\n font-weight: bolder;\n}\n\nsmall, .small {\n font-size: 0.875em;\n}\n\nsub,\nsup {\n position: relative;\n font-size: 0.75em;\n line-height: 0;\n vertical-align: baseline;\n}\n\nsub {\n bottom: -.25em;\n}\n\nsup {\n top: -.5em;\n}\n\na {\n color: #0d6efd;\n text-decoration: none;\n}\n\na:hover {\n color: #024dbc;\n text-decoration: underline;\n}\n\na:not([href]), a:not([href]):hover {\n color: inherit;\n text-decoration: none;\n}\n\npre,\ncode,\nkbd,\nsamp {\n font-family: SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace;\n font-size: 1em;\n}\n\npre {\n display: block;\n margin-top: 0;\n margin-bottom: 1rem;\n overflow: auto;\n font-size: 0.875em;\n}\n\npre code {\n font-size: inherit;\n color: inherit;\n word-break: normal;\n}\n\ncode {\n font-size: 0.875em;\n color: #d63384;\n word-wrap: break-word;\n}\n\na > code {\n color: inherit;\n}\n\nkbd {\n padding: 0.2rem 0.4rem;\n font-size: 0.875em;\n color: #fff;\n background-color: #212529;\n border-radius: 0.2rem;\n}\n\nkbd kbd {\n padding: 0;\n font-size: 1em;\n font-weight: 700;\n}\n\nfigure {\n margin: 0 0 1rem;\n}\n\nimg {\n vertical-align: middle;\n}\n\nsvg {\n overflow: hidden;\n vertical-align: middle;\n}\n\ntable {\n border-collapse: collapse;\n}\n\ncaption {\n padding-top: 0.5rem;\n padding-bottom: 0.5rem;\n color: #6c757d;\n text-align: left;\n caption-side: bottom;\n}\n\nth {\n text-align: inherit;\n}\n\nlabel {\n display: inline-block;\n margin-bottom: 0.5rem;\n}\n\nbutton {\n border-radius: 0;\n}\n\nbutton:focus {\n outline: 1px dotted;\n outline: 5px auto -webkit-focus-ring-color;\n}\n\ninput,\nbutton,\nselect,\noptgroup,\ntextarea {\n margin: 0;\n font-family: inherit;\n font-size: inherit;\n line-height: inherit;\n}\n\nbutton,\ninput {\n overflow: visible;\n}\n\nbutton,\nselect {\n text-transform: none;\n}\n\nselect {\n word-wrap: normal;\n}\n\n[list]::-webkit-calendar-picker-indicator {\n display: none;\n}\n\nbutton,\n[type=\"button\"],\n[type=\"reset\"],\n[type=\"submit\"] {\n -webkit-appearance: button;\n}\n\nbutton:not(:disabled),\n[type=\"button\"]:not(:disabled),\n[type=\"reset\"]:not(:disabled),\n[type=\"submit\"]:not(:disabled) {\n cursor: pointer;\n}\n\n::-moz-focus-inner {\n padding: 0;\n border-style: none;\n}\n\ninput[type=\"date\"],\ninput[type=\"time\"],\ninput[type=\"datetime-local\"],\ninput[type=\"month\"] {\n -webkit-appearance: textfield;\n}\n\ntextarea {\n overflow: auto;\n resize: vertical;\n}\n\nfieldset {\n min-width: 0;\n padding: 0;\n margin: 0;\n border: 0;\n}\n\nlegend {\n float: left;\n width: 100%;\n padding: 0;\n margin-bottom: 0.5rem;\n font-size: 1.5rem;\n line-height: inherit;\n color: inherit;\n white-space: normal;\n}\n\nmark, .mark {\n padding: 0.2em;\n background-color: #fcf8e3;\n}\n\nprogress {\n vertical-align: baseline;\n}\n\n::-webkit-datetime-edit {\n overflow: visible;\n line-height: 0;\n}\n\n[type=\"search\"] {\n outline-offset: -2px;\n -webkit-appearance: textfield;\n}\n\n::-webkit-search-decoration {\n -webkit-appearance: none;\n}\n\n::-webkit-color-swatch-wrapper {\n padding: 0;\n}\n\n::-webkit-file-upload-button {\n font: inherit;\n -webkit-appearance: button;\n}\n\noutput {\n display: inline-block;\n}\n\nsummary {\n display: list-item;\n cursor: pointer;\n}\n\ntemplate {\n display: none;\n}\n\nmain {\n display: block;\n}\n\n[hidden] {\n display: none !important;\n}\n\n.lead {\n font-size: 1.25rem;\n font-weight: 300;\n}\n\n.display-1 {\n font-size: 6rem;\n font-weight: 300;\n line-height: 1.2;\n}\n\n.display-2 {\n font-size: 5.5rem;\n font-weight: 300;\n line-height: 1.2;\n}\n\n.display-3 {\n font-size: 4.5rem;\n font-weight: 300;\n line-height: 1.2;\n}\n\n.display-4 {\n font-size: 3.5rem;\n font-weight: 300;\n line-height: 1.2;\n}\n\n.list-unstyled {\n padding-left: 0;\n list-style: none;\n}\n\n.list-inline {\n padding-left: 0;\n list-style: none;\n}\n\n.list-inline-item {\n display: inline-block;\n}\n\n.list-inline-item:not(:last-child) {\n margin-right: 0.5rem;\n}\n\n.initialism {\n font-size: 0.875em;\n text-transform: uppercase;\n}\n\n.blockquote {\n margin-bottom: 1rem;\n font-size: 1.25rem;\n}\n\n.blockquote-footer {\n display: block;\n font-size: 0.875em;\n color: #6c757d;\n}\n\n.blockquote-footer::before {\n content: \"\\2014\\00A0\";\n}\n\n.img-fluid {\n max-width: 100%;\n height: auto;\n}\n\n.img-thumbnail {\n padding: 0.25rem;\n background-color: #fff;\n border: 1px solid #dee2e6;\n border-radius: 0.25rem;\n max-width: 100%;\n height: auto;\n}\n\n.figure {\n display: inline-block;\n}\n\n.figure-img {\n margin-bottom: 0.5rem;\n line-height: 1;\n}\n\n.figure-caption {\n font-size: 0.875em;\n color: #6c757d;\n}\n\n.container {\n width: 100%;\n padding-right: 15px;\n padding-left: 15px;\n margin-right: auto;\n margin-left: auto;\n}\n\n@media (min-width: 576px) {\n .container {\n max-width: 540px;\n }\n}\n\n@media (min-width: 768px) {\n .container {\n max-width: 720px;\n }\n}\n\n@media (min-width: 992px) {\n .container {\n max-width: 960px;\n }\n}\n\n@media (min-width: 1200px) {\n .container {\n max-width: 1140px;\n }\n}\n\n.container-fluid, .container-sm, .container-md, .container-lg, .container-xl {\n width: 100%;\n padding-right: 15px;\n padding-left: 15px;\n margin-right: auto;\n margin-left: auto;\n}\n\n@media (min-width: 576px) {\n .container, .container-sm {\n max-width: 540px;\n }\n}\n\n@media (min-width: 768px) {\n .container, .container-sm, .container-md {\n max-width: 720px;\n }\n}\n\n@media (min-width: 992px) {\n .container, .container-sm, .container-md, .container-lg {\n max-width: 960px;\n }\n}\n\n@media (min-width: 1200px) {\n .container, .container-sm, .container-md, .container-lg, .container-xl {\n max-width: 1140px;\n }\n}\n\n.row {\n display: flex;\n flex-wrap: wrap;\n margin-right: -15px;\n margin-left: -15px;\n}\n\n.no-gutters {\n margin-right: 0;\n margin-left: 0;\n}\n\n.no-gutters > .col,\n.no-gutters > [class*=\"col-\"] {\n padding-right: 0;\n padding-left: 0;\n}\n\n.col-1, .col-2, .col-3, .col-4, .col-5, .col-6, .col-7, .col-8, .col-9, .col-10, .col-11, .col-12, .col,\n.col-auto, .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12, .col-sm,\n.col-sm-auto, .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12, .col-md,\n.col-md-auto, .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12, .col-lg,\n.col-lg-auto, .col-xl-1, .col-xl-2, .col-xl-3, .col-xl-4, .col-xl-5, .col-xl-6, .col-xl-7, .col-xl-8, .col-xl-9, .col-xl-10, .col-xl-11, .col-xl-12, .col-xl,\n.col-xl-auto {\n position: relative;\n width: 100%;\n padding-right: 15px;\n padding-left: 15px;\n}\n\n.col {\n flex-basis: 0;\n flex-grow: 1;\n max-width: 100%;\n}\n\n.row-cols-1 > * {\n flex: 0 0 100%;\n max-width: 100%;\n}\n\n.row-cols-2 > * {\n flex: 0 0 50%;\n max-width: 50%;\n}\n\n.row-cols-3 > * {\n flex: 0 0 33.333333%;\n max-width: 33.333333%;\n}\n\n.row-cols-4 > * {\n flex: 0 0 25%;\n max-width: 25%;\n}\n\n.row-cols-5 > * {\n flex: 0 0 20%;\n max-width: 20%;\n}\n\n.row-cols-6 > * {\n flex: 0 0 16.666667%;\n max-width: 16.666667%;\n}\n\n.col-auto {\n flex: 0 0 auto;\n width: auto;\n max-width: 100%;\n}\n\n.col-1 {\n flex: 0 0 8.333333%;\n max-width: 8.333333%;\n}\n\n.col-2 {\n flex: 0 0 16.666667%;\n max-width: 16.666667%;\n}\n\n.col-3 {\n flex: 0 0 25%;\n max-width: 25%;\n}\n\n.col-4 {\n flex: 0 0 33.333333%;\n max-width: 33.333333%;\n}\n\n.col-5 {\n flex: 0 0 41.666667%;\n max-width: 41.666667%;\n}\n\n.col-6 {\n flex: 0 0 50%;\n max-width: 50%;\n}\n\n.col-7 {\n flex: 0 0 58.333333%;\n max-width: 58.333333%;\n}\n\n.col-8 {\n flex: 0 0 66.666667%;\n max-width: 66.666667%;\n}\n\n.col-9 {\n flex: 0 0 75%;\n max-width: 75%;\n}\n\n.col-10 {\n flex: 0 0 83.333333%;\n max-width: 83.333333%;\n}\n\n.col-11 {\n flex: 0 0 91.666667%;\n max-width: 91.666667%;\n}\n\n.col-12 {\n flex: 0 0 100%;\n max-width: 100%;\n}\n\n.offset-1 {\n margin-left: 8.333333%;\n}\n\n.offset-2 {\n margin-left: 16.666667%;\n}\n\n.offset-3 {\n margin-left: 25%;\n}\n\n.offset-4 {\n margin-left: 33.333333%;\n}\n\n.offset-5 {\n margin-left: 41.666667%;\n}\n\n.offset-6 {\n margin-left: 50%;\n}\n\n.offset-7 {\n margin-left: 58.333333%;\n}\n\n.offset-8 {\n margin-left: 66.666667%;\n}\n\n.offset-9 {\n margin-left: 75%;\n}\n\n.offset-10 {\n margin-left: 83.333333%;\n}\n\n.offset-11 {\n margin-left: 91.666667%;\n}\n\n@media (min-width: 576px) {\n .col-sm {\n flex-basis: 0;\n flex-grow: 1;\n max-width: 100%;\n }\n .row-cols-sm-1 > * {\n flex: 0 0 100%;\n max-width: 100%;\n }\n .row-cols-sm-2 > * {\n flex: 0 0 50%;\n max-width: 50%;\n }\n .row-cols-sm-3 > * {\n flex: 0 0 33.333333%;\n max-width: 33.333333%;\n }\n .row-cols-sm-4 > * {\n flex: 0 0 25%;\n max-width: 25%;\n }\n .row-cols-sm-5 > * {\n flex: 0 0 20%;\n max-width: 20%;\n }\n .row-cols-sm-6 > * {\n flex: 0 0 16.666667%;\n max-width: 16.666667%;\n }\n .col-sm-auto {\n flex: 0 0 auto;\n width: auto;\n max-width: 100%;\n }\n .col-sm-1 {\n flex: 0 0 8.333333%;\n max-width: 8.333333%;\n }\n .col-sm-2 {\n flex: 0 0 16.666667%;\n max-width: 16.666667%;\n }\n .col-sm-3 {\n flex: 0 0 25%;\n max-width: 25%;\n }\n .col-sm-4 {\n flex: 0 0 33.333333%;\n max-width: 33.333333%;\n }\n .col-sm-5 {\n flex: 0 0 41.666667%;\n max-width: 41.666667%;\n }\n .col-sm-6 {\n flex: 0 0 50%;\n max-width: 50%;\n }\n .col-sm-7 {\n flex: 0 0 58.333333%;\n max-width: 58.333333%;\n }\n .col-sm-8 {\n flex: 0 0 66.666667%;\n max-width: 66.666667%;\n }\n .col-sm-9 {\n flex: 0 0 75%;\n max-width: 75%;\n }\n .col-sm-10 {\n flex: 0 0 83.333333%;\n max-width: 83.333333%;\n }\n .col-sm-11 {\n flex: 0 0 91.666667%;\n max-width: 91.666667%;\n }\n .col-sm-12 {\n flex: 0 0 100%;\n max-width: 100%;\n }\n .offset-sm-0 {\n margin-left: 0;\n }\n .offset-sm-1 {\n margin-left: 8.333333%;\n }\n .offset-sm-2 {\n margin-left: 16.666667%;\n }\n .offset-sm-3 {\n margin-left: 25%;\n }\n .offset-sm-4 {\n margin-left: 33.333333%;\n }\n .offset-sm-5 {\n margin-left: 41.666667%;\n }\n .offset-sm-6 {\n margin-left: 50%;\n }\n .offset-sm-7 {\n margin-left: 58.333333%;\n }\n .offset-sm-8 {\n margin-left: 66.666667%;\n }\n .offset-sm-9 {\n margin-left: 75%;\n }\n .offset-sm-10 {\n margin-left: 83.333333%;\n }\n .offset-sm-11 {\n margin-left: 91.666667%;\n }\n}\n\n@media (min-width: 768px) {\n .col-md {\n flex-basis: 0;\n flex-grow: 1;\n max-width: 100%;\n }\n .row-cols-md-1 > * {\n flex: 0 0 100%;\n max-width: 100%;\n }\n .row-cols-md-2 > * {\n flex: 0 0 50%;\n max-width: 50%;\n }\n .row-cols-md-3 > * {\n flex: 0 0 33.333333%;\n max-width: 33.333333%;\n }\n .row-cols-md-4 > * {\n flex: 0 0 25%;\n max-width: 25%;\n }\n .row-cols-md-5 > * {\n flex: 0 0 20%;\n max-width: 20%;\n }\n .row-cols-md-6 > * {\n flex: 0 0 16.666667%;\n max-width: 16.666667%;\n }\n .col-md-auto {\n flex: 0 0 auto;\n width: auto;\n max-width: 100%;\n }\n .col-md-1 {\n flex: 0 0 8.333333%;\n max-width: 8.333333%;\n }\n .col-md-2 {\n flex: 0 0 16.666667%;\n max-width: 16.666667%;\n }\n .col-md-3 {\n flex: 0 0 25%;\n max-width: 25%;\n }\n .col-md-4 {\n flex: 0 0 33.333333%;\n max-width: 33.333333%;\n }\n .col-md-5 {\n flex: 0 0 41.666667%;\n max-width: 41.666667%;\n }\n .col-md-6 {\n flex: 0 0 50%;\n max-width: 50%;\n }\n .col-md-7 {\n flex: 0 0 58.333333%;\n max-width: 58.333333%;\n }\n .col-md-8 {\n flex: 0 0 66.666667%;\n max-width: 66.666667%;\n }\n .col-md-9 {\n flex: 0 0 75%;\n max-width: 75%;\n }\n .col-md-10 {\n flex: 0 0 83.333333%;\n max-width: 83.333333%;\n }\n .col-md-11 {\n flex: 0 0 91.666667%;\n max-width: 91.666667%;\n }\n .col-md-12 {\n flex: 0 0 100%;\n max-width: 100%;\n }\n .offset-md-0 {\n margin-left: 0;\n }\n .offset-md-1 {\n margin-left: 8.333333%;\n }\n .offset-md-2 {\n margin-left: 16.666667%;\n }\n .offset-md-3 {\n margin-left: 25%;\n }\n .offset-md-4 {\n margin-left: 33.333333%;\n }\n .offset-md-5 {\n margin-left: 41.666667%;\n }\n .offset-md-6 {\n margin-left: 50%;\n }\n .offset-md-7 {\n margin-left: 58.333333%;\n }\n .offset-md-8 {\n margin-left: 66.666667%;\n }\n .offset-md-9 {\n margin-left: 75%;\n }\n .offset-md-10 {\n margin-left: 83.333333%;\n }\n .offset-md-11 {\n margin-left: 91.666667%;\n }\n}\n\n@media (min-width: 992px) {\n .col-lg {\n flex-basis: 0;\n flex-grow: 1;\n max-width: 100%;\n }\n .row-cols-lg-1 > * {\n flex: 0 0 100%;\n max-width: 100%;\n }\n .row-cols-lg-2 > * {\n flex: 0 0 50%;\n max-width: 50%;\n }\n .row-cols-lg-3 > * {\n flex: 0 0 33.333333%;\n max-width: 33.333333%;\n }\n .row-cols-lg-4 > * {\n flex: 0 0 25%;\n max-width: 25%;\n }\n .row-cols-lg-5 > * {\n flex: 0 0 20%;\n max-width: 20%;\n }\n .row-cols-lg-6 > * {\n flex: 0 0 16.666667%;\n max-width: 16.666667%;\n }\n .col-lg-auto {\n flex: 0 0 auto;\n width: auto;\n max-width: 100%;\n }\n .col-lg-1 {\n flex: 0 0 8.333333%;\n max-width: 8.333333%;\n }\n .col-lg-2 {\n flex: 0 0 16.666667%;\n max-width: 16.666667%;\n }\n .col-lg-3 {\n flex: 0 0 25%;\n max-width: 25%;\n }\n .col-lg-4 {\n flex: 0 0 33.333333%;\n max-width: 33.333333%;\n }\n .col-lg-5 {\n flex: 0 0 41.666667%;\n max-width: 41.666667%;\n }\n .col-lg-6 {\n flex: 0 0 50%;\n max-width: 50%;\n }\n .col-lg-7 {\n flex: 0 0 58.333333%;\n max-width: 58.333333%;\n }\n .col-lg-8 {\n flex: 0 0 66.666667%;\n max-width: 66.666667%;\n }\n .col-lg-9 {\n flex: 0 0 75%;\n max-width: 75%;\n }\n .col-lg-10 {\n flex: 0 0 83.333333%;\n max-width: 83.333333%;\n }\n .col-lg-11 {\n flex: 0 0 91.666667%;\n max-width: 91.666667%;\n }\n .col-lg-12 {\n flex: 0 0 100%;\n max-width: 100%;\n }\n .offset-lg-0 {\n margin-left: 0;\n }\n .offset-lg-1 {\n margin-left: 8.333333%;\n }\n .offset-lg-2 {\n margin-left: 16.666667%;\n }\n .offset-lg-3 {\n margin-left: 25%;\n }\n .offset-lg-4 {\n margin-left: 33.333333%;\n }\n .offset-lg-5 {\n margin-left: 41.666667%;\n }\n .offset-lg-6 {\n margin-left: 50%;\n }\n .offset-lg-7 {\n margin-left: 58.333333%;\n }\n .offset-lg-8 {\n margin-left: 66.666667%;\n }\n .offset-lg-9 {\n margin-left: 75%;\n }\n .offset-lg-10 {\n margin-left: 83.333333%;\n }\n .offset-lg-11 {\n margin-left: 91.666667%;\n }\n}\n\n@media (min-width: 1200px) {\n .col-xl {\n flex-basis: 0;\n flex-grow: 1;\n max-width: 100%;\n }\n .row-cols-xl-1 > * {\n flex: 0 0 100%;\n max-width: 100%;\n }\n .row-cols-xl-2 > * {\n flex: 0 0 50%;\n max-width: 50%;\n }\n .row-cols-xl-3 > * {\n flex: 0 0 33.333333%;\n max-width: 33.333333%;\n }\n .row-cols-xl-4 > * {\n flex: 0 0 25%;\n max-width: 25%;\n }\n .row-cols-xl-5 > * {\n flex: 0 0 20%;\n max-width: 20%;\n }\n .row-cols-xl-6 > * {\n flex: 0 0 16.666667%;\n max-width: 16.666667%;\n }\n .col-xl-auto {\n flex: 0 0 auto;\n width: auto;\n max-width: 100%;\n }\n .col-xl-1 {\n flex: 0 0 8.333333%;\n max-width: 8.333333%;\n }\n .col-xl-2 {\n flex: 0 0 16.666667%;\n max-width: 16.666667%;\n }\n .col-xl-3 {\n flex: 0 0 25%;\n max-width: 25%;\n }\n .col-xl-4 {\n flex: 0 0 33.333333%;\n max-width: 33.333333%;\n }\n .col-xl-5 {\n flex: 0 0 41.666667%;\n max-width: 41.666667%;\n }\n .col-xl-6 {\n flex: 0 0 50%;\n max-width: 50%;\n }\n .col-xl-7 {\n flex: 0 0 58.333333%;\n max-width: 58.333333%;\n }\n .col-xl-8 {\n flex: 0 0 66.666667%;\n max-width: 66.666667%;\n }\n .col-xl-9 {\n flex: 0 0 75%;\n max-width: 75%;\n }\n .col-xl-10 {\n flex: 0 0 83.333333%;\n max-width: 83.333333%;\n }\n .col-xl-11 {\n flex: 0 0 91.666667%;\n max-width: 91.666667%;\n }\n .col-xl-12 {\n flex: 0 0 100%;\n max-width: 100%;\n }\n .offset-xl-0 {\n margin-left: 0;\n }\n .offset-xl-1 {\n margin-left: 8.333333%;\n }\n .offset-xl-2 {\n margin-left: 16.666667%;\n }\n .offset-xl-3 {\n margin-left: 25%;\n }\n .offset-xl-4 {\n margin-left: 33.333333%;\n }\n .offset-xl-5 {\n margin-left: 41.666667%;\n }\n .offset-xl-6 {\n margin-left: 50%;\n }\n .offset-xl-7 {\n margin-left: 58.333333%;\n }\n .offset-xl-8 {\n margin-left: 66.666667%;\n }\n .offset-xl-9 {\n margin-left: 75%;\n }\n .offset-xl-10 {\n margin-left: 83.333333%;\n }\n .offset-xl-11 {\n margin-left: 91.666667%;\n }\n}\n\n.table {\n width: 100%;\n margin-bottom: 1rem;\n color: #212529;\n vertical-align: top;\n}\n\n.table th,\n.table td {\n padding: 0.5rem;\n border-bottom: 1px solid #dee2e6;\n}\n\n.table tbody {\n vertical-align: inherit;\n}\n\n.table thead th {\n vertical-align: bottom;\n border-bottom-color: #495057;\n}\n\n.table tbody + tbody {\n border-top: 2px solid #dee2e6;\n}\n\n.table-sm th,\n.table-sm td {\n padding: 0.25rem;\n}\n\n.table-bordered {\n border: 1px solid #dee2e6;\n}\n\n.table-bordered th,\n.table-bordered td {\n border: 1px solid #dee2e6;\n}\n\n.table-bordered thead th,\n.table-bordered thead td {\n border-bottom-width: 2px;\n}\n\n.table-borderless th,\n.table-borderless td,\n.table-borderless thead th,\n.table-borderless tbody + tbody {\n border: 0;\n}\n\n.table-striped tbody tr:nth-of-type(odd) {\n background-color: rgba(0, 0, 0, 0.05);\n}\n\n.table-hover tbody tr:hover {\n color: #212529;\n background-color: rgba(0, 0, 0, 0.075);\n}\n\n.table-primary,\n.table-primary > th,\n.table-primary > td {\n background-color: #bbd6fe;\n}\n\n.table-primary th,\n.table-primary td,\n.table-primary thead th,\n.table-primary tbody + tbody {\n border-color: #81b4fe;\n}\n\n.table-hover .table-primary:hover {\n background-color: #a2c7fe;\n}\n\n.table-hover .table-primary:hover > td,\n.table-hover .table-primary:hover > th {\n background-color: #a2c7fe;\n}\n\n.table-secondary,\n.table-secondary > th,\n.table-secondary > td {\n background-color: #d6d8db;\n}\n\n.table-secondary th,\n.table-secondary td,\n.table-secondary thead th,\n.table-secondary tbody + tbody {\n border-color: #b3b7bb;\n}\n\n.table-hover .table-secondary:hover {\n background-color: #c8cbcf;\n}\n\n.table-hover .table-secondary:hover > td,\n.table-hover .table-secondary:hover > th {\n background-color: #c8cbcf;\n}\n\n.table-success,\n.table-success > th,\n.table-success > td {\n background-color: #c3e6cb;\n}\n\n.table-success th,\n.table-success td,\n.table-success thead th,\n.table-success tbody + tbody {\n border-color: #8fd19e;\n}\n\n.table-hover .table-success:hover {\n background-color: #b1dfbb;\n}\n\n.table-hover .table-success:hover > td,\n.table-hover .table-success:hover > th {\n background-color: #b1dfbb;\n}\n\n.table-info,\n.table-info > th,\n.table-info > td {\n background-color: #bee5eb;\n}\n\n.table-info th,\n.table-info td,\n.table-info thead th,\n.table-info tbody + tbody {\n border-color: #86cfda;\n}\n\n.table-hover .table-info:hover {\n background-color: #abdde5;\n}\n\n.table-hover .table-info:hover > td,\n.table-hover .table-info:hover > th {\n background-color: #abdde5;\n}\n\n.table-warning,\n.table-warning > th,\n.table-warning > td {\n background-color: #ffeeba;\n}\n\n.table-warning th,\n.table-warning td,\n.table-warning thead th,\n.table-warning tbody + tbody {\n border-color: #ffdf7e;\n}\n\n.table-hover .table-warning:hover {\n background-color: #ffe8a1;\n}\n\n.table-hover .table-warning:hover > td,\n.table-hover .table-warning:hover > th {\n background-color: #ffe8a1;\n}\n\n.table-danger,\n.table-danger > th,\n.table-danger > td {\n background-color: #f5c6cb;\n}\n\n.table-danger th,\n.table-danger td,\n.table-danger thead th,\n.table-danger tbody + tbody {\n border-color: #ed969e;\n}\n\n.table-hover .table-danger:hover {\n background-color: #f1b0b7;\n}\n\n.table-hover .table-danger:hover > td,\n.table-hover .table-danger:hover > th {\n background-color: #f1b0b7;\n}\n\n.table-light,\n.table-light > th,\n.table-light > td {\n background-color: #fdfdfe;\n}\n\n.table-light th,\n.table-light td,\n.table-light thead th,\n.table-light tbody + tbody {\n border-color: #fbfcfc;\n}\n\n.table-hover .table-light:hover {\n background-color: #ececf6;\n}\n\n.table-hover .table-light:hover > td,\n.table-hover .table-light:hover > th {\n background-color: #ececf6;\n}\n\n.table-dark,\n.table-dark > th,\n.table-dark > td {\n background-color: #c6c8ca;\n}\n\n.table-dark th,\n.table-dark td,\n.table-dark thead th,\n.table-dark tbody + tbody {\n border-color: #95999c;\n}\n\n.table-hover .table-dark:hover {\n background-color: #b9bbbe;\n}\n\n.table-hover .table-dark:hover > td,\n.table-hover .table-dark:hover > th {\n background-color: #b9bbbe;\n}\n\n.table-active,\n.table-active > th,\n.table-active > td {\n background-color: rgba(0, 0, 0, 0.075);\n}\n\n.table-hover .table-active:hover {\n background-color: rgba(0, 0, 0, 0.075);\n}\n\n.table-hover .table-active:hover > td,\n.table-hover .table-active:hover > th {\n background-color: rgba(0, 0, 0, 0.075);\n}\n\n.table .thead-dark th {\n color: #fff;\n background-color: #343a40;\n border-color: #454d55;\n}\n\n.table .thead-light th {\n color: #495057;\n background-color: #e9ecef;\n border-color: #dee2e6;\n}\n\n.table-dark {\n color: #fff;\n background-color: #343a40;\n}\n\n.table-dark th,\n.table-dark td,\n.table-dark thead th {\n border-color: #454d55;\n}\n\n.table-dark.table-bordered {\n border: 0;\n}\n\n.table-dark.table-striped tbody tr:nth-of-type(odd) {\n background-color: rgba(255, 255, 255, 0.05);\n}\n\n.table-dark.table-hover tbody tr:hover {\n color: #fff;\n background-color: rgba(255, 255, 255, 0.075);\n}\n\n@media (max-width: 575.98px) {\n .table-responsive-sm {\n display: block;\n width: 100%;\n overflow-x: auto;\n -webkit-overflow-scrolling: touch;\n }\n .table-responsive-sm > .table-bordered {\n border: 0;\n }\n}\n\n@media (max-width: 767.98px) {\n .table-responsive-md {\n display: block;\n width: 100%;\n overflow-x: auto;\n -webkit-overflow-scrolling: touch;\n }\n .table-responsive-md > .table-bordered {\n border: 0;\n }\n}\n\n@media (max-width: 991.98px) {\n .table-responsive-lg {\n display: block;\n width: 100%;\n overflow-x: auto;\n -webkit-overflow-scrolling: touch;\n }\n .table-responsive-lg > .table-bordered {\n border: 0;\n }\n}\n\n@media (max-width: 1199.98px) {\n .table-responsive-xl {\n display: block;\n width: 100%;\n overflow-x: auto;\n -webkit-overflow-scrolling: touch;\n }\n .table-responsive-xl > .table-bordered {\n border: 0;\n }\n}\n\n.table-responsive {\n display: block;\n width: 100%;\n overflow-x: auto;\n -webkit-overflow-scrolling: touch;\n}\n\n.table-responsive > .table-bordered {\n border: 0;\n}\n\n.col-form-label {\n padding-top: calc(0.375rem + 1px);\n padding-bottom: calc(0.375rem + 1px);\n margin-bottom: 0;\n font-size: inherit;\n line-height: 1.5;\n}\n\n.col-form-label-lg {\n padding-top: calc(0.5rem + 1px);\n padding-bottom: calc(0.5rem + 1px);\n font-size: 1.25rem;\n}\n\n.col-form-label-sm {\n padding-top: calc(0.25rem + 1px);\n padding-bottom: calc(0.25rem + 1px);\n font-size: 0.875rem;\n}\n\n.form-control {\n display: block;\n width: 100%;\n min-height: calc(1.5em + 0.75rem + 2px);\n padding: 0.375rem 0.75rem;\n font-size: 1rem;\n font-weight: 400;\n line-height: 1.5;\n color: #495057;\n background-color: #fff;\n background-clip: padding-box;\n border: 1px solid #ced4da;\n border-radius: 0.25rem;\n transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .form-control {\n transition: none;\n }\n}\n\n.form-control::-ms-expand {\n background-color: transparent;\n border: 0;\n}\n\n.form-control:focus {\n color: #495057;\n background-color: #fff;\n border-color: #8bbafe;\n outline: 0;\n box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25);\n}\n\n.form-control::-webkit-input-placeholder {\n color: #6c757d;\n opacity: 1;\n}\n\n.form-control::-moz-placeholder {\n color: #6c757d;\n opacity: 1;\n}\n\n.form-control:-ms-input-placeholder {\n color: #6c757d;\n opacity: 1;\n}\n\n.form-control::-ms-input-placeholder {\n color: #6c757d;\n opacity: 1;\n}\n\n.form-control::placeholder {\n color: #6c757d;\n opacity: 1;\n}\n\n.form-control:disabled, .form-control[readonly] {\n background-color: #e9ecef;\n opacity: 1;\n}\n\n.form-control-plaintext {\n display: block;\n width: 100%;\n padding: 0.375rem 0;\n margin-bottom: 0;\n line-height: 1.5;\n color: #212529;\n background-color: transparent;\n border: solid transparent;\n border-width: 1px 0;\n}\n\n.form-control-plaintext.form-control-sm, .form-control-plaintext.form-control-lg {\n padding-right: 0;\n padding-left: 0;\n}\n\n.form-control-sm {\n min-height: calc(1.5em + 0.5rem + 2px);\n padding: 0.25rem 0.5rem;\n font-size: 0.875rem;\n border-radius: 0.2rem;\n}\n\n.form-control-lg {\n min-height: calc(1.5em + 1rem + 2px);\n padding: 0.5rem 1rem;\n font-size: 1.25rem;\n border-radius: 0.3rem;\n}\n\n.form-control-color {\n max-width: 3rem;\n padding: 0.375rem;\n}\n\n.form-control-color::-moz-color-swatch {\n border-radius: 0.25rem;\n}\n\n.form-control-color::-webkit-color-swatch {\n border-radius: 0.25rem;\n}\n\n.form-select {\n display: inline-block;\n width: 100%;\n height: calc(1.5em + 0.75rem + 2px);\n padding: 0.375rem 1.75rem 0.375rem 0.75rem;\n font-size: 1rem;\n font-weight: 400;\n line-height: 1.5;\n color: #495057;\n vertical-align: middle;\n background: #fff url(\"data:image\/svg+xml,%3csvg xmlns='http:\/\/www.w3.org\/2000\/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'\/%3e%3c\/svg%3e\") no-repeat right 0.75rem center\/16px 12px;\n border: 1px solid #ced4da;\n border-radius: 0.25rem;\n -webkit-appearance: none;\n -moz-appearance: none;\n appearance: none;\n}\n\n.form-select:focus {\n border-color: #8bbafe;\n outline: 0;\n box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25);\n}\n\n.form-select:focus::-ms-value {\n color: #495057;\n background-color: #fff;\n}\n\n.form-select[multiple], .form-select[size]:not([size=\"1\"]) {\n height: auto;\n padding-right: 0.75rem;\n background-image: none;\n}\n\n.form-select:disabled {\n color: #6c757d;\n background-color: #e9ecef;\n}\n\n.form-select::-ms-expand {\n display: none;\n}\n\n.form-select:-moz-focusring {\n color: transparent;\n text-shadow: 0 0 0 #495057;\n}\n\n.form-select-sm {\n height: calc(1.5em + 0.5rem + 2px);\n padding-top: 0.25rem;\n padding-bottom: 0.25rem;\n padding-left: 0.5rem;\n font-size: 0.875rem;\n}\n\n.form-select-lg {\n height: calc(1.5em + 1rem + 2px);\n padding-top: 0.5rem;\n padding-bottom: 0.5rem;\n padding-left: 1rem;\n font-size: 1.25rem;\n}\n\n.form-check {\n display: block;\n min-height: 1.5rem;\n padding-left: 1.75em;\n margin-bottom: 0.125rem;\n}\n\n.form-check .form-check-input {\n float: left;\n margin-left: -1.75em;\n}\n\n.form-check-input {\n width: 1.25em;\n height: 1.25em;\n margin-top: 0.125em;\n background-color: #fff;\n border: 1px solid rgba(0, 0, 0, 0.25);\n -webkit-appearance: none;\n -moz-appearance: none;\n appearance: none;\n}\n\n.form-check-input[type=\"checkbox\"] {\n border-radius: 0.25em;\n}\n\n.form-check-input[type=\"radio\"] {\n border-radius: 50%;\n}\n\n.form-check-input:active {\n -webkit-filter: brightness(90%);\n filter: brightness(90%);\n}\n\n.form-check-input:focus {\n border-color: #8bbafe;\n outline: 0;\n box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25);\n}\n\n.form-check-input:checked {\n background-color: #0d6efd;\n background-repeat: no-repeat;\n background-position: center center;\n background-size: 1em;\n border-color: #0d6efd;\n}\n\n.form-check-input:checked[type=\"checkbox\"] {\n background-image: url(\"data:image\/svg+xml,%3csvg xmlns='http:\/\/www.w3.org\/2000\/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M4 8.5L6.5 11l6-6'\/%3e%3c\/svg%3e\");\n}\n\n.form-check-input:checked[type=\"radio\"] {\n background-image: url(\"data:image\/svg+xml,%3csvg xmlns='http:\/\/www.w3.org\/2000\/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%23fff'\/%3e%3c\/svg%3e\");\n}\n\n.form-check-input[type=\"checkbox\"]:indeterminate {\n background-color: #0d6efd;\n background-repeat: no-repeat;\n background-position: center center;\n background-image: url(\"data:image\/svg+xml,%3csvg xmlns='http:\/\/www.w3.org\/2000\/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M5 8h6'\/%3e%3c\/svg%3e\");\n background-size: 1em;\n border-color: #0d6efd;\n}\n\n.form-check-input[disabled] {\n pointer-events: none;\n -webkit-filter: none;\n filter: none;\n opacity: .5;\n}\n\n.form-check-input[disabled] ~ .form-check-label {\n opacity: .5;\n}\n\n.form-check-label {\n margin-bottom: 0;\n}\n\n.form-switch {\n padding-left: 2.5em;\n}\n\n.form-switch .form-check-input {\n width: 2em;\n margin-left: -2.5em;\n background-image: url(\"data:image\/svg+xml,%3csvg xmlns='http:\/\/www.w3.org\/2000\/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba(0, 0, 0, 0.25)'\/%3e%3c\/svg%3e\");\n background-repeat: no-repeat;\n background-position: left center;\n background-size: contain;\n border-radius: 2em;\n}\n\n.form-switch .form-check-input:focus {\n background-image: url(\"data:image\/svg+xml,%3csvg xmlns='http:\/\/www.w3.org\/2000\/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%2380bdff'\/%3e%3c\/svg%3e\");\n}\n\n.form-switch .form-check-input:checked {\n background-position: right center;\n background-image: url(\"data:image\/svg+xml,%3csvg xmlns='http:\/\/www.w3.org\/2000\/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'\/%3e%3c\/svg%3e\");\n}\n\n.form-check-inline {\n display: inline-block;\n margin-right: 1rem;\n}\n\n.form-file {\n position: relative;\n display: inline-block;\n width: 100%;\n height: calc(1.5em + 0.75rem + 2px);\n margin-bottom: 0;\n}\n\n.form-file-input {\n position: relative;\n z-index: 2;\n width: 100%;\n height: calc(1.5em + 0.75rem + 2px);\n margin: 0;\n opacity: 0;\n}\n\n.form-file-input:focus ~ .form-file-label {\n border-color: #8bbafe;\n box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25);\n}\n\n.form-file-input:focus-within ~ .form-file-label {\n border-color: #8bbafe;\n box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25);\n}\n\n.form-file-input[disabled] ~ .form-file-label .form-file-text {\n background-color: #e9ecef;\n}\n\n.form-file-label {\n position: absolute;\n top: 0;\n right: 0;\n left: 0;\n z-index: 1;\n display: flex;\n height: calc(1.5em + 0.75rem + 2px);\n border-color: #ced4da;\n border-radius: 0.25rem;\n}\n\n.form-file-text {\n display: block;\n flex-grow: 1;\n padding: 0.375rem 0.75rem;\n overflow: hidden;\n font-weight: 400;\n line-height: 1.5;\n color: #495057;\n text-overflow: ellipsis;\n white-space: nowrap;\n background-color: #fff;\n border-color: inherit;\n border-style: solid;\n border-width: 1px;\n border-top-left-radius: inherit;\n border-bottom-left-radius: inherit;\n}\n\n.form-file-button {\n display: block;\n flex-shrink: 0;\n padding: 0.375rem 0.75rem;\n margin-left: -1px;\n line-height: 1.5;\n color: #495057;\n background-color: #e9ecef;\n border-color: inherit;\n border-style: solid;\n border-width: 1px;\n border-top-right-radius: inherit;\n border-bottom-right-radius: inherit;\n}\n\n.form-range {\n width: 100%;\n height: 1.4rem;\n padding: 0;\n background-color: transparent;\n -webkit-appearance: none;\n -moz-appearance: none;\n appearance: none;\n}\n\n.form-range:focus {\n outline: none;\n}\n\n.form-range:focus::-webkit-slider-thumb {\n box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(13, 110, 253, 0.25);\n}\n\n.form-range:focus::-moz-range-thumb {\n box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(13, 110, 253, 0.25);\n}\n\n.form-range:focus::-ms-thumb {\n box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(13, 110, 253, 0.25);\n}\n\n.form-range::-moz-focus-outer {\n border: 0;\n}\n\n.form-range::-webkit-slider-thumb {\n width: 1rem;\n height: 1rem;\n margin-top: -0.25rem;\n background-color: #0d6efd;\n border: 0;\n border-radius: 1rem;\n -webkit-transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n -webkit-appearance: none;\n appearance: none;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .form-range::-webkit-slider-thumb {\n -webkit-transition: none;\n transition: none;\n }\n}\n\n.form-range::-webkit-slider-thumb:active {\n background-color: #bed8fe;\n}\n\n.form-range::-webkit-slider-runnable-track {\n width: 100%;\n height: 0.5rem;\n color: transparent;\n cursor: pointer;\n background-color: #dee2e6;\n border-color: transparent;\n border-radius: 1rem;\n}\n\n.form-range::-moz-range-thumb {\n width: 1rem;\n height: 1rem;\n background-color: #0d6efd;\n border: 0;\n border-radius: 1rem;\n -moz-transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n -moz-appearance: none;\n appearance: none;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .form-range::-moz-range-thumb {\n -moz-transition: none;\n transition: none;\n }\n}\n\n.form-range::-moz-range-thumb:active {\n background-color: #bed8fe;\n}\n\n.form-range::-moz-range-track {\n width: 100%;\n height: 0.5rem;\n color: transparent;\n cursor: pointer;\n background-color: #dee2e6;\n border-color: transparent;\n border-radius: 1rem;\n}\n\n.form-range::-ms-thumb {\n width: 1rem;\n height: 1rem;\n margin-top: 0;\n margin-right: 0.2rem;\n margin-left: 0.2rem;\n background-color: #0d6efd;\n border: 0;\n border-radius: 1rem;\n -ms-transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n appearance: none;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .form-range::-ms-thumb {\n -ms-transition: none;\n transition: none;\n }\n}\n\n.form-range::-ms-thumb:active {\n background-color: #bed8fe;\n}\n\n.form-range::-ms-track {\n width: 100%;\n height: 0.5rem;\n color: transparent;\n cursor: pointer;\n background-color: transparent;\n border-color: transparent;\n border-width: 0.5rem;\n}\n\n.form-range::-ms-fill-lower {\n background-color: #dee2e6;\n border-radius: 1rem;\n}\n\n.form-range::-ms-fill-upper {\n margin-right: 15px;\n background-color: #dee2e6;\n border-radius: 1rem;\n}\n\n.form-range:disabled::-webkit-slider-thumb {\n background-color: #adb5bd;\n}\n\n.form-range:disabled::-webkit-slider-runnable-track {\n cursor: default;\n}\n\n.form-range:disabled::-moz-range-thumb {\n background-color: #adb5bd;\n}\n\n.form-range:disabled::-moz-range-track {\n cursor: default;\n}\n\n.form-range:disabled::-ms-thumb {\n background-color: #adb5bd;\n}\n\n.form-row {\n display: flex;\n flex-wrap: wrap;\n margin-right: -5px;\n margin-left: -5px;\n}\n\n.form-row > .col,\n.form-row > [class*=\"col-\"] {\n padding-right: 5px;\n padding-left: 5px;\n}\n\n.form-inline {\n display: flex;\n flex-flow: row wrap;\n align-items: center;\n}\n\n.form-inline .form-check {\n width: 100%;\n}\n\n@media (min-width: 576px) {\n .form-inline label {\n display: flex;\n align-items: center;\n justify-content: center;\n margin-bottom: 0;\n }\n .form-inline .form-group {\n display: flex;\n flex: 0 0 auto;\n flex-flow: row wrap;\n align-items: center;\n margin-bottom: 0;\n }\n .form-inline .form-control {\n display: inline-block;\n width: auto;\n vertical-align: middle;\n }\n .form-inline .form-control-plaintext {\n display: inline-block;\n }\n .form-inline .input-group,\n .form-inline .form-select {\n width: auto;\n }\n .form-inline .form-check {\n display: flex;\n align-items: center;\n justify-content: center;\n width: auto;\n padding-left: 0;\n }\n .form-inline .form-check-input {\n position: relative;\n flex-shrink: 0;\n margin-top: 0;\n margin-right: 0.25rem;\n margin-left: 0;\n }\n}\n\n.input-group {\n position: relative;\n display: flex;\n flex-wrap: wrap;\n align-items: stretch;\n width: 100%;\n}\n\n.input-group > .form-control,\n.input-group > .form-select,\n.input-group > .form-file {\n position: relative;\n flex: 1 1 0%;\n min-width: 0;\n margin-bottom: 0;\n}\n\n.input-group > .form-control + .form-control,\n.input-group > .form-control + .form-select,\n.input-group > .form-control + .form-file,\n.input-group > .form-select + .form-control,\n.input-group > .form-select + .form-select,\n.input-group > .form-select + .form-file,\n.input-group > .form-file + .form-control,\n.input-group > .form-file + .form-select,\n.input-group > .form-file + .form-file {\n margin-left: -1px;\n}\n\n.input-group > .form-control:focus,\n.input-group > .form-select:focus,\n.input-group > .form-file .form-file-input:focus ~ .form-file-label {\n z-index: 3;\n}\n\n.input-group > .form-file .form-file-input:focus {\n z-index: 4;\n}\n\n.input-group > .form-control:not(:last-child),\n.input-group > .form-select:not(:last-child) {\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n}\n\n.input-group > .form-control:not(:first-child),\n.input-group > .form-select:not(:first-child) {\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n}\n\n.input-group > .form-file {\n display: flex;\n align-items: center;\n}\n\n.input-group > .form-file:not(:last-child) .form-file-label {\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n}\n\n.input-group > .form-file:not(:first-child) .form-file-label {\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n}\n\n.input-group-prepend,\n.input-group-append {\n display: flex;\n}\n\n.input-group-prepend .btn,\n.input-group-append .btn {\n position: relative;\n z-index: 2;\n}\n\n.input-group-prepend .btn:focus,\n.input-group-append .btn:focus {\n z-index: 3;\n}\n\n.input-group-prepend .btn + .btn,\n.input-group-prepend .btn + .input-group-text,\n.input-group-prepend .input-group-text + .input-group-text,\n.input-group-prepend .input-group-text + .btn,\n.input-group-append .btn + .btn,\n.input-group-append .btn + .input-group-text,\n.input-group-append .input-group-text + .input-group-text,\n.input-group-append .input-group-text + .btn {\n margin-left: -1px;\n}\n\n.input-group-prepend {\n margin-right: -1px;\n}\n\n.input-group-append {\n margin-left: -1px;\n}\n\n.input-group-text {\n display: flex;\n align-items: center;\n padding: 0.375rem 0.75rem;\n margin-bottom: 0;\n font-size: 1rem;\n font-weight: 400;\n line-height: 1.5;\n color: #495057;\n text-align: center;\n white-space: nowrap;\n background-color: #e9ecef;\n border: 1px solid #ced4da;\n border-radius: 0.25rem;\n}\n\n.input-group-text input[type=\"radio\"],\n.input-group-text input[type=\"checkbox\"] {\n margin-top: 0;\n}\n\n.input-group-lg > .form-control {\n min-height: calc(1.5em + 1rem + 2px);\n}\n\n.input-group-lg > .form-select {\n height: calc(1.5em + 1rem + 2px);\n}\n\n.input-group-lg > .form-control,\n.input-group-lg > .form-select,\n.input-group-lg > .input-group-prepend > .input-group-text,\n.input-group-lg > .input-group-append > .input-group-text,\n.input-group-lg > .input-group-prepend > .btn,\n.input-group-lg > .input-group-append > .btn {\n padding: 0.5rem 1rem;\n font-size: 1.25rem;\n border-radius: 0.3rem;\n}\n\n.input-group-sm > .form-control {\n min-height: calc(1.5em + 0.5rem + 2px);\n}\n\n.input-group-sm > .form-select {\n height: calc(1.5em + 0.5rem + 2px);\n}\n\n.input-group-sm > .form-control,\n.input-group-sm > .form-select,\n.input-group-sm > .input-group-prepend > .input-group-text,\n.input-group-sm > .input-group-append > .input-group-text,\n.input-group-sm > .input-group-prepend > .btn,\n.input-group-sm > .input-group-append > .btn {\n padding: 0.25rem 0.5rem;\n font-size: 0.875rem;\n border-radius: 0.2rem;\n}\n\n.input-group-lg > .form-select,\n.input-group-sm > .form-select {\n padding-right: 1.75rem;\n}\n\n.input-group > .input-group-prepend > .btn,\n.input-group > .input-group-prepend > .input-group-text,\n.input-group > .input-group-append:not(:last-child) > .btn,\n.input-group > .input-group-append:not(:last-child) > .input-group-text,\n.input-group > .input-group-append:last-child > .btn:not(:last-child):not(.dropdown-toggle),\n.input-group > .input-group-append:last-child > .input-group-text:not(:last-child) {\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n}\n\n.input-group > .input-group-append > .btn,\n.input-group > .input-group-append > .input-group-text,\n.input-group > .input-group-prepend:not(:first-child) > .btn,\n.input-group > .input-group-prepend:not(:first-child) > .input-group-text,\n.input-group > .input-group-prepend:first-child > .btn:not(:first-child),\n.input-group > .input-group-prepend:first-child > .input-group-text:not(:first-child) {\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n}\n\n.valid-feedback {\n display: none;\n width: 100%;\n margin-top: 0.25rem;\n font-size: 0.875em;\n color: #28a745;\n}\n\n.valid-tooltip {\n position: absolute;\n top: 100%;\n z-index: 5;\n display: none;\n max-width: 100%;\n padding: 0.25rem 0.5rem;\n margin-top: .1rem;\n font-size: 0.875rem;\n color: #fff;\n background-color: rgba(40, 167, 69, 0.9);\n border-radius: 0.25rem;\n}\n\n.was-validated :valid ~ .valid-feedback,\n.was-validated :valid ~ .valid-tooltip,\n.is-valid ~ .valid-feedback,\n.is-valid ~ .valid-tooltip {\n display: block;\n}\n\n.was-validated .form-control:valid, .form-control.is-valid {\n border-color: #28a745;\n padding-right: calc(1.5em + 0.75rem);\n background-image: url(\"data:image\/svg+xml,%3csvg xmlns='http:\/\/www.w3.org\/2000\/svg' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'\/%3e%3c\/svg%3e\");\n background-repeat: no-repeat;\n background-position: right calc(0.375em + 0.1875rem) center;\n background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem);\n}\n\n.was-validated .form-control:valid:focus, .form-control.is-valid:focus {\n border-color: #28a745;\n box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25);\n}\n\n.was-validated textarea.form-control:valid, textarea.form-control.is-valid {\n padding-right: calc(1.5em + 0.75rem);\n background-position: top calc(0.375em + 0.1875rem) right calc(0.375em + 0.1875rem);\n}\n\n.was-validated .form-select:valid, .form-select.is-valid {\n border-color: #28a745;\n padding-right: calc(0.75em + 2.3125rem);\n background: url(\"data:image\/svg+xml,%3csvg xmlns='http:\/\/www.w3.org\/2000\/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'\/%3e%3c\/svg%3e\") no-repeat right 0.75rem center\/16px 12px, url(\"data:image\/svg+xml,%3csvg xmlns='http:\/\/www.w3.org\/2000\/svg' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'\/%3e%3c\/svg%3e\") #fff no-repeat center right 1.75rem\/calc(0.75em + 0.375rem) calc(0.75em + 0.375rem);\n}\n\n.was-validated .form-select:valid:focus, .form-select.is-valid:focus {\n border-color: #28a745;\n box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25);\n}\n\n.was-validated .form-check-input:valid, .form-check-input.is-valid {\n border-color: #28a745;\n}\n\n.was-validated .form-check-input:valid:checked, .form-check-input.is-valid:checked {\n background-color: #34ce57;\n}\n\n.was-validated .form-check-input:valid:focus, .form-check-input.is-valid:focus {\n box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25);\n}\n\n.was-validated .form-check-input:valid ~ .form-check-label, .form-check-input.is-valid ~ .form-check-label {\n color: #28a745;\n}\n\n.form-check-inline .form-check-input ~ .valid-feedback {\n margin-left: .5em;\n}\n\n.was-validated .form-file-input:valid ~ .form-file-label, .form-file-input.is-valid ~ .form-file-label {\n border-color: #28a745;\n}\n\n.was-validated .form-file-input:valid:focus ~ .form-file-label, .form-file-input.is-valid:focus ~ .form-file-label {\n border-color: #28a745;\n box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25);\n}\n\n.invalid-feedback {\n display: none;\n width: 100%;\n margin-top: 0.25rem;\n font-size: 0.875em;\n color: #dc3545;\n}\n\n.invalid-tooltip {\n position: absolute;\n top: 100%;\n z-index: 5;\n display: none;\n max-width: 100%;\n padding: 0.25rem 0.5rem;\n margin-top: .1rem;\n font-size: 0.875rem;\n color: #fff;\n background-color: rgba(220, 53, 69, 0.9);\n border-radius: 0.25rem;\n}\n\n.was-validated :invalid ~ .invalid-feedback,\n.was-validated :invalid ~ .invalid-tooltip,\n.is-invalid ~ .invalid-feedback,\n.is-invalid ~ .invalid-tooltip {\n display: block;\n}\n\n.was-validated .form-control:invalid, .form-control.is-invalid {\n border-color: #dc3545;\n padding-right: calc(1.5em + 0.75rem);\n background-image: url(\"data:image\/svg+xml,%3csvg xmlns='http:\/\/www.w3.org\/2000\/svg' width='12' height='12' fill='none' stroke='%23dc3545' viewBox='0 0 12 12'%3e%3ccircle cx='6' cy='6' r='4.5'\/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'\/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'\/%3e%3c\/svg%3e\");\n background-repeat: no-repeat;\n background-position: right calc(0.375em + 0.1875rem) center;\n background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem);\n}\n\n.was-validated .form-control:invalid:focus, .form-control.is-invalid:focus {\n border-color: #dc3545;\n box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25);\n}\n\n.was-validated textarea.form-control:invalid, textarea.form-control.is-invalid {\n padding-right: calc(1.5em + 0.75rem);\n background-position: top calc(0.375em + 0.1875rem) right calc(0.375em + 0.1875rem);\n}\n\n.was-validated .form-select:invalid, .form-select.is-invalid {\n border-color: #dc3545;\n padding-right: calc(0.75em + 2.3125rem);\n background: url(\"data:image\/svg+xml,%3csvg xmlns='http:\/\/www.w3.org\/2000\/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'\/%3e%3c\/svg%3e\") no-repeat right 0.75rem center\/16px 12px, url(\"data:image\/svg+xml,%3csvg xmlns='http:\/\/www.w3.org\/2000\/svg' width='12' height='12' fill='none' stroke='%23dc3545' viewBox='0 0 12 12'%3e%3ccircle cx='6' cy='6' r='4.5'\/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'\/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'\/%3e%3c\/svg%3e\") #fff no-repeat center right 1.75rem\/calc(0.75em + 0.375rem) calc(0.75em + 0.375rem);\n}\n\n.was-validated .form-select:invalid:focus, .form-select.is-invalid:focus {\n border-color: #dc3545;\n box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25);\n}\n\n.was-validated .form-check-input:invalid, .form-check-input.is-invalid {\n border-color: #dc3545;\n}\n\n.was-validated .form-check-input:invalid:checked, .form-check-input.is-invalid:checked {\n background-color: #e4606d;\n}\n\n.was-validated .form-check-input:invalid:focus, .form-check-input.is-invalid:focus {\n box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25);\n}\n\n.was-validated .form-check-input:invalid ~ .form-check-label, .form-check-input.is-invalid ~ .form-check-label {\n color: #dc3545;\n}\n\n.form-check-inline .form-check-input ~ .invalid-feedback {\n margin-left: .5em;\n}\n\n.was-validated .form-file-input:invalid ~ .form-file-label, .form-file-input.is-invalid ~ .form-file-label {\n border-color: #dc3545;\n}\n\n.was-validated .form-file-input:invalid:focus ~ .form-file-label, .form-file-input.is-invalid:focus ~ .form-file-label {\n border-color: #dc3545;\n box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25);\n}\n\n.btn {\n display: inline-block;\n font-weight: 400;\n line-height: 1.5;\n color: #212529;\n text-align: center;\n vertical-align: middle;\n cursor: pointer;\n -webkit-user-select: none;\n -moz-user-select: none;\n -ms-user-select: none;\n user-select: none;\n background-color: transparent;\n border: 1px solid transparent;\n padding: 0.375rem 0.75rem;\n font-size: 1rem;\n border-radius: 0.25rem;\n transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .btn {\n transition: none;\n }\n}\n\n.btn:hover {\n color: #212529;\n text-decoration: none;\n}\n\n.btn:focus, .btn.focus {\n outline: 0;\n box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25);\n}\n\n.btn.disabled, .btn:disabled {\n opacity: 0.65;\n}\n\na.btn.disabled,\nfieldset:disabled a.btn {\n pointer-events: none;\n}\n\n.btn-primary {\n color: #fff;\n background-color: #0d6efd;\n border-color: #0d6efd;\n}\n\n.btn-primary:hover {\n color: #fff;\n background-color: #025ce2;\n border-color: #0257d5;\n}\n\n.btn-primary:focus, .btn-primary.focus {\n color: #fff;\n background-color: #025ce2;\n border-color: #0257d5;\n box-shadow: 0 0 0 0.2rem rgba(49, 132, 253, 0.5);\n}\n\n.btn-primary.disabled, .btn-primary:disabled {\n color: #fff;\n background-color: #0d6efd;\n border-color: #0d6efd;\n}\n\n.btn-primary:not(:disabled):not(.disabled):active, .btn-primary:not(:disabled):not(.disabled).active,\n.show > .btn-primary.dropdown-toggle {\n color: #fff;\n background-color: #0257d5;\n border-color: #0252c9;\n}\n\n.btn-primary:not(:disabled):not(.disabled):active:focus, .btn-primary:not(:disabled):not(.disabled).active:focus,\n.show > .btn-primary.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(49, 132, 253, 0.5);\n}\n\n.btn-secondary {\n color: #fff;\n background-color: #6c757d;\n border-color: #6c757d;\n}\n\n.btn-secondary:hover {\n color: #fff;\n background-color: #5a6268;\n border-color: #545b62;\n}\n\n.btn-secondary:focus, .btn-secondary.focus {\n color: #fff;\n background-color: #5a6268;\n border-color: #545b62;\n box-shadow: 0 0 0 0.2rem rgba(130, 138, 145, 0.5);\n}\n\n.btn-secondary.disabled, .btn-secondary:disabled {\n color: #fff;\n background-color: #6c757d;\n border-color: #6c757d;\n}\n\n.btn-secondary:not(:disabled):not(.disabled):active, .btn-secondary:not(:disabled):not(.disabled).active,\n.show > .btn-secondary.dropdown-toggle {\n color: #fff;\n background-color: #545b62;\n border-color: #4e555b;\n}\n\n.btn-secondary:not(:disabled):not(.disabled):active:focus, .btn-secondary:not(:disabled):not(.disabled).active:focus,\n.show > .btn-secondary.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(130, 138, 145, 0.5);\n}\n\n.btn-success {\n color: #fff;\n background-color: #28a745;\n border-color: #28a745;\n}\n\n.btn-success:hover {\n color: #fff;\n background-color: #218838;\n border-color: #1e7e34;\n}\n\n.btn-success:focus, .btn-success.focus {\n color: #fff;\n background-color: #218838;\n border-color: #1e7e34;\n box-shadow: 0 0 0 0.2rem rgba(72, 180, 97, 0.5);\n}\n\n.btn-success.disabled, .btn-success:disabled {\n color: #fff;\n background-color: #28a745;\n border-color: #28a745;\n}\n\n.btn-success:not(:disabled):not(.disabled):active, .btn-success:not(:disabled):not(.disabled).active,\n.show > .btn-success.dropdown-toggle {\n color: #fff;\n background-color: #1e7e34;\n border-color: #1c7430;\n}\n\n.btn-success:not(:disabled):not(.disabled):active:focus, .btn-success:not(:disabled):not(.disabled).active:focus,\n.show > .btn-success.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(72, 180, 97, 0.5);\n}\n\n.btn-info {\n color: #fff;\n background-color: #17a2b8;\n border-color: #17a2b8;\n}\n\n.btn-info:hover {\n color: #fff;\n background-color: #138496;\n border-color: #117a8b;\n}\n\n.btn-info:focus, .btn-info.focus {\n color: #fff;\n background-color: #138496;\n border-color: #117a8b;\n box-shadow: 0 0 0 0.2rem rgba(58, 176, 195, 0.5);\n}\n\n.btn-info.disabled, .btn-info:disabled {\n color: #fff;\n background-color: #17a2b8;\n border-color: #17a2b8;\n}\n\n.btn-info:not(:disabled):not(.disabled):active, .btn-info:not(:disabled):not(.disabled).active,\n.show > .btn-info.dropdown-toggle {\n color: #fff;\n background-color: #117a8b;\n border-color: #10707f;\n}\n\n.btn-info:not(:disabled):not(.disabled):active:focus, .btn-info:not(:disabled):not(.disabled).active:focus,\n.show > .btn-info.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(58, 176, 195, 0.5);\n}\n\n.btn-warning {\n color: #212529;\n background-color: #ffc107;\n border-color: #ffc107;\n}\n\n.btn-warning:hover {\n color: #212529;\n background-color: #e0a800;\n border-color: #d39e00;\n}\n\n.btn-warning:focus, .btn-warning.focus {\n color: #212529;\n background-color: #e0a800;\n border-color: #d39e00;\n box-shadow: 0 0 0 0.2rem rgba(222, 170, 12, 0.5);\n}\n\n.btn-warning.disabled, .btn-warning:disabled {\n color: #212529;\n background-color: #ffc107;\n border-color: #ffc107;\n}\n\n.btn-warning:not(:disabled):not(.disabled):active, .btn-warning:not(:disabled):not(.disabled).active,\n.show > .btn-warning.dropdown-toggle {\n color: #212529;\n background-color: #d39e00;\n border-color: #c69500;\n}\n\n.btn-warning:not(:disabled):not(.disabled):active:focus, .btn-warning:not(:disabled):not(.disabled).active:focus,\n.show > .btn-warning.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(222, 170, 12, 0.5);\n}\n\n.btn-danger {\n color: #fff;\n background-color: #dc3545;\n border-color: #dc3545;\n}\n\n.btn-danger:hover {\n color: #fff;\n background-color: #c82333;\n border-color: #bd2130;\n}\n\n.btn-danger:focus, .btn-danger.focus {\n color: #fff;\n background-color: #c82333;\n border-color: #bd2130;\n box-shadow: 0 0 0 0.2rem rgba(225, 83, 97, 0.5);\n}\n\n.btn-danger.disabled, .btn-danger:disabled {\n color: #fff;\n background-color: #dc3545;\n border-color: #dc3545;\n}\n\n.btn-danger:not(:disabled):not(.disabled):active, .btn-danger:not(:disabled):not(.disabled).active,\n.show > .btn-danger.dropdown-toggle {\n color: #fff;\n background-color: #bd2130;\n border-color: #b21f2d;\n}\n\n.btn-danger:not(:disabled):not(.disabled):active:focus, .btn-danger:not(:disabled):not(.disabled).active:focus,\n.show > .btn-danger.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(225, 83, 97, 0.5);\n}\n\n.btn-light {\n color: #212529;\n background-color: #f8f9fa;\n border-color: #f8f9fa;\n}\n\n.btn-light:hover {\n color: #212529;\n background-color: #e2e6ea;\n border-color: #dae0e5;\n}\n\n.btn-light:focus, .btn-light.focus {\n color: #212529;\n background-color: #e2e6ea;\n border-color: #dae0e5;\n box-shadow: 0 0 0 0.2rem rgba(216, 217, 219, 0.5);\n}\n\n.btn-light.disabled, .btn-light:disabled {\n color: #212529;\n background-color: #f8f9fa;\n border-color: #f8f9fa;\n}\n\n.btn-light:not(:disabled):not(.disabled):active, .btn-light:not(:disabled):not(.disabled).active,\n.show > .btn-light.dropdown-toggle {\n color: #212529;\n background-color: #dae0e5;\n border-color: #d3d9df;\n}\n\n.btn-light:not(:disabled):not(.disabled):active:focus, .btn-light:not(:disabled):not(.disabled).active:focus,\n.show > .btn-light.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(216, 217, 219, 0.5);\n}\n\n.btn-dark {\n color: #fff;\n background-color: #343a40;\n border-color: #343a40;\n}\n\n.btn-dark:hover {\n color: #fff;\n background-color: #23272b;\n border-color: #1d2124;\n}\n\n.btn-dark:focus, .btn-dark.focus {\n color: #fff;\n background-color: #23272b;\n border-color: #1d2124;\n box-shadow: 0 0 0 0.2rem rgba(82, 88, 93, 0.5);\n}\n\n.btn-dark.disabled, .btn-dark:disabled {\n color: #fff;\n background-color: #343a40;\n border-color: #343a40;\n}\n\n.btn-dark:not(:disabled):not(.disabled):active, .btn-dark:not(:disabled):not(.disabled).active,\n.show > .btn-dark.dropdown-toggle {\n color: #fff;\n background-color: #1d2124;\n border-color: #171a1d;\n}\n\n.btn-dark:not(:disabled):not(.disabled):active:focus, .btn-dark:not(:disabled):not(.disabled).active:focus,\n.show > .btn-dark.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(82, 88, 93, 0.5);\n}\n\n.btn-outline-primary {\n color: #0d6efd;\n border-color: #0d6efd;\n}\n\n.btn-outline-primary:hover {\n color: #fff;\n background-color: #0d6efd;\n border-color: #0d6efd;\n}\n\n.btn-outline-primary:focus, .btn-outline-primary.focus {\n box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.5);\n}\n\n.btn-outline-primary.disabled, .btn-outline-primary:disabled {\n color: #0d6efd;\n background-color: transparent;\n}\n\n.btn-outline-primary:not(:disabled):not(.disabled):active, .btn-outline-primary:not(:disabled):not(.disabled).active,\n.show > .btn-outline-primary.dropdown-toggle {\n color: #fff;\n background-color: #0d6efd;\n border-color: #0d6efd;\n}\n\n.btn-outline-primary:not(:disabled):not(.disabled):active:focus, .btn-outline-primary:not(:disabled):not(.disabled).active:focus,\n.show > .btn-outline-primary.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.5);\n}\n\n.btn-outline-secondary {\n color: #6c757d;\n border-color: #6c757d;\n}\n\n.btn-outline-secondary:hover {\n color: #fff;\n background-color: #6c757d;\n border-color: #6c757d;\n}\n\n.btn-outline-secondary:focus, .btn-outline-secondary.focus {\n box-shadow: 0 0 0 0.2rem rgba(108, 117, 125, 0.5);\n}\n\n.btn-outline-secondary.disabled, .btn-outline-secondary:disabled {\n color: #6c757d;\n background-color: transparent;\n}\n\n.btn-outline-secondary:not(:disabled):not(.disabled):active, .btn-outline-secondary:not(:disabled):not(.disabled).active,\n.show > .btn-outline-secondary.dropdown-toggle {\n color: #fff;\n background-color: #6c757d;\n border-color: #6c757d;\n}\n\n.btn-outline-secondary:not(:disabled):not(.disabled):active:focus, .btn-outline-secondary:not(:disabled):not(.disabled).active:focus,\n.show > .btn-outline-secondary.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(108, 117, 125, 0.5);\n}\n\n.btn-outline-success {\n color: #28a745;\n border-color: #28a745;\n}\n\n.btn-outline-success:hover {\n color: #fff;\n background-color: #28a745;\n border-color: #28a745;\n}\n\n.btn-outline-success:focus, .btn-outline-success.focus {\n box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5);\n}\n\n.btn-outline-success.disabled, .btn-outline-success:disabled {\n color: #28a745;\n background-color: transparent;\n}\n\n.btn-outline-success:not(:disabled):not(.disabled):active, .btn-outline-success:not(:disabled):not(.disabled).active,\n.show > .btn-outline-success.dropdown-toggle {\n color: #fff;\n background-color: #28a745;\n border-color: #28a745;\n}\n\n.btn-outline-success:not(:disabled):not(.disabled):active:focus, .btn-outline-success:not(:disabled):not(.disabled).active:focus,\n.show > .btn-outline-success.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5);\n}\n\n.btn-outline-info {\n color: #17a2b8;\n border-color: #17a2b8;\n}\n\n.btn-outline-info:hover {\n color: #fff;\n background-color: #17a2b8;\n border-color: #17a2b8;\n}\n\n.btn-outline-info:focus, .btn-outline-info.focus {\n box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5);\n}\n\n.btn-outline-info.disabled, .btn-outline-info:disabled {\n color: #17a2b8;\n background-color: transparent;\n}\n\n.btn-outline-info:not(:disabled):not(.disabled):active, .btn-outline-info:not(:disabled):not(.disabled).active,\n.show > .btn-outline-info.dropdown-toggle {\n color: #fff;\n background-color: #17a2b8;\n border-color: #17a2b8;\n}\n\n.btn-outline-info:not(:disabled):not(.disabled):active:focus, .btn-outline-info:not(:disabled):not(.disabled).active:focus,\n.show > .btn-outline-info.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5);\n}\n\n.btn-outline-warning {\n color: #ffc107;\n border-color: #ffc107;\n}\n\n.btn-outline-warning:hover {\n color: #212529;\n background-color: #ffc107;\n border-color: #ffc107;\n}\n\n.btn-outline-warning:focus, .btn-outline-warning.focus {\n box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5);\n}\n\n.btn-outline-warning.disabled, .btn-outline-warning:disabled {\n color: #ffc107;\n background-color: transparent;\n}\n\n.btn-outline-warning:not(:disabled):not(.disabled):active, .btn-outline-warning:not(:disabled):not(.disabled).active,\n.show > .btn-outline-warning.dropdown-toggle {\n color: #212529;\n background-color: #ffc107;\n border-color: #ffc107;\n}\n\n.btn-outline-warning:not(:disabled):not(.disabled):active:focus, .btn-outline-warning:not(:disabled):not(.disabled).active:focus,\n.show > .btn-outline-warning.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5);\n}\n\n.btn-outline-danger {\n color: #dc3545;\n border-color: #dc3545;\n}\n\n.btn-outline-danger:hover {\n color: #fff;\n background-color: #dc3545;\n border-color: #dc3545;\n}\n\n.btn-outline-danger:focus, .btn-outline-danger.focus {\n box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5);\n}\n\n.btn-outline-danger.disabled, .btn-outline-danger:disabled {\n color: #dc3545;\n background-color: transparent;\n}\n\n.btn-outline-danger:not(:disabled):not(.disabled):active, .btn-outline-danger:not(:disabled):not(.disabled).active,\n.show > .btn-outline-danger.dropdown-toggle {\n color: #fff;\n background-color: #dc3545;\n border-color: #dc3545;\n}\n\n.btn-outline-danger:not(:disabled):not(.disabled):active:focus, .btn-outline-danger:not(:disabled):not(.disabled).active:focus,\n.show > .btn-outline-danger.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5);\n}\n\n.btn-outline-light {\n color: #f8f9fa;\n border-color: #f8f9fa;\n}\n\n.btn-outline-light:hover {\n color: #212529;\n background-color: #f8f9fa;\n border-color: #f8f9fa;\n}\n\n.btn-outline-light:focus, .btn-outline-light.focus {\n box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5);\n}\n\n.btn-outline-light.disabled, .btn-outline-light:disabled {\n color: #f8f9fa;\n background-color: transparent;\n}\n\n.btn-outline-light:not(:disabled):not(.disabled):active, .btn-outline-light:not(:disabled):not(.disabled).active,\n.show > .btn-outline-light.dropdown-toggle {\n color: #212529;\n background-color: #f8f9fa;\n border-color: #f8f9fa;\n}\n\n.btn-outline-light:not(:disabled):not(.disabled):active:focus, .btn-outline-light:not(:disabled):not(.disabled).active:focus,\n.show > .btn-outline-light.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5);\n}\n\n.btn-outline-dark {\n color: #343a40;\n border-color: #343a40;\n}\n\n.btn-outline-dark:hover {\n color: #fff;\n background-color: #343a40;\n border-color: #343a40;\n}\n\n.btn-outline-dark:focus, .btn-outline-dark.focus {\n box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5);\n}\n\n.btn-outline-dark.disabled, .btn-outline-dark:disabled {\n color: #343a40;\n background-color: transparent;\n}\n\n.btn-outline-dark:not(:disabled):not(.disabled):active, .btn-outline-dark:not(:disabled):not(.disabled).active,\n.show > .btn-outline-dark.dropdown-toggle {\n color: #fff;\n background-color: #343a40;\n border-color: #343a40;\n}\n\n.btn-outline-dark:not(:disabled):not(.disabled):active:focus, .btn-outline-dark:not(:disabled):not(.disabled).active:focus,\n.show > .btn-outline-dark.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5);\n}\n\n.btn-link {\n font-weight: 400;\n color: #0d6efd;\n text-decoration: none;\n}\n\n.btn-link:hover {\n color: #024dbc;\n text-decoration: underline;\n}\n\n.btn-link:focus, .btn-link.focus {\n text-decoration: underline;\n box-shadow: none;\n}\n\n.btn-link:disabled, .btn-link.disabled {\n color: #6c757d;\n pointer-events: none;\n}\n\n.btn-lg, .btn-group-lg > .btn {\n padding: 0.5rem 1rem;\n font-size: 1.25rem;\n border-radius: 0.3rem;\n}\n\n.btn-sm, .btn-group-sm > .btn {\n padding: 0.25rem 0.5rem;\n font-size: 0.875rem;\n border-radius: 0.2rem;\n}\n\n.btn-block {\n display: block;\n width: 100%;\n}\n\n.btn-block + .btn-block {\n margin-top: 0.5rem;\n}\n\n.fade {\n transition: opacity 0.15s linear;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .fade {\n transition: none;\n }\n}\n\n.fade:not(.show) {\n opacity: 0;\n}\n\n.collapse:not(.show) {\n display: none;\n}\n\n.collapsing {\n height: 0;\n overflow: hidden;\n transition: height 0.35s ease;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .collapsing {\n transition: none;\n }\n}\n\n.dropup,\n.dropright,\n.dropdown,\n.dropleft {\n position: relative;\n}\n\n.dropdown-toggle {\n white-space: nowrap;\n}\n\n.dropdown-toggle::after {\n display: inline-block;\n margin-left: 0.255em;\n vertical-align: 0.255em;\n content: \"\";\n border-top: 0.3em solid;\n border-right: 0.3em solid transparent;\n border-bottom: 0;\n border-left: 0.3em solid transparent;\n}\n\n.dropdown-toggle:empty::after {\n margin-left: 0;\n}", + { + "line": 1, + "column": 1, + "index": 0 + } + ], + [ + ".dropdown-menu {\n position: absolute;\n top: 100%;\n left: 0;\n z-index: 1000;\n display: none;\n min-width: 10rem;\n padding: 0.5rem 0;\n margin: 0.125rem 0 0;\n font-size: 1rem;\n color: #212529;\n text-align: left;\n list-style: none;\n background-color: #fff;\n background-clip: padding-box;\n border: 1px solid rgba(0, 0, 0, 0.15);\n border-radius: 0.25rem;\n}\n\n.dropdown-menu-left {\n right: auto;\n left: 0;\n}\n\n.dropdown-menu-right {\n right: 0;\n left: auto;\n}\n\n@media (min-width: 576px) {\n .dropdown-menu-sm-left {\n right: auto;\n left: 0;\n }\n .dropdown-menu-sm-right {\n right: 0;\n left: auto;\n }\n}\n\n@media (min-width: 768px) {\n .dropdown-menu-md-left {\n right: auto;\n left: 0;\n }\n .dropdown-menu-md-right {\n right: 0;\n left: auto;\n }\n}\n\n@media (min-width: 992px) {\n .dropdown-menu-lg-left {\n right: auto;\n left: 0;\n }\n .dropdown-menu-lg-right {\n right: 0;\n left: auto;\n }\n}\n\n@media (min-width: 1200px) {\n .dropdown-menu-xl-left {\n right: auto;\n left: 0;\n }\n .dropdown-menu-xl-right {\n right: 0;\n left: auto;\n }\n}\n\n.dropup .dropdown-menu {\n top: auto;\n bottom: 100%;\n margin-top: 0;\n margin-bottom: 0.125rem;\n}\n\n.dropup .dropdown-toggle::after {\n display: inline-block;\n margin-left: 0.255em;\n vertical-align: 0.255em;\n content: \"\";\n border-top: 0;\n border-right: 0.3em solid transparent;\n border-bottom: 0.3em solid;\n border-left: 0.3em solid transparent;\n}\n\n.dropup .dropdown-toggle:empty::after {\n margin-left: 0;\n}\n\n.dropright .dropdown-menu {\n top: 0;\n right: auto;\n left: 100%;\n margin-top: 0;\n margin-left: 0.125rem;\n}\n\n.dropright .dropdown-toggle::after {\n display: inline-block;\n margin-left: 0.255em;\n vertical-align: 0.255em;\n content: \"\";\n border-top: 0.3em solid transparent;\n border-right: 0;\n border-bottom: 0.3em solid transparent;\n border-left: 0.3em solid;\n}\n\n.dropright .dropdown-toggle:empty::after {\n margin-left: 0;\n}\n\n.dropright .dropdown-toggle::after {\n vertical-align: 0;\n}\n\n.dropleft .dropdown-menu {\n top: 0;\n right: 100%;\n left: auto;\n margin-top: 0;\n margin-right: 0.125rem;\n}\n\n.dropleft .dropdown-toggle::after {\n display: inline-block;\n margin-left: 0.255em;\n vertical-align: 0.255em;\n content: \"\";\n}\n\n.dropleft .dropdown-toggle::after {\n display: none;\n}\n\n.dropleft .dropdown-toggle::before {\n display: inline-block;\n margin-right: 0.255em;\n vertical-align: 0.255em;\n content: \"\";\n border-top: 0.3em solid transparent;\n border-right: 0.3em solid;\n border-bottom: 0.3em solid transparent;\n}\n\n.dropleft .dropdown-toggle:empty::after {\n margin-left: 0;\n}\n\n.dropleft .dropdown-toggle::before {\n vertical-align: 0;\n}\n\n.dropdown-menu[x-placement^=\"top\"], .dropdown-menu[x-placement^=\"right\"], .dropdown-menu[x-placement^=\"bottom\"], .dropdown-menu[x-placement^=\"left\"] {\n right: auto;\n bottom: auto;\n}\n\n.dropdown-divider {\n height: 0;\n margin: 0.5rem 0;\n overflow: hidden;\n border-top: 1px solid #e9ecef;\n}\n\n.dropdown-item {\n display: block;\n width: 100%;\n padding: 0.25rem 1.5rem;\n clear: both;\n font-weight: 400;\n color: #212529;\n text-align: inherit;\n white-space: nowrap;\n background-color: transparent;\n border: 0;\n}\n\n.dropdown-item:hover, .dropdown-item:focus {\n color: #16181b;\n text-decoration: none;\n background-color: #f8f9fa;\n}\n\n.dropdown-item.active, .dropdown-item:active {\n color: #fff;\n text-decoration: none;\n background-color: #0d6efd;\n}\n\n.dropdown-item.disabled, .dropdown-item:disabled {\n color: #6c757d;\n pointer-events: none;\n background-color: transparent;\n}\n\n.dropdown-menu.show {\n display: block;\n}\n\n.dropdown-header {\n display: block;\n padding: 0.5rem 1.5rem;\n margin-bottom: 0;\n font-size: 0.875rem;\n color: #6c757d;\n white-space: nowrap;\n}\n\n.dropdown-item-text {\n display: block;\n padding: 0.25rem 1.5rem;\n color: #212529;\n}\n\n.btn-group,\n.btn-group-vertical {\n position: relative;\n display: inline-flex;\n vertical-align: middle;\n}\n\n.btn-group > .btn,\n.btn-group-vertical > .btn {\n position: relative;\n flex: 1 1 auto;\n}\n\n.btn-group > .btn:hover, .btn-group > .btn:focus, .btn-group > .btn:active, .btn-group > .btn.active,\n.btn-group-vertical > .btn:hover,\n.btn-group-vertical > .btn:focus,\n.btn-group-vertical > .btn:active,\n.btn-group-vertical > .btn.active {\n z-index: 1;\n}\n\n.btn-toolbar {\n display: flex;\n flex-wrap: wrap;\n justify-content: flex-start;\n}\n\n.btn-toolbar .input-group {\n width: auto;\n}\n\n.btn-group > .btn:not(:first-child),\n.btn-group > .btn-group:not(:first-child) {\n margin-left: -1px;\n}\n\n.btn-group > .btn:not(:last-child):not(.dropdown-toggle),\n.btn-group > .btn-group:not(:last-child) > .btn {\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n}\n\n.btn-group > .btn:not(:first-child),\n.btn-group > .btn-group:not(:first-child) > .btn {\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n}\n\n.dropdown-toggle-split {\n padding-right: 0.5625rem;\n padding-left: 0.5625rem;\n}\n\n.dropdown-toggle-split::after,\n.dropup .dropdown-toggle-split::after,\n.dropright .dropdown-toggle-split::after {\n margin-left: 0;\n}\n\n.dropleft .dropdown-toggle-split::before {\n margin-right: 0;\n}\n\n.btn-sm + .dropdown-toggle-split, .btn-group-sm > .btn + .dropdown-toggle-split {\n padding-right: 0.375rem;\n padding-left: 0.375rem;\n}\n\n.btn-lg + .dropdown-toggle-split, .btn-group-lg > .btn + .dropdown-toggle-split {\n padding-right: 0.75rem;\n padding-left: 0.75rem;\n}\n\n.btn-group-vertical {\n flex-direction: column;\n align-items: flex-start;\n justify-content: center;\n}\n\n.btn-group-vertical > .btn,\n.btn-group-vertical > .btn-group {\n width: 100%;\n}\n\n.btn-group-vertical > .btn:not(:first-child),\n.btn-group-vertical > .btn-group:not(:first-child) {\n margin-top: -1px;\n}\n\n.btn-group-vertical > .btn:not(:last-child):not(.dropdown-toggle),\n.btn-group-vertical > .btn-group:not(:last-child) > .btn {\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 0;\n}\n\n.btn-group-vertical > .btn:not(:first-child),\n.btn-group-vertical > .btn-group:not(:first-child) > .btn {\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n}\n\n.btn-group-toggle > .btn,\n.btn-group-toggle > .btn-group > .btn {\n margin-bottom: 0;\n}\n\n.btn-group-toggle > .btn input[type=\"radio\"],\n.btn-group-toggle > .btn input[type=\"checkbox\"],\n.btn-group-toggle > .btn-group > .btn input[type=\"radio\"],\n.btn-group-toggle > .btn-group > .btn input[type=\"checkbox\"] {\n position: absolute;\n clip: rect(0, 0, 0, 0);\n pointer-events: none;\n}\n\n.nav {\n display: flex;\n flex-wrap: wrap;\n padding-left: 0;\n margin-bottom: 0;\n list-style: none;\n}\n\n.nav-link {\n display: block;\n padding: 0.5rem 1rem;\n}\n\n.nav-link:hover, .nav-link:focus {\n text-decoration: none;\n}\n\n.nav-link.disabled {\n color: #6c757d;\n pointer-events: none;\n cursor: default;\n}\n\n.nav-tabs {\n border-bottom: 1px solid #dee2e6;\n}\n\n.nav-tabs .nav-item {\n margin-bottom: -1px;\n}\n\n.nav-tabs .nav-link {\n border: 1px solid transparent;\n border-top-left-radius: 0.25rem;\n border-top-right-radius: 0.25rem;\n}\n\n.nav-tabs .nav-link:hover, .nav-tabs .nav-link:focus {\n border-color: #e9ecef #e9ecef #dee2e6;\n}\n\n.nav-tabs .nav-link.disabled {\n color: #6c757d;\n background-color: transparent;\n border-color: transparent;\n}\n\n.nav-tabs .nav-link.active,\n.nav-tabs .nav-item.show .nav-link {\n color: #495057;\n background-color: #fff;\n border-color: #dee2e6 #dee2e6 #fff;\n}\n\n.nav-tabs .dropdown-menu {\n margin-top: -1px;\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n}\n\n.nav-pills .nav-link {\n border-radius: 0.25rem;\n}\n\n.nav-pills .nav-link.active,\n.nav-pills .show > .nav-link {\n color: #fff;\n background-color: #0d6efd;\n}\n\n.nav-fill .nav-item {\n flex: 1 1 auto;\n text-align: center;\n}\n\n.nav-justified .nav-item {\n flex-basis: 0;\n flex-grow: 1;\n text-align: center;\n}\n\n.tab-content > .tab-pane {\n display: none;\n}\n\n.tab-content > .active {\n display: block;\n}\n\n.navbar {\n position: relative;\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n justify-content: space-between;\n padding-top: 0.5rem;\n padding-bottom: 0.5rem;\n}\n\n.navbar .container,\n.navbar .container-fluid, .navbar .container-sm, .navbar .container-md, .navbar .container-lg, .navbar .container-xl {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n justify-content: space-between;\n}\n\n.navbar-brand {\n display: inline-block;\n padding-top: 0.3125rem;\n padding-bottom: 0.3125rem;\n font-size: 1.25rem;\n line-height: inherit;\n white-space: nowrap;\n}\n\n.navbar-brand:hover, .navbar-brand:focus {\n text-decoration: none;\n}\n\n.navbar-nav {\n display: flex;\n flex-direction: column;\n padding-left: 0;\n margin-bottom: 0;\n list-style: none;\n}\n\n.navbar-nav .nav-link {\n padding-right: 0;\n padding-left: 0;\n}\n\n.navbar-nav .dropdown-menu {\n position: static;\n}\n\n.navbar-text {\n display: inline-block;\n padding-top: 0.5rem;\n padding-bottom: 0.5rem;\n}\n\n.navbar-collapse {\n flex-basis: 100%;\n flex-grow: 1;\n align-items: center;\n}\n\n.navbar-toggler {\n padding: 0.25rem 0.75rem;\n font-size: 1.25rem;\n line-height: 1;\n background-color: transparent;\n border: 1px solid transparent;\n border-radius: 0.25rem;\n}\n\n.navbar-toggler:hover, .navbar-toggler:focus {\n text-decoration: none;\n}\n\n.navbar-toggler-icon {\n display: inline-block;\n width: 1.5em;\n height: 1.5em;\n vertical-align: middle;\n content: \"\";\n background: no-repeat center center;\n background-size: 100% 100%;\n}\n\n@media (min-width: 576px) {\n .navbar-expand-sm {\n flex-flow: row nowrap;\n justify-content: flex-start;\n }\n .navbar-expand-sm .navbar-nav {\n flex-direction: row;\n }\n .navbar-expand-sm .navbar-nav .dropdown-menu {\n position: absolute;\n }\n .navbar-expand-sm .navbar-nav .nav-link {\n padding-right: 0.5rem;\n padding-left: 0.5rem;\n }\n .navbar-expand-sm > .container,\n .navbar-expand-sm > .container-fluid, .navbar-expand-sm > .container-sm, .navbar-expand-sm > .container-md, .navbar-expand-sm > .container-lg, .navbar-expand-sm > .container-xl {\n flex-wrap: nowrap;\n }\n .navbar-expand-sm .navbar-collapse {\n display: flex !important;\n flex-basis: auto;\n }\n .navbar-expand-sm .navbar-toggler {\n display: none;\n }\n}\n\n@media (min-width: 768px) {\n .navbar-expand-md {\n flex-flow: row nowrap;\n justify-content: flex-start;\n }\n .navbar-expand-md .navbar-nav {\n flex-direction: row;\n }\n .navbar-expand-md .navbar-nav .dropdown-menu {\n position: absolute;\n }\n .navbar-expand-md .navbar-nav .nav-link {\n padding-right: 0.5rem;\n padding-left: 0.5rem;\n }\n .navbar-expand-md > .container,\n .navbar-expand-md > .container-fluid, .navbar-expand-md > .container-sm, .navbar-expand-md > .container-md, .navbar-expand-md > .container-lg, .navbar-expand-md > .container-xl {\n flex-wrap: nowrap;\n }\n .navbar-expand-md .navbar-collapse {\n display: flex !important;\n flex-basis: auto;\n }\n .navbar-expand-md .navbar-toggler {\n display: none;\n }\n}\n\n@media (min-width: 992px) {\n .navbar-expand-lg {\n flex-flow: row nowrap;\n justify-content: flex-start;\n }\n .navbar-expand-lg .navbar-nav {\n flex-direction: row;\n }\n .navbar-expand-lg .navbar-nav .dropdown-menu {\n position: absolute;\n }\n .navbar-expand-lg .navbar-nav .nav-link {\n padding-right: 0.5rem;\n padding-left: 0.5rem;\n }\n .navbar-expand-lg > .container,\n .navbar-expand-lg > .container-fluid, .navbar-expand-lg > .container-sm, .navbar-expand-lg > .container-md, .navbar-expand-lg > .container-lg, .navbar-expand-lg > .container-xl {\n flex-wrap: nowrap;\n }\n .navbar-expand-lg .navbar-collapse {\n display: flex !important;\n flex-basis: auto;\n }\n .navbar-expand-lg .navbar-toggler {\n display: none;\n }\n}\n\n@media (min-width: 1200px) {\n .navbar-expand-xl {\n flex-flow: row nowrap;\n justify-content: flex-start;\n }\n .navbar-expand-xl .navbar-nav {\n flex-direction: row;\n }\n .navbar-expand-xl .navbar-nav .dropdown-menu {\n position: absolute;\n }\n .navbar-expand-xl .navbar-nav .nav-link {\n padding-right: 0.5rem;\n padding-left: 0.5rem;\n }\n .navbar-expand-xl > .container,\n .navbar-expand-xl > .container-fluid, .navbar-expand-xl > .container-sm, .navbar-expand-xl > .container-md, .navbar-expand-xl > .container-lg, .navbar-expand-xl > .container-xl {\n flex-wrap: nowrap;\n }\n .navbar-expand-xl .navbar-collapse {\n display: flex !important;\n flex-basis: auto;\n }\n .navbar-expand-xl .navbar-toggler {\n display: none;\n }\n}\n\n.navbar-expand {\n flex-flow: row nowrap;\n justify-content: flex-start;\n}\n\n.navbar-expand .navbar-nav {\n flex-direction: row;\n}\n\n.navbar-expand .navbar-nav .dropdown-menu {\n position: absolute;\n}\n\n.navbar-expand .navbar-nav .nav-link {\n padding-right: 0.5rem;\n padding-left: 0.5rem;\n}\n\n.navbar-expand > .container,\n.navbar-expand > .container-fluid, .navbar-expand > .container-sm, .navbar-expand > .container-md, .navbar-expand > .container-lg, .navbar-expand > .container-xl {\n flex-wrap: nowrap;\n}\n\n.navbar-expand .navbar-collapse {\n display: flex !important;\n flex-basis: auto;\n}\n\n.navbar-expand .navbar-toggler {\n display: none;\n}\n\n.navbar-light .navbar-brand {\n color: rgba(0, 0, 0, 0.9);\n}\n\n.navbar-light .navbar-brand:hover, .navbar-light .navbar-brand:focus {\n color: rgba(0, 0, 0, 0.9);\n}\n\n.navbar-light .navbar-nav .nav-link {\n color: rgba(0, 0, 0, 0.5);\n}\n\n.navbar-light .navbar-nav .nav-link:hover, .navbar-light .navbar-nav .nav-link:focus {\n color: rgba(0, 0, 0, 0.7);\n}\n\n.navbar-light .navbar-nav .nav-link.disabled {\n color: rgba(0, 0, 0, 0.3);\n}\n\n.navbar-light .navbar-nav .show > .nav-link,\n.navbar-light .navbar-nav .active > .nav-link,\n.navbar-light .navbar-nav .nav-link.show,\n.navbar-light .navbar-nav .nav-link.active {\n color: rgba(0, 0, 0, 0.9);\n}\n\n.navbar-light .navbar-toggler {\n color: rgba(0, 0, 0, 0.5);\n border-color: rgba(0, 0, 0, 0.1);\n}\n\n.navbar-light .navbar-toggler-icon {\n background-image: url(\"data:image\/svg+xml,%3csvg xmlns='http:\/\/www.w3.org\/2000\/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba(0, 0, 0, 0.5)' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'\/%3e%3c\/svg%3e\");\n}\n\n.navbar-light .navbar-text {\n color: rgba(0, 0, 0, 0.5);\n}\n\n.navbar-light .navbar-text a,\n.navbar-light .navbar-text a:hover,\n.navbar-light .navbar-text a:focus {\n color: rgba(0, 0, 0, 0.9);\n}\n\n.navbar-dark .navbar-brand {\n color: #fff;\n}\n\n.navbar-dark .navbar-brand:hover, .navbar-dark .navbar-brand:focus {\n color: #fff;\n}\n\n.navbar-dark .navbar-nav .nav-link {\n color: rgba(255, 255, 255, 0.5);\n}\n\n.navbar-dark .navbar-nav .nav-link:hover, .navbar-dark .navbar-nav .nav-link:focus {\n color: rgba(255, 255, 255, 0.75);\n}\n\n.navbar-dark .navbar-nav .nav-link.disabled {\n color: rgba(255, 255, 255, 0.25);\n}\n\n.navbar-dark .navbar-nav .show > .nav-link,\n.navbar-dark .navbar-nav .active > .nav-link,\n.navbar-dark .navbar-nav .nav-link.show,\n.navbar-dark .navbar-nav .nav-link.active {\n color: #fff;\n}\n\n.navbar-dark .navbar-toggler {\n color: rgba(255, 255, 255, 0.5);\n border-color: rgba(255, 255, 255, 0.1);\n}\n\n.navbar-dark .navbar-toggler-icon {\n background-image: url(\"data:image\/svg+xml,%3csvg xmlns='http:\/\/www.w3.org\/2000\/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba(255, 255, 255, 0.5)' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'\/%3e%3c\/svg%3e\");\n}\n\n.navbar-dark .navbar-text {\n color: rgba(255, 255, 255, 0.5);\n}\n\n.navbar-dark .navbar-text a,\n.navbar-dark .navbar-text a:hover,\n.navbar-dark .navbar-text a:focus {\n color: #fff;\n}\n\n.card {\n position: relative;\n display: flex;\n flex-direction: column;\n min-width: 0;\n word-wrap: break-word;\n background-color: #fff;\n background-clip: border-box;\n border: 1px solid rgba(0, 0, 0, 0.125);\n border-radius: 0.25rem;\n}\n\n.card > hr {\n margin-right: 0;\n margin-left: 0;\n}\n\n.card > .list-group:first-child .list-group-item:first-child {\n border-top-left-radius: 0.25rem;\n border-top-right-radius: 0.25rem;\n}\n\n.card > .list-group:last-child .list-group-item:last-child {\n border-bottom-right-radius: 0.25rem;\n border-bottom-left-radius: 0.25rem;\n}\n\n.card-body {\n flex: 1 1 auto;\n min-height: 1px;\n padding: 1.25rem;\n}\n\n.card-title {\n margin-bottom: 0.75rem;\n}\n\n.card-subtitle {\n margin-top: -0.375rem;\n margin-bottom: 0;\n}\n\n.card-text:last-child {\n margin-bottom: 0;\n}\n\n.card-link:hover {\n text-decoration: none;\n}\n\n.card-link + .card-link {\n margin-left: 1.25rem;\n}\n\n.card-header {\n padding: 0.75rem 1.25rem;\n margin-bottom: 0;\n background-color: rgba(0, 0, 0, 0.03);\n border-bottom: 1px solid rgba(0, 0, 0, 0.125);\n}\n\n.card-header:first-child {\n border-radius: calc(0.25rem - 1px) calc(0.25rem - 1px) 0 0;\n}\n\n.card-header + .list-group .list-group-item:first-child {\n border-top: 0;\n}\n\n.card-footer {\n padding: 0.75rem 1.25rem;\n background-color: rgba(0, 0, 0, 0.03);\n border-top: 1px solid rgba(0, 0, 0, 0.125);\n}\n\n.card-footer:last-child {\n border-radius: 0 0 calc(0.25rem - 1px) calc(0.25rem - 1px);\n}\n\n.card-header-tabs {\n margin-right: -0.625rem;\n margin-bottom: -0.75rem;\n margin-left: -0.625rem;\n border-bottom: 0;\n}\n\n.card-header-pills {\n margin-right: -0.625rem;\n margin-left: -0.625rem;\n}\n\n.card-img-overlay {\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n padding: 1.25rem;\n}\n\n.card-img,\n.card-img-top,\n.card-img-bottom {\n flex-shrink: 0;\n width: 100%;\n}\n\n.card-img,\n.card-img-top {\n border-top-left-radius: calc(0.25rem - 1px);\n border-top-right-radius: calc(0.25rem - 1px);\n}\n\n.card-img,\n.card-img-bottom {\n border-bottom-right-radius: calc(0.25rem - 1px);\n border-bottom-left-radius: calc(0.25rem - 1px);\n}\n\n.card-deck .card {\n margin-bottom: 15px;\n}\n\n@media (min-width: 576px) {\n .card-deck {\n display: flex;\n flex-flow: row wrap;\n margin-right: -15px;\n margin-left: -15px;\n }\n .card-deck .card {\n flex: 1 0 0%;\n margin-right: 15px;\n margin-bottom: 0;\n margin-left: 15px;\n }\n}\n\n.card-group > .card {\n margin-bottom: 15px;\n}\n\n@media (min-width: 576px) {\n .card-group {\n display: flex;\n flex-flow: row wrap;\n }\n .card-group > .card {\n flex: 1 0 0%;\n margin-bottom: 0;\n }\n .card-group > .card + .card {\n margin-left: 0;\n border-left: 0;\n }\n .card-group > .card:not(:last-child) {\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n }\n .card-group > .card:not(:last-child) .card-img-top,\n .card-group > .card:not(:last-child) .card-header {\n border-top-right-radius: 0;\n }\n .card-group > .card:not(:last-child) .card-img-bottom,\n .card-group > .card:not(:last-child) .card-footer {\n border-bottom-right-radius: 0;\n }\n .card-group > .card:not(:first-child) {\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n }\n .card-group > .card:not(:first-child) .card-img-top,\n .card-group > .card:not(:first-child) .card-header {\n border-top-left-radius: 0;\n }\n .card-group > .card:not(:first-child) .card-img-bottom,\n .card-group > .card:not(:first-child) .card-footer {\n border-bottom-left-radius: 0;\n }\n}\n\n.accordion > .card {\n overflow: hidden;\n}\n\n.accordion > .card:not(:last-of-type) {\n border-bottom: 0;\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 0;\n}\n\n.accordion > .card:not(:first-of-type) {\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n}\n\n.accordion > .card > .card-header {\n border-radius: 0;\n margin-bottom: -1px;\n}\n\n.breadcrumb {\n display: flex;\n flex-wrap: wrap;\n padding: 0.75rem 1rem;\n margin-bottom: 1rem;\n list-style: none;\n background-color: #e9ecef;\n border-radius: 0.25rem;\n}\n\n.breadcrumb-item + .breadcrumb-item {\n padding-left: 0.5rem;\n}\n\n.breadcrumb-item + .breadcrumb-item::before {\n display: inline-block;\n padding-right: 0.5rem;\n color: #6c757d;\n content: \"\/\";\n}\n\n.breadcrumb-item + .breadcrumb-item:hover::before {\n text-decoration: underline;\n}\n\n.breadcrumb-item + .breadcrumb-item:hover::before {\n text-decoration: none;\n}\n\n.breadcrumb-item.active {\n color: #6c757d;\n}\n\n.pagination {\n display: flex;\n padding-left: 0;\n list-style: none;\n}\n\n.page-link {\n position: relative;\n display: block;\n color: #0d6efd;\n background-color: #fff;\n border: 1px solid #dee2e6;\n}\n\n.page-link:hover {\n z-index: 2;\n color: #024dbc;\n text-decoration: none;\n background-color: #e9ecef;\n border-color: #dee2e6;\n}\n\n.page-link:focus {\n z-index: 3;\n outline: 0;\n box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25);\n}\n\n.page-item:not(:first-child) .page-link {\n margin-left: -1px;\n}\n\n.page-item.active .page-link {\n z-index: 3;\n color: #fff;\n background-color: #0d6efd;\n border-color: #0d6efd;\n}\n\n.page-item.disabled .page-link {\n color: #6c757d;\n pointer-events: none;\n cursor: auto;\n background-color: #fff;\n border-color: #dee2e6;\n}\n\n.page-link {\n padding: 0.375rem 0.75rem;\n}\n\n.page-item:first-child .page-link {\n border-top-left-radius: 0.25rem;\n border-bottom-left-radius: 0.25rem;\n}\n\n.page-item:last-child .page-link {\n border-top-right-radius: 0.25rem;\n border-bottom-right-radius: 0.25rem;\n}\n\n.pagination-lg .page-link {\n padding: 0.75rem 1.5rem;\n font-size: 1.25rem;\n}\n\n.pagination-lg .page-item:first-child .page-link {\n border-top-left-radius: 0.3rem;\n border-bottom-left-radius: 0.3rem;\n}\n\n.pagination-lg .page-item:last-child .page-link {\n border-top-right-radius: 0.3rem;\n border-bottom-right-radius: 0.3rem;\n}\n\n.pagination-sm .page-link {\n padding: 0.25rem 0.5rem;\n font-size: 0.875rem;\n}\n\n.pagination-sm .page-item:first-child .page-link {\n border-top-left-radius: 0.2rem;\n border-bottom-left-radius: 0.2rem;\n}\n\n.pagination-sm .page-item:last-child .page-link {\n border-top-right-radius: 0.2rem;\n border-bottom-right-radius: 0.2rem;\n}\n\n.badge {\n display: inline-block;\n padding: 0.25em 0.5em;\n font-size: 0.75em;\n font-weight: 700;\n line-height: 1;\n color: #fff;\n text-align: center;\n white-space: nowrap;\n vertical-align: baseline;\n border-radius: 0.25rem;\n}\n\n.badge:empty {\n display: none;\n}\n\n.btn .badge {\n position: relative;\n top: -1px;\n}\n\n.alert {\n position: relative;\n padding: 0.75rem 1.25rem;\n margin-bottom: 1rem;\n border: 1px solid transparent;\n border-radius: 0.25rem;\n}\n\n.alert-heading {\n color: inherit;\n}\n\n.alert-link {\n font-weight: 700;\n}\n\n.alert-dismissible {\n padding-right: 4rem;\n}\n\n.alert-dismissible .close {\n position: absolute;\n top: 0;\n right: 0;\n padding: 0.75rem 1.25rem;\n color: inherit;\n}\n\n.alert-primary {\n color: #073984;\n background-color: #cfe2ff;\n border-color: #bbd6fe;\n}\n\n.alert-primary .alert-link {\n color: #042454;\n}\n\n.alert-secondary {\n color: #383d41;\n background-color: #e2e3e5;\n border-color: #d6d8db;\n}\n\n.alert-secondary .alert-link {\n color: #202326;\n}\n\n.alert-success {\n color: #155724;\n background-color: #d4edda;\n border-color: #c3e6cb;\n}\n\n.alert-success .alert-link {\n color: #0b2e13;\n}\n\n.alert-info {\n color: #0c5460;\n background-color: #d1ecf1;\n border-color: #bee5eb;\n}\n\n.alert-info .alert-link {\n color: #062c33;\n}\n\n.alert-warning {\n color: #856404;\n background-color: #fff3cd;\n border-color: #ffeeba;\n}\n\n.alert-warning .alert-link {\n color: #533f03;\n}\n\n.alert-danger {\n color: #721c24;\n background-color: #f8d7da;\n border-color: #f5c6cb;\n}\n\n.alert-danger .alert-link {\n color: #491217;\n}\n\n.alert-light {\n color: #818182;\n background-color: #fefefe;\n border-color: #fdfdfe;\n}\n\n.alert-light .alert-link {\n color: #686868;\n}\n\n.alert-dark {\n color: #1b1e21;\n background-color: #d6d8d9;\n border-color: #c6c8ca;\n}\n\n.alert-dark .alert-link {\n color: #040505;\n}\n\n@-webkit-keyframes progress-bar-stripes {\n 0% {\n background-position-x: 1rem;\n }\n}\n\n@keyframes progress-bar-stripes {\n 0% {\n background-position-x: 1rem;\n }\n}\n\n.progress {\n display: flex;\n height: 1rem;\n overflow: hidden;\n font-size: 0.75rem;\n background-color: #e9ecef;\n border-radius: 0.25rem;\n}\n\n.progress-bar {\n display: flex;\n flex-direction: column;\n justify-content: center;\n overflow: hidden;\n color: #fff;\n text-align: center;\n white-space: nowrap;\n background-color: #0d6efd;\n transition: width 0.6s ease;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .progress-bar {\n transition: none;\n }\n}\n\n.progress-bar-striped {\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-size: 1rem 1rem;\n}\n\n.progress-bar-animated {\n -webkit-animation: progress-bar-stripes 1s linear infinite;\n animation: progress-bar-stripes 1s linear infinite;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .progress-bar-animated {\n -webkit-animation: none;\n animation: none;\n }\n}\n\n.list-group {\n display: flex;\n flex-direction: column;\n padding-left: 0;\n margin-bottom: 0;\n}\n\n.list-group-item-action {\n width: 100%;\n color: #495057;\n text-align: inherit;\n}\n\n.list-group-item-action:hover, .list-group-item-action:focus {\n z-index: 1;\n color: #495057;\n text-decoration: none;\n background-color: #f8f9fa;\n}\n\n.list-group-item-action:active {\n color: #212529;\n background-color: #e9ecef;\n}\n\n.list-group-item {\n position: relative;\n display: block;\n padding: 0.75rem 1.25rem;\n margin-bottom: 0;\n background-color: #fff;\n border: 1px solid rgba(0, 0, 0, 0.125);\n}\n\n.list-group-item:first-child {\n border-top-left-radius: 0.25rem;\n border-top-right-radius: 0.25rem;\n}\n\n.list-group-item:last-child {\n border-bottom-right-radius: 0.25rem;\n border-bottom-left-radius: 0.25rem;\n}\n\n.list-group-item.disabled, .list-group-item:disabled {\n color: #6c757d;\n pointer-events: none;\n background-color: #fff;\n}\n\n.list-group-item.active {\n z-index: 2;\n color: #fff;\n background-color: #0d6efd;\n border-color: #0d6efd;\n}\n\n.list-group-item + .list-group-item {\n border-top-width: 0;\n}\n\n.list-group-item + .list-group-item.active {\n margin-top: -1px;\n border-top-width: 1px;\n}\n\n.list-group-horizontal {\n flex-direction: row;\n}\n\n.list-group-horizontal .list-group-item:first-child {\n border-bottom-left-radius: 0.25rem;\n border-top-right-radius: 0;\n}\n\n.list-group-horizontal .list-group-item:last-child {\n border-top-right-radius: 0.25rem;\n border-bottom-left-radius: 0;\n}\n\n.list-group-horizontal .list-group-item.active {\n margin-top: 0;\n}\n\n.list-group-horizontal .list-group-item + .list-group-item {\n border-top-width: 1px;\n border-left-width: 0;\n}\n\n.list-group-horizontal .list-group-item + .list-group-item.active {\n margin-left: -1px;\n border-left-width: 1px;\n}\n\n@media (min-width: 576px) {\n .list-group-horizontal-sm {\n flex-direction: row;\n }\n .list-group-horizontal-sm .list-group-item:first-child {\n border-bottom-left-radius: 0.25rem;\n border-top-right-radius: 0;\n }\n .list-group-horizontal-sm .list-group-item:last-child {\n border-top-right-radius: 0.25rem;\n border-bottom-left-radius: 0;\n }\n .list-group-horizontal-sm .list-group-item.active {\n margin-top: 0;\n }\n .list-group-horizontal-sm .list-group-item + .list-group-item {\n border-top-width: 1px;\n border-left-width: 0;\n }\n .list-group-horizontal-sm .list-group-item + .list-group-item.active {\n margin-left: -1px;\n border-left-width: 1px;\n }\n}\n\n@media (min-width: 768px) {\n .list-group-horizontal-md {\n flex-direction: row;\n }\n .list-group-horizontal-md .list-group-item:first-child {\n border-bottom-left-radius: 0.25rem;\n border-top-right-radius: 0;\n }\n .list-group-horizontal-md .list-group-item:last-child {\n border-top-right-radius: 0.25rem;\n border-bottom-left-radius: 0;\n }\n .list-group-horizontal-md .list-group-item.active {\n margin-top: 0;\n }\n .list-group-horizontal-md .list-group-item + .list-group-item {\n border-top-width: 1px;\n border-left-width: 0;\n }\n .list-group-horizontal-md .list-group-item + .list-group-item.active {\n margin-left: -1px;\n border-left-width: 1px;\n }\n}\n\n@media (min-width: 992px) {\n .list-group-horizontal-lg {\n flex-direction: row;\n }\n .list-group-horizontal-lg .list-group-item:first-child {\n border-bottom-left-radius: 0.25rem;\n border-top-right-radius: 0;\n }\n .list-group-horizontal-lg .list-group-item:last-child {\n border-top-right-radius: 0.25rem;\n border-bottom-left-radius: 0;\n }\n .list-group-horizontal-lg .list-group-item.active {\n margin-top: 0;\n }\n .list-group-horizontal-lg .list-group-item + .list-group-item {\n border-top-width: 1px;\n border-left-width: 0;\n }\n .list-group-horizontal-lg .list-group-item + .list-group-item.active {\n margin-left: -1px;\n border-left-width: 1px;\n }\n}\n\n@media (min-width: 1200px) {\n .list-group-horizontal-xl {\n flex-direction: row;\n }\n .list-group-horizontal-xl .list-group-item:first-child {\n border-bottom-left-radius: 0.25rem;\n border-top-right-radius: 0;\n }\n .list-group-horizontal-xl .list-group-item:last-child {\n border-top-right-radius: 0.25rem;\n border-bottom-left-radius: 0;\n }\n .list-group-horizontal-xl .list-group-item.active {\n margin-top: 0;\n }\n .list-group-horizontal-xl .list-group-item + .list-group-item {\n border-top-width: 1px;\n border-left-width: 0;\n }\n .list-group-horizontal-xl .list-group-item + .list-group-item.active {\n margin-left: -1px;\n border-left-width: 1px;\n }\n}\n\n.list-group-flush .list-group-item {\n border-right-width: 0;\n border-left-width: 0;\n border-radius: 0;\n}\n\n.list-group-flush .list-group-item:first-child {\n border-top-width: 0;\n}\n\n.list-group-flush:last-child .list-group-item:last-child {\n border-bottom-width: 0;\n}\n\n.list-group-item-primary {\n color: #073984;\n background-color: #bbd6fe;\n}\n\n.list-group-item-primary.list-group-item-action:hover, .list-group-item-primary.list-group-item-action:focus {\n color: #073984;\n background-color: #a2c7fe;\n}\n\n.list-group-item-primary.list-group-item-action.active {\n color: #fff;\n background-color: #073984;\n border-color: #073984;\n}\n\n.list-group-item-secondary {\n color: #383d41;\n background-color: #d6d8db;\n}\n\n.list-group-item-secondary.list-group-item-action:hover, .list-group-item-secondary.list-group-item-action:focus {\n color: #383d41;\n background-color: #c8cbcf;\n}\n\n.list-group-item-secondary.list-group-item-action.active {\n color: #fff;\n background-color: #383d41;\n border-color: #383d41;\n}\n\n.list-group-item-success {\n color: #155724;\n background-color: #c3e6cb;\n}\n\n.list-group-item-success.list-group-item-action:hover, .list-group-item-success.list-group-item-action:focus {\n color: #155724;\n background-color: #b1dfbb;\n}\n\n.list-group-item-success.list-group-item-action.active {\n color: #fff;\n background-color: #155724;\n border-color: #155724;\n}\n\n.list-group-item-info {\n color: #0c5460;\n background-color: #bee5eb;\n}\n\n.list-group-item-info.list-group-item-action:hover, .list-group-item-info.list-group-item-action:focus {\n color: #0c5460;\n background-color: #abdde5;\n}\n\n.list-group-item-info.list-group-item-action.active {\n color: #fff;\n background-color: #0c5460;\n border-color: #0c5460;\n}\n\n.list-group-item-warning {\n color: #856404;\n background-color: #ffeeba;\n}\n\n.list-group-item-warning.list-group-item-action:hover, .list-group-item-warning.list-group-item-action:focus {\n color: #856404;\n background-color: #ffe8a1;\n}\n\n.list-group-item-warning.list-group-item-action.active {\n color: #fff;\n background-color: #856404;\n border-color: #856404;\n}\n\n.list-group-item-danger {\n color: #721c24;\n background-color: #f5c6cb;\n}\n\n.list-group-item-danger.list-group-item-action:hover, .list-group-item-danger.list-group-item-action:focus {\n color: #721c24;\n background-color: #f1b0b7;\n}\n\n.list-group-item-danger.list-group-item-action.active {\n color: #fff;\n background-color: #721c24;\n border-color: #721c24;\n}\n\n.list-group-item-light {\n color: #818182;\n background-color: #fdfdfe;\n}\n\n.list-group-item-light.list-group-item-action:hover, .list-group-item-light.list-group-item-action:focus {\n color: #818182;\n background-color: #ececf6;\n}\n\n.list-group-item-light.list-group-item-action.active {\n color: #fff;\n background-color: #818182;\n border-color: #818182;\n}\n\n.list-group-item-dark {\n color: #1b1e21;\n background-color: #c6c8ca;\n}\n\n.list-group-item-dark.list-group-item-action:hover, .list-group-item-dark.list-group-item-action:focus {\n color: #1b1e21;\n background-color: #b9bbbe;\n}\n\n.list-group-item-dark.list-group-item-action.active {\n color: #fff;\n background-color: #1b1e21;\n border-color: #1b1e21;\n}\n\n.close {\n font-size: 1.5rem;\n font-weight: 700;\n line-height: 1;\n color: #000;\n text-shadow: 0 1px 0 #fff;\n opacity: .5;\n}\n\n.close:hover {\n color: #000;\n text-decoration: none;\n}\n\n.close:not(:disabled):not(.disabled):hover, .close:not(:disabled):not(.disabled):focus {\n opacity: .75;\n}\n\nbutton.close {\n padding: 0;\n background-color: transparent;\n border: 0;\n -webkit-appearance: none;\n -moz-appearance: none;\n appearance: none;\n}\n\na.close.disabled {\n pointer-events: none;\n}\n\n.toast {\n max-width: 350px;\n overflow: hidden;\n font-size: 0.875rem;\n background-color: rgba(255, 255, 255, 0.85);\n background-clip: padding-box;\n border: 1px solid rgba(0, 0, 0, 0.1);\n box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);\n -webkit-backdrop-filter: blur(10px);\n backdrop-filter: blur(10px);\n opacity: 0;\n border-radius: 0.25rem;\n}\n\n.toast:not(:last-child) {\n margin-bottom: 0.75rem;\n}\n\n.toast.showing {\n opacity: 1;\n}\n\n.toast.show {\n display: block;\n opacity: 1;\n}\n\n.toast.hide {\n display: none;\n}\n\n.toast-header {\n display: flex;\n align-items: center;\n padding: 0.25rem 0.75rem;\n color: #6c757d;\n background-color: rgba(255, 255, 255, 0.85);\n background-clip: padding-box;\n border-bottom: 1px solid rgba(0, 0, 0, 0.05);\n}\n\n.toast-body {\n padding: 0.75rem;\n}\n\n.modal-open {\n overflow: hidden;\n}\n\n.modal-open .modal {\n overflow-x: hidden;\n overflow-y: auto;\n}\n\n.modal {\n position: fixed;\n top: 0;\n left: 0;\n z-index: 1050;\n display: none;\n width: 100%;\n height: 100%;\n overflow: hidden;\n outline: 0;\n}\n\n.modal-dialog {\n position: relative;\n width: auto;\n margin: 0.5rem;\n pointer-events: none;\n}\n\n.modal.fade .modal-dialog {\n transition: transform 0.3s ease-out;\n transform: translate(0, -50px);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .modal.fade .modal-dialog {\n transition: none;\n }\n}\n\n.modal.show .modal-dialog {\n transform: none;\n}\n\n.modal.modal-static .modal-dialog {\n transform: scale(1.02);\n}\n\n.modal-dialog-scrollable {\n display: flex;\n max-height: calc(100% - 1rem);\n}\n\n.modal-dialog-scrollable .modal-content {\n max-height: calc(100vh - 1rem);\n overflow: hidden;\n}\n\n.modal-dialog-scrollable .modal-header,\n.modal-dialog-scrollable .modal-footer {\n flex-shrink: 0;\n}\n\n.modal-dialog-scrollable .modal-body {\n overflow-y: auto;\n}\n\n.modal-dialog-centered {\n display: flex;\n align-items: center;\n min-height: calc(100% - 1rem);\n}\n\n.modal-dialog-centered::before {\n display: block;\n height: calc(100vh - 1rem);\n content: \"\";\n}\n\n.modal-dialog-centered.modal-dialog-scrollable {\n flex-direction: column;\n justify-content: center;\n height: 100%;\n}\n\n.modal-dialog-centered.modal-dialog-scrollable .modal-content {\n max-height: none;\n}\n\n.modal-dialog-centered.modal-dialog-scrollable::before {\n content: none;\n}\n\n.modal-content {\n position: relative;\n display: flex;\n flex-direction: column;\n width: 100%;\n pointer-events: auto;\n background-color: #fff;\n background-clip: padding-box;\n border: 1px solid rgba(0, 0, 0, 0.2);\n border-radius: 0.3rem;\n outline: 0;\n}\n\n.modal-backdrop {\n position: fixed;\n top: 0;\n left: 0;\n z-index: 1040;\n width: 100vw;\n height: 100vh;\n background-color: #000;\n}\n\n.modal-backdrop.fade {\n opacity: 0;\n}\n\n.modal-backdrop.show {\n opacity: 0.5;\n}\n\n.modal-header {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n padding: 1rem 1rem;\n border-bottom: 1px solid #dee2e6;\n border-top-left-radius: calc(0.3rem - 1px);\n border-top-right-radius: calc(0.3rem - 1px);\n}\n\n.modal-header .close {\n padding: 1rem 1rem;\n margin: -1rem -1rem -1rem auto;\n}\n\n.modal-title {\n margin-bottom: 0;\n line-height: 1.5;\n}\n\n.modal-body {\n position: relative;\n flex: 1 1 auto;\n padding: 1rem;\n}\n\n.modal-footer {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n justify-content: flex-end;\n padding: 0.75rem;\n border-top: 1px solid #dee2e6;\n border-bottom-right-radius: calc(0.3rem - 1px);\n border-bottom-left-radius: calc(0.3rem - 1px);\n}\n\n.modal-footer > * {\n margin: 0.25rem;\n}\n\n.modal-scrollbar-measure {\n position: absolute;\n top: -9999px;\n width: 50px;\n height: 50px;\n overflow: scroll;\n}\n\n@media (min-width: 576px) {\n .modal-dialog {\n max-width: 500px;\n margin: 1.75rem auto;\n }\n .modal-dialog-scrollable {\n max-height: calc(100% - 3.5rem);\n }\n .modal-dialog-scrollable .modal-content {\n max-height: calc(100vh - 3.5rem);\n }\n .modal-dialog-centered {\n min-height: calc(100% - 3.5rem);\n }\n .modal-dialog-centered::before {\n height: calc(100vh - 3.5rem);\n }\n .modal-sm {\n max-width: 300px;\n }\n}\n\n@media (min-width: 992px) {\n .modal-lg,\n .modal-xl {\n max-width: 800px;\n }\n}\n\n@media (min-width: 1200px) {\n .modal-xl {\n max-width: 1140px;\n }\n}\n\n.tooltip {\n position: absolute;\n z-index: 1070;\n display: block;\n margin: 0;\n font-family: -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\";\n font-style: normal;\n font-weight: 400;\n line-height: 1.5;\n text-align: left;\n text-align: start;\n text-decoration: none;\n text-shadow: none;\n text-transform: none;\n letter-spacing: normal;\n word-break: normal;\n word-spacing: normal;\n white-space: normal;\n line-break: auto;\n font-size: 0.875rem;\n word-wrap: break-word;\n opacity: 0;\n}\n\n.tooltip.show {\n opacity: 0.9;\n}\n\n.tooltip .tooltip-arrow {\n position: absolute;\n display: block;\n width: 0.8rem;\n height: 0.4rem;\n}\n\n.tooltip .tooltip-arrow::before {\n position: absolute;\n content: \"\";\n border-color: transparent;\n border-style: solid;\n}\n\n.bs-tooltip-top, .bs-tooltip-auto[x-placement^=\"top\"] {\n padding: 0.4rem 0;\n}\n\n.bs-tooltip-top .tooltip-arrow, .bs-tooltip-auto[x-placement^=\"top\"] .tooltip-arrow {\n bottom: 0;\n}\n\n.bs-tooltip-top .tooltip-arrow::before, .bs-tooltip-auto[x-placement^=\"top\"] .tooltip-arrow::before {\n top: 0;\n border-width: 0.4rem 0.4rem 0;\n border-top-color: #000;\n}\n\n.bs-tooltip-right, .bs-tooltip-auto[x-placement^=\"right\"] {\n padding: 0 0.4rem;\n}\n\n.bs-tooltip-right .tooltip-arrow, .bs-tooltip-auto[x-placement^=\"right\"] .tooltip-arrow {\n left: 0;\n width: 0.4rem;\n height: 0.8rem;\n}\n\n.bs-tooltip-right .tooltip-arrow::before, .bs-tooltip-auto[x-placement^=\"right\"] .tooltip-arrow::before {\n right: 0;\n border-width: 0.4rem 0.4rem 0.4rem 0;\n border-right-color: #000;\n}\n\n.bs-tooltip-bottom, .bs-tooltip-auto[x-placement^=\"bottom\"] {\n padding: 0.4rem 0;\n}\n\n.bs-tooltip-bottom .tooltip-arrow, .bs-tooltip-auto[x-placement^=\"bottom\"] .tooltip-arrow {\n top: 0;\n}\n\n.bs-tooltip-bottom .tooltip-arrow::before, .bs-tooltip-auto[x-placement^=\"bottom\"] .tooltip-arrow::before {\n bottom: 0;\n border-width: 0 0.4rem 0.4rem;\n border-bottom-color: #000;\n}\n\n.bs-tooltip-left, .bs-tooltip-auto[x-placement^=\"left\"] {\n padding: 0 0.4rem;\n}\n\n.bs-tooltip-left .tooltip-arrow, .bs-tooltip-auto[x-placement^=\"left\"] .tooltip-arrow {\n right: 0;\n width: 0.4rem;\n height: 0.8rem;\n}\n\n.bs-tooltip-left .tooltip-arrow::before, .bs-tooltip-auto[x-placement^=\"left\"] .tooltip-arrow::before {\n left: 0;\n border-width: 0.4rem 0 0.4rem 0.4rem;\n border-left-color: #000;\n}\n\n.tooltip-inner {\n max-width: 200px;\n padding: 0.25rem 0.5rem;\n color: #fff;\n text-align: center;\n background-color: #000;\n border-radius: 0.25rem;\n}\n\n.popover {\n position: absolute;\n top: 0;\n left: 0;\n z-index: 1060;\n display: block;\n max-width: 276px;\n font-family: -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\";\n font-style: normal;\n font-weight: 400;\n line-height: 1.5;\n text-align: left;\n text-align: start;\n text-decoration: none;\n text-shadow: none;\n text-transform: none;\n letter-spacing: normal;\n word-break: normal;\n word-spacing: normal;\n white-space: normal;\n line-break: auto;\n font-size: 0.875rem;\n word-wrap: break-word;\n background-color: #fff;\n background-clip: padding-box;\n border: 1px solid rgba(0, 0, 0, 0.2);\n border-radius: 0.3rem;\n}\n\n.popover .popover-arrow {\n position: absolute;\n display: block;\n width: 1rem;\n height: 0.5rem;\n margin: 0 0.3rem;\n}\n\n.popover .popover-arrow::before, .popover .popover-arrow::after {\n position: absolute;\n display: block;\n content: \"\";\n border-color: transparent;\n border-style: solid;\n}\n\n.bs-popover-top, .bs-popover-auto[x-placement^=\"top\"] {\n margin-bottom: 0.5rem;\n}\n\n.bs-popover-top > .popover-arrow, .bs-popover-auto[x-placement^=\"top\"] > .popover-arrow {\n bottom: calc(-0.5rem - 1px);\n}\n\n.bs-popover-top > .popover-arrow::before, .bs-popover-auto[x-placement^=\"top\"] > .popover-arrow::before {\n bottom: 0;\n border-width: 0.5rem 0.5rem 0;\n border-top-color: rgba(0, 0, 0, 0.25);\n}\n\n.bs-popover-top > .popover-arrow::after, .bs-popover-auto[x-placement^=\"top\"] > .popover-arrow::after {\n bottom: 1px;\n border-width: 0.5rem 0.5rem 0;\n border-top-color: #fff;\n}\n\n.bs-popover-right, .bs-popover-auto[x-placement^=\"right\"] {\n margin-left: 0.5rem;\n}\n\n.bs-popover-right > .popover-arrow, .bs-popover-auto[x-placement^=\"right\"] > .popover-arrow {\n left: calc(-0.5rem - 1px);\n width: 0.5rem;\n height: 1rem;\n margin: 0.3rem 0;\n}\n\n.bs-popover-right > .popover-arrow::before, .bs-popover-auto[x-placement^=\"right\"] > .popover-arrow::before {\n left: 0;\n border-width: 0.5rem 0.5rem 0.5rem 0;\n border-right-color: rgba(0, 0, 0, 0.25);\n}\n\n.bs-popover-right > .popover-arrow::after, .bs-popover-auto[x-placement^=\"right\"] > .popover-arrow::after {\n left: 1px;\n border-width: 0.5rem 0.5rem 0.5rem 0;\n border-right-color: #fff;\n}\n\n.bs-popover-bottom, .bs-popover-auto[x-placement^=\"bottom\"] {\n margin-top: 0.5rem;\n}\n\n.bs-popover-bottom > .popover-arrow, .bs-popover-auto[x-placement^=\"bottom\"] > .popover-arrow {\n top: calc(-0.5rem - 1px);\n}\n\n.bs-popover-bottom > .popover-arrow::before, .bs-popover-auto[x-placement^=\"bottom\"] > .popover-arrow::before {\n top: 0;\n border-width: 0 0.5rem 0.5rem 0.5rem;\n border-bottom-color: rgba(0, 0, 0, 0.25);\n}\n\n.bs-popover-bottom > .popover-arrow::after, .bs-popover-auto[x-placement^=\"bottom\"] > .popover-arrow::after {\n top: 1px;\n border-width: 0 0.5rem 0.5rem 0.5rem;\n border-bottom-color: #fff;\n}\n\n.bs-popover-bottom .popover-header::before, .bs-popover-auto[x-placement^=\"bottom\"] .popover-header::before {\n position: absolute;\n top: 0;\n left: 50%;\n display: block;\n width: 1rem;\n margin-left: -0.5rem;\n content: \"\";\n border-bottom: 1px solid #f7f7f7;\n}\n\n.bs-popover-left, .bs-popover-auto[x-placement^=\"left\"] {\n margin-right: 0.5rem;\n}\n\n.bs-popover-left > .popover-arrow, .bs-popover-auto[x-placement^=\"left\"] > .popover-arrow {\n right: calc(-0.5rem - 1px);\n width: 0.5rem;\n height: 1rem;\n margin: 0.3rem 0;\n}\n\n.bs-popover-left > .popover-arrow::before, .bs-popover-auto[x-placement^=\"left\"] > .popover-arrow::before {\n right: 0;\n border-width: 0.5rem 0 0.5rem 0.5rem;\n border-left-color: rgba(0, 0, 0, 0.25);\n}\n\n.bs-popover-left > .popover-arrow::after, .bs-popover-auto[x-placement^=\"left\"] > .popover-arrow::after {\n right: 1px;\n border-width: 0.5rem 0 0.5rem 0.5rem;\n border-left-color: #fff;\n}\n\n.popover-header {\n padding: 0.5rem 0.75rem;\n margin-bottom: 0;\n font-size: 1rem;\n background-color: #f7f7f7;\n border-bottom: 1px solid #ebebeb;\n border-top-left-radius: calc(0.3rem - 1px);\n border-top-right-radius: calc(0.3rem - 1px);\n}\n\n.popover-header:empty {\n display: none;\n}\n\n.popover-body {\n padding: 0.5rem 0.75rem;\n color: #212529;\n}\n\n.carousel {\n position: relative;\n}\n\n.carousel.pointer-event {\n touch-action: pan-y;\n}\n\n.carousel-inner {\n position: relative;\n width: 100%;\n overflow: hidden;\n}\n\n.carousel-inner::after {\n display: block;\n clear: both;\n content: \"\";\n}\n\n.carousel-item {\n position: relative;\n display: none;\n float: left;\n width: 100%;\n margin-right: -100%;\n -webkit-backface-visibility: hidden;\n backface-visibility: hidden;\n transition: transform 0.6s ease-in-out;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .carousel-item {\n transition: none;\n }\n}\n\n.carousel-item.active,\n.carousel-item-next,\n.carousel-item-prev {\n display: block;\n}\n\n.carousel-item-next:not(.carousel-item-left),\n.active.carousel-item-right {\n transform: translateX(100%);\n}\n\n.carousel-item-prev:not(.carousel-item-right),\n.active.carousel-item-left {\n transform: translateX(-100%);\n}\n\n.carousel-fade .carousel-item {\n opacity: 0;\n transition-property: opacity;\n transform: none;\n}\n\n.carousel-fade .carousel-item.active,\n.carousel-fade .carousel-item-next.carousel-item-left,\n.carousel-fade .carousel-item-prev.carousel-item-right {\n z-index: 1;\n opacity: 1;\n}\n\n.carousel-fade .active.carousel-item-left,\n.carousel-fade .active.carousel-item-right {\n z-index: 0;\n opacity: 0;\n transition: opacity 0s 0.6s;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .carousel-fade .active.carousel-item-left,\n .carousel-fade .active.carousel-item-right {\n transition: none;\n }\n}\n\n.carousel-control-prev,\n.carousel-control-next {\n position: absolute;\n top: 0;\n bottom: 0;\n z-index: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 15%;\n color: #fff;\n text-align: center;\n opacity: 0.5;\n transition: opacity 0.15s ease;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .carousel-control-prev,\n .carousel-control-next {\n transition: none;\n }\n}\n\n.carousel-control-prev:hover, .carousel-control-prev:focus,\n.carousel-control-next:hover,\n.carousel-control-next:focus {\n color: #fff;\n text-decoration: none;\n outline: 0;\n opacity: 0.9;\n}\n\n.carousel-control-prev {\n left: 0;\n}\n\n.carousel-control-next {\n right: 0;\n}\n\n.carousel-control-prev-icon,\n.carousel-control-next-icon {\n display: inline-block;\n width: 20px;\n height: 20px;\n background: no-repeat 50% \/ 100% 100%;\n}\n\n.carousel-control-prev-icon {\n background-image: url(\"data:image\/svg+xml,%3csvg xmlns='http:\/\/www.w3.org\/2000\/svg' fill='%23fff' viewBox='0 0 8 8'%3e%3cpath d='M5.25 0l-4 4 4 4 1.5-1.5L4.25 4l2.5-2.5L5.25 0z'\/%3e%3c\/svg%3e\");\n}\n\n.carousel-control-next-icon {\n background-image: url(\"data:image\/svg+xml,%3csvg xmlns='http:\/\/www.w3.org\/2000\/svg' fill='%23fff' viewBox='0 0 8 8'%3e%3cpath d='M2.75 0l-1.5 1.5L3.75 4l-2.5 2.5L2.75 8l4-4-4-4z'\/%3e%3c\/svg%3e\");\n}\n\n.carousel-indicators {\n position: absolute;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: 2;\n display: flex;\n justify-content: center;\n padding-left: 0;\n margin-right: 15%;\n margin-left: 15%;\n list-style: none;\n}\n\n.carousel-indicators li {\n box-sizing: content-box;\n flex: 0 1 auto;\n width: 30px;\n height: 3px;\n margin-right: 3px;\n margin-left: 3px;\n text-indent: -999px;\n cursor: pointer;\n background-color: #fff;\n background-clip: padding-box;\n border-top: 10px solid transparent;\n border-bottom: 10px solid transparent;\n opacity: 0.5;\n transition: opacity 0.6s ease;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .carousel-indicators li {\n transition: none;\n }\n}\n\n.carousel-indicators .active {\n opacity: 1;\n}\n\n.carousel-caption {\n position: absolute;\n right: 15%;\n bottom: 1.25rem;\n left: 15%;\n padding-top: 1.25rem;\n padding-bottom: 1.25rem;\n color: #fff;\n text-align: center;\n}\n\n@-webkit-keyframes spinner-border {\n to {\n transform: rotate(360deg);\n }\n}\n\n@keyframes spinner-border {\n to {\n transform: rotate(360deg);\n }\n}\n\n.spinner-border {\n display: inline-block;\n width: 2rem;\n height: 2rem;\n vertical-align: text-bottom;\n border: 0.25em solid currentColor;\n border-right-color: transparent;\n border-radius: 50%;\n -webkit-animation: spinner-border .75s linear infinite;\n animation: spinner-border .75s linear infinite;\n}\n\n.spinner-border-sm {\n width: 1rem;\n height: 1rem;\n border-width: 0.2em;\n}\n\n@-webkit-keyframes spinner-grow {\n 0% {\n transform: scale(0);\n }\n 50% {\n opacity: 1;\n }\n}\n\n@keyframes spinner-grow {\n 0% {\n transform: scale(0);\n }\n 50% {\n opacity: 1;\n }\n}\n\n.spinner-grow {\n display: inline-block;\n width: 2rem;\n height: 2rem;\n vertical-align: text-bottom;\n background-color: currentColor;\n border-radius: 50%;\n opacity: 0;\n -webkit-animation: spinner-grow .75s linear infinite;\n animation: spinner-grow .75s linear infinite;\n}\n\n.spinner-grow-sm {\n width: 1rem;\n height: 1rem;\n}\n\n.clearfix::after {\n display: block;\n clear: both;\n content: \"\";\n}\n\n.link-primary {\n color: #0d6efd;\n}\n\n.link-primary:hover, .link-primary:focus {\n color: #024dbc;\n}\n\n.link-secondary {\n color: #6c757d;\n}\n\n.link-secondary:hover, .link-secondary:focus {\n color: #494f54;\n}\n\n.link-success {\n color: #28a745;\n}\n\n.link-success:hover, .link-success:focus {\n color: #19692c;\n}\n\n.link-info {\n color: #17a2b8;\n}\n\n.link-info:hover, .link-info:focus {\n color: #0f6674;\n}\n\n.link-warning {\n color: #ffc107;\n}\n\n.link-warning:hover, .link-warning:focus {\n color: #ba8b00;\n}\n\n.link-danger {\n color: #dc3545;\n}\n\n.link-danger:hover, .link-danger:focus {\n color: #a71d2a;\n}\n\n.link-light {\n color: #f8f9fa;\n}\n\n.link-light:hover, .link-light:focus {\n color: #cbd3da;\n}\n\n.link-dark {\n color: #343a40;\n}\n\n.link-dark:hover, .link-dark:focus {\n color: #121416;\n}\n\n.embed-responsive {\n position: relative;\n width: 100%;\n}\n\n.embed-responsive::before {\n display: block;\n content: \"\";\n}\n\n.embed-responsive .embed-responsive-item,\n.embed-responsive iframe,\n.embed-responsive embed,\n.embed-responsive object,\n.embed-responsive video {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n border: 0;\n}\n\n.embed-responsive-21by9::before {\n padding-top: 42.857143%;\n}\n\n.embed-responsive-16by9::before {\n padding-top: 56.25%;\n}\n\n.embed-responsive-4by3::before {\n padding-top: 75%;\n}\n\n.embed-responsive-1by1::before {\n padding-top: 100%;\n}\n\n.fixed-top {\n position: fixed;\n top: 0;\n right: 0;\n left: 0;\n z-index: 1030;\n}\n\n.fixed-bottom {\n position: fixed;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: 1030;\n}\n\n@supports ((position: -webkit-sticky) or (position: sticky)) {\n .sticky-top {\n position: -webkit-sticky;\n position: sticky;\n top: 0;\n z-index: 1020;\n }\n @media (min-width: 576px) {\n .sticky-sm-top {\n position: -webkit-sticky;\n position: sticky;\n top: 0;\n z-index: 1020;\n }\n }\n @media (min-width: 768px) {\n .sticky-md-top {\n position: -webkit-sticky;\n position: sticky;\n top: 0;\n z-index: 1020;\n }\n }\n @media (min-width: 992px) {\n .sticky-lg-top {\n position: -webkit-sticky;\n position: sticky;\n top: 0;\n z-index: 1020;\n }\n }\n @media (min-width: 1200px) {\n .sticky-xl-top {\n position: -webkit-sticky;\n position: sticky;\n top: 0;\n z-index: 1020;\n }\n }\n}\n\n.sr-only,\n.sr-only-focusable:not(:focus) {\n position: absolute !important;\n width: 1px !important;\n height: 1px !important;\n padding: 0 !important;\n margin: -1px !important;\n overflow: hidden !important;\n clip: rect(0, 0, 0, 0) !important;\n white-space: nowrap !important;\n border: 0 !important;\n}\n\n.stretched-link::after {\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: 1;\n content: \"\";\n}\n\n.text-truncate {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.align-baseline {\n vertical-align: baseline !important;\n}\n\n.align-top {\n vertical-align: top !important;\n}\n\n.align-middle {\n vertical-align: middle !important;\n}\n\n.align-bottom {\n vertical-align: bottom !important;\n}\n\n.align-text-bottom {\n vertical-align: text-bottom !important;\n}\n\n.align-text-top {\n vertical-align: text-top !important;\n}\n\n.float-left {\n float: left !important;\n}\n\n.float-right {\n float: right !important;\n}\n\n.float-none {\n float: none !important;\n}\n\n.overflow-auto {\n overflow: auto !important;\n}\n\n.overflow-hidden {\n overflow: hidden !important;\n}\n\n.d-none {\n display: none !important;\n}\n\n.d-inline {\n display: inline !important;\n}\n\n.d-inline-block {\n display: inline-block !important;\n}\n\n.d-block {\n display: block !important;\n}\n\n.d-table {\n display: table !important;\n}\n\n.d-table-row {\n display: table-row !important;\n}\n\n.d-table-cell {\n display: table-cell !important;\n}\n\n.d-flex {\n display: flex !important;\n}\n\n.d-inline-flex {\n display: inline-flex !important;\n}\n\n.shadow-sm {\n box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075) !important;\n}\n\n.shadow {\n box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15) !important;\n}\n\n.shadow-lg {\n box-shadow: 0 1rem 3rem rgba(0, 0, 0, 0.175) !important;\n}\n\n.shadow-none {\n box-shadow: none !important;\n}\n\n.position-static {\n position: static !important;\n}\n\n.position-relative {\n position: relative !important;\n}\n\n.position-absolute {\n position: absolute !important;\n}\n\n.position-fixed {\n position: fixed !important;\n}\n\n.position-sticky {\n position: -webkit-sticky !important;\n position: sticky !important;\n}\n\n.border {\n border: 1px solid #dee2e6 !important;\n}\n\n.border-0 {\n border: 0 !important;\n}\n\n.border-top {\n border-top: 1px solid #dee2e6 !important;\n}\n\n.border-top-0 {\n border-top: 0 !important;\n}\n\n.border-right {\n border-right: 1px solid #dee2e6 !important;\n}\n\n.border-right-0 {\n border-right: 0 !important;\n}\n\n.border-bottom {\n border-bottom: 1px solid #dee2e6 !important;\n}\n\n.border-bottom-0 {\n border-bottom: 0 !important;\n}\n\n.border-left {\n border-left: 1px solid #dee2e6 !important;\n}\n\n.border-left-0 {\n border-left: 0 !important;\n}\n\n.border-primary {\n border-color: #0d6efd !important;\n}\n\n.border-secondary {\n border-color: #6c757d !important;\n}\n\n.border-success {\n border-color: #28a745 !important;\n}\n\n.border-info {\n border-color: #17a2b8 !important;\n}\n\n.border-warning {\n border-color: #ffc107 !important;\n}\n\n.border-danger {\n border-color: #dc3545 !important;\n}\n\n.border-light {\n border-color: #f8f9fa !important;\n}\n\n.border-dark {\n border-color: #343a40 !important;\n}\n\n.border-white {\n border-color: #fff !important;\n}\n\n.w-25 {\n width: 25% !important;\n}\n\n.w-50 {\n width: 50% !important;\n}\n\n.w-75 {\n width: 75% !important;\n}\n\n.w-100 {\n width: 100% !important;\n}\n\n.w-auto {\n width: auto !important;\n}\n\n.mw-100 {\n max-width: 100% !important;\n}\n\n.vw-100 {\n width: 100vw !important;\n}\n\n.min-vw-100 {\n min-width: 100vw !important;\n}\n\n.h-25 {\n height: 25% !important;\n}\n\n.h-50 {\n height: 50% !important;\n}\n\n.h-75 {\n height: 75% !important;\n}\n\n.h-100 {\n height: 100% !important;\n}\n\n.h-auto {\n height: auto !important;\n}\n\n.mh-100 {\n max-height: 100% !important;\n}\n\n.vh-100 {\n height: 100vh !important;\n}\n\n.min-vh-100 {\n min-height: 100vh !important;\n}\n\n.flex-fill {\n flex: 1 1 auto !important;\n}\n\n.flex-row {\n flex-direction: row !important;\n}\n\n.flex-column {\n flex-direction: column !important;\n}\n\n.flex-row-reverse {\n flex-direction: row-reverse !important;\n}\n\n.flex-column-reverse {\n flex-direction: column-reverse !important;\n}\n\n.flex-grow-0 {\n flex-grow: 0 !important;\n}\n\n.flex-grow-1 {\n flex-grow: 1 !important;\n}\n\n.flex-shrink-0 {\n flex-shrink: 0 !important;\n}\n\n.flex-shrink-1 {\n flex-shrink: 1 !important;\n}\n\n.flex-wrap {\n flex-wrap: wrap !important;\n}\n\n.flex-nowrap {\n flex-wrap: nowrap !important;\n}\n\n.flex-wrap-reverse {\n flex-wrap: wrap-reverse !important;\n}\n\n.justify-content-start {\n justify-content: flex-start !important;\n}\n\n.justify-content-end {\n justify-content: flex-end !important;\n}\n\n.justify-content-center {\n justify-content: center !important;\n}\n\n.justify-content-between {\n justify-content: space-between !important;\n}\n\n.justify-content-around {\n justify-content: space-around !important;\n}\n\n.align-items-start {\n align-items: flex-start !important;\n}\n\n.align-items-end {\n align-items: flex-end !important;\n}\n\n.align-items-center {\n align-items: center !important;\n}\n\n.align-items-baseline {\n align-items: baseline !important;\n}\n\n.align-items-stretch {\n align-items: stretch !important;\n}\n\n.align-content-start {\n align-content: flex-start !important;\n}\n\n.align-content-end {\n align-content: flex-end !important;\n}\n\n.align-content-center {\n align-content: center !important;\n}\n\n.align-content-between {\n align-content: space-between !important;\n}\n\n.align-content-around {\n align-content: space-around !important;\n}\n\n.align-content-stretch {\n align-content: stretch !important;\n}\n\n.align-self-auto {\n align-self: auto !important;\n}\n\n.align-self-start {\n align-self: flex-start !important;\n}\n\n.align-self-end {\n align-self: flex-end !important;\n}\n\n.align-self-center {\n align-self: center !important;\n}\n\n.align-self-baseline {\n align-self: baseline !important;\n}\n\n.align-self-stretch {\n align-self: stretch !important;\n}\n\n.order-first {\n order: -1 !important;\n}\n\n.order-0 {\n order: 0 !important;\n}\n\n.order-1 {\n order: 1 !important;\n}\n\n.order-2 {\n order: 2 !important;\n}\n\n.order-3 {\n order: 3 !important;\n}\n\n.order-4 {\n order: 4 !important;\n}\n\n.order-5 {\n order: 5 !important;\n}\n\n.order-last {\n order: 6 !important;\n}\n\n.m-0 {\n margin: 0 !important;\n}\n\n.m-1 {\n margin: 0.25rem !important;\n}\n\n.m-2 {\n margin: 0.5rem !important;\n}\n\n.m-3 {\n margin: 1rem !important;\n}\n\n.m-4 {\n margin: 1.5rem !important;\n}\n\n.m-5 {\n margin: 3rem !important;\n}\n\n.m-auto {\n margin: auto !important;\n}\n\n.mx-0 {\n margin-right: 0 !important;\n margin-left: 0 !important;\n}\n\n.mx-1 {\n margin-right: 0.25rem !important;\n margin-left: 0.25rem !important;\n}\n\n.mx-2 {\n margin-right: 0.5rem !important;\n margin-left: 0.5rem !important;\n}\n\n.mx-3 {\n margin-right: 1rem !important;\n margin-left: 1rem !important;\n}\n\n.mx-4 {\n margin-right: 1.5rem !important;\n margin-left: 1.5rem !important;\n}\n\n.mx-5 {\n margin-right: 3rem !important;\n margin-left: 3rem !important;\n}\n\n.mx-auto {\n margin-right: auto !important;\n margin-left: auto !important;\n}\n\n.my-0 {\n margin-top: 0 !important;\n margin-bottom: 0 !important;\n}\n\n.my-1 {\n margin-top: 0.25rem !important;\n margin-bottom: 0.25rem !important;\n}\n\n.my-2 {\n margin-top: 0.5rem !important;\n margin-bottom: 0.5rem !important;\n}\n\n.my-3 {\n margin-top: 1rem !important;\n margin-bottom: 1rem !important;\n}\n\n.my-4 {\n margin-top: 1.5rem !important;\n margin-bottom: 1.5rem !important;\n}\n\n.my-5 {\n margin-top: 3rem !important;\n margin-bottom: 3rem !important;\n}\n\n.my-auto {\n margin-top: auto !important;\n margin-bottom: auto !important;\n}\n\n.mt-0 {\n margin-top: 0 !important;\n}\n\n.mt-1 {\n margin-top: 0.25rem !important;\n}\n\n.mt-2 {\n margin-top: 0.5rem !important;\n}\n\n.mt-3 {\n margin-top: 1rem !important;\n}\n\n.mt-4 {\n margin-top: 1.5rem !important;\n}\n\n.mt-5 {\n margin-top: 3rem !important;\n}\n\n.mt-auto {\n margin-top: auto !important;\n}\n\n.mr-0 {\n margin-right: 0 !important;\n}\n\n.mr-1 {\n margin-right: 0.25rem !important;\n}\n\n.mr-2 {\n margin-right: 0.5rem !important;\n}\n\n.mr-3 {\n margin-right: 1rem !important;\n}\n\n.mr-4 {\n margin-right: 1.5rem !important;\n}\n\n.mr-5 {\n margin-right: 3rem !important;\n}\n\n.mr-auto {\n margin-right: auto !important;\n}\n\n.mb-0 {\n margin-bottom: 0 !important;\n}\n\n.mb-1 {\n margin-bottom: 0.25rem !important;\n}\n\n.mb-2 {\n margin-bottom: 0.5rem !important;\n}\n\n.mb-3 {\n margin-bottom: 1rem !important;\n}\n\n.mb-4 {\n margin-bottom: 1.5rem !important;\n}\n\n.mb-5 {\n margin-bottom: 3rem !important;\n}\n\n.mb-auto {\n margin-bottom: auto !important;\n}\n\n.ml-0 {\n margin-left: 0 !important;\n}\n\n.ml-1 {\n margin-left: 0.25rem !important;\n}\n\n.ml-2 {\n margin-left: 0.5rem !important;\n}\n\n.ml-3 {\n margin-left: 1rem !important;\n}\n\n.ml-4 {\n margin-left: 1.5rem !important;\n}\n\n.ml-5 {\n margin-left: 3rem !important;\n}\n\n.ml-auto {\n margin-left: auto !important;\n}\n\n.m-n1 {\n margin: -0.25rem !important;\n}\n\n.m-n2 {\n margin: -0.5rem !important;\n}\n\n.m-n3 {\n margin: -1rem !important;\n}\n\n.m-n4 {\n margin: -1.5rem !important;\n}\n\n.m-n5 {\n margin: -3rem !important;\n}\n\n.mx-n1 {\n margin-right: -0.25rem !important;\n margin-left: -0.25rem !important;\n}\n\n.mx-n2 {\n margin-right: -0.5rem !important;\n margin-left: -0.5rem !important;\n}\n\n.mx-n3 {\n margin-right: -1rem !important;\n margin-left: -1rem !important;\n}\n\n.mx-n4 {\n margin-right: -1.5rem !important;\n margin-left: -1.5rem !important;\n}\n\n.mx-n5 {\n margin-right: -3rem !important;\n margin-left: -3rem !important;\n}\n\n.my-n1 {\n margin-top: -0.25rem !important;\n margin-bottom: -0.25rem !important;\n}\n\n.my-n2 {\n margin-top: -0.5rem !important;\n margin-bottom: -0.5rem !important;\n}\n\n.my-n3 {\n margin-top: -1rem !important;\n margin-bottom: -1rem !important;\n}\n\n.my-n4 {\n margin-top: -1.5rem !important;\n margin-bottom: -1.5rem !important;\n}\n\n.my-n5 {\n margin-top: -3rem !important;\n margin-bottom: -3rem !important;\n}\n\n.mt-n1 {\n margin-top: -0.25rem !important;\n}\n\n.mt-n2 {\n margin-top: -0.5rem !important;\n}\n\n.mt-n3 {\n margin-top: -1rem !important;\n}\n\n.mt-n4 {\n margin-top: -1.5rem !important;\n}\n\n.mt-n5 {\n margin-top: -3rem !important;\n}\n\n.mr-n1 {\n margin-right: -0.25rem !important;\n}\n\n.mr-n2 {\n margin-right: -0.5rem !important;\n}\n\n.mr-n3 {\n margin-right: -1rem !important;\n}\n\n.mr-n4 {\n margin-right: -1.5rem !important;\n}\n\n.mr-n5 {\n margin-right: -3rem !important;\n}\n\n.mb-n1 {\n margin-bottom: -0.25rem !important;\n}\n\n.mb-n2 {\n margin-bottom: -0.5rem !important;\n}\n\n.mb-n3 {\n margin-bottom: -1rem !important;\n}\n\n.mb-n4 {\n margin-bottom: -1.5rem !important;\n}\n\n.mb-n5 {\n margin-bottom: -3rem !important;\n}\n\n.ml-n1 {\n margin-left: -0.25rem !important;\n}\n\n.ml-n2 {\n margin-left: -0.5rem !important;\n}\n\n.ml-n3 {\n margin-left: -1rem !important;\n}\n\n.ml-n4 {\n margin-left: -1.5rem !important;\n}\n\n.ml-n5 {\n margin-left: -3rem !important;\n}\n\n.p-0 {\n padding: 0 !important;\n}\n\n.p-1 {\n padding: 0.25rem !important;\n}\n\n.p-2 {\n padding: 0.5rem !important;\n}\n\n.p-3 {\n padding: 1rem !important;\n}\n\n.p-4 {\n padding: 1.5rem !important;\n}\n\n.p-5 {\n padding: 3rem !important;\n}\n\n.px-0 {\n padding-right: 0 !important;\n padding-left: 0 !important;\n}\n\n.px-1 {\n padding-right: 0.25rem !important;\n padding-left: 0.25rem !important;\n}\n\n.px-2 {\n padding-right: 0.5rem !important;\n padding-left: 0.5rem !important;\n}\n\n.px-3 {\n padding-right: 1rem !important;\n padding-left: 1rem !important;\n}\n\n.px-4 {\n padding-right: 1.5rem !important;\n padding-left: 1.5rem !important;\n}\n\n.px-5 {\n padding-right: 3rem !important;\n padding-left: 3rem !important;\n}\n\n.py-0 {\n padding-top: 0 !important;\n padding-bottom: 0 !important;\n}\n\n.py-1 {\n padding-top: 0.25rem !important;\n padding-bottom: 0.25rem !important;\n}\n\n.py-2 {\n padding-top: 0.5rem !important;\n padding-bottom: 0.5rem !important;\n}\n\n.py-3 {\n padding-top: 1rem !important;\n padding-bottom: 1rem !important;\n}\n\n.py-4 {\n padding-top: 1.5rem !important;\n padding-bottom: 1.5rem !important;\n}\n\n.py-5 {\n padding-top: 3rem !important;\n padding-bottom: 3rem !important;\n}\n\n.pt-0 {\n padding-top: 0 !important;\n}\n\n.pt-1 {\n padding-top: 0.25rem !important;\n}\n\n.pt-2 {\n padding-top: 0.5rem !important;\n}\n\n.pt-3 {\n padding-top: 1rem !important;\n}\n\n.pt-4 {\n padding-top: 1.5rem !important;\n}\n\n.pt-5 {\n padding-top: 3rem !important;\n}\n\n.pr-0 {\n padding-right: 0 !important;\n}\n\n.pr-1 {\n padding-right: 0.25rem !important;\n}\n\n.pr-2 {\n padding-right: 0.5rem !important;\n}\n\n.pr-3 {\n padding-right: 1rem !important;\n}\n\n.pr-4 {\n padding-right: 1.5rem !important;\n}\n\n.pr-5 {\n padding-right: 3rem !important;\n}\n\n.pb-0 {\n padding-bottom: 0 !important;\n}\n\n.pb-1 {\n padding-bottom: 0.25rem !important;\n}\n\n.pb-2 {\n padding-bottom: 0.5rem !important;\n}\n\n.pb-3 {\n padding-bottom: 1rem !important;\n}\n\n.pb-4 {\n padding-bottom: 1.5rem !important;\n}\n\n.pb-5 {\n padding-bottom: 3rem !important;\n}\n\n.pl-0 {\n padding-left: 0 !important;\n}\n\n.pl-1 {\n padding-left: 0.25rem !important;\n}\n\n.pl-2 {\n padding-left: 0.5rem !important;\n}\n\n.pl-3 {\n padding-left: 1rem !important;\n}\n\n.pl-4 {\n padding-left: 1.5rem !important;\n}\n\n.pl-5 {\n padding-left: 3rem !important;\n}\n\n.font-weight-light {\n font-weight: 300 !important;\n}\n\n.font-weight-lighter {\n font-weight: lighter !important;\n}\n\n.font-weight-normal {\n font-weight: 400 !important;\n}\n\n.font-weight-bold {\n font-weight: 700 !important;\n}\n\n.font-weight-bolder {\n font-weight: bolder !important;\n}\n\n.text-lowercase {\n text-transform: lowercase !important;\n}\n\n.text-uppercase {\n text-transform: uppercase !important;\n}\n\n.text-capitalize {\n text-transform: capitalize !important;\n}\n\n.text-left {\n text-align: left !important;\n}\n\n.text-right {\n text-align: right !important;\n}\n\n.text-center {\n text-align: center !important;\n}\n\n.text-justify {\n text-align: justify !important;\n}\n\n.text-primary {\n color: #0d6efd !important;\n}\n\n.text-secondary {\n color: #6c757d !important;\n}\n\n.text-success {\n color: #28a745 !important;\n}\n\n.text-info {\n color: #17a2b8 !important;\n}\n\n.text-warning {\n color: #ffc107 !important;\n}\n\n.text-danger {\n color: #dc3545 !important;\n}\n\n.text-light {\n color: #f8f9fa !important;\n}\n\n.text-dark {\n color: #343a40 !important;\n}\n\n.text-white {\n color: #fff !important;\n}\n\n.text-body {\n color: #212529 !important;\n}\n\n.text-muted {\n color: #6c757d !important;\n}\n\n.text-black-50 {\n color: rgba(0, 0, 0, 0.5) !important;\n}\n\n.text-white-50 {\n color: rgba(255, 255, 255, 0.5) !important;\n}\n\n.text-reset {\n color: inherit !important;\n}\n\n.lh-1 {\n line-height: 1 !important;\n}\n\n.lh-sm {\n line-height: 1.25 !important;\n}\n\n.lh-base {\n line-height: 1.5 !important;\n}\n\n.lh-lg {\n line-height: 2 !important;\n}", + { + "line": 3241, + "column": 1, + "index": 65557 + } + ], + [ + ".bg-primary {\n background-color: #0d6efd !important;\n}\n\n.bg-secondary {\n background-color: #6c757d !important;\n}\n\n.bg-success {\n background-color: #28a745 !important;\n}\n\n.bg-info {\n background-color: #17a2b8 !important;\n}\n\n.bg-warning {\n background-color: #ffc107 !important;\n}\n\n.bg-danger {\n background-color: #dc3545 !important;\n}\n\n.bg-light {\n background-color: #f8f9fa !important;\n}\n\n.bg-dark {\n background-color: #343a40 !important;\n}\n\n.bg-body {\n background-color: #fff !important;\n}\n\n.bg-white {\n background-color: #fff !important;\n}\n\n.bg-transparent {\n background-color: transparent !important;\n}\n\n.text-wrap {\n white-space: normal !important;\n}\n\n.text-nowrap {\n white-space: nowrap !important;\n}\n\n.text-decoration-none {\n text-decoration: none !important;\n}\n\n.font-italic {\n font-style: italic !important;\n}\n\n.text-break {\n overflow-wrap: break-word !important;\n word-break: break-word !important;\n}\n\n.font-monospace {\n font-family: SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace !important;\n}\n\n.rounded {\n border-radius: 0.25rem !important;\n}\n\n.rounded-sm {\n border-radius: 0.2rem !important;\n}\n\n.rounded-lg {\n border-radius: 0.3rem !important;\n}\n\n.rounded-circle {\n border-radius: 50% !important;\n}\n\n.rounded-pill {\n border-radius: 50rem !important;\n}\n\n.rounded-0 {\n border-radius: 0 !important;\n}\n\n.rounded-top {\n border-top-left-radius: 0.25rem !important;\n border-top-right-radius: 0.25rem !important;\n}\n\n.rounded-right {\n border-top-right-radius: 0.25rem !important;\n border-bottom-right-radius: 0.25rem !important;\n}\n\n.rounded-bottom {\n border-bottom-right-radius: 0.25rem !important;\n border-bottom-left-radius: 0.25rem !important;\n}\n\n.rounded-left {\n border-bottom-left-radius: 0.25rem !important;\n border-top-left-radius: 0.25rem !important;\n}\n\n.visible {\n visibility: visible !important;\n}\n\n.invisible {\n visibility: hidden !important;\n}\n\n@media (min-width: 576px) {\n .float-sm-left {\n float: left !important;\n }\n .float-sm-right {\n float: right !important;\n }\n .float-sm-none {\n float: none !important;\n }\n .d-sm-none {\n display: none !important;\n }\n .d-sm-inline {\n display: inline !important;\n }\n .d-sm-inline-block {\n display: inline-block !important;\n }\n .d-sm-block {\n display: block !important;\n }\n .d-sm-table {\n display: table !important;\n }\n .d-sm-table-row {\n display: table-row !important;\n }\n .d-sm-table-cell {\n display: table-cell !important;\n }\n .d-sm-flex {\n display: flex !important;\n }\n .d-sm-inline-flex {\n display: inline-flex !important;\n }\n .flex-sm-fill {\n flex: 1 1 auto !important;\n }\n .flex-sm-row {\n flex-direction: row !important;\n }\n .flex-sm-column {\n flex-direction: column !important;\n }\n .flex-sm-row-reverse {\n flex-direction: row-reverse !important;\n }\n .flex-sm-column-reverse {\n flex-direction: column-reverse !important;\n }\n .flex-sm-grow-0 {\n flex-grow: 0 !important;\n }\n .flex-sm-grow-1 {\n flex-grow: 1 !important;\n }\n .flex-sm-shrink-0 {\n flex-shrink: 0 !important;\n }\n .flex-sm-shrink-1 {\n flex-shrink: 1 !important;\n }\n .flex-sm-wrap {\n flex-wrap: wrap !important;\n }\n .flex-sm-nowrap {\n flex-wrap: nowrap !important;\n }\n .flex-sm-wrap-reverse {\n flex-wrap: wrap-reverse !important;\n }\n .justify-content-sm-start {\n justify-content: flex-start !important;\n }\n .justify-content-sm-end {\n justify-content: flex-end !important;\n }\n .justify-content-sm-center {\n justify-content: center !important;\n }\n .justify-content-sm-between {\n justify-content: space-between !important;\n }\n .justify-content-sm-around {\n justify-content: space-around !important;\n }\n .align-items-sm-start {\n align-items: flex-start !important;\n }\n .align-items-sm-end {\n align-items: flex-end !important;\n }\n .align-items-sm-center {\n align-items: center !important;\n }\n .align-items-sm-baseline {\n align-items: baseline !important;\n }\n .align-items-sm-stretch {\n align-items: stretch !important;\n }\n .align-content-sm-start {\n align-content: flex-start !important;\n }\n .align-content-sm-end {\n align-content: flex-end !important;\n }\n .align-content-sm-center {\n align-content: center !important;\n }\n .align-content-sm-between {\n align-content: space-between !important;\n }\n .align-content-sm-around {\n align-content: space-around !important;\n }\n .align-content-sm-stretch {\n align-content: stretch !important;\n }\n .align-self-sm-auto {\n align-self: auto !important;\n }\n .align-self-sm-start {\n align-self: flex-start !important;\n }\n .align-self-sm-end {\n align-self: flex-end !important;\n }\n .align-self-sm-center {\n align-self: center !important;\n }\n .align-self-sm-baseline {\n align-self: baseline !important;\n }\n .align-self-sm-stretch {\n align-self: stretch !important;\n }\n .order-sm-first {\n order: -1 !important;\n }\n .order-sm-0 {\n order: 0 !important;\n }\n .order-sm-1 {\n order: 1 !important;\n }\n .order-sm-2 {\n order: 2 !important;\n }\n .order-sm-3 {\n order: 3 !important;\n }\n .order-sm-4 {\n order: 4 !important;\n }\n .order-sm-5 {\n order: 5 !important;\n }\n .order-sm-last {\n order: 6 !important;\n }\n .m-sm-0 {\n margin: 0 !important;\n }\n .m-sm-1 {\n margin: 0.25rem !important;\n }\n .m-sm-2 {\n margin: 0.5rem !important;\n }\n .m-sm-3 {\n margin: 1rem !important;\n }\n .m-sm-4 {\n margin: 1.5rem !important;\n }\n .m-sm-5 {\n margin: 3rem !important;\n }\n .m-sm-auto {\n margin: auto !important;\n }\n .mx-sm-0 {\n margin-right: 0 !important;\n margin-left: 0 !important;\n }\n .mx-sm-1 {\n margin-right: 0.25rem !important;\n margin-left: 0.25rem !important;\n }\n .mx-sm-2 {\n margin-right: 0.5rem !important;\n margin-left: 0.5rem !important;\n }\n .mx-sm-3 {\n margin-right: 1rem !important;\n margin-left: 1rem !important;\n }\n .mx-sm-4 {\n margin-right: 1.5rem !important;\n margin-left: 1.5rem !important;\n }\n .mx-sm-5 {\n margin-right: 3rem !important;\n margin-left: 3rem !important;\n }\n .mx-sm-auto {\n margin-right: auto !important;\n margin-left: auto !important;\n }\n .my-sm-0 {\n margin-top: 0 !important;\n margin-bottom: 0 !important;\n }\n .my-sm-1 {\n margin-top: 0.25rem !important;\n margin-bottom: 0.25rem !important;\n }\n .my-sm-2 {\n margin-top: 0.5rem !important;\n margin-bottom: 0.5rem !important;\n }\n .my-sm-3 {\n margin-top: 1rem !important;\n margin-bottom: 1rem !important;\n }\n .my-sm-4 {\n margin-top: 1.5rem !important;\n margin-bottom: 1.5rem !important;\n }\n .my-sm-5 {\n margin-top: 3rem !important;\n margin-bottom: 3rem !important;\n }\n .my-sm-auto {\n margin-top: auto !important;\n margin-bottom: auto !important;\n }\n .mt-sm-0 {\n margin-top: 0 !important;\n }\n .mt-sm-1 {\n margin-top: 0.25rem !important;\n }\n .mt-sm-2 {\n margin-top: 0.5rem !important;\n }\n .mt-sm-3 {\n margin-top: 1rem !important;\n }\n .mt-sm-4 {\n margin-top: 1.5rem !important;\n }\n .mt-sm-5 {\n margin-top: 3rem !important;\n }\n .mt-sm-auto {\n margin-top: auto !important;\n }\n .mr-sm-0 {\n margin-right: 0 !important;\n }\n .mr-sm-1 {\n margin-right: 0.25rem !important;\n }\n .mr-sm-2 {\n margin-right: 0.5rem !important;\n }\n .mr-sm-3 {\n margin-right: 1rem !important;\n }\n .mr-sm-4 {\n margin-right: 1.5rem !important;\n }\n .mr-sm-5 {\n margin-right: 3rem !important;\n }\n .mr-sm-auto {\n margin-right: auto !important;\n }\n .mb-sm-0 {\n margin-bottom: 0 !important;\n }\n .mb-sm-1 {\n margin-bottom: 0.25rem !important;\n }\n .mb-sm-2 {\n margin-bottom: 0.5rem !important;\n }\n .mb-sm-3 {\n margin-bottom: 1rem !important;\n }\n .mb-sm-4 {\n margin-bottom: 1.5rem !important;\n }\n .mb-sm-5 {\n margin-bottom: 3rem !important;\n }\n .mb-sm-auto {\n margin-bottom: auto !important;\n }\n .ml-sm-0 {\n margin-left: 0 !important;\n }\n .ml-sm-1 {\n margin-left: 0.25rem !important;\n }\n .ml-sm-2 {\n margin-left: 0.5rem !important;\n }\n .ml-sm-3 {\n margin-left: 1rem !important;\n }\n .ml-sm-4 {\n margin-left: 1.5rem !important;\n }\n .ml-sm-5 {\n margin-left: 3rem !important;\n }\n .ml-sm-auto {\n margin-left: auto !important;\n }\n .m-sm-n1 {\n margin: -0.25rem !important;\n }\n .m-sm-n2 {\n margin: -0.5rem !important;\n }\n .m-sm-n3 {\n margin: -1rem !important;\n }\n .m-sm-n4 {\n margin: -1.5rem !important;\n }\n .m-sm-n5 {\n margin: -3rem !important;\n }\n .mx-sm-n1 {\n margin-right: -0.25rem !important;\n margin-left: -0.25rem !important;\n }\n .mx-sm-n2 {\n margin-right: -0.5rem !important;\n margin-left: -0.5rem !important;\n }\n .mx-sm-n3 {\n margin-right: -1rem !important;\n margin-left: -1rem !important;\n }\n .mx-sm-n4 {\n margin-right: -1.5rem !important;\n margin-left: -1.5rem !important;\n }\n .mx-sm-n5 {\n margin-right: -3rem !important;\n margin-left: -3rem !important;\n }\n .my-sm-n1 {\n margin-top: -0.25rem !important;\n margin-bottom: -0.25rem !important;\n }\n .my-sm-n2 {\n margin-top: -0.5rem !important;\n margin-bottom: -0.5rem !important;\n }\n .my-sm-n3 {\n margin-top: -1rem !important;\n margin-bottom: -1rem !important;\n }\n .my-sm-n4 {\n margin-top: -1.5rem !important;\n margin-bottom: -1.5rem !important;\n }\n .my-sm-n5 {\n margin-top: -3rem !important;\n margin-bottom: -3rem !important;\n }\n .mt-sm-n1 {\n margin-top: -0.25rem !important;\n }\n .mt-sm-n2 {\n margin-top: -0.5rem !important;\n }\n .mt-sm-n3 {\n margin-top: -1rem !important;\n }\n .mt-sm-n4 {\n margin-top: -1.5rem !important;\n }\n .mt-sm-n5 {\n margin-top: -3rem !important;\n }\n .mr-sm-n1 {\n margin-right: -0.25rem !important;\n }\n .mr-sm-n2 {\n margin-right: -0.5rem !important;\n }\n .mr-sm-n3 {\n margin-right: -1rem !important;\n }\n .mr-sm-n4 {\n margin-right: -1.5rem !important;\n }\n .mr-sm-n5 {\n margin-right: -3rem !important;\n }\n .mb-sm-n1 {\n margin-bottom: -0.25rem !important;\n }\n .mb-sm-n2 {\n margin-bottom: -0.5rem !important;\n }\n .mb-sm-n3 {\n margin-bottom: -1rem !important;\n }\n .mb-sm-n4 {\n margin-bottom: -1.5rem !important;\n }\n .mb-sm-n5 {\n margin-bottom: -3rem !important;\n }\n .ml-sm-n1 {\n margin-left: -0.25rem !important;\n }\n .ml-sm-n2 {\n margin-left: -0.5rem !important;\n }\n .ml-sm-n3 {\n margin-left: -1rem !important;\n }\n .ml-sm-n4 {\n margin-left: -1.5rem !important;\n }\n .ml-sm-n5 {\n margin-left: -3rem !important;\n }\n .p-sm-0 {\n padding: 0 !important;\n }\n .p-sm-1 {\n padding: 0.25rem !important;\n }\n .p-sm-2 {\n padding: 0.5rem !important;\n }\n .p-sm-3 {\n padding: 1rem !important;\n }\n .p-sm-4 {\n padding: 1.5rem !important;\n }\n .p-sm-5 {\n padding: 3rem !important;\n }\n .px-sm-0 {\n padding-right: 0 !important;\n padding-left: 0 !important;\n }\n .px-sm-1 {\n padding-right: 0.25rem !important;\n padding-left: 0.25rem !important;\n }\n .px-sm-2 {\n padding-right: 0.5rem !important;\n padding-left: 0.5rem !important;\n }\n .px-sm-3 {\n padding-right: 1rem !important;\n padding-left: 1rem !important;\n }\n .px-sm-4 {\n padding-right: 1.5rem !important;\n padding-left: 1.5rem !important;\n }\n .px-sm-5 {\n padding-right: 3rem !important;\n padding-left: 3rem !important;\n }\n .py-sm-0 {\n padding-top: 0 !important;\n padding-bottom: 0 !important;\n }\n .py-sm-1 {\n padding-top: 0.25rem !important;\n padding-bottom: 0.25rem !important;\n }\n .py-sm-2 {\n padding-top: 0.5rem !important;\n padding-bottom: 0.5rem !important;\n }\n .py-sm-3 {\n padding-top: 1rem !important;\n padding-bottom: 1rem !important;\n }\n .py-sm-4 {\n padding-top: 1.5rem !important;\n padding-bottom: 1.5rem !important;\n }\n .py-sm-5 {\n padding-top: 3rem !important;\n padding-bottom: 3rem !important;\n }\n .pt-sm-0 {\n padding-top: 0 !important;\n }\n .pt-sm-1 {\n padding-top: 0.25rem !important;\n }\n .pt-sm-2 {\n padding-top: 0.5rem !important;\n }\n .pt-sm-3 {\n padding-top: 1rem !important;\n }\n .pt-sm-4 {\n padding-top: 1.5rem !important;\n }\n .pt-sm-5 {\n padding-top: 3rem !important;\n }\n .pr-sm-0 {\n padding-right: 0 !important;\n }\n .pr-sm-1 {\n padding-right: 0.25rem !important;\n }\n .pr-sm-2 {\n padding-right: 0.5rem !important;\n }\n .pr-sm-3 {\n padding-right: 1rem !important;\n }\n .pr-sm-4 {\n padding-right: 1.5rem !important;\n }\n .pr-sm-5 {\n padding-right: 3rem !important;\n }\n .pb-sm-0 {\n padding-bottom: 0 !important;\n }\n .pb-sm-1 {\n padding-bottom: 0.25rem !important;\n }\n .pb-sm-2 {\n padding-bottom: 0.5rem !important;\n }\n .pb-sm-3 {\n padding-bottom: 1rem !important;\n }\n .pb-sm-4 {\n padding-bottom: 1.5rem !important;\n }\n .pb-sm-5 {\n padding-bottom: 3rem !important;\n }\n .pl-sm-0 {\n padding-left: 0 !important;\n }\n .pl-sm-1 {\n padding-left: 0.25rem !important;\n }\n .pl-sm-2 {\n padding-left: 0.5rem !important;\n }\n .pl-sm-3 {\n padding-left: 1rem !important;\n }\n .pl-sm-4 {\n padding-left: 1.5rem !important;\n }\n .pl-sm-5 {\n padding-left: 3rem !important;\n }\n .text-sm-left {\n text-align: left !important;\n }\n .text-sm-right {\n text-align: right !important;\n }\n .text-sm-center {\n text-align: center !important;\n }\n .text-sm-justify {\n text-align: justify !important;\n }\n}\n\n@media (min-width: 768px) {\n .float-md-left {\n float: left !important;\n }\n .float-md-right {\n float: right !important;\n }\n .float-md-none {\n float: none !important;\n }\n .d-md-none {\n display: none !important;\n }\n .d-md-inline {\n display: inline !important;\n }\n .d-md-inline-block {\n display: inline-block !important;\n }\n .d-md-block {\n display: block !important;\n }\n .d-md-table {\n display: table !important;\n }\n .d-md-table-row {\n display: table-row !important;\n }\n .d-md-table-cell {\n display: table-cell !important;\n }\n .d-md-flex {\n display: flex !important;\n }\n .d-md-inline-flex {\n display: inline-flex !important;\n }\n .flex-md-fill {\n flex: 1 1 auto !important;\n }\n .flex-md-row {\n flex-direction: row !important;\n }\n .flex-md-column {\n flex-direction: column !important;\n }\n .flex-md-row-reverse {\n flex-direction: row-reverse !important;\n }\n .flex-md-column-reverse {\n flex-direction: column-reverse !important;\n }\n .flex-md-grow-0 {\n flex-grow: 0 !important;\n }\n .flex-md-grow-1 {\n flex-grow: 1 !important;\n }\n .flex-md-shrink-0 {\n flex-shrink: 0 !important;\n }\n .flex-md-shrink-1 {\n flex-shrink: 1 !important;\n }\n .flex-md-wrap {\n flex-wrap: wrap !important;\n }\n .flex-md-nowrap {\n flex-wrap: nowrap !important;\n }\n .flex-md-wrap-reverse {\n flex-wrap: wrap-reverse !important;\n }\n .justify-content-md-start {\n justify-content: flex-start !important;\n }\n .justify-content-md-end {\n justify-content: flex-end !important;\n }\n .justify-content-md-center {\n justify-content: center !important;\n }\n .justify-content-md-between {\n justify-content: space-between !important;\n }\n .justify-content-md-around {\n justify-content: space-around !important;\n }\n .align-items-md-start {\n align-items: flex-start !important;\n }\n .align-items-md-end {\n align-items: flex-end !important;\n }\n .align-items-md-center {\n align-items: center !important;\n }\n .align-items-md-baseline {\n align-items: baseline !important;\n }\n .align-items-md-stretch {\n align-items: stretch !important;\n }\n .align-content-md-start {\n align-content: flex-start !important;\n }\n .align-content-md-end {\n align-content: flex-end !important;\n }\n .align-content-md-center {\n align-content: center !important;\n }\n .align-content-md-between {\n align-content: space-between !important;\n }\n .align-content-md-around {\n align-content: space-around !important;\n }\n .align-content-md-stretch {\n align-content: stretch !important;\n }\n .align-self-md-auto {\n align-self: auto !important;\n }\n .align-self-md-start {\n align-self: flex-start !important;\n }\n .align-self-md-end {\n align-self: flex-end !important;\n }\n .align-self-md-center {\n align-self: center !important;\n }\n .align-self-md-baseline {\n align-self: baseline !important;\n }\n .align-self-md-stretch {\n align-self: stretch !important;\n }\n .order-md-first {\n order: -1 !important;\n }\n .order-md-0 {\n order: 0 !important;\n }\n .order-md-1 {\n order: 1 !important;\n }\n .order-md-2 {\n order: 2 !important;\n }\n .order-md-3 {\n order: 3 !important;\n }\n .order-md-4 {\n order: 4 !important;\n }\n .order-md-5 {\n order: 5 !important;\n }\n .order-md-last {\n order: 6 !important;\n }\n .m-md-0 {\n margin: 0 !important;\n }\n .m-md-1 {\n margin: 0.25rem !important;\n }\n .m-md-2 {\n margin: 0.5rem !important;\n }\n .m-md-3 {\n margin: 1rem !important;\n }\n .m-md-4 {\n margin: 1.5rem !important;\n }\n .m-md-5 {\n margin: 3rem !important;\n }\n .m-md-auto {\n margin: auto !important;\n }\n .mx-md-0 {\n margin-right: 0 !important;\n margin-left: 0 !important;\n }\n .mx-md-1 {\n margin-right: 0.25rem !important;\n margin-left: 0.25rem !important;\n }\n .mx-md-2 {\n margin-right: 0.5rem !important;\n margin-left: 0.5rem !important;\n }\n .mx-md-3 {\n margin-right: 1rem !important;\n margin-left: 1rem !important;\n }\n .mx-md-4 {\n margin-right: 1.5rem !important;\n margin-left: 1.5rem !important;\n }\n .mx-md-5 {\n margin-right: 3rem !important;\n margin-left: 3rem !important;\n }\n .mx-md-auto {\n margin-right: auto !important;\n margin-left: auto !important;\n }\n .my-md-0 {\n margin-top: 0 !important;\n margin-bottom: 0 !important;\n }\n .my-md-1 {\n margin-top: 0.25rem !important;\n margin-bottom: 0.25rem !important;\n }\n .my-md-2 {\n margin-top: 0.5rem !important;\n margin-bottom: 0.5rem !important;\n }\n .my-md-3 {\n margin-top: 1rem !important;\n margin-bottom: 1rem !important;\n }\n .my-md-4 {\n margin-top: 1.5rem !important;\n margin-bottom: 1.5rem !important;\n }\n .my-md-5 {\n margin-top: 3rem !important;\n margin-bottom: 3rem !important;\n }\n .my-md-auto {\n margin-top: auto !important;\n margin-bottom: auto !important;\n }\n .mt-md-0 {\n margin-top: 0 !important;\n }\n .mt-md-1 {\n margin-top: 0.25rem !important;\n }\n .mt-md-2 {\n margin-top: 0.5rem !important;\n }\n .mt-md-3 {\n margin-top: 1rem !important;\n }\n .mt-md-4 {\n margin-top: 1.5rem !important;\n }\n .mt-md-5 {\n margin-top: 3rem !important;\n }\n .mt-md-auto {\n margin-top: auto !important;\n }\n .mr-md-0 {\n margin-right: 0 !important;\n }\n .mr-md-1 {\n margin-right: 0.25rem !important;\n }\n .mr-md-2 {\n margin-right: 0.5rem !important;\n }\n .mr-md-3 {\n margin-right: 1rem !important;\n }\n .mr-md-4 {\n margin-right: 1.5rem !important;\n }\n .mr-md-5 {\n margin-right: 3rem !important;\n }\n .mr-md-auto {\n margin-right: auto !important;\n }\n .mb-md-0 {\n margin-bottom: 0 !important;\n }\n .mb-md-1 {\n margin-bottom: 0.25rem !important;\n }\n .mb-md-2 {\n margin-bottom: 0.5rem !important;\n }\n .mb-md-3 {\n margin-bottom: 1rem !important;\n }\n .mb-md-4 {\n margin-bottom: 1.5rem !important;\n }\n .mb-md-5 {\n margin-bottom: 3rem !important;\n }\n .mb-md-auto {\n margin-bottom: auto !important;\n }\n .ml-md-0 {\n margin-left: 0 !important;\n }\n .ml-md-1 {\n margin-left: 0.25rem !important;\n }\n .ml-md-2 {\n margin-left: 0.5rem !important;\n }\n .ml-md-3 {\n margin-left: 1rem !important;\n }\n .ml-md-4 {\n margin-left: 1.5rem !important;\n }\n .ml-md-5 {\n margin-left: 3rem !important;\n }\n .ml-md-auto {\n margin-left: auto !important;\n }\n .m-md-n1 {\n margin: -0.25rem !important;\n }\n .m-md-n2 {\n margin: -0.5rem !important;\n }\n .m-md-n3 {\n margin: -1rem !important;\n }\n .m-md-n4 {\n margin: -1.5rem !important;\n }\n .m-md-n5 {\n margin: -3rem !important;\n }\n .mx-md-n1 {\n margin-right: -0.25rem !important;\n margin-left: -0.25rem !important;\n }\n .mx-md-n2 {\n margin-right: -0.5rem !important;\n margin-left: -0.5rem !important;\n }\n .mx-md-n3 {\n margin-right: -1rem !important;\n margin-left: -1rem !important;\n }\n .mx-md-n4 {\n margin-right: -1.5rem !important;\n margin-left: -1.5rem !important;\n }\n .mx-md-n5 {\n margin-right: -3rem !important;\n margin-left: -3rem !important;\n }\n .my-md-n1 {\n margin-top: -0.25rem !important;\n margin-bottom: -0.25rem !important;\n }\n .my-md-n2 {\n margin-top: -0.5rem !important;\n margin-bottom: -0.5rem !important;\n }\n .my-md-n3 {\n margin-top: -1rem !important;\n margin-bottom: -1rem !important;\n }\n .my-md-n4 {\n margin-top: -1.5rem !important;\n margin-bottom: -1.5rem !important;\n }\n .my-md-n5 {\n margin-top: -3rem !important;\n margin-bottom: -3rem !important;\n }\n .mt-md-n1 {\n margin-top: -0.25rem !important;\n }\n .mt-md-n2 {\n margin-top: -0.5rem !important;\n }\n .mt-md-n3 {\n margin-top: -1rem !important;\n }\n .mt-md-n4 {\n margin-top: -1.5rem !important;\n }\n .mt-md-n5 {\n margin-top: -3rem !important;\n }\n .mr-md-n1 {\n margin-right: -0.25rem !important;\n }\n .mr-md-n2 {\n margin-right: -0.5rem !important;\n }\n .mr-md-n3 {\n margin-right: -1rem !important;\n }\n .mr-md-n4 {\n margin-right: -1.5rem !important;\n }\n .mr-md-n5 {\n margin-right: -3rem !important;\n }\n .mb-md-n1 {\n margin-bottom: -0.25rem !important;\n }\n .mb-md-n2 {\n margin-bottom: -0.5rem !important;\n }\n .mb-md-n3 {\n margin-bottom: -1rem !important;\n }\n .mb-md-n4 {\n margin-bottom: -1.5rem !important;\n }\n .mb-md-n5 {\n margin-bottom: -3rem !important;\n }\n .ml-md-n1 {\n margin-left: -0.25rem !important;\n }\n .ml-md-n2 {\n margin-left: -0.5rem !important;\n }\n .ml-md-n3 {\n margin-left: -1rem !important;\n }\n .ml-md-n4 {\n margin-left: -1.5rem !important;\n }\n .ml-md-n5 {\n margin-left: -3rem !important;\n }\n .p-md-0 {\n padding: 0 !important;\n }\n .p-md-1 {\n padding: 0.25rem !important;\n }\n .p-md-2 {\n padding: 0.5rem !important;\n }\n .p-md-3 {\n padding: 1rem !important;\n }\n .p-md-4 {\n padding: 1.5rem !important;\n }\n .p-md-5 {\n padding: 3rem !important;\n }\n .px-md-0 {\n padding-right: 0 !important;\n padding-left: 0 !important;\n }\n .px-md-1 {\n padding-right: 0.25rem !important;\n padding-left: 0.25rem !important;\n }\n .px-md-2 {\n padding-right: 0.5rem !important;\n padding-left: 0.5rem !important;\n }\n .px-md-3 {\n padding-right: 1rem !important;\n padding-left: 1rem !important;\n }\n .px-md-4 {\n padding-right: 1.5rem !important;\n padding-left: 1.5rem !important;\n }\n .px-md-5 {\n padding-right: 3rem !important;\n padding-left: 3rem !important;\n }\n .py-md-0 {\n padding-top: 0 !important;\n padding-bottom: 0 !important;\n }\n .py-md-1 {\n padding-top: 0.25rem !important;\n padding-bottom: 0.25rem !important;\n }\n .py-md-2 {\n padding-top: 0.5rem !important;\n padding-bottom: 0.5rem !important;\n }\n .py-md-3 {\n padding-top: 1rem !important;\n padding-bottom: 1rem !important;\n }\n .py-md-4 {\n padding-top: 1.5rem !important;\n padding-bottom: 1.5rem !important;\n }\n .py-md-5 {\n padding-top: 3rem !important;\n padding-bottom: 3rem !important;\n }\n .pt-md-0 {\n padding-top: 0 !important;\n }\n .pt-md-1 {\n padding-top: 0.25rem !important;\n }\n .pt-md-2 {\n padding-top: 0.5rem !important;\n }\n .pt-md-3 {\n padding-top: 1rem !important;\n }\n .pt-md-4 {\n padding-top: 1.5rem !important;\n }\n .pt-md-5 {\n padding-top: 3rem !important;\n }\n .pr-md-0 {\n padding-right: 0 !important;\n }\n .pr-md-1 {\n padding-right: 0.25rem !important;\n }\n .pr-md-2 {\n padding-right: 0.5rem !important;\n }\n .pr-md-3 {\n padding-right: 1rem !important;\n }\n .pr-md-4 {\n padding-right: 1.5rem !important;\n }\n .pr-md-5 {\n padding-right: 3rem !important;\n }\n .pb-md-0 {\n padding-bottom: 0 !important;\n }\n .pb-md-1 {\n padding-bottom: 0.25rem !important;\n }\n .pb-md-2 {\n padding-bottom: 0.5rem !important;\n }\n .pb-md-3 {\n padding-bottom: 1rem !important;\n }\n .pb-md-4 {\n padding-bottom: 1.5rem !important;\n }\n .pb-md-5 {\n padding-bottom: 3rem !important;\n }\n .pl-md-0 {\n padding-left: 0 !important;\n }\n .pl-md-1 {\n padding-left: 0.25rem !important;\n }\n .pl-md-2 {\n padding-left: 0.5rem !important;\n }\n .pl-md-3 {\n padding-left: 1rem !important;\n }\n .pl-md-4 {\n padding-left: 1.5rem !important;\n }\n .pl-md-5 {\n padding-left: 3rem !important;\n }\n .text-md-left {\n text-align: left !important;\n }\n .text-md-right {\n text-align: right !important;\n }\n .text-md-center {\n text-align: center !important;\n }\n .text-md-justify {\n text-align: justify !important;\n }\n}\n\n@media (min-width: 992px) {\n .float-lg-left {\n float: left !important;\n }\n .float-lg-right {\n float: right !important;\n }\n .float-lg-none {\n float: none !important;\n }\n .d-lg-none {\n display: none !important;\n }\n .d-lg-inline {\n display: inline !important;\n }\n .d-lg-inline-block {\n display: inline-block !important;\n }\n .d-lg-block {\n display: block !important;\n }\n .d-lg-table {\n display: table !important;\n }\n .d-lg-table-row {\n display: table-row !important;\n }\n .d-lg-table-cell {\n display: table-cell !important;\n }\n .d-lg-flex {\n display: flex !important;\n }\n .d-lg-inline-flex {\n display: inline-flex !important;\n }\n .flex-lg-fill {\n flex: 1 1 auto !important;\n }\n .flex-lg-row {\n flex-direction: row !important;\n }\n .flex-lg-column {\n flex-direction: column !important;\n }\n .flex-lg-row-reverse {\n flex-direction: row-reverse !important;\n }\n .flex-lg-column-reverse {\n flex-direction: column-reverse !important;\n }\n .flex-lg-grow-0 {\n flex-grow: 0 !important;\n }\n .flex-lg-grow-1 {\n flex-grow: 1 !important;\n }\n .flex-lg-shrink-0 {\n flex-shrink: 0 !important;\n }\n .flex-lg-shrink-1 {\n flex-shrink: 1 !important;\n }\n .flex-lg-wrap {\n flex-wrap: wrap !important;\n }\n .flex-lg-nowrap {\n flex-wrap: nowrap !important;\n }\n .flex-lg-wrap-reverse {\n flex-wrap: wrap-reverse !important;\n }\n .justify-content-lg-start {\n justify-content: flex-start !important;\n }\n .justify-content-lg-end {\n justify-content: flex-end !important;\n }\n .justify-content-lg-center {\n justify-content: center !important;\n }\n .justify-content-lg-between {\n justify-content: space-between !important;\n }\n .justify-content-lg-around {\n justify-content: space-around !important;\n }\n .align-items-lg-start {\n align-items: flex-start !important;\n }\n .align-items-lg-end {\n align-items: flex-end !important;\n }\n .align-items-lg-center {\n align-items: center !important;\n }\n .align-items-lg-baseline {\n align-items: baseline !important;\n }\n .align-items-lg-stretch {\n align-items: stretch !important;\n }\n .align-content-lg-start {\n align-content: flex-start !important;\n }\n .align-content-lg-end {\n align-content: flex-end !important;\n }\n .align-content-lg-center {\n align-content: center !important;\n }\n .align-content-lg-between {\n align-content: space-between !important;\n }\n .align-content-lg-around {\n align-content: space-around !important;\n }\n .align-content-lg-stretch {\n align-content: stretch !important;\n }\n .align-self-lg-auto {\n align-self: auto !important;\n }\n .align-self-lg-start {\n align-self: flex-start !important;\n }\n .align-self-lg-end {\n align-self: flex-end !important;\n }\n .align-self-lg-center {\n align-self: center !important;\n }\n .align-self-lg-baseline {\n align-self: baseline !important;\n }\n .align-self-lg-stretch {\n align-self: stretch !important;\n }\n .order-lg-first {\n order: -1 !important;\n }\n .order-lg-0 {\n order: 0 !important;\n }\n .order-lg-1 {\n order: 1 !important;\n }\n .order-lg-2 {\n order: 2 !important;\n }\n .order-lg-3 {\n order: 3 !important;\n }\n .order-lg-4 {\n order: 4 !important;\n }\n .order-lg-5 {\n order: 5 !important;\n }\n .order-lg-last {\n order: 6 !important;\n }\n .m-lg-0 {\n margin: 0 !important;\n }\n .m-lg-1 {\n margin: 0.25rem !important;\n }\n .m-lg-2 {\n margin: 0.5rem !important;\n }\n .m-lg-3 {\n margin: 1rem !important;\n }\n .m-lg-4 {\n margin: 1.5rem !important;\n }\n .m-lg-5 {\n margin: 3rem !important;\n }\n .m-lg-auto {\n margin: auto !important;\n }\n .mx-lg-0 {\n margin-right: 0 !important;\n margin-left: 0 !important;\n }\n .mx-lg-1 {\n margin-right: 0.25rem !important;\n margin-left: 0.25rem !important;\n }\n .mx-lg-2 {\n margin-right: 0.5rem !important;\n margin-left: 0.5rem !important;\n }\n .mx-lg-3 {\n margin-right: 1rem !important;\n margin-left: 1rem !important;\n }\n .mx-lg-4 {\n margin-right: 1.5rem !important;\n margin-left: 1.5rem !important;\n }\n .mx-lg-5 {\n margin-right: 3rem !important;\n margin-left: 3rem !important;\n }\n .mx-lg-auto {\n margin-right: auto !important;\n margin-left: auto !important;\n }\n .my-lg-0 {\n margin-top: 0 !important;\n margin-bottom: 0 !important;\n }\n .my-lg-1 {\n margin-top: 0.25rem !important;\n margin-bottom: 0.25rem !important;\n }\n .my-lg-2 {\n margin-top: 0.5rem !important;\n margin-bottom: 0.5rem !important;\n }\n .my-lg-3 {\n margin-top: 1rem !important;\n margin-bottom: 1rem !important;\n }\n .my-lg-4 {\n margin-top: 1.5rem !important;\n margin-bottom: 1.5rem !important;\n }\n .my-lg-5 {\n margin-top: 3rem !important;\n margin-bottom: 3rem !important;\n }\n .my-lg-auto {\n margin-top: auto !important;\n margin-bottom: auto !important;\n }\n .mt-lg-0 {\n margin-top: 0 !important;\n }\n .mt-lg-1 {\n margin-top: 0.25rem !important;\n }\n .mt-lg-2 {\n margin-top: 0.5rem !important;\n }\n .mt-lg-3 {\n margin-top: 1rem !important;\n }\n .mt-lg-4 {\n margin-top: 1.5rem !important;\n }\n .mt-lg-5 {\n margin-top: 3rem !important;\n }\n .mt-lg-auto {\n margin-top: auto !important;\n }\n .mr-lg-0 {\n margin-right: 0 !important;\n }\n .mr-lg-1 {\n margin-right: 0.25rem !important;\n }\n .mr-lg-2 {\n margin-right: 0.5rem !important;\n }\n .mr-lg-3 {\n margin-right: 1rem !important;\n }\n .mr-lg-4 {\n margin-right: 1.5rem !important;\n }\n .mr-lg-5 {\n margin-right: 3rem !important;\n }\n .mr-lg-auto {\n margin-right: auto !important;\n }\n .mb-lg-0 {\n margin-bottom: 0 !important;\n }\n .mb-lg-1 {\n margin-bottom: 0.25rem !important;\n }\n .mb-lg-2 {\n margin-bottom: 0.5rem !important;\n }\n .mb-lg-3 {\n margin-bottom: 1rem !important;\n }\n .mb-lg-4 {\n margin-bottom: 1.5rem !important;\n }\n .mb-lg-5 {\n margin-bottom: 3rem !important;\n }\n .mb-lg-auto {\n margin-bottom: auto !important;\n }\n .ml-lg-0 {\n margin-left: 0 !important;\n }\n .ml-lg-1 {\n margin-left: 0.25rem !important;\n }\n .ml-lg-2 {\n margin-left: 0.5rem !important;\n }\n .ml-lg-3 {\n margin-left: 1rem !important;\n }\n .ml-lg-4 {\n margin-left: 1.5rem !important;\n }\n .ml-lg-5 {\n margin-left: 3rem !important;\n }\n .ml-lg-auto {\n margin-left: auto !important;\n }\n .m-lg-n1 {\n margin: -0.25rem !important;\n }\n .m-lg-n2 {\n margin: -0.5rem !important;\n }\n .m-lg-n3 {\n margin: -1rem !important;\n }\n .m-lg-n4 {\n margin: -1.5rem !important;\n }\n .m-lg-n5 {\n margin: -3rem !important;\n }\n .mx-lg-n1 {\n margin-right: -0.25rem !important;\n margin-left: -0.25rem !important;\n }\n .mx-lg-n2 {\n margin-right: -0.5rem !important;\n margin-left: -0.5rem !important;\n }\n .mx-lg-n3 {\n margin-right: -1rem !important;\n margin-left: -1rem !important;\n }\n .mx-lg-n4 {\n margin-right: -1.5rem !important;\n margin-left: -1.5rem !important;\n }\n .mx-lg-n5 {\n margin-right: -3rem !important;\n margin-left: -3rem !important;\n }\n .my-lg-n1 {\n margin-top: -0.25rem !important;\n margin-bottom: -0.25rem !important;\n }\n .my-lg-n2 {\n margin-top: -0.5rem !important;\n margin-bottom: -0.5rem !important;\n }\n .my-lg-n3 {\n margin-top: -1rem !important;\n margin-bottom: -1rem !important;\n }\n .my-lg-n4 {\n margin-top: -1.5rem !important;\n margin-bottom: -1.5rem !important;\n }\n .my-lg-n5 {\n margin-top: -3rem !important;\n margin-bottom: -3rem !important;\n }\n .mt-lg-n1 {\n margin-top: -0.25rem !important;\n }\n .mt-lg-n2 {\n margin-top: -0.5rem !important;\n }\n .mt-lg-n3 {\n margin-top: -1rem !important;\n }\n .mt-lg-n4 {\n margin-top: -1.5rem !important;\n }\n .mt-lg-n5 {\n margin-top: -3rem !important;\n }\n .mr-lg-n1 {\n margin-right: -0.25rem !important;\n }\n .mr-lg-n2 {\n margin-right: -0.5rem !important;\n }\n .mr-lg-n3 {\n margin-right: -1rem !important;\n }\n .mr-lg-n4 {\n margin-right: -1.5rem !important;\n }\n .mr-lg-n5 {\n margin-right: -3rem !important;\n }\n .mb-lg-n1 {\n margin-bottom: -0.25rem !important;\n }\n .mb-lg-n2 {\n margin-bottom: -0.5rem !important;\n }\n .mb-lg-n3 {\n margin-bottom: -1rem !important;\n }\n .mb-lg-n4 {\n margin-bottom: -1.5rem !important;\n }\n .mb-lg-n5 {\n margin-bottom: -3rem !important;\n }\n .ml-lg-n1 {\n margin-left: -0.25rem !important;\n }\n .ml-lg-n2 {\n margin-left: -0.5rem !important;\n }\n .ml-lg-n3 {\n margin-left: -1rem !important;\n }\n .ml-lg-n4 {\n margin-left: -1.5rem !important;\n }\n .ml-lg-n5 {\n margin-left: -3rem !important;\n }\n .p-lg-0 {\n padding: 0 !important;\n }\n .p-lg-1 {\n padding: 0.25rem !important;\n }\n .p-lg-2 {\n padding: 0.5rem !important;\n }\n .p-lg-3 {\n padding: 1rem !important;\n }\n .p-lg-4 {\n padding: 1.5rem !important;\n }\n .p-lg-5 {\n padding: 3rem !important;\n }\n .px-lg-0 {\n padding-right: 0 !important;\n padding-left: 0 !important;\n }\n .px-lg-1 {\n padding-right: 0.25rem !important;\n padding-left: 0.25rem !important;\n }\n .px-lg-2 {\n padding-right: 0.5rem !important;\n padding-left: 0.5rem !important;\n }\n .px-lg-3 {\n padding-right: 1rem !important;\n padding-left: 1rem !important;\n }\n .px-lg-4 {\n padding-right: 1.5rem !important;\n padding-left: 1.5rem !important;\n }\n .px-lg-5 {\n padding-right: 3rem !important;\n padding-left: 3rem !important;\n }\n .py-lg-0 {\n padding-top: 0 !important;\n padding-bottom: 0 !important;\n }\n .py-lg-1 {\n padding-top: 0.25rem !important;\n padding-bottom: 0.25rem !important;\n }\n .py-lg-2 {\n padding-top: 0.5rem !important;\n padding-bottom: 0.5rem !important;\n }\n .py-lg-3 {\n padding-top: 1rem !important;\n padding-bottom: 1rem !important;\n }\n .py-lg-4 {\n padding-top: 1.5rem !important;\n padding-bottom: 1.5rem !important;\n }\n .py-lg-5 {\n padding-top: 3rem !important;\n padding-bottom: 3rem !important;\n }\n .pt-lg-0 {\n padding-top: 0 !important;\n }\n .pt-lg-1 {\n padding-top: 0.25rem !important;\n }\n .pt-lg-2 {\n padding-top: 0.5rem !important;\n }\n .pt-lg-3 {\n padding-top: 1rem !important;\n }\n .pt-lg-4 {\n padding-top: 1.5rem !important;\n }\n .pt-lg-5 {\n padding-top: 3rem !important;\n }\n .pr-lg-0 {\n padding-right: 0 !important;\n }\n .pr-lg-1 {\n padding-right: 0.25rem !important;\n }\n .pr-lg-2 {\n padding-right: 0.5rem !important;\n }\n .pr-lg-3 {\n padding-right: 1rem !important;\n }\n .pr-lg-4 {\n padding-right: 1.5rem !important;\n }\n .pr-lg-5 {\n padding-right: 3rem !important;\n }\n .pb-lg-0 {\n padding-bottom: 0 !important;\n }\n .pb-lg-1 {\n padding-bottom: 0.25rem !important;\n }\n .pb-lg-2 {\n padding-bottom: 0.5rem !important;\n }\n .pb-lg-3 {\n padding-bottom: 1rem !important;\n }\n .pb-lg-4 {\n padding-bottom: 1.5rem !important;\n }\n .pb-lg-5 {\n padding-bottom: 3rem !important;\n }\n .pl-lg-0 {\n padding-left: 0 !important;\n }\n .pl-lg-1 {\n padding-left: 0.25rem !important;\n }\n .pl-lg-2 {\n padding-left: 0.5rem !important;\n }\n .pl-lg-3 {\n padding-left: 1rem !important;\n }\n .pl-lg-4 {\n padding-left: 1.5rem !important;\n }\n .pl-lg-5 {\n padding-left: 3rem !important;\n }\n .text-lg-left {\n text-align: left !important;\n }\n .text-lg-right {\n text-align: right !important;\n }\n .text-lg-center {\n text-align: center !important;\n }\n .text-lg-justify {\n text-align: justify !important;\n }\n}\n\n@media (min-width: 1200px) {\n .float-xl-left {\n float: left !important;\n }\n .float-xl-right {\n float: right !important;\n }\n .float-xl-none {\n float: none !important;\n }\n .d-xl-none {\n display: none !important;\n }\n .d-xl-inline {\n display: inline !important;\n }\n .d-xl-inline-block {\n display: inline-block !important;\n }\n .d-xl-block {\n display: block !important;\n }\n .d-xl-table {\n display: table !important;\n }\n .d-xl-table-row {\n display: table-row !important;\n }\n .d-xl-table-cell {\n display: table-cell !important;\n }\n .d-xl-flex {\n display: flex !important;\n }\n .d-xl-inline-flex {\n display: inline-flex !important;\n }\n .flex-xl-fill {\n flex: 1 1 auto !important;\n }\n .flex-xl-row {\n flex-direction: row !important;\n }\n .flex-xl-column {\n flex-direction: column !important;\n }\n .flex-xl-row-reverse {\n flex-direction: row-reverse !important;\n }\n .flex-xl-column-reverse {\n flex-direction: column-reverse !important;\n }\n .flex-xl-grow-0 {\n flex-grow: 0 !important;\n }\n .flex-xl-grow-1 {\n flex-grow: 1 !important;\n }\n .flex-xl-shrink-0 {\n flex-shrink: 0 !important;\n }\n .flex-xl-shrink-1 {\n flex-shrink: 1 !important;\n }\n .flex-xl-wrap {\n flex-wrap: wrap !important;\n }\n .flex-xl-nowrap {\n flex-wrap: nowrap !important;\n }\n .flex-xl-wrap-reverse {\n flex-wrap: wrap-reverse !important;\n }\n .justify-content-xl-start {\n justify-content: flex-start !important;\n }\n .justify-content-xl-end {\n justify-content: flex-end !important;\n }\n .justify-content-xl-center {\n justify-content: center !important;\n }\n .justify-content-xl-between {\n justify-content: space-between !important;\n }\n .justify-content-xl-around {\n justify-content: space-around !important;\n }\n .align-items-xl-start {\n align-items: flex-start !important;\n }\n .align-items-xl-end {\n align-items: flex-end !important;\n }\n .align-items-xl-center {\n align-items: center !important;\n }\n .align-items-xl-baseline {\n align-items: baseline !important;\n }\n .align-items-xl-stretch {\n align-items: stretch !important;\n }\n .align-content-xl-start {\n align-content: flex-start !important;\n }\n .align-content-xl-end {\n align-content: flex-end !important;\n }\n .align-content-xl-center {\n align-content: center !important;\n }\n .align-content-xl-between {\n align-content: space-between !important;\n }\n .align-content-xl-around {\n align-content: space-around !important;\n }\n .align-content-xl-stretch {\n align-content: stretch !important;\n }\n .align-self-xl-auto {\n align-self: auto !important;\n }\n .align-self-xl-start {\n align-self: flex-start !important;\n }\n .align-self-xl-end {\n align-self: flex-end !important;\n }\n .align-self-xl-center {\n align-self: center !important;\n }\n .align-self-xl-baseline {\n align-self: baseline !important;\n }\n .align-self-xl-stretch {\n align-self: stretch !important;\n }\n .order-xl-first {\n order: -1 !important;\n }\n .order-xl-0 {\n order: 0 !important;\n }\n .order-xl-1 {\n order: 1 !important;\n }\n .order-xl-2 {\n order: 2 !important;\n }\n .order-xl-3 {\n order: 3 !important;\n }\n .order-xl-4 {\n order: 4 !important;\n }\n .order-xl-5 {\n order: 5 !important;\n }\n .order-xl-last {\n order: 6 !important;\n }\n .m-xl-0 {\n margin: 0 !important;\n }\n .m-xl-1 {\n margin: 0.25rem !important;\n }\n .m-xl-2 {\n margin: 0.5rem !important;\n }\n .m-xl-3 {\n margin: 1rem !important;\n }\n .m-xl-4 {\n margin: 1.5rem !important;\n }\n .m-xl-5 {\n margin: 3rem !important;\n }\n .m-xl-auto {\n margin: auto !important;\n }\n .mx-xl-0 {\n margin-right: 0 !important;\n margin-left: 0 !important;\n }\n .mx-xl-1 {\n margin-right: 0.25rem !important;\n margin-left: 0.25rem !important;\n }\n .mx-xl-2 {\n margin-right: 0.5rem !important;\n margin-left: 0.5rem !important;\n }\n .mx-xl-3 {\n margin-right: 1rem !important;\n margin-left: 1rem !important;\n }\n .mx-xl-4 {\n margin-right: 1.5rem !important;\n margin-left: 1.5rem !important;\n }\n .mx-xl-5 {\n margin-right: 3rem !important;\n margin-left: 3rem !important;\n }\n .mx-xl-auto {\n margin-right: auto !important;\n margin-left: auto !important;\n }\n .my-xl-0 {\n margin-top: 0 !important;\n margin-bottom: 0 !important;\n }\n .my-xl-1 {\n margin-top: 0.25rem !important;\n margin-bottom: 0.25rem !important;\n }\n .my-xl-2 {\n margin-top: 0.5rem !important;\n margin-bottom: 0.5rem !important;\n }\n .my-xl-3 {\n margin-top: 1rem !important;\n margin-bottom: 1rem !important;\n }\n .my-xl-4 {\n margin-top: 1.5rem !important;\n margin-bottom: 1.5rem !important;\n }\n .my-xl-5 {\n margin-top: 3rem !important;\n margin-bottom: 3rem !important;\n }\n .my-xl-auto {\n margin-top: auto !important;\n margin-bottom: auto !important;\n }\n .mt-xl-0 {\n margin-top: 0 !important;\n }\n .mt-xl-1 {\n margin-top: 0.25rem !important;\n }\n .mt-xl-2 {\n margin-top: 0.5rem !important;\n }\n .mt-xl-3 {\n margin-top: 1rem !important;\n }\n .mt-xl-4 {\n margin-top: 1.5rem !important;\n }\n .mt-xl-5 {\n margin-top: 3rem !important;\n }\n .mt-xl-auto {\n margin-top: auto !important;\n }\n .mr-xl-0 {\n margin-right: 0 !important;\n }\n .mr-xl-1 {\n margin-right: 0.25rem !important;\n }\n .mr-xl-2 {\n margin-right: 0.5rem !important;\n }\n .mr-xl-3 {\n margin-right: 1rem !important;\n }\n .mr-xl-4 {\n margin-right: 1.5rem !important;\n }\n .mr-xl-5 {\n margin-right: 3rem !important;\n }\n .mr-xl-auto {\n margin-right: auto !important;\n }\n .mb-xl-0 {\n margin-bottom: 0 !important;\n }\n .mb-xl-1 {\n margin-bottom: 0.25rem !important;\n }\n .mb-xl-2 {\n margin-bottom: 0.5rem !important;\n }\n .mb-xl-3 {\n margin-bottom: 1rem !important;\n }\n .mb-xl-4 {\n margin-bottom: 1.5rem !important;\n }\n .mb-xl-5 {\n margin-bottom: 3rem !important;\n }\n .mb-xl-auto {\n margin-bottom: auto !important;\n }\n .ml-xl-0 {\n margin-left: 0 !important;\n }\n .ml-xl-1 {\n margin-left: 0.25rem !important;\n }\n .ml-xl-2 {\n margin-left: 0.5rem !important;\n }\n .ml-xl-3 {\n margin-left: 1rem !important;\n }\n .ml-xl-4 {\n margin-left: 1.5rem !important;\n }\n .ml-xl-5 {\n margin-left: 3rem !important;\n }\n .ml-xl-auto {\n margin-left: auto !important;\n }\n .m-xl-n1 {\n margin: -0.25rem !important;\n }\n .m-xl-n2 {\n margin: -0.5rem !important;\n }\n .m-xl-n3 {\n margin: -1rem !important;\n }\n .m-xl-n4 {\n margin: -1.5rem !important;\n }\n .m-xl-n5 {\n margin: -3rem !important;\n }\n .mx-xl-n1 {\n margin-right: -0.25rem !important;\n margin-left: -0.25rem !important;\n }\n .mx-xl-n2 {\n margin-right: -0.5rem !important;\n margin-left: -0.5rem !important;\n }\n .mx-xl-n3 {\n margin-right: -1rem !important;\n margin-left: -1rem !important;\n }\n .mx-xl-n4 {\n margin-right: -1.5rem !important;\n margin-left: -1.5rem !important;\n }\n .mx-xl-n5 {\n margin-right: -3rem !important;\n margin-left: -3rem !important;\n }\n .my-xl-n1 {\n margin-top: -0.25rem !important;\n margin-bottom: -0.25rem !important;\n }\n .my-xl-n2 {\n margin-top: -0.5rem !important;\n margin-bottom: -0.5rem !important;\n }\n .my-xl-n3 {\n margin-top: -1rem !important;\n margin-bottom: -1rem !important;\n }\n .my-xl-n4 {\n margin-top: -1.5rem !important;\n margin-bottom: -1.5rem !important;\n }\n .my-xl-n5 {\n margin-top: -3rem !important;\n margin-bottom: -3rem !important;\n }\n .mt-xl-n1 {\n margin-top: -0.25rem !important;\n }\n .mt-xl-n2 {\n margin-top: -0.5rem !important;\n }\n .mt-xl-n3 {\n margin-top: -1rem !important;\n }\n .mt-xl-n4 {\n margin-top: -1.5rem !important;\n }\n .mt-xl-n5 {\n margin-top: -3rem !important;\n }\n .mr-xl-n1 {\n margin-right: -0.25rem !important;\n }\n .mr-xl-n2 {\n margin-right: -0.5rem !important;\n }\n .mr-xl-n3 {\n margin-right: -1rem !important;\n }\n .mr-xl-n4 {\n margin-right: -1.5rem !important;\n }\n .mr-xl-n5 {\n margin-right: -3rem !important;\n }\n .mb-xl-n1 {\n margin-bottom: -0.25rem !important;\n }\n .mb-xl-n2 {\n margin-bottom: -0.5rem !important;\n }\n .mb-xl-n3 {\n margin-bottom: -1rem !important;\n }\n .mb-xl-n4 {\n margin-bottom: -1.5rem !important;\n }\n .mb-xl-n5 {\n margin-bottom: -3rem !important;\n }\n .ml-xl-n1 {\n margin-left: -0.25rem !important;\n }\n .ml-xl-n2 {\n margin-left: -0.5rem !important;\n }\n .ml-xl-n3 {\n margin-left: -1rem !important;\n }\n .ml-xl-n4 {\n margin-left: -1.5rem !important;\n }\n .ml-xl-n5 {\n margin-left: -3rem !important;\n }\n .p-xl-0 {\n padding: 0 !important;\n }\n .p-xl-1 {\n padding: 0.25rem !important;\n }\n .p-xl-2 {\n padding: 0.5rem !important;\n }\n .p-xl-3 {\n padding: 1rem !important;\n }\n .p-xl-4 {\n padding: 1.5rem !important;\n }\n .p-xl-5 {\n padding: 3rem !important;\n }\n .px-xl-0 {\n padding-right: 0 !important;\n padding-left: 0 !important;\n }\n .px-xl-1 {\n padding-right: 0.25rem !important;\n padding-left: 0.25rem !important;\n }\n .px-xl-2 {\n padding-right: 0.5rem !important;\n padding-left: 0.5rem !important;\n }\n .px-xl-3 {\n padding-right: 1rem !important;\n padding-left: 1rem !important;\n }\n .px-xl-4 {\n padding-right: 1.5rem !important;\n padding-left: 1.5rem !important;\n }\n .px-xl-5 {\n padding-right: 3rem !important;\n padding-left: 3rem !important;\n }\n .py-xl-0 {\n padding-top: 0 !important;\n padding-bottom: 0 !important;\n }\n .py-xl-1 {\n padding-top: 0.25rem !important;\n padding-bottom: 0.25rem !important;\n }\n .py-xl-2 {\n padding-top: 0.5rem !important;\n padding-bottom: 0.5rem !important;\n }\n .py-xl-3 {\n padding-top: 1rem !important;\n padding-bottom: 1rem !important;\n }\n .py-xl-4 {\n padding-top: 1.5rem !important;\n padding-bottom: 1.5rem !important;\n }\n .py-xl-5 {\n padding-top: 3rem !important;\n padding-bottom: 3rem !important;\n }\n .pt-xl-0 {\n padding-top: 0 !important;\n }\n .pt-xl-1 {\n padding-top: 0.25rem !important;\n }\n .pt-xl-2 {\n padding-top: 0.5rem !important;\n }\n .pt-xl-3 {\n padding-top: 1rem !important;\n }\n .pt-xl-4 {\n padding-top: 1.5rem !important;\n }\n .pt-xl-5 {\n padding-top: 3rem !important;\n }\n .pr-xl-0 {\n padding-right: 0 !important;\n }\n .pr-xl-1 {\n padding-right: 0.25rem !important;\n }\n .pr-xl-2 {\n padding-right: 0.5rem !important;\n }\n .pr-xl-3 {\n padding-right: 1rem !important;\n }\n .pr-xl-4 {\n padding-right: 1.5rem !important;\n }\n .pr-xl-5 {\n padding-right: 3rem !important;\n }\n .pb-xl-0 {\n padding-bottom: 0 !important;\n }\n .pb-xl-1 {\n padding-bottom: 0.25rem !important;\n }\n .pb-xl-2 {\n padding-bottom: 0.5rem !important;\n }\n .pb-xl-3 {\n padding-bottom: 1rem !important;\n }\n .pb-xl-4 {\n padding-bottom: 1.5rem !important;\n }\n .pb-xl-5 {\n padding-bottom: 3rem !important;\n }\n .pl-xl-0 {\n padding-left: 0 !important;\n }\n .pl-xl-1 {\n padding-left: 0.25rem !important;\n }\n .pl-xl-2 {\n padding-left: 0.5rem !important;\n }\n .pl-xl-3 {\n padding-left: 1rem !important;\n }\n .pl-xl-4 {\n padding-left: 1.5rem !important;\n }\n .pl-xl-5 {\n padding-left: 3rem !important;\n }\n .text-xl-left {\n text-align: left !important;\n }\n .text-xl-right {\n text-align: right !important;\n }\n .text-xl-center {\n text-align: center !important;\n }\n .text-xl-justify {\n text-align: justify !important;\n }\n}\n\n@media print {\n .d-print-none {\n display: none !important;\n }\n .d-print-inline {\n display: inline !important;\n }\n .d-print-inline-block {\n display: inline-block !important;\n }\n .d-print-block {\n display: block !important;\n }\n .d-print-table {\n display: table !important;\n }\n .d-print-table-row {\n display: table-row !important;\n }\n .d-print-table-cell {\n display: table-cell !important;\n }\n .d-print-flex {\n display: flex !important;\n }\n .d-print-inline-flex {\n display: inline-flex !important;\n }\n}\n\/*# sourceMappingURL=bootstrap.css.map *\/", + { + "line": 6920, + "column": 1, + "index": 131116 + } + ] +] \ No newline at end of file diff --git a/test/multiprocessing/nested-slice.json b/test/multiprocessing/nested-slice.json index 52a664de..56f2cc6b 100644 --- a/test/multiprocessing/nested-slice.json +++ b/test/multiprocessing/nested-slice.json @@ -4,7 +4,7 @@ { "line": 1, "column": 1, - "index": 1 + "index": 0 } ], [ @@ -12,7 +12,7 @@ { "line": 29, "column": 1, - "index": 569 + "index": 567 } ], [ @@ -20,7 +20,7 @@ { "line": 33, "column": 1, - "index": 615 + "index": 613 } ], [ @@ -28,7 +28,7 @@ { "line": 37, "column": 1, - "index": 662 + "index": 660 } ] ] \ No newline at end of file diff --git a/test/multiprocessing/nested-slice.min.json b/test/multiprocessing/nested-slice.min.json index 4b7fae8c..83c20326 100644 --- a/test/multiprocessing/nested-slice.min.json +++ b/test/multiprocessing/nested-slice.min.json @@ -4,7 +4,7 @@ { "line": 1, "column": 1, - "index": 1 + "index": 0 } ], [ @@ -12,7 +12,7 @@ { "line": 1, "column": 255, - "index": 255 + "index": 253 } ], [ @@ -20,7 +20,7 @@ { "line": 1, "column": 286, - "index": 286 + "index": 284 } ], [ @@ -28,7 +28,7 @@ { "line": 1, "column": 318, - "index": 318 + "index": 316 } ] ] \ No newline at end of file diff --git a/test/server.php b/test/server.php old mode 100644 new mode 100755 index 0cbb5a13..62a7ddda --- a/test/server.php +++ b/test/server.php @@ -18,11 +18,28 @@ function ellipsis ($string, $length = 15) { return mb_substr($string, 0, $length - 3).'...'; } +function toFileSize(float $size, array $units = []) +{ + + if ($size == 0) return 0; + + $s = ['B', 'KB', 'MB', 'GB', 'TB', 'PB']; + $e = floor(log($size) / log(1024)); + + return sprintf("%.2f%s", $size / pow(1024, floor($e)), $units[$e] ?? $s[$e]); +} + +function toDuration(float $duration) +{ + + return sprintf("%.2f%s", $duration < 1 ? $duration * 1000 : $duration, $duration < 1 ? 'ms' : 's'); +} + $parser = new Parser(); -fwrite(STDERR, sprintf("curl dir %s >>>\n", getcwd())); +fwrite(STDERR, sprintf("\n\n\ncurl dir %s >>>\n", getcwd())); echo $parser->setOptions([ - 'multi_processing' => false, + 'multi_processing' => true, 'flatten_import' => true, 'capture_errors' => false ])-> @@ -90,4 +107,14 @@ function ellipsis ($string, $length = 15) { )); } })-> +on('pool.start', function (int $index, $thread) use (&$parsingStartTime) { + + fwrite(STDERR, sprintf("starting %s #%d - elapsed %s\n", preg_replace('#.*\\\\(.+)$#', '$1', $thread::class), $index, toDuration(microtime(true) - $parsingStartTime))); + fwrite(STDERR, sprintf("memory usage: %s peak: %s\n", toFileSize(memory_get_usage(true)), toFileSize(memory_get_peak_usage(true)))); +})-> +on('pool.finish', function (array|string $result, int $index, ?string $stderr, ?int $exitCode, ?string $duration, $process) use (&$parsingStartTime) { + + fwrite(STDERR, sprintf("%s %d finished in %s and exited with status %s - elapsed %s\n", preg_replace('#.*\\\\(.+)$#', '$1', $process::class), $index, $duration, $exitCode, toDuration(microtime(true) - $parsingStartTime))); + fwrite(STDERR, sprintf("memory usage: %s peak: %s\n", toFileSize(memory_get_usage(true)), toFileSize(memory_get_peak_usage(true)))); +})-> load($_SERVER['REQUEST_URI']); \ No newline at end of file diff --git a/test/src/AstTest.php b/test/src/AstTest.php index d370410f..9b562550 100755 --- a/test/src/AstTest.php +++ b/test/src/AstTest.php @@ -2,6 +2,7 @@ declare(strict_types=1); use PHPUnit\Framework\TestCase; +use TBela\CSS\Exceptions\IOException; use TBela\CSS\Parser; use TBela\CSS\Renderer; @@ -16,7 +17,7 @@ final class AstTest extends TestCase * @covers \TBela\CSS\Parser * @covers \TBela\CSS\Renderer */ - public function testIdentifier($expected, $actual): void + public function testIdentifier(string $expected, string $actual): void { $this->assertEquals( @@ -30,7 +31,7 @@ public function testIdentifier($expected, $actual): void * @param string $actual * @dataProvider mediaAllProvider */ - public function testMediaAll($expected, $actual): void + public function testMediaAll(string $expected, string $actual): void { $this->assertEquals( @@ -39,10 +40,16 @@ public function testMediaAll($expected, $actual): void ); } - public function identifierProvider() { + /** + * @throws Parser\SyntaxError + * @throws IOException + * @throws Exception + */ + public function identifierProvider(): array + { $data = []; - + $parser = new TBela\CSS\Parser(' * { text-shadow: none!important /* comment 7 */; @@ -191,7 +198,12 @@ public function identifierProvider() { return $data; } - public function mediaAllProvider() { + /** + * @throws IOException + * @throws Exception + */ + public function mediaAllProvider(): array + { $data = []; diff --git a/test/src/MultiProcessingTest.php b/test/src/MultiProcessingTest.php index 29ec080e..472136c3 100755 --- a/test/src/MultiProcessingTest.php +++ b/test/src/MultiProcessingTest.php @@ -2,7 +2,6 @@ declare(strict_types=1); use PHPUnit\Framework\TestCase; -use TBela\CSS\Event\Event as EventTest; use TBela\CSS\Exceptions\IOException; use TBela\CSS\Parser; use TBela\CSS\Renderer; @@ -65,31 +64,52 @@ public function testAst(string $expected, string $actual): void ); } - public function sliceProvider () { + public function sliceProvider (): array + { $data = []; $parser = new Parser(); - $size = 20; - $file = __DIR__.'/../nested/nested'; + $size = 64 * 1024; + $file = __DIR__.'/../perf_files/bs.4'; - $json = []; + $json = []; - foreach ($parser->slice(css: file_get_contents($file.'.css'), size: $size, position: (object) [ - 'line' => 1, - 'column' => 1, - 'index' => 0 - ]) as $line) { + foreach ($parser->slice(css: file_get_contents($file.'.css'), size: $size, position: (object) [ + 'line' => 1, + 'column' => 1, + 'index' => 0 + ]) as $line) { - $json[] = $line; - } + $json[] = $line; + } - $data[] = [ + $data[] = [ - file_get_contents(__DIR__.'/../multiprocessing/nested-slice.json'), - json_encode($json, JSON_PRETTY_PRINT) - ]; + file_get_contents(__DIR__.'/../multiprocessing/bs.4-slice.json'), + json_encode($json, JSON_PRETTY_PRINT) + ]; + + $size = 20; + $file = __DIR__.'/../nested/nested'; + + $json = []; + + foreach ($parser->slice(css: file_get_contents($file.'.css'), size: $size, position: (object) [ + 'line' => 1, + 'column' => 1, + 'index' => 0 + ]) as $line) { + + $json[] = $line; + } + + $data[] = [ + + file_get_contents(__DIR__.'/../multiprocessing/nested-slice.json'), + json_encode($json, JSON_PRETTY_PRINT) + ]; $json = []; @@ -111,10 +131,10 @@ public function sliceProvider () { return $data; } - /** - * @throws IOException - * @throws Parser\SyntaxError - */ + /** + * @throws Parser\SyntaxError + * @throws Exception + */ public function loadProvider (): array { @@ -123,19 +143,19 @@ public function loadProvider (): array __DIR__.'/../nested/nested.min.css', __DIR__.'/../sourcemap/sourcemap.import.css', __DIR__.'/../perf_files/bs-mtrl.css', - __DIR__.'/../perf_files/bs-reboot.css', - __DIR__.'/../perf_files/bs.3.css', +// __DIR__.'/../perf_files/bs-reboot.css', +// __DIR__.'/../perf_files/bs.3.css', __DIR__.'/../perf_files/bs.4.css', - __DIR__.'/../perf_files/none.css', - __DIR__.'/../perf_files/row.css', - __DIR__.'/../perf_files/row.min.css', - __DIR__.'/../perf_files/main.min.css', +// __DIR__.'/../perf_files/none.css', +// __DIR__.'/../perf_files/row.css', +// __DIR__.'/../perf_files/row.min.css', +// __DIR__.'/../perf_files/main.min.css', __DIR__.'/../perf_files/perf.css', - __DIR__.'/../perf_files/php-net.css', - __DIR__.'/../perf_files/main.min.css', - __DIR__.'/../perf_files/uncut.css', - __DIR__.'/../perf_files/uncut.css', - __DIR__.'/../perf_files/uncut.min.css' +// __DIR__.'/../perf_files/php-net.css', +// __DIR__.'/../perf_files/main.min.css', +// __DIR__.'/../perf_files/uncut.css', +// __DIR__.'/../perf_files/uncut.css', +// __DIR__.'/../perf_files/uncut.min.css' ]; @@ -164,6 +184,7 @@ public function loadProvider (): array /** * @throws Parser\SyntaxError * @throws IOException + * @throws Exception */ public function fromProvider (): array { @@ -193,8 +214,6 @@ public function fromProvider (): array foreach ($files as $file) { -// $content = file_get_contents($file); - $data[] = [ Renderer::fromFile($file, parseOptions: [ 'flatten_import' => true @@ -208,10 +227,10 @@ public function fromProvider (): array return $data; } - /** - * @throws Parser\SyntaxError - * @throws IOException - */ + /** + * @throws Parser\SyntaxError + * @throws Exception + */ public function astProvider (): array { @@ -224,7 +243,6 @@ public function astProvider (): array // __DIR__.'/../perf_files/row.min.css' ]; - $data = []; foreach ($files as $file) { @@ -240,7 +258,6 @@ public function astProvider (): array 'ast_src' => $file, 'flatten_import' => true ]))->appendContent($content)->getAst(), JSON_PRETTY_PRINT) - ]; } diff --git a/test/src/RendererTest.php b/test/src/RendererTest.php index fbf25518..c039ecdd 100755 --- a/test/src/RendererTest.php +++ b/test/src/RendererTest.php @@ -20,7 +20,6 @@ final class RendererTest extends TestCase * @param string $expected * @param string $actual * @dataProvider provider - * @medium */ public function test($expected, $actual): void { diff --git a/test/src/SelectorTest.php b/test/src/SelectorTest.php index 3b3f991d..6c978b9a 100755 --- a/test/src/SelectorTest.php +++ b/test/src/SelectorTest.php @@ -12,7 +12,6 @@ final class SelectorTest extends TestCase * @param string $expected * @param string $actual * @dataProvider selectorRemoveProvider - * @medium */ public function testSelectorRemove(string $expected, string $actual): void { @@ -27,7 +26,6 @@ public function testSelectorRemove(string $expected, string $actual): void * @param string $expected * @param string $actual * @dataProvider selectorSiblingProvider - * @medium */ public function testSelectorSibling(string $expected, string $actual): void { diff --git a/test/src/SourcemapTest.php b/test/src/SourcemapTest.php index ab122c79..a5962b1d 100755 --- a/test/src/SourcemapTest.php +++ b/test/src/SourcemapTest.php @@ -14,7 +14,6 @@ final class SourcemapTest extends TestCase * @param string $expected * @param string $actual * @dataProvider sourcemapProvider - * @medium */ public function testSourcemap($expected, $actual): void { @@ -29,7 +28,6 @@ public function testSourcemap($expected, $actual): void * @param string $expected * @param string $actual * @dataProvider sourcemapImportProvider - * @medium */ public function testSourcemapImport($expected, $actual): void { @@ -44,7 +42,6 @@ public function testSourcemapImport($expected, $actual): void * @param string $expected * @param string $actual * @dataProvider sourcemapUrlProvider - * @medium */ public function testSourcemapUrl($expected, $actual): void { @@ -59,7 +56,6 @@ public function testSourcemapUrl($expected, $actual): void * @param string $expected * @param string $actual * @dataProvider sourcemapNestedProvider - * @medium */ public function testSourcemapNested($expected, $actual): void { diff --git a/test/src/UnitsTest.php b/test/src/UnitsTest.php index 5bc76c30..11be8031 100755 --- a/test/src/UnitsTest.php +++ b/test/src/UnitsTest.php @@ -12,7 +12,6 @@ final class UnitsTest extends TestCase * @param string $expected * @param string $actual * @dataProvider zeroProvider - * @small */ public function testZero(string $expected, string $actual): void { diff --git a/test/src/VendorTest.php b/test/src/VendorTest.php index a9dec107..043c209f 100755 --- a/test/src/VendorTest.php +++ b/test/src/VendorTest.php @@ -13,7 +13,6 @@ final class VendorTest extends TestCase * @param string $expected * @param string $actual * @dataProvider vendorProvider - * @small */ public function testVendor($expected, $actual): void {